ng-change not working on ng-select - javascript

I'm using a select box that is populated by the ng-options. Unfortunately, I cannot get my ng-change function to call.
Here is my Fiddle
Here is my js:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.typeOptions = [
{ name: 'Feature', value: 'feature' },
{ name: 'Bug', value: 'bug' },
{ name: 'Enhancement', value: 'enhancement' }
];
$scope.scopeMessage = "default text";
var changeCount = 0;
$scope.onSelectChange = function()
{
changeCount++;
this.message = "Change detected: " + changeCount;
}.bind($scope);
//$scope.form = {type : $scope.typeOptions[0].value};
$scope.form = $scope.typeOptions[0].value;
}
And here is my HTML:
<div ng-controller="MyCtrl" class="row">
<select ng-model='form' required ng-options='option.value as option.name for option in typeOptions' ng-change="onSelectChange"></select>
<div style="border:1px solid black; width: 100px; height:20px;">{{scopeMessage}}<div>
</div>
This is currently holding me up on my project for work, so any help will be geatly appreciated. Thanks!

2 things... both simple :)
ng-change=onSelectChange should be onSelectChange()
this.message should be this.scopeMessage

Your problem is that you are passing in the function reference to the ng-change directive. However, that directive expects an expression which can evaluated. So attach the parentheses to the function so that it can be evaluated as a function call.
Like here: http://jsfiddle.net/MTfRD/1101/
<select ng-model='form' required ng-options='option.value as option.name for option in typeOptions' ng-change="onSelectChange()"></select>

Related

VueJs directive two way binding

I created a custom directive to handle select2 in VueJs. The code below works when I am binding a select to a data property in my viewmodel that is not a propert of an object within data.
Like this.userId but if it is bound to something like this.user.id, it would not update the value in my viewmodel data object.
Vue.directive('selected', {
bind: function (el, binding, vnode) {
var key = binding.expression;
var select = $(el);
select.select2();
vnode.context.$data[binding.expression] = select.val();
select.on('change', function () {
vnode.context.$data[binding.expression] = select.val();
});
},
update: function (el, binding, newVnode, oldVnode) {
var select = $(el);
select.val(binding.value).trigger('change');
}
});
<select v-selected="userEditor.Id">
<option v-for="user in users" v-bind:value="user.id" >
{{ user.fullName}}
</option>
</select>
Related fiddle:
https://jsfiddle.net/raime910/rHm4e/4/
When you using 1st level $data's-property, it accessing to $data object directly through []-brackets
But you want to pass to selected-directive the path to nested object, so you should do something like this:
// source: https://stackoverflow.com/a/6842900/8311719
function deepSet(obj, value, path) {
var i;
path = path.split('.');
for (i = 0; i < path.length - 1; i++)
obj = obj[path[i]];
obj[path[i]] = value;
}
Vue.directive('selected', {
bind: function (el, binding, vnode) {
var select = $(el);
select.select2();
deepSet(vnode.context.$data, select.val(), binding.expression);
select.on('change', function () {
deepSet(vnode.context.$data, select.val(), binding.expression);
});
},
update: function (el, binding, newVnode, oldVnode) {
var select = $(el);
select.val(binding.value).trigger('change');
}
});
<select v-selected="userEditor.Id">
<option v-for="user in users" v-bind:value="user.id" >
{{ user.fullName}}
</option>
</select>
Description:
Suppose we have two $data's props: valOrObjectWithoutNesting and objLvl1:
data: function(){
return{
valOrObjectWithoutNesting: 'let it be some string',
objLvl1:{
objLvl2:{
objLvl3:{
objField: 'primitive string'
}
}
}
}
}
Variant with 1st level $data's-property:
<select v-selected="valOrObjectWithoutNesting">
// Now this code:
vnode.context.$data[binding.expression] = select.val();
// Equals to:
vnode.context.$data['valOrObjectWithoutNesting'] = select.val();
Variant with 4th level $data's-property:
<select v-selected="objLvl1.objLvl2.objLvl3.objField">
// Now this code:
vnode.context.$data[binding.expression] = select.val();
// Equals to:
vnode.context.$data['objLvl1.objLvl2.objLvl3.objField'] = select.val(); // error here
So the deepSet function in my code above "converting" $data['objLvl1.objLvl2.objLvl3.objField'] to $data['objLvl1']['objLvl2']['objLvl3']['objField'].
As you see, as I mentioned in comments to your question, when you want make select2-wrapper more customisable, the directive-way much more complicated, than separate component-way. In component, you would pass as much configuration props and event subscriptions as you want, you would avoid doing side mutations like vnode.context.$data[binding.expression] and your code would become more understandable and simpler for further support.
A custom directive is perfectly fine, except use the insertedhook instead of bind. Adapted from Vue Wrapper Component Example.
To bind to an object property, the simplest way is to wrap it in a computed setter Computed Setter and bind to that.
Note, 'deep setting' does not appear to work. The problem is one of change detection, which the computed setter overcomes. (Note that the on('change' function is jQuery not Vue.)
console.clear()
Vue.directive('selected', {
inserted: function (el, binding, vnode) {
var select = $(el);
select
.select2()
.val(binding.value)
.trigger('change')
.on('change', function () {
if (vnode.context[binding.expression]) {
vnode.context[binding.expression] = select.val();
}
})
},
});
var vm = new Vue({
el: '#my-app',
computed: {
selectedValue: {
get: function() { return this.myObj.type },
set: function (value) { this.myObj.type = value }
}
},
data: {
selectedVal: 0,
myObj: { type: 3 },
opts: [{
id: 1,
text: 'Test 1'
}, {
id: 2,
text: 'Test 2'
}, {
id: 3,
text: 'Test 3'
}]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/css/select2.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/js/select2.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.4/vue.js"></script>
<div id="my-app">
<div>
<label for="example">Test dropdown list ({{ myObj.type }})</label>
</div>
<div>
<select id="example" style="width: 300px" v-selected="selectedValue">
<option v-for="(opt,index) in opts" :value="opt.id" :key="index">
{{ opt.text }}
</option>
</select>
</div>
</div>

angularjs select depending on number of options in model

I currently have a select in an angular app :
http://jsfiddle.net/4qKyx/251/
And I'm trying to manage my select depending on the number of result.
HTML
<div ng-controller="MyCtrl">
<select ng-model="form.type" required="required" ng-options="option.value as option.name for option in typeOptions" >
</select>
</div>
JS
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.typeOptions = [
{ name: 'Feature', value: 'feature' },
{ name: 'Bug', value: 'bug' },
{ name: 'Enhancement', value: 'enhancement' }
];
if($scope.typeOptions.length == 1){
$scope.form = {type : $scope.typeOptions[0].value};
}else{
// first option set to "select an option" and null -> won't work with required
}
}
If I have only one element in my typeOptions, i want the only option to be pre-selected. Now if I have more than one element, I want an option saying "Select an option" but which can't be let selected in a required select. Thank you in advance for any help !
Can you try you controller code as like below,
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.form = {};
$scope.typeOptions = [
{ name: 'Feature', value: 'feature' },
{ name: 'Bug', value: 'bug' },
{ name: 'Enhancement', value: 'enhancement' }
];
$scope.form.type=($scope.typeOptions.length===1) ? $scope.typeOptions[0].value : '';
}
also updated your jsfiddler
The code you've provided on SO works.
Your issue is only on the fiddler with the line
<option style="display:none" value="">select a type</option>
if you want your "placeholder" inside the select, you can do it like that :
if($scope.typeOptions.length == 1){
$scope.form = {type : $scope.typeOptions[0].value};
}else{
$scope.typeOptions.unshift( { name: 'Select a value', value: '' });
}
you can add option element to your select to be like
<select ng-model="" required
ng-options="option.value for option in typeOptions">
<option value=''>- Please Choose -</option>
</select>
and just do the check in you controller if the options.length equals 1 then set the ng-model the good thing is the required validation still works.
here is jsfiddle
if you removed the comment it show select option

Cannot select default value in dropdown using ng-model and ng-repeat or ng-select in AngularJS

i'm really new to AngularJS and i like it very much.
But i'm experiencing a problem trying to initialize a prealoaded dropdown with a specific value.
The dropdown is initialized with values available from JSON array, but when i try to select a default value in this dropdown, i don't see that value selected but the ng-model variable is set correctly.
I created a plunker example here http://plnkr.co/edit/7su3Etr1JNYEz324CMy7?p=preview tryng to achieve what i want, but i can't get it to work. I tried with ng-repeat and ng-select, with no luck. Another try i did (in this example) is trying to set the ng-selected property.
This is a part of my html
<body ng-controller="MySampleController">
<select name="repeatSelect" id="repeatSelect" ng-model="SelectedStatus" ng-init="SelectedStatus">
<option ng-repeat="option in StatusList[0]" value="{{option.key}}" ng-selected="{{option.key==SelectedStatus}}">{{option.name}}</option>
</select>
<select name="repeatSelect" id="repeatSelect" ng-model="SelectedOrigin">
<option ng-repeat="option in OriginList[0]" value="{{option.key}}" ng-selected="{{option.key == SelectedOrigin}}">{{option.key}} - {{option.name}}</option>
</select>
<pre>Selected Value For Status: {{SelectedStatus}}</pre>
<pre>{{StatusList[0]}}</pre>
<pre>Selected Value For Origin: {{SelectedOrigin}}</pre>
<pre>{{OriginList[0]}}</pre>
</body>
And this is code from my controller
function MySampleController($scope) {
$scope.StatusList = [];
$scope.OriginList = [];
$scope.ServiceCall = {};
$scope.EntityList = [];
$scope.SelectedStatus = -3;
$scope.SelectedOrigin = 1;
var myList = [
{
item: 'Status',
values: [{ key: -3, name: 'Aperto' },
{ key: -1, name: 'Chiuso' }]
},
{
item: 'Origin',
values: [{ key: 1, name: 'Origin1' },
{ key: 2, name: 'Origin2' },
{ key: 3, name: 'Origin3' }]
}
];
$scope.documentsData = myList;
angular.forEach($scope.documentsData, function (value) {
$scope.EntityList.push(value);
switch ($scope.EntityList[0].item) {
case 'Status':
$scope.StatusList.push($scope.EntityList[0].values);
$scope.EntityList = [];
break;
case 'Origin':
$scope.OriginList.push($scope.EntityList[0].values);
$scope.EntityList = [];
break;
}
});
}
Any help would be appreciated!
Thanks in advance.
You can at least use ng-options instead of ng-repeat + option, in which case the default value works just fine.
<select name="repeatSelect" id="repeatSelect"
ng-options="opt.key as opt.key+'-'+opt.name for opt in StatusList[0]"
ng-model="SelectedStatus"></select>`
You can also make it a bit more readable by specifying the option label as a scope function.
HTML: ng-options="opt.key as getOptionLabel(opt) for opt in StatusList[0]"
Controller:
$scope.getOptionLabel = function(option) {
return option.key + " - " + option.name;
}
Plunker: http://plnkr.co/edit/7BcAuzX5JV7lCQh772oo?p=preview
Value of a select directive used without ngOptions is always a string.
Set as following and it would work
$scope.SelectedStatus = '-3';
$scope.SelectedOrigin = '1';
Read answer here in details ng-selected does not work with ng-repeat to set default value

Angular change default value of select from filter

I have a select box which is populated with some data from my controller. When an input value changes the contents of the select box should be filtered and a default value should be assigned based on the is default property of the data object.
Is there any way this can be done using angular directives or would it need to be done as a custom filter function doing something along the lines of
angular.forEach(vm.data,function(item){
if (vm.q == item.someId && item.isDefault) {
vm.result = item.value;
}
});
My html looks something like
<div ng-app="myApp" ng-controller="ctrl as vm">
<input type="text" ng-model="vm.q">
<select ng-options="item.value as item.description for item in vm.data | filter:{someId:vm.q}" ng-model="vm.result"></select>
</div>
and my controller looks like:
(function(){
angular.module('myApp',[]);
angular
.module('myApp')
.controller('ctrl',ctrl);
function ctrl()
{
var vm = this;
vm.data = [
{
someId: '1',
description: 'test1',
value: 100,
isDefault: true
},
{
someId: '2',
description: 'test2',
value: 200,
isDefault: false
},
{
someId: '3',
description: 'test3',
value: 100,
isDefault: true
},
];
}
})();
See my plunkr demo here: http://plnkr.co/edit/RDhQWQcHFMQJvwOyHI4r?p=preview
Desired behaviour:
1) Enter 1 into text box
2) List should be filtered to 2 items
3) Select box should pre-select item 1 based on property isDefault set to true
Thanks in advance
I'd suggest you include some 3rd party library, like lodash, into your project to make working with arrays/collections that much easier.
After that you could add ng-change directive for your input.
<input type="text" ng-model="vm.q" ng-change="vm.onChange(vm.q)">
And the actual onChange function in the controller
vm.onChange = function(id) {
var item = _.findWhere(vm.data, { someId: id, isDefault: true });
vm.result = item ? item.value : null;
};
And there you have it.

space is shown when selecting the dropsdown option - angularjs

I have created an application in AngularJS with a drop down with space in option using a filter. The application is working fine with the options in drop down with indentation space before the values but the problem is when a select an option which is having a space, the space is also shown in the view like as shown below
actually I want the indentation space within the drop-down options but only thing is that I don't want that space to be get displayed when selection shown above
can anyone please tell me some solution to prevent the space to display when selection
My code is as given below
JSFiddle
<select>
<option ng-repeat="(key, value) in headers">{{value | space}}
</option>
</select>
<select ng-model="selectedValue" ng-change="selVal = selectedValue.trim(); selectedValue=selVal">
<option ng-if="selVal">{{selVal}}</option>
<option ng-repeat="(key, value) in headers" ng-if="selVal != value.value">
{{value | space}}
</option>
</select>
This is as close as it gets without jquery, temporarily changing the view state of the option when it's chosen. If that doesn't satisfy you and you still want it to be shown indented when the dropdown menu is open, check out this question on how to detect the state of the select component and update the display function accordingly. Alternatively, you can create your own select directive and manage the details within that, but I doubt that's worth the trouble if you're not using it in many places.
var app = angular.module('myApp', []);
app.controller('ArrayController', function ($scope) {
$scope.headers = [{
value: 'value 1'
}, {
value: 'value 2',
mainId: 12
}, {
value: 'value 3'
}, {
value: 'value 4',
mainId: 14
}, {
value: 'value 5'
}, {
value: 'value 6',
mainId: 18
}];
$scope.chosen = $scope.headers[0].value;
$scope.display = function(header) {
var chosenObject = _.find($scope.headers, {value: $scope.chosen});
if (!_.isUndefined(header.mainId) && header !== chosenObject) {
return '\u00A0\u00A0' + header.value;
} else {
return header.value;
}
}
});
HTML here:
<div ng-app='myApp' ng-controller="ArrayController">
<br></br>
SELECT:
<select ng-model="chosen" ng-options="header.value as display(header) for header in headers">
</select>
</div>
There's yet another alternative with CSS and ng-class, fiddle here:
var app = angular.module('myApp', []);
app.controller('ArrayController', function ($scope) {
$scope.headers = [{
value: 'value 1'
}, {
value: 'value 2',
mainId: 12
}, {
value: 'value 3'
}, {
value: 'value 4',
mainId: 14
}, {
value: 'value 5'
}, {
value: 'value 6',
mainId: 18
}];
$scope.chosen = $scope.headers[0].value;
$scope.isIndented = function(header) {
var chosenObject = _.find($scope.headers, {value: header});
return !_.isUndefined(chosenObject.mainId);
};
});
app.filter('space', function() {
return function(text) {
if(_.isUndefined(text.mainId))
{
console.log('entered mainId');
return text.value;
}
else
{
console.log('entered');
return '\u00A0\u00A0' + text.value;
}
};
});
HTML:
<div ng-app='myApp' ng-controller="ArrayController">
<br></br>
SELECT:
<select ng-model="chosen" ng-options="header.value as (header | space) for header in headers" ng-class="{'indented-value': isIndented(chosen)}">
</select>
</div>
CSS:
.indented-value {
text-indent: -9px;
}
First off, this was a great question. I've unfortunately spent way too much time trying to come up with a solution to this. I've tried everything from CSS, to using ngModelController $formatters, to the solution (which isn't optimal as you'll see) I've posted below. Note, I do not think my solution deserves to be selected as the answer. It just "sorta" works, but like other solutions, it has a fatal flaw.
However, since your question was:
can anyone please tell me some solution to prevent the space to
display when selection
My official answer is:
No
There is no cross-browser way to get this working. No amount of CSS, jQuery, or Angular magic will make this work. While it may be disappointing, I think that is going to be the only correct answer to your question.
No one is going to be able to give you a solution that prevents the space from being displayed in the select box while maintaining it in the options that works reliably across browsers. Chrome and Firefox allow some amount of styling of elements in the select, options, and optgroup family, but nothing is consistent and works everywhere.
My best run at it is in this Plunk
It uses the fact that optgroup will do indentations for you, but it comes with terrible differences in how different browsers handle it. With some you can style away the problem, but others do not work (and never will). I'm posting it so maybe someone will be inspired and figure out a way to prove me wrong.
<body ng-app="app">
<div ng-app='myApp' ng-controller="ArrayController">
<div>{{selectedValue}}</div>
SELECT:
<select ng-model="selectedValue" >
<option indented="item.mainId" ng-repeat="item in headers">{{item.value}}</option>
</select>
</div>
</body>
(function() {
var app = angular.module('app', []);
app.controller('ArrayController', function($scope, $timeout) {
$scope.headers = [{
value: 'value 1'
}, {
value: 'value 2',
mainId: 12
}, {
value: 'value 3'
}, {
value: 'value 4',
mainId: 14
}, {
value: 'value 5'
}, {
value: 'value 6',
mainId: 18
}];
});
app.directive('indented', function($parse) {
return {
link:function(scope, element, attr){
if($parse(attr.indented)(scope)) {
var opt = angular.element('<optgroup></optgroup>');
element.after(opt).detach();
opt.append(element);
}
}
};
});
})();
If you opened the question up and allowed the implementation of a directive that mimicked the behavior of select but was instead built with li elements, then this would be trivial. But you simply can't do it with select.
Given your code, you can add one helper directive, to remove space in link function, once your option got selected.
As an example, add an id to your select, and directive
<select id="mySelect" option-without-space>
<option ng-repeat="(key, value) in headers">{{value | space}}
</option>
</select>
And directive might look like,
app.directive('optionWithoutSpace', () => {
return {
restrict: 'A',
link: function(scope, elem, attributes) {
var selectedOptionWithoutSpace = function () {
var selectedOption = $('#mySelect option:selected');
var optionText = selectedOption.text();
selectedOption.text(optionText.trim())
return false;
};
$(elem).on('change', selectedOptionWithoutSpace);
}
}
})
This is with a little help of jQuery, but I assume it is not a big problem.
Here is a fiddle.
Try this...
Change select box html following
<select ng-model="selectedOption" ng-options="item.value for item in headers"></select>
var app = angular.module('myApp', []);
app.controller('ArrayController', function ($scope) {
$scope.headers = [{
value: 'value 1'
}, {
value:'value 2',
mainId: 12
}, {
value: 'value 3'
}, {
value: 'value 4',
mainId: 14
}, {
value: 'value 5'
}, {
value: 'value 6',
mainId: 18
}];
$scope.selectedOption = $scope.headers[0]; //Add this line
});
app.filter('space', function() {
return function(text) {
if(_.isUndefined(text.mainId))
{
console.log('entered mainId');
return text.value;
}
else
{
console.log('entered');
return '\u00A0\u00A0' + text.value;
}
};
});

Categories

Resources