I've got some dynamically generated values in select:
<select ng-model="selectedValue" ng-options="o.id as o.name for o in values"></select>
And I have selectedValue that holds the selected value. I want it to be updated to null or undefined when the selectedValue is no longer present in values.
I can clear the selectedValue in $scope.$watch(values) but is there a better solution?
Basically this situation in this plunker is what I want to avoid:
I want it to be "Selected value: null" or something similar.
You can create a directive that will make the select behave in the way that you'd like. The HTML would then change to:
<select clearable ng-model="selectedValue" ng-options="o.id as o.name for o in values"></select>
The JS for the directive would be:
app.directive('clearable', ['$parse',function($parse){
return {
restrict : 'A',
scope : {
'ngModel' : '=',
'ngOptions' : '#'
},
require: ['select','?ngModel'],
link : function(scope, elem, attr,ctrl){
// Regex copied from the Angular Source
var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
var match = scope.ngOptions.match(NG_OPTIONS_REGEXP);
var valuesFn = $parse(match[7]), //Options list
key = match[1].split('.')[1]; //Property in options list
scope.$watch(
function(){
// Watch for when the options list changes
return valuesFn(scope.$parent);
},
function(newList){
var isFound, i,
currentModelVal = scope.ngModel;
// Iterate through the new list to see if the current value is present
for(i=0;i<newList.length;i++){
if (newList[i][key] === currentModelVal){
isFound = true;
break;
}
}
// If not found, reset the ng-model value to null
if (!isFound){
ctrl[1].$setViewValue(null);
}
}
);
}
}
}]);
See the Plunker at http://plnkr.co/edit/yAk9YSoMf88rtTqLXTiI?p=preview
You can use an option with empty value, and model will be set to empty when no item is selected
check this
<option value="">--select--</option>
http://plnkr.co/edit/66KN7ae0q2v3IUnw47lx?p=preview
Related
I have a below code snippet
HTML
<div ng-controller="MyCtrl">
<div ng-if="quan!=true">
<select ng-model="selectedItems" ng-init="selectedItems = selectedItems + ''">
<option ng-repeat="value in arr | limitTo:quantity">{{value}}</option>
</select>
</div>
<div>
Submit
</div>
</div>
JS
$scope.arr = [];
$scope.quan=false;
for(var a=1; a<=10; a++) {
$scope.arr.push(a);
}
$scope.selectedItems = $scope.arr[0];
$scope.quantity = 10; //just hardcoded
Here, when I click submit button, I didn't get the value whatever I have selected in the dropdown. I was getting undefined for selectedItems.
And also first option needs to be selected in the select box.
I think I'm missing something!
After changing to $scope.selectedItems = $scope.arr[0];, you need to initialize your option value ng-init="selectedItems = selectedItems + ''" because angular use strict comparison.
See this working fiddle.
also you don't need to pass selected item in click event, submit(selectedItems). because it is already in controller scope.
Also I would recommend changing your options structure to avoid ng-init.
Something like :
options = [{
name: 'key1',
value: 'value1'
}, {
name: 'key2',
value: 'value2'
}];
HTML:
<select ng-model="selectedItem" >
<option ng-repeat="value in arr | limitTo:quantity">{{value}}</option>
</select>
<div>
Submit
</div>
JS:
$scope.arr = [];
for(var a=1; a<=10; a++) {
$scope.arr.push(a);
}
$scope.selectedItem = $scope.arr[0];
$scope.quantity = 10; //just hardcoded
$scope.submit = function(){
console.log($scope.selectedItem); // you will get the selected value here, if you want it after button click.
};
Because of angular's 2 way binding, it would be available as soon as you select something. You do not have to pass it as function parameter from HTML
I'm having trouble properly initializing the select boxes in a form, built with nested ng-repeats. I have parent table rows built with an ng-repeat, and a select box whose value is bound to an attribute in the parent row. The values in the select box are pulled from a nested child array, constituting available selections for the row.
<tr ng-repeat="asv in asvs">
<td>{{asv.scenario_asv_id}}</td>
<td>{{asv.asv_target_id}}</td>
<td><select ng-model="asv.asv_target_id">
<option ng-repeat="version in asv.asv_targets"
value="{{version.asv_target_id}}"
ng-selected="asv.asv_target_id == version.asv_target_id">
ID: {{version.asv_target_id}} - Name: {{version.asv_target_desc}}
</option>
</select>
</td>
</tr>
In my Plunker, you'll see that the first 2 selects initialize properly, yet the 3rd does not. Can someone advise how to implement this properly?
https://plnkr.co/edit/NGcEMNSHCDAYK8UBOKYl
It is recommended that you use ng-options with select elements. Please replace your select code with the example below:
<select convert-to-number
ng-options="version.asv_target_id as ('ID: ' + version.asv_target_id + ' - Name: ' + version.asv_target_desc) for version in asv.asv_targets track by version.asv_target_id"
ng-model="asv.asv_target_id">
</select>
Because your value is numeric you will have to add the following directive as per https://code.angularjs.org/1.4.7/docs/api/ng/directive/select
module.directive('convertToNumber', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$parsers.push(function(val) {
return val != null ? parseInt(val, 10) : null;
});
ngModel.$formatters.push(function(val) {
return val != null ? '' + val : null;
});
}
};
});
The problem happening here is during the creation of select options using ng-repeat
{
"scenario_asv_asv_target_id": "16150-CI101592-3475",
"scenario_asv_id": 16150,
"appl_ci_id": "CI101592",
"asv_target_id": 3475,
"asv_target_desc": "Default General Purpose",
}
If you move your data which are going to match your criteria to the end of the json, your value will get selected.
Because in the loop the select option is getting created,so it resets the already created ng-select values.
For example, I have moved the option which are going to be by default to the end of json set and the value is getting selected.
This is not recommended, for understandging.
Check into the plunker:
https://plnkr.co/edit/G83l6OdIfr5vlNgMSeR7?p=preview
I haven't edited your code, i have just replaced your data set.
So,
you should use ng-options as follows
<tr ng-repeat="asv in asvs">
<td>{{asv.scenario_asv_id}}</td>
<td>{{asv.asv_target_id}}</td>
<td>
<select ng-model="asv.asv_target_id"
ng-options="version.asv_target_id as version.asv_target_desc for version in asv.asv_targets"
ng-selected="true">
</select>
</td>
</tr>
final code
https://plnkr.co/edit/w8nOENAiL72SX68L7LVv?p=preview
disclaimer:
I have concentrate on explaining and helping you to select default option, so don't expect me to prettify the option data by appending data and desc in it. Do it on your own.
I have hundreds of cases like this (so the fix would have to be global and not tied to just this particular example)
There is a lot of select boxes like this:
<select ng-model="selectedItem">
<option ng-repeat="item in items | filter:attributes" value="{{item.id}}">{{item.name}}</option>
</select>
The selectedItem variable is null (and it will always initialize as null, which can't be changed in the controller in this particular situation).
What I'm trying to figure out is a way to globally watch all <select> elements in a view, see if the ng-model variable for that <select> box is null and if it is null set it to the first valid option in that select box, anytime the scope changes it will need to check if the ng-model is null and auto-select the first valid option.
The key thing to realise with this is that you can define more than one Angular directive with the same name, and all of them will be run for matching elements.
This is very powerful, as it enables you to extend functionality of built-in directives, or third party ones, etc.
Using this, I was able to create a select directive that would select the first valid option in the list whenever the model value is null.
One thing it doesn't do, however, is cope if you remove the selected item from the list (it goes back to being blank). But hopefully it is enough to get you started.
var app = angular.module('stackoverflow', []);
app.controller('MainCtrl', function($scope) {
$scope.selectedItem = null;
$scope.items = [1, 2, 3, 4].map(function(id) {
return {
id: id,
visible: true,
text: 'Item ' + id
};
});
});
app.directive('select', function() {
return {
restrict: 'E',
require: '?ngModel',
link: function($scope, $elem, $attrs, ngModel) {
// don't do anything for selects without ng-model attribute
if (!ngModel) return;
// also allow specifying a special "no-default" attribute to opt out of this behaviour
if ($attrs.hasOwnProperty('noDefault')) return;
// watch the model value for null
var deregWatch = $scope.$watch(function() {
return ngModel.$modelValue;
}, function(modelValue) {
if (modelValue === null) {
// delay to allow the expressions to be interpolated correctly
setTimeout(function() {
// find the first option with valid text
var $options = $elem.find('option'),
$firstValidOption, optionText;
for (var i = 0, len = $options.length; i < len; i++) {
optionText = $options.eq(i).text();
if (optionText !== '' && !optionText.match(/^(\?|{)/)) {
$firstValidOption = $options.eq(i);
break;
}
}
if ($firstValidOption) {
$firstValidOption.prop('selected', true);
ngModel.$setViewValue($firstValidOption.attr('value'));
// trigger a digest so Angular sees the change
$scope.$evalAsync();
}
}, 0);
}
});
// clean up in destroy method to prevent any memory leaks
var deregDestroy = $scope.$on('$destroy', function() {
deregWatch();
deregDestroy();
});
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<div ng-app="stackoverflow">
<div ng-controller="MainCtrl">
<select ng-model="selectedItem">
<option ng-repeat="item in items | filter:{visible:true} track by item.id" value="{{item.id}}">{{item.text}}</option>
</select>
<p>Visible items:</p>
<ul>
<li ng-repeat="item in items track by item.id">
<label>
<input type="checkbox" ng-model="item.visible">{{item.text}}
</label>
</li>
</ul>
</div>
</div>
I have three select menu that bind to each other and want to set selected value of two select menus. Value that I want to set comes with scope. I try ng-init but that didn't work. Working Plunker
<select ng-init="selManga=direk" ng-model="selManga" ng-options="manga.seri for manga in mangas">
<option value="">Select a Manga</option>
</select>
<select ng-init="selChapter=bacanak" ng-show="selManga" class="browser-default" ng-model="selChapter" ng-options="+idx as chapter.klasor for (idx, chapter) in selManga.randomword">
<option value="">Chapter</option>
</select>
<select ng-show="selManga.randomword[selChapter].yol" class="browser-default" ng-model="selPage" ng-options="+idx as (+idx + 1) for (idx, page) in selManga.randomword[selChapter].yol">
<option value="">Page</option>
</select>
Javascript:
.controller('nbgCtrl',function ($scope, MMG, $stateParams) {
$scope.direk = $stateParams.seri;
$scope.bacanak = $stateParams.klasor;
MMG.adlar.success(function(loHemen) {
$scope.mangas = loHemen;
});
$scope.next = function (manga, chapter, page) {
var nextPage = page + 1;
if (angular.isDefined(manga.randomword[chapter].yol[nextPage])) {
$scope.selPage = nextPage;
} else if (angular.isDefined(manga.randomword[chapter + 1])) {
$scope.selChapter = chapter + 1;
$scope.selPage = 0;
}
}
})
To extract the values for the manga,chapter and page from the url you should use the $routeParams object, which your module should include ngRoute.
I've prepared an example here it might not be obvious but if you download the code and run it in your own browser you can access routes like Manga/Naruto/ch/100 from the url bar.
Once you've restructured your application to use routes and routeParams, in my example I directly bind the chapter and page I get from the url to the view but if you bind them to the models of your dropdowns the dropdowns will update.
$scope.params = $routeParams;
$scope.selManga = $scope.params.bookId
This does not update the url as you select from the dropdown.
In your code you have assign direk and bacanak value more than one times which can cause issue. Set variable property only once.
$scope.direk = $stateParams.seri;
$scope.bacanak = $stateParams.klasor;
$scope.direk = $stateParams.xy; //Remove if not required
$scope.bacanak = $stateParams.xz; //Remove if not required
Set selManga value from $stateParams instead of setting it to direk
$scope.selManga = $stateParams.seri; // Set selManga from $stateParams
$scope.selChapter = $stateParams.klasor; // Set selChapter from $stateParams
The reason why ng-init="selManga=direk" not work is because Syntax error: ng-init="selManga=direk" should be ng-init="selManga='direk'" but after making syntax correct, dropdown will not set because of ng-repeat. ng-init set value to model but at time of setting value ng-repeat doesn't complete setting value for options so selManga set from ng-init not update option to dropdown.
I am working on a drop down menu within a TR .. I have true, false or none as the value that I receive from server and I want that to change the drop down option as in example below.
The first one is working but I want the second one to function as the first one
Example is here: http://jsfiddle.net/3xLgJ/
This is my HTML:
<div data-bind='text: incomingValue'></div>
<select data-bind="value: incomingValue">
<option value="true">Yes</option>
<option value="false">No</option>
<option value="none">Don't Know</option>
</select>
How can I implment this as above as this is within a tr and to function as above
<select data-bind='options: yesno, value: incomingValue'/>
Here is my knockout
var myModelView = function () {
self = this;
self.yesno = ko.observableArray(['Yes', 'No', 'Don\'t know']);
self.incomingValue = ko.observable('none');
};
var moView = new myModelView();
ko.applyBindings(moView);
Thanks
Thanks
The best solution is probably to slightly reconstruct the view model to use objects instead of simple strings:
// Create a "class" that represents an option
var Option = function(id, caption) {
this.id = id;
this.caption = caption;
};
Now you populate the observable array with objects constructed from this function:
self.yesno = ko.observableArray([
new Option('true', 'Yes'),
new Option('false', 'No'),
new Option('none', 'Don\'t know')
]);
You can use the "optionsText" binding to correctly bind these objects to the markup:
<select data-bind="options: yesno,
optionsText: 'caption',
value: selectedItem"></select>
If you receive a string "none" from the server, you need to find the object representing this option:
var incomingValue = 'none';
// Find the first object that is a match in the observable array "yesno"
var incomingItem = ko.utils.arrayFirst(self.yesno(), function(item) {
return item.id == incomingValue;
});
self.selectedItem = ko.observable(incomingItem);
When displaying the selection somewhere else you'll need to consider that the selection is represented by an object:
<div data-bind='text: selectedItem().caption'></div>
Demo: http://jsfiddle.net/3xLgJ/2/
You need to use the optionsText and optionsValue bindings. You'll need to make an observable array of values and text:
self.yesno = ko.observableArray([
{value:"true",text:"Yes"},
{value:"false",text:"No"},
{value:"none",text:"Don't Know"}]);
then, you need to do something like this in your html:
<select data-bind="options: yesno2, optionsText: 'text',optionsValue: 'value', value: incomingValue"></select>
See here for a working example