Confused of what really are optionsValue and value - javascript

I am using knockout js 3.2 , I have basic confusion on how select binding works
<select data-bind="options: choices,
optionstext: 'DisplayName' ,
optionsvalue :'Id' ,
value:id,
optionscaption :'Select...'"></select>
In the above example if we have an object of type
var choices = [
{ id: 'M', DisplayName: "Male" },
{ id: 'F', DisplayName: "Female" }
];
What is the differences and uses of value and optionsvalue?Could some one help out.

The value parameter tells the binding the name of the observable to set with the selected value of the select. So in your example, a little bit modified
var choices = [
{ id: 'M', DisplayName: "Male" },
{ id: 'F', DisplayName: "Female" }
];
With the binding:
<select data-bind="options: choices,
optionstext: 'DisplayName' ,
value: selectedChoice,
optionscaption :'Select...'"></select>
This binding assumes that your viewModel (the object that contains the choices array) also contains an object (observable) called selectedChoice which will contain either { id: 'M', DisplayName: "Male" } or { id: 'F', DisplayName: "Female"}.
Now, let's add in the optionsValue binding, which tells the binding which property of the selected option to put into the selected value binding. So let's add that in (note that it is case sensitive, since it's referencing a javascript object property, which are case sensitive:
<select data-bind="options: choices,
optionstext: 'DisplayName' ,
value: selectedChoice,
optionsValue: 'id',
optionscaption :'Select...'"></select>
Now, when user selects a choice from the select element, selectedChoice will not contain the entire choice object, but rather, just the id property. So, selectedChoice will either be 'F' or 'M'.
Put more simply optionsValue: 'id' means "set the selected value to the id property of the selected item" and value: selectedChoice means "store the selected item in the selectedChoice observable.
vm = {
choices: [ { id: 'M', DisplayName: 'Male' }, { id: 'F', DisplayName: 'Female' } ],
selectedChoice1: ko.observable(),
selectedChoice2: ko.observable()
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Using optionsValue='id':
<select data-bind="options: choices, value: selectedChoice1, optionsText: 'DisplayName', optionsValue: 'id'"></select>
Selected Option: <span data-bind="text: selectedChoice1"></span>
<br/>
Without optionsValue:
<select data-bind="options: choices, value: selectedChoice2, optionsText: 'DisplayName'"></select>
Selected Option: <span data-bind="text: JSON.stringify(selectedChoice2())"></span>

Related

Modifying observableArray does not instantly update select UI

I have a multi-select dropdown. If the user selects the option all, I want all the other options to be deselected and only select all. I have this almost working, but my issue is that the select does not show the updated value until minimise the dropdown. The state of the observableArray appears to be correct.
Here is the HTML:
<select data-bind="options: games, selectedOptions: selectedGame, optionsText: 'name', optionsValue: 'id'" multiple="true"></select>
And the javascript:
this.games= [
{
name: 'All',
id: 'all'
},
{
name: 'Game1',
id: 'game1'
},
{
name: 'Game2',
id: 'game2'
},
]
this.selectedGame = ko.observableArray(['all']);
this.selectedGameBeforeChange = ko.observableArray([]);
this.selectedGame.subscribe((oldValue) =>
{
this.selectedGameBeforeChange(oldValue);
}, null, 'beforeChange');
this.selectedGame.subscribe((newValue) =>
{
const newValueAdded = newValue.filter(x => !this.selectedGameBeforeChange().includes(x));
if (newValueAdded.length > 0 && newValueAdded[0] === 'all'){
this.selectedGame.removeAll();
this.selectedGame.push('allCombined');
}
this.updateTable();
});
The code above works, but the change is only reflected in the UI once I have 'minimised' the select and reopen it. Is there a way to force the UI to update as soon my observableArray is updated?
You've got 2 bugs:
Instead of push('allCombined'), it should be push('all').
It works when all is selected last, but not when it's selected as the first option. To fix that, we need to modify the condition a bit.
Here's the final code (with few more minor modifications, e.g using self instead of this):
var vm = function () {
var self = this;
self.games = [
{ name: 'All', id: 'all' },
{ name: 'Game1', id: 'game1' },
{ name: 'Game2', id: 'game2' }
];
self.selectedGames = ko.observableArray(['all']);
self.selectedGamesBeforeChange = ko.observableArray([]);
self.selectedGames.subscribe((oldValue) =>
{
self.selectedGamesBeforeChange(oldValue);
}, null, 'beforeChange');
self.selectedGames.subscribe((newValue) =>
{
if (newValue.length > 1 &&
newValue.includes('all')){
self.selectedGames.removeAll();
self.selectedGamesBeforeChange.removeAll();
self.selectedGames.push('all');
}
});
};
ko.applyBindings(new vm());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<select data-bind="options: games, selectedOptions: selectedGames, optionsText: 'name', optionsValue: 'id'" multiple="true"></select>

KnockoutJs binding issue on select list

I'm using Typescript with KnockoutJs and I am having issues with binding the optionsText and optionsValue. The model is:
export interface LanguageProxy {
ID: number;
Name: string;
Code: string;
IsSparse: boolean;
HasAudio: boolean;
ReadsRightToLeft: boolean;
IsAsian: boolean;
ShortCode: string;
LongCode: string;
CultureCode: string;
IsEnabled: boolean;
IsCustom: boolean;
}
we are setting up the binding as (response being a response from a web service call):
var langs = ko.observableArray([]);
response.LanguageProxyListResult.forEach(lang => {
langs.push(ko.observable(lang));
});
this.Languages = langs;
ko.applyBindings(this, jQuery("#QuickSearchContainer")[0]);
and we are binding using the following HTML below:
<select name="ddlLanguage" id="ddlLanguage" class="LanguageList"
data-bind="options: Languages,
optionsText: 'Name',
optionsValue: 'ID',
optionsCaption: 'Choose...',
optionsAfterRender: function (e) { jQuery('#ddlLanguage')[0].selectedIndex = 1;}">
</select>
The data binds correctly, removing the optionsText and optionsValue returns the list of [object] [OBJECT], but when adding the properties of optionsText and value it sets up a blank list.
Looking at a knockoutJs context debugger for chrome, the data appears correctly in the element (under $data.Languages.Symbol(_latestValue) and the parsed context) . Is there something fundamentally I am doing wrong?
I don't think the options binding supports observables in the array.
If you replace langs.push(ko.observable(lang)); by just langs.push(lang);, it should work.
There's no real point in wrapping an object in an observable when it's in an observable array.
Reproduction of problem, note that the second select will throw an error.
var opts = ko.observableArray([
{ name: "Option 1" },
{ name: "Option 2" }
]);
var obsObs = ko.observableArray([
ko.observable({ name: "Option 1" }),
ko.observable({ name: "Option 2" })
]);
ko.applyBindings({
opts: opts,
obsObs: obsObs
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: opts, optionsText: 'name'"></select>
<select data-bind="options: obsOpts, optionsText: 'name'"></select>

Set Value of Dynamically Populated Select in Knockout

So I'm using KnockoutJS to populate a <select> with options and to get the value of the select.
<select data-bind="enable: cols1().length > 0, options: cols1(), optionsText: 'name', value: jCol1" id="col1"></select>
The variable cols1 holds objects with the simple format of { name: "name" } just because it needs to be objects for some of the other stuff I do on the page. Is there any way to set the value of the select from outside of the data-binds on this element?
The value part of the binding says:
Store a reference to an item that is in cols1 in jCol1
If you want to change the selection from outside of the UI, you'll have to set jCol1 to a value that is in the cols1 array. If you try to set it to anything else, knockout will reset it to the first value immediately. Switch out the commented lines of code in the example below to see this happen:
var ViewModel = function() {
this.options = ko.observableArray([
{ name: "Item 1" },
{ name: "Item 2" },
{ name: "Item 3" }
]);
this.selection = ko.observable();
this.selection.subscribe(function(newValue) {
console.log(newValue)
});
this.changeSelectionFromOutside = function() {
// This does not work because knockout does not do a
// deep comparison of objects
// this.selection({ name: "Item 3" });
// This _does_ work, because it references one of the
// options objects
this.selection(this.options()[2]);
}.bind(this);
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: options, value: selection, optionsText: 'name'"></select>
<button data-bind="click: changeSelectionFromOutside">
Set option 3
</button>
Now, you can also choose to just store a string ID (or other primitive) of your selection. This makes it easier to set things from the outside, because you only need the ID instead of a reference to the actual item:
var ViewModel = function() {
this.options = ko.observableArray([
{ name: "Item 1" },
{ name: "Item 2" },
{ name: "Item 3" }
]);
this.selection = ko.observable();
this.selection.subscribe(function(newValue) {
console.log(newValue)
});
this.changeSelectionFromOutside = function() {
this.selection("Item 3");
}.bind(this);
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: options, value: selection, optionsText: 'name', optionsValue: 'name'"></select>
<button data-bind="click: changeSelectionFromOutside">
Set option 3
</button>
Let's use the states example:
//list of US states in array
self.usStates = [
{ StateName: 'Alabama', Abbr: 'AL' },
{ StateName: 'Alaska', Abbr: 'AK' },
...
//observable from that array
self.States = ko.observableArray(self.usStates);
//the selected state
self.selectedState = ko.observable();
//set selectedState from some value received from server
self.selectedState(self.States.find("Abbr", { StateName: "", Abbr: '<<Value i.e. TX>>' }).Abbr);
//finds TX, sets state to 'Texas'
//find custom function used to find specific object in array
ko.observableArray.fn.find = function (prop, data) {
var valueToMatch = data[prop];
return ko.utils.arrayFirst(this(), function (item) {
return item[prop] === valueToMatch;
});
};
This may be overly complicated for what you're looking to do, but this is how I do it when I want to choose a value from a select based on a value from the record in the database.

Angularjs: update select options

I have two select menus . One for country selection and other for state. I need to update states based country selected. I am able to log states but not able to list them in select menu.Please help.
Angular:
angular.module('demoApp', []).controller('DemoController', function($scope) {
$scope.countries = [
{ label: 'Please select', value: 0 },
{ label: 'India', value: 1 },
{ label: 'US', value: 2 }
];
$scope.data = [{'1':[{ label: 'Delhi', value: 0 },{ label: 'Mumbai', value: 1 },{ label: 'Chennai', value: 2 }]},
{'2':[{ label: 'Alabama', value: 3 },{ label: 'Alaska', value: 4 },{ label: 'Arizona', value: 5 }]}];
$scope.vm = {states: []};
$scope.updateStates = function(countryCode){
$scope.vm.states = $scope.data[countryCode-1];
console.log($scope.vm.states);
};
$scope.correctlySelected = $scope.countries[0];
});
HTML:
<body ng-app="demoApp">
<div ng-controller="DemoController">
<select ng-model="correctlySelected" ng-change="updateStates(correctlySelected.value)" ng-options="opt as opt.label for opt in countries">
</select>
<select ng-options="opt as opt.label for opt in vm.states">
</select>
</div>
</body>
JS Bin:
http://jsbin.com/pafosewedo/1/edit?html,js,console,output
You need to add ng-model to your states <select> - this is required when you are using ng-options
You also have an inconvenient model for the states data. Each element of the data array that corresponds to the country's states is an object with a changing key whose value is an array of states. You could make it work, but it's better to change it to something more reasonable:
$scope.data = {
1: [{ label: 'Delhi', value: 0 }, {...}, ],
2: [{...}, {...}, ] // same for US
}
Then it would work with how you specified your ng-options for states, and you wouldn't have to deal with indices:
$scope.updateStates = function(countryCode){
$scope.vm.states = $scope.data[countryCode]; // access by property
};
I think, that you should use some filter like that if you don't want to change your model:
.filter('stateFilter', function() {
return function(states, countryID) {
var filtered = [];
angular.forEach(states, function(state){
if(state.value === countryID)
filtered.push(state);
});
return filtered;
};
});
to filter out all values that have value equal to selected country.value in first select control.
To use that filter you need to modify your ng-repeat directive value in state select control:
ng-options="state as state.label for data | stateFilter:correctlySelected"
I came up with the following solution, view my JSBin
This solutions works by setting the countryCode in the scope when we are updatingStates.
$scope.updateStates = function(countryCode){
$scope.countryCode = countryCode;
$scope.vm.states = $scope.data[countryCode-1];
console.log($scope.vm.states[countryCode]);
};
This change is then reflected in the view.
<select>
<option ng-repeat='i in vm.states[countryCode]'> {{i.label}}
</option>
</select>

Knockoutjs <select> based on another <select> not working

I am trying to activate two select fields with options having values, eg. <option value='...'>...</option> using Knockoutjs.
And it populates second select field options with values based on the selected value in the first select field.
FYI, I found http://knockoutjs.com/examples/cartEditor.html, but this does not use optionsValue either so it was not helpful.
Here's my view:
<select data-bind="options: list,
optionsCaption: 'Select...',
optionsText: 'location',
optionsValue: 'code',
value: selectedRegion">
</select>
<!-- ko with : selectedRegion -->
<select data-bind="options: countries,
optionsCaption: 'Select...',
optionsText: 'location',
optionsValue: 'code',
value: $parent.selectedCountry">
</select>
<!-- /ko -->
Here's my view:
var packageData = [
{
code : "EU",
location: 'Euprope',
countries : [
{ location: "England", code: 'EN' },
{ location: "France", code: 'FR' }
]
},
{
code : "AS",
location: 'Asia',
countries : [
{ location: "Korea", code: 'KO' },
{ location: "Japan", code: 'JP' },
]
}
];
function viewModel(list, addons) {
this.list = list;
this.selectedRegion = ko.observable();
this.selectedCountry = ko.observable();
}
ko.applyBindings(new viewModel(packageData));
If run above, I get the following JS error.
Uncaught ReferenceError: Unable to parse bindings.
Bindings value: options: countries,
optionsCaption: 'Select...',
optionsText: 'location',
optionsValue: 'code',
value: $parent.selectedCountry
Message: countries is not defined
Above works if I lose 'optionsValue: 'code,' lines in my view (one for first select field, another for second select field. However this does not populate the option values and this is not what I want.
For example, <option value>...</option> instead of <option value="[country code]">...</option>.
Can someone please help how I can fix my code so I get <option value="[country code]">...<option>?
Thanks so much in advance.
The problem is that when you set the optionsValue property selectedRegion is now populated with only the code. The code property does not have a countries property underneath and so the binding fails. One way to work around this is to use a computed observable the returns the countries based on the selectedRegion code.
self.countryList = ko.computed(function () {
var region = self.selectedRegion();
var filtered = ko.utils.arrayFirst(self.list, function (item) {
return item.code == region;
});
if (!filtered) {
return []
} else {
return filtered.countries;
}
});
Then you just change the binding to use the computed: options: $root.countryList.
Working example: http://jsfiddle.net/infiniteloops/AF2ct/

Categories

Resources