Binding radio buttons to array of objects - javascript

I have my view model:
function (dataservice, Person){
var genders = ko.observableArray();
var languages = ko.observableArray();
var person = ko.observableArray();
function activate(){
dataservice.getGenders(genders);
dataservice.getGenders(languages);
}
var vm = {
genders: genders,
languages: languages,
person: person
};
}
function Person(person){
var firtstName = person.firtstName;
var familyName = person.familyName;
var genderId = person.genderId;
var languageId = person.languageId;
}
It's simplified for clarity.
Then I have my `Genders' data from server, it looks like this:
[{
$id: "1",
GenderId: 2,
GenderName: "Female",
GenderDescription: "Female",
GenderCode: "F"
}]
I also have Languages that looks like this:
[{
"$id": "1",
"LanguageId": 2,
"LanguageName": "Afar",
"LanguageDescription": "Afar",
"LanguageCode": "aa"
}]
What I am trying to achieve is to bind the genders array from my view model as a data source and use Person.GenderId as the value to be updated, in a way such that the correct radio button is initially selected. This selection depended on Person.GenderId.
I did something similar with Languages using a drop down and that works just fine:
<section data-bind="with: $root.personModel">
<select id="language"
data-bind="options: $root.languages,
optionsText: 'LanguageName',
optionsValue: 'LanguageId',
value: LanguageId,
optionsCaption: 'none'">
</select>
</section>
Now I am trying to do the same thing with radio buttons, but I don't know how to make it work. Here's what I have:
<section data-bind="with: $root.personModel">
<!-- ko foreach: $root.genders -->
<input type="radio"
name="genders"
data-bind="attr: {value: GenderId}, checked: GenderId" />
<span data-bind="text: $data.GenderName"></span>
<!-- /ko -->
</section>
If I understood things correctly, the foreach binding works like with and changes my context, so I can't reach GenderId from my exposed Person.

change
<input type="radio" name="genders" data-bind="attr: {value: GenderId}, checked: GenderId" />
to
<input type="radio" name="genders" data-bind="attr: {value: GenderId}, checked: $parent.GenderId" />
as explained here
$parent: This is the view model object in the parent context, the one immeditely outside the current context. In the root context, this is undefined.

You will need to utilize the checkdValue binding, see the lower part of the "checked" documentation.
Your code didn't quite translate to a repro, but here's my version more or less in your scenario. Suppose this bit of code:
var Person = function(person) {
var self = this;
self.name = ko.observable(person.name);
self.gender = ko.observable(person.gender);
};
var root = {
genders: [{ $id: "1", GenderId: 1, GenderName: "Female"},
{ $id: "2", GenderId: 2, GenderName: "Male"}]
};
root.personModel = new Person({name: 'john', gender: root.genders[1]});
ko.applyBindings(root);
Alongside this markup:
<section data-bind="with: $root.personModel">
<!-- ko foreach: $root.genders -->
<input type="radio"
name="genders"
data-bind="checkedValue: $data, checked: $root.personModel.gender" />
<span data-bind="text: $data.GenderName"></span><br />
<!-- /ko -->
</section>
This should work, see this fiddle.
The objects from the genders array are bound to the checkedValue of each input, and the personModel's gender is bound to checked.

Related

How to combine templates and radio buttons in knockoutjs scripts?

I am trying to show a group of radio buttons using knockoutjs. I also want to have one of the radio buttons selected based on a certain value and know the value of the selected radio button in case someone selects a different item.
This is the code I am trying to use:
<form>
<div data-bind="template: { name: 'items-template', foreach: items }"></div>
<h3 data-bind="text: selectedItem"></h3>
</form>
</body>
<script type="text/html" id="items-template">
<input type="radio" name="skuitem" data-bind="value: id, checked: selectedItem"><span data-bind="text: name"></span><br /></input>
</script>
<script type="text/javascript">
function ItemsViewModel() {
var _this = this;
_this.items = ko.observableArray();
_this.items.push({id: "1", name: "Item A"});
_this.items.push({id: "7", name: "Item B"});
_this.items.push({id: "10", name: "Item C"});
_this.selectedItem = ko.observable("1");
}
ko.applyBindings(new ItemsViewModel());
</script>
The problem is that I get an error in the console:
ReferenceError: selectedItem is not defined
From what I understand, knockout looks into the objects contained in the items array for "selectedItem" instead of binding to the "selectedItem" variable in my ItemsViewModel.
How can I bind my radio buttons to the selectedItem observable when I'm using templates ?
Inside your template, your binding context is an item, you'll only have id and name available to link to directly.
Any properties in the ItemsViewModel can be accessed using $parent or $root.
So, you'll have to write: data-bind="value: id, checked: $parent.selectedItem"
function ItemsViewModel() {
var _this = this;
_this.items = ko.observableArray();
_this.items.push({
id: "1",
name: "Item A"
});
_this.items.push({
id: "7",
name: "Item B"
});
_this.items.push({
id: "10",
name: "Item C"
});
_this.selectedItem = ko.observable("1");
}
ko.applyBindings(new ItemsViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<form>
<div data-bind="template: { name: 'items-template', foreach: items }"></div>
<h3 data-bind="text: selectedItem"></h3>
</form>
<script type="text/html" id="items-template">
<label>
<input type="radio" data-bind="value: id,
checked: $parent.selectedItem" />
<span data-bind="text: name"></span>
</label>
</script>
Instead of solving the issue in the view, you can also solve it in your models. You can pass a reference to the selection observable to each item:
function ItemsViewModel() {
var _this = this;
_this.items = ko.observableArray();
_this.selectedItem = ko.observable("1");
_this.items.push({
id: "1",
name: "Item A",
selectedItem: _this.selectedItem
});
_this.items.push({
id: "7",
name: "Item B",
selectedItem: _this.selectedItem
});
_this.items.push({
id: "10",
name: "Item C",
selectedItem: _this.selectedItem
});
}
ko.applyBindings(new ItemsViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<form>
<div data-bind="template: { name: 'items-template', foreach: items }"></div>
<h3 data-bind="text: selectedItem"></h3>
</form>
<script type="text/html" id="items-template">
<label>
<input type="radio" data-bind="value: id,
checked: selectedItem"/>
<span data-bind="text: name"></span>
</label>
</script>

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-->

Knockoutjs model does not update when select options change

I have a computed function which doesn't update the UI when a select options change. But works fine if I add or remove a line.
This is the HTML:
<button data-bind="click: add">Add New</button>
<ul data-bind="foreach: items">
<li>
<label data-bind="text: name"></label>
<select data-bind="options: [1,2,3], value: val"> </select>
</li>
</ul>
TOTAL: <span data-bind="text: total"></span>
And this the JavaScritp:
function viewModel (initialItems) {
this.items = ko.observableArray(initialItems);
this.total = ko.computed(function () {
var total = 0;
for (var i = 0; i < this.items().length; i++)
total += this.items()[i].val;
return total;
}, this);
this.add = function() { this.items.push({name: "New", val: 1}); };
}
ko.applyBindings(new viewModel([{name: "Alpha", val: 2},
{name: "Beta", val: 3},
{name: "Gamma", val: 1}]));
And here is the fiddle: http://jsfiddle.net/waUE4/
How can I get the model update when selection change?
Thanks for your help.
Edit
Working version: http://jsfiddle.net/fCE3a/1/
The reason why the val property is not updated is that it is not declared as an Observable property.
Check out this sample code from the official KnockoutJS website, it looks like what you want to do: Cart editor example

knockout js json bind list to clicked listitem

I would like to select a specific location by Continent / Country / State / etc.
I get a JSON and I managed to display/select the Continent, but I cannot get the list of Countries to populate.
I am rather new to knockout and JS, so I am probably doing something wrong (I managed to do this based on the live examples from knockout, but those were using select, and I cannot seem to get it to work on ul)
The JSON (composed by hand, so might have errors)
var Continent = [
{ Name: "Europe", Countries: [{ Name: "England", ID: 1 }, { Name: "Wales", ID: 2 }] },
{ Name: "America", Countries: [{ Name: "US", ID: 3 }, { Name: "Canada", ID: 4 }] },
{ Name: "Asia", Countries: [{ Name: "India", ID: 5 }, { Name: "China", ID: 6 }] }
];
The javascript:
var locationVM = function () {
var self = this;
self.selectedContinent = ko.observable("none");
self.selectedCountry = ko.observable();
self.selectedContinent.subscribe(function () {
self.selectedCountry(undefined);
});
self.onClickContinent = function (data) {
self.selectedContinent(data.Name);
};
};
ko.applyBindings(new locationVM());
The HTML
<div>
<ul data-bind="foreach: Continent">
<li data-bind="text: Name, click: $parent.onClickContinent" />
</ul>
</div>Selected Continent : <span data-bind="text: selectedContinent">text</span>
<div data-bind="with: selectedContinent">
<ul data-bind="foreach: Countries">
<li data-bind="text: Name" />
</ul>
</div>
and here is the fiddle http://jsfiddle.net/norbert/WB46V/
Also, if there is anything I missed, or is not needed, please point it out, or provide link for further studies ;)
You're trying to set the continent value to a string and then later trying to iterate thru the string. What you need to do is set the continent to the data.
Something like this:
self.selectedContinent(data);
Working fiddle here.
http://jsfiddle.net/WB46V/6/
Just to add, I haven't used knockout before, so my solution in the fiddle might not be the best one.

knockout.js selectedOptions is not updated

I'm struggling with knockout.js selectedOptions binding.
I fill multiselect with items from observableArray A, choose some, store result in observableArray B. When item gets removed from array A, the B array is not updated.
Is this knockout issue or am I doing something wrong?
HTML code:
<h4>All items:</h4>
<div data-bind="foreach: items">
<p data-bind="text: name"></p>
<button data-bind="click: $parent.remove">Remove item</button>
</div>
<select multiple="multiple" data-bind="
options: items,
selectedOptions: selectedItems,
optionsText: 'name',
optionsCaption: 'Choose one or more...'
"></select>
<h4>Selected items:</h4>
<div data-bind="foreach: selectedItems">
<p data-bind="text: name"></p>
</div>
Javascript:
var viewModel = {
items: ko.observableArray([
{ name: "Item 1", id: "1" },
{ name: "Item 2", id: "2" },
{ name: "Item 3", id: "3" }
]),
selectedItems: ko.observableArray(),
remove: function(item) {
viewModel.items.remove(item);
}
}
ko.applyBindings(viewModel);
Here's the fiddle: http://jsfiddle.net/3FYAe/
How to reproduce:
select one or more items in the multiselect field, they appear in the list below ("Selected items")
remove one of the selected items
the selectbox is updated
4.
Expected: "Selected items" is updated
Actual: "Selected items" keeps deleted values
Answering my own question:
The trivial solution would be to remove the item from selectedItems array as well, i. e.
remove: function(item) {
viewModel.items.remove(item);
viewModel.selectedItems.remove(item);
}
Updated fiddle: http://jsfiddle.net/3FYAe/1/
However, I would like to find a nicer solution as I'm dealing with many more lists and many more dependencies; this is just a simplified example.

Categories

Resources