KnockoutJs binding issue on select list - javascript

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>

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>

how do you set a value that is an observable in a dropdown?

var vm = (function() {
var selectedFoo = ko.observable(),
foos = [
{ id: 1, fooName: 'fooName1', fooType: 'fooType1' },
{ id: 2, fooName: 'fooName2', fooType: 'fooType2' },
{ id: 3, fooName: 'fooName3', fooType: 'fooType3' },
];
return {
selectedFoo: selectedFoo,
foos: foos
};
}());
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: foos,
optionsText: 'fooName',
optionsCaption: 'Select foo',
value: selectedFoo"></select><br />
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
While above code works, how would set the initial value of the dropdown? Say you got an id value of 2 from an ajax call. How would you set the selected option based on the id?
I've looked in the for solutions but I only found adding a optionsValue but I need the member of the selected option as display
Any help would be much appreciated.
You're misundertanding something. I've added the selected value in your snippet, and, if you change the id, the select list is updated correcty, and you still display what you want. I've added a bound textbox where you can type the id so that you can check it works as expected.
NOTE: just in case the comment below is what I couldn't understand from your question, I'm implementing a new writable computed observable that allos to use the whole object as selection.
var vm = (function() {
var foos = [
{ id: 1, fooName: 'fooName1', fooType: 'fooType1' },
{ id: 2, fooName: 'fooName2', fooType: 'fooType2' },
{ id: 3, fooName: 'fooName3', fooType: 'fooType3' },
],
selectedFoo = ko.observable(),
selectedFooId = ko.computed({
read: function() {
return selectedFoo() ? selectedFoo().id : null;
},
write: function(value) {
var newSel = foos.find(function(f) {return f.id == value;});
selectedFoo(newSel);
}
});
return {
selectedFooId: selectedFooId,
selectedFoo: selectedFoo,
foos: foos
};
}());
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: foos,
optionsText: 'fooName',
optionsCaption: 'Select foo',
value: selectedFoo"></select><br />
<input type=text data-bind="value: selectedFooId, valueUpdate:'keyup'" />
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

Instantiate a property from viewModel within foreach

I am iterating over an object with a knockout's foreach. Inside this foreach I render a table, and each table has a dropdown.
I need the value of the select, however the ko.observable() is not working within the foreach, because it sets each select value simultaneously. I need the individual select value of each field, not set each select to the same value.
Is there a solution to this?
<!--ko foreach: {data: thing, as: 'blah'}-->
<div data-bind="text: JSON.stringify(blah)"></div>
<select data-bind="options: $root.countries, optionsText: 'name', optionsValue: 'id', value: $root.selectedChoice, optionsCaption: 'Choose..'"></select>
<br/>
<input type="button" data-bind="click: $root.sendMe, enable: $root.selectedChoice" Value="Click Me"/>
<!--/ko-->
This is a fiddle that demonstrates with a simple example.
If you have multiple dropdowns, you're going to need multiple observables to store the selected value if you want to save individual selections. For example:
var CountryModel = function (data) {
var self = this;
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
};
var ViewModel = function (data) {
var self = this;
self.things = ko.observableArray([
{ blarg: 'blarg', selectedChoice: ko.observable() },
{ means: 'means', selectedChoice: ko.observable() },
{ yes: 'yes', selectedChoice: ko.observable() }
]);
self.countries = ko.observableArray([
new CountryModel({ id: "1", name: "Russia" }),
new CountryModel({ id: "2", name: "Qatar" })
]);
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<!--ko foreach: {data: things, as: 'thing'}-->
<div data-bind="text: ko.toJSON(thing)"></div>
<select data-bind="options: $root.countries,
optionsText: 'name',
optionsValue: 'id',
value: selectedChoice,
optionsCaption: 'Choose..'">
</select>
<hr>
<!--/ko-->

Confused of what really are optionsValue and value

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>

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