I'm using Knockout 3.2 and i'm trying to display a multiple select dropdown with some selected values, but the values are not being selected. The problem is that KO does not populate the 'value' attribute of the options:
<select data-bind="options: availableCountries, selectedOptions: chosenCountries, optionsText: 'name'" size="5" multiple="true">
<option value="">France</option>
<option value="">Germany</option>
<option value="">Spain</option>
</select>
VM:
var viewModel = {
availableCountries : ko.observableArray([{name:'France'}, {name:'Germany'}, {name:'Spain'}]),
chosenCountries : ko.observableArray(['Germany'])
};
If instead of object i turn availableCountries into a simple strings array, it works.
You can see a live sample here
var viewModel = {
availableCountries : ko.observableArray([{name:'France'}, {name:'Germany'}, {name:'Spain'}]),
chosenCountries : ko.observableArray(['Germany'])
};
['Germany'] is not {name: 'Germany'}!
And also if you, would write chosenCountries : ko.observableArray([{name: 'Germany'}]), this would lead to two different objects, with the same property name and the value 'Germany'.
var viewModel = (function() {
var self = {};
self.availableCountries = ko.observableArray([{name:'France'}, {name:'Germany'}, {name:'Spain'}]);
self.chosenCountries = ko.observableArray([self.availableCountries()[1]]);
return self;
})();
ko.applyBindings(viewModel);
I changed the viewModel, to an instand called function which returns the viewModel.
(function () {...})()<-call
http://jsbin.com/monasijufaya/1/edit?html,js,output
Related
I have the below code in jQuery. I want to know what is the equivalent code of above in Angular Js 1.x Versions?
<select id="select">
<option value="1" data-foo="dogs">this</option>
<option value="2" data-foo="cats">that</option>
<option value="3" data-foo="gerbils">other</option>
</select>
// JavaScript using jQuery
$(function() {
$('select').change(function() {
var selected = $(this).find('option:selected');
var extra = selected.data('foo');
});
});
var sel = document.getElementById('select');
var selected = sel.options[sel.selectedIndex];
var extra = selected.getAttribute('data-foo');
You don't need to use jQuery when you are using angularjs.
To get the selected value in dropdown, you need to model select with ng-model and then the value will be there on the fly. That's the beauty of angularjs.
Moreover, if you are interested to add options on fly, better use ng-repeat to do that. And push in the array when you need to add another option. The option will be added to select. Another piece beauty of angularjs
Here
angular.module('app',[]).controller('ctrl', function($scope){
$scope.options = [{value : 1, attr: "dogs" },{value : 2, attr: "cats" },{value : 3, attr: "gerbils" }];
$scope.data = $scope.options[0]
$scope.select = 1;
$scope.add = function(){
$scope.options.push({value : $scope.options.length + 1, attr: 'option'})
};
$scope.getFoo = function(){
$scope.data = $scope.options.find(o=> o.value == $scope.select)
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='app' ng-controller='ctrl'>
<select id="select" ng-model="select" ng-change="getFoo()">
<option ng-repeat="option in options" value="{{option.value}}" data-foo="{{option.attr}}">{{option.attr}}</option>
</select>
<button ng-click="add()">Add option</button>
{{data.value}}
{{data.attr}}
</div>
Add the value to the model that is provided as the data source for ng-repeat and set ng-model for dropdown .And get value of dropdown using ng-model.
I have a knockout example, using "With" binding to create a cascading drop-down.
The Drop-down works fine, if i select the values, 4 drop-downs cascading with each corresponding select options.
However I would like to save the drop-down setup, so at a page load, i could get back the saved values, just like presetting the values.
Logging out the values the observables get by calling 'save', after selecting from drop-down. But doesn't work when calling 'loadPresetData', to simulate the data mapping into the observable selected values.
I have forked the fiddle below.
http://jsfiddle.net/turrytheman/3urLenmd/
var sampleModel=[{"products":[{"name":"1948 Porsche 356-A Roadster","year":[{"name":2015,"months":[{"name":"jan"},{"name":"april"},{"name":"dec"}]}]},{"name":"1948 Porsche Type 356 Roadster","year":[{"name":2014,"months":[{"name":"jan"},{"name":"april"},{"name":"dec"}]},{"name":2015,"months":[{"name":"oct"},{"name":"marc"},{"name":"feb"}]}]},{"name":"1949 Jaguar XK 120","year":[{"name":2019,"months":[{"name":"oct"},{"name":"jun"},{"name":"jul"}]},{"name":2013,"months":[{"name":"oct"},{"name":"marc"},{"name":"feb"}]}]}],"name":"Classic Cars"},{"products":[{"name":"1936 Harley Davidson El Knucklehead","year":[{"name":2011,"months":[{"name":"jan"},{"name":"nov"},{"name":"sep"}]}]},{"name":"1957 Vespa GS150","year":[{"name":2014,"months":[{"name":"jan"},{"name":"april"},{"name":"dec"}]},{"name":2015,"months":[{"name":"another"},{"name":"yet"},{"name":"another"}]}]}],"name":"Motorcycles"}];
var Cascading = function() {
var self = this;
self.category = ko.observable();
self.product = ko.observable();
self.years = ko.observable();
self.month = ko.observable();
// Whenever the category changes, reset the product selection
self.category.subscribe(function(val) {
self.product(undefined);
});
self.product.subscribe(function(val) {
self.years(undefined);
});
self.years.subscribe(function(val) {
self.month(undefined);
});
// Operations
self.loadPresetData = function() { //simulating a load, recieved from AJAX, setting saved values
self.category(JSON.parse('{"products":[{"name":"1936 Harley Davidson El Knucklehead","year":[{"name":2011,"months":[{"name":"jan"},{"name":"nov"},{"name":"sep"}]}]},{"name":"1957 Vespa GS150","year":[{"name":2014,"months":[{"name":"jan"},{"name":"april"},{"name":"dec"}]},{"name":2015,"months":[{"name":"another"},{"name":"yet"},{"name":"another"}]}]}],"name":"Motorcycles"}'));
self.product(JSON.parse('{"name":"1936 Harley Davidson El Knucklehead","year":[{"name":2011,"months":[{"name":"jan"},{"name":"nov"},{"name":"sep"}]}]}'));
self.years(JSON.parse('{"name":2015,"months":[{"name":"jan"},{"name":"april"},{"name":"dec"}]}'));
self.month(JSON.parse('{"name":"april"}'));
}
self.save = function() {
var data = {"category": ko.toJSON(ko.toJS(self.category)),
"product": ko.toJSON(ko.toJS(self.product)),
"years": ko.toJSON(ko.toJS(self.years)) ,
"month": ko.toJSON(ko.toJS(self.month))
}
console.log(data);
};
};
ko.applyBindings(new Cascading());
HTML:
<div class='liveExample'>
<div>
<select data-bind='options: sampleModel, optionsText: "name", optionsCaption: "Select...", value: category'> </select>
</div>
<div data-bind="with: category">
<select data-bind='options: products, optionsText: "name", optionsCaption: "Select...", value: $parent.product'> </select>
</div>
<div data-bind="with: product">
<select data-bind='options: year, optionsText: "name", optionsCaption: "Select...", value: $parent.years'> </select>
</div>
<div data-bind="with: years">
<select data-bind='options: months, optionsText: "name", optionsCaption: "Select...", value: $parent.month'> </select>
</div>
<button data-bind='click: loadPresetData'>Load</button>
<button data-bind='click: save'>Save</button>
<div style="color: red"data-bind="text:'Category :' + ko.toJSON(category)"></div>
<div style="color: green"data-bind="text:'Product :' + ko.toJSON(product)"></div>
<div style="color: blue"data-bind="text:'Year :' + ko.toJSON(years)"></div>
<div style="color: black"data-bind="text:'Months :' + ko.toJSON(month)"></div>
</div>
Short answer: The dropdowns are not getting set because the object you are setting to self.category() and other dropdowns in loadPresetData don't exist in sampleModel (or sampleProductCategories in the fiddle).
Yes, there is an object that looks like and has the same properties and nested arrays as the object JSON.parse() creates, but they are totally different objects. They would fail a Strict Equality Comparison or "=== comparison". You can prove this hypothesis by setting the category and other cascading values from the sampleProductCategories array itself.
self.loadPresetData = function() {
self.category(sampleProductCategories[1]);
self.product(sampleProductCategories[1].products[0]);
self.years(sampleProductCategories[1].products[0].year[0]);
self.month(sampleProductCategories[1].products[0].year[0].months[0]);
};
Now, when category is updated, knockout goes and looks for this object in sampleProductCategories. It exists and hence category won't be set to undefined.
Here's an updated fiddle
I have a page that populates its fields(dropdowns and textboxes) from the DB when the page loads via VIEWDATA object from controller.
I am using KnockoutJS for data-binding, below is the scenario
HTML & JS(dropdown)
#if (Filter!= null)
{
<select data-bind="options: RsnAdmAct,
value: selectedRsnAdmAct,
optionsCaption:'Choose...',
optionsValue:'RsnAdminActionID',
optionsText:'RsnAdminActionDescp',
optionsAfterRender: function()
{
setOptionRSN(#Filter.RsnForAdminAction);
}">
</select>
}
self.RsnAdmAct = ko.observableArray([]);
self.selectedRsnAdmAct = ko.observable();
$.getJSON("GetRAA", null, function (data) {
self.RsnAdmAct(data);
});
self.selectedRsnAdmAct = ko.observable();
self.setOptionRSN = function (x) {//reason for admin action dd
self.selectedRsnAdmAct(x);
};
this does update the drop-down with the assigned value "X".
But the same does not assign value for the text-boxes as below
HTML & JS(textbox)
#if (#Filter != null)
{
<input placeholder="Downstream"
data-bind="value: DownStream,
optionsAfterRender:function()
{
setOptionDstream(#Filter.DownstreamNumber);
}">
}
self.DownStream = ko.observable();
self.setOptionDstream = function (x) {//down stream number
self.DownStream(x);
};
optionsAfterRender applies to options; a text input has no options. Setting the value of DownStream will set the value of the text box, because that's how the value binding works.
I tried to search for a proper way to do this but below is the way it works and no other alternative worked.I had to move the function which assigns the value to the texbox observable into the options after render attribute of the drop down as below
#if (Filter!= null)
{
<select data-bind="options: RsnAdmAct,
value: selectedRsnAdmAct,
optionsCaption:'Choose...',
optionsValue:'RsnAdminActionID',
optionsText:'RsnAdminActionDescp',
optionsAfterRender: function()
{
setOptionRSN(#Filter.RsnForAdminAction);
setOptionDstream(#Filter.DownstreamNumber);
}">
</select>
}
This is the only way it worked for me.Thank you for the posts and comments
I have a select with the attribute multiple. For each option in the select I want the title attribute set (which shows a tooltip). I also want to retrieve the selected options as an array of objects. I managed to get what I want except for the fact that the selected options doesnt return an array of objects but an array of valueTexts. I can't figure out how I get objects in that array.
This is the code I got so far:
HTML:
<select multiple style="width: 150px;" size=15
data-bind="foreach: options, selectedOptions: selectedOptions">
<option data-bind="text: Name, attr: { 'title': Name}"></option>
</select><br />
<button data-bind="click: showSelectedOptions">Show selection</button>
Javascript:
function Option(id, name){
var self = this;
self.Id = id;
self.Name = name;
}
function ViewModel(){
var self = this;
self.options = ko.observableArray([
new Option(0, "NormalText"),
new Option(1, "AnotherText"),
new Option(2, "WaaaaaaaaaaaaaaaayTooLongText")
]);
self.selectedOptions = ko.observableArray([]);
self.showSelectedOptions = function(){
alert(self.selectedOptions());
//what I would like to have:
//if (self.selectedOptions().length > 0)
// alert(self.selectedOptions()[0].Name);
}
}
ko.applyBindings(new ViewModel());
And the fiddle link for demonstration: http://jsfiddle.net/c63Bb/1/
What do I need to add or change so the array selectedOptions contains objects instead of strings?
Try your html like this
<select
data-bind="
options: options,
selectedOptions : selectedOptions,
optionsText: 'Name',
optionsCaption: 'Choose...'
"
size="5" multiple="true"></select>
Demo
See the console for output
EDITS :
To add attributes to option you need to use optionsAfterRender.
This is available only in version 3.1.0. I noticed your fiddle is using 3.0.0.
<select
data-bind="
options: options,
selectedOptions : selectedOptions,
optionsText: 'Name',
optionsAfterRender: $root.setTitle
"
size="5" multiple="true"></select><br />
<button data-bind="click: showSelectedOptions">Show selection</button>
And create a fnction
self.setTitle = function(option, item) {
option.title = item.Name
}
Demo
Reference
See Note 2
Similar to #MuhammadRaheel, I used optionsAfterRender:
<select data-bind="optionsAfterRender: myFunc, ...">
But I needed to use ko.applyBindingsToNode:
var self.myFunc = function(option, item) {
ko.applyBindingsToNode(option, { attr: { title: 'Tooltip!' } }, item);
}
Use options and optionsText bindings instead of foreach:
<select multiple style="width: 150px;" size=15
data-bind="options: options, optionsText: 'Name', selectedOptions: selectedOptions">
<option data-bind="text: Name, attr: { 'title': Name}"></option>
</select>
Here is demo: http://jsfiddle.net/p5E8y/
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