Bind a checkbox to a plain string in Knockout.js - javascript

In my viewmodel i have tons of checkboxes bound to plain strings:
<input type="checkbox" value="CODE" data-bind="checked: itemValue" />
Until now, i'm using an observable array to resolve the true/false value of the checkbox to the value that i need:
var viewModel = {
itemValue: ko.observableArray()
};
Which is the simplest and shortest way, if there is one, to bind a checkbox to a string value without the need to reference it as itemValue[0] ?
What i need is the string value if checked, null if unchecked.
Due to the large amount of observables in my viewmodel, i would avoid to use tons of conditions like if(itemValue) ...
Fiddle using an observableArray: https://jsfiddle.net/wu470qup/

If you want effortless markup, you'll have to keep track of the checkboxes you want to render in your viewmodel. Here's the easiest example:
allValues is a regular array of strings. Each of these strings will get a checkbox. itemValues is an observable array of the strings that have a checked checkbox. correctedValues is a computed array with null for each unchecked box, and a string for each checked one.
Notice that I use $data to refer to the current string value in the foreach.
var allValues = ["CODE", "ANOTHER_VAL"];
var itemValues = ko.observableArray();
var correctedValues = ko.computed(function() {
var all = allValues;
var checked = itemValues();
return all.map(function(val) {
return checked.indexOf(val) === -1 ? null : val;
});
});
var viewModel = {
allValues: allValues,
itemValues: itemValues,
correctedValues: correctedValues
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<!-- ko foreach: allValues -->
<label>
<input type="checkbox" data-bind="checked: $parent.itemValues, checkedValue: $data" />
<span data-bind="text: $data"><span/>
</label>
<!-- /ko -->
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
A more elegant approach would be to create viewmodels for each checkbox. For example:
var CheckboxItem = function() {
this.label = ko.observable("CODE");
this.checked = ko.observable(false);
this.id = ko.computed(function() {
return this.checked() ? this.label() : null;
}, this);
};
With the HTML template:
<label>
<input type="checkbox" data-bind="checked: checked" />
<input data-bind="value: label" />
</label>
Then, in the parent viewmodel, you can store an observable array of CheckBoxItems and have a computed array that holds all id properties.

Related

knockout.js checkboxes used to control enabled/disabled state of input fields

I am trying to bind a checkbox to each line in a list of objects, in a very similar fashion to a question asked/answered here: Binding a list of objects to a list of checkboxes
Essentially, as follows:
<ul data-bind="foreach: phones">
<li>
<input type='text' data-bind="attr: {value:phone}, disable: $root.selectedPhones"/>
<input type="checkbox" data-bind="attr: {value:id}, checked: $root.selectedPhones" />
</li>
</ul>
<hr/> selected phones:
<div data-bind="text: ko.toJSON($root.selectedPhones)"></div>
<hr/> phones:
<div data-bind="text: ko.toJSON($root.phones)"></div>
with js as follows:
function Phone(id,phone) {
this.id = id;
this.phone = phone;
}
var phones_list = [
new Phone(1, '11111'),
new Phone(2, '22222'),
new Phone(3, '33333')
];
var viewModel = {
phones: ko.observableArray(phones_list),
selectedPhones: ko.observableArray()
};
ko.applyBindings(viewModel);
The idea being that in the initial state, all of the input boxes are disabled and that clicking a checkbox will enable the input box in that row.
The data is coming from a fairly deeply nested object from the server-side so I'd like to avoid 'padding' the data with an additional boolean ie avoiding new Phone(1,'xx', false)
(a) because it's probably unnecessary (b) because the structure is almost certainly going to change...
Can the selectedPhones observable be used by the enable/disable functionality to control the status of fields in that 'row'?
Hope someone can help....
I have a jsfiddle here
You can create a small helper function which checks that a given id appers in the selectedPhones:
var viewModel = {
phones: ko.observableArray(phones_list),
selectedPhones: ko.observableArray(),
enableEdit: function(id) {
return ko.utils.arrayFirst(viewModel.selectedPhones(),
function(p) { return p == id })
}
};
Then you can use this helper function in your enable binding:
<input type='text' data-bind="attr: {value:phone}, disable: $root.enableEdit(id)"/>
Demo JSFiddle.

knockout checked binding - only one checked

I have a list of Admins with a check box. I want to be able to select only one Admin.
HTML:
<tbody data-bind="foreach: people">
<tr>
<td>
<input type="checkbox" data-bind="attr: { value: id }, checked: $root.selectedAdmin">
<span data-bind="text: name"/>
</td>
</tr>
</tbody
JS:
function Admin(id, name) {
this.id = id;
this.name = name;
}
var listOfAdmin = [
new Admin(10, 'Wendy'),
new Admin(20, 'Rishi'),
new Admin(30, 'Christian')];
var viewModel = {
people: ko.observableArray(listOfAdmin),
selectedAdmin: ko.observableArray()
};
ko.applyBindings(viewModel);
For Example if Admin id 10 is selected the other admins should be deselected.
Is that Possible to do with Knockout?
You should really use radio buttons if you only want to allow multiple selection.
However if you still want to use checkboxes then on solution would be to combine the checked and the click binding:
Use the checked to check only when the current id equal to the selectedAdmin property and use the click binding to set the selectedAdmin.
So you HTML should look like this:
<input type="checkbox" data-bind="attr: { value: id },
checked: $root.selectedAdmin() == id,
click: $parent.select.bind($parent)" />
And in your view model you just need to implement the select function:
var viewModel = {
people: ko.observableArray(listOfAdmin),
selectedAdmin: ko.observableArray(),
select: function(data) {
this.selectedAdmin(data.id);
return true;
}
};
Demo JSFiddle.
Notes:
the return true; at the end of the select function. This is required to trigger the browser default behavior in this case to check the checkbox.
the .bind($parent) is needed to set the this in the select function to be the "parent" viewModel object.

Create checkbox list from array of objects with knockout

I would like to know what is the proper way to create a list of checkboxes bound to a selected list for an array of objects using knockout. It is very straight forward using basic values like a list of words but can it be done with an array of objects.
this works:
<script type="text/JavaScript">
function ViewModel(){
self.choices = ["one","two","three","four","five"];
self.selectedChoices = ko.observableArray(["two", "four"]);
self.selectedChoicesDelimited = ko.dependentObservable(function () {
return self.selectedChoices().join(",");
});
}
</script>
<ul class="options" data-bind="foreach: choices">
<li><input type="checkbox" data-bind="attr: { value: $data }, checked: selectedChoices" /><span data-bind="text: $data"></span></li>
</ul>
<div data-bind="text: selectedChoicesDelimited"></div>
But I can't figure out how to get it to work if the items are objects like this:
self.choices = [{"Id":1,"Name":"one"},{"Id":2,"Name":"two"}, {"Id":3,"Name":"three"}, {"Id":4,"Name":"four"}, {"Id":5,"Name":"five"}];
self.selectedChoices = ko.observableArray([{"Id":2,"Name":"two"}, {"Id":4,"Name":"four"}]);
In the end I would like to be able to create checkbox lists from MultiSelectList object created in my MVC controller and passed to the view in the ViewBag.
#ebohlman answer about using CheckedValue is what I was looking for. One other note however, is that specifying the SelectedChoices directly as I did above will not work because the objects will not equal the objects in the Choices array even though the values in them are the same. Instead I've added code to push the selected choices onto the SelectedChoices array after defining it:
self.choices = [{"Id":1,"Name":"one","Selected":true},{"Id":2,"Name":"two","Selected":false}];
self.selectedChoices = ko.observableArray();
$.each(self.choices, function (index, value) {
if(value.Selected) self.selectedChoices.push(value);
});
Use the checkedValue binding:
<li>
<input type="checkbox" data-bind="checkedValue: $data, checked: $root.selectedChoices" />
<span data-bind="text: Name"></span>
</li>
And pick out the Name values when calculating the delimited string:
self.selectedChoicesDelimited = ko.computed(function () {
return ko.utils.arrayMap(self.selectedChoices(), function(v) {
return v.Name;
}).join(",");
});

How to subscribe to child observable from parent observable in Knockout

I have an KO observables like this DEMO
var list = function(){
var array = [{val :'1'}, {val :'2'}, {val :'3'}, {val :'4'}];
var that = this;
this.inputs = ko.observableArray();
array.forEach(function(obj){
var val = ko.observable(obj.val);
that.inputs.push(val);
//Subscribe to each element
val.subscribe(function(val){
console.log(val);
});
});
}
ko.applyBindings(new list());
And I am using inputs to populate data in fields like
<ul data-bind="foreach: inputs">
<input type="text" data-bind="value :$data" />
</ul>
So when ever I change the input data is there a way to know the parent is getting updated with new entered data since all values are observable.
So as per knockout-js-observable-array-changes-to-individual-observable-items
I make each item as observable and try to subscribe the changes
//Subscribe to each element
val.subscribe(function(val){
console.log(val);
});
but that too doesn't work , how to implement this knockout-js-observable-array-changes-to-individual-observable-items solution.
Don't put ko.observable directly into your ko.observableArray but create objects which have observable properties.
So change your array filling code to:
var val = ko.observable(obj.val);
that.obs.push({val: val});
And your view to:
<ul data-bind="foreach: inputs">
<input type="text" data-bind="value: val" />
</ul>
Demo JSFiddle.

Knockoutjs. How to compute data changed inside observable array

Please, look at my text. I try to use observableArray of knockoutjs and foreach to compute data of array.
Example 1 works fine: total sum computed if you change data in the fields. But Example 2 is not working.
<html>
<head>
<title></title>
<script type='text/javascript' src='/js/jquery-1.8.2.min.js'></script>
<script type='text/javascript' src='/js/knockout-2.1.0.debug.js'></script>
</head>
<body>
<p>Example 1</p>
<div>
<p>
<input data-bind="value: fnum1" />
<input data-bind="value: fnum2" />
<span data-bind="text: ftotsum"></span>
</p>
</div>
<p>Example 2</p>
<div>
<p>
<!-- ko foreach: fields -->
<input data-bind="value: $data" />
<!-- /ko -->
<span data-bind="text: ltotsum"></span>
</p>
</div>
</body>
<script>
function vm(){
//Calc Example 1
var self = this;
self.fnum1 = ko.observable(1);
self.fnum2 = ko.observable(2);
self.ftotsum = ko.computed(function(){
return parseFloat(self.fnum1()) + parseFloat(self.fnum2());
});
//Calc Example 2
self.fields = ko.observableArray([1, 2]);
self.ltotsum = ko.computed(function(){
var total = 0;
ko.utils.arrayForEach(self.fields(), function(item) {
total += parseFloat(item);
})
return total;
});
};
ko.applyBindings(new vm());
</script>
</html>
EDIT: Got fiddle working, Raffaele is correct in saying you need to wrap the observable inside an object, but you can do it within the array creation itself and I like to use the ko.utils to unwrap my observables, it does the same thing for observables but it won't crash if there is a non-observable passed to it. See fiddle for full example.
An observableArray doesn't make the values passed observable, this is a common mistake. An observableArray just observes the modifications to the array and not the values. If you want to have your values inside your array be observable you have to make them so.
function vm(){
//Calc Example 1
var self = this;
self.fnum1 = ko.observable(1);
self.fnum2 = ko.observable(2);
self.ftotsum = ko.computed(function(){
return parseFloat(self.fnum1()) + parseFloat(self.fnum2());
});
//Calc Example 2
self.fields = ko.observableArray([{"num":ko.observable(1)},{"num":ko.observable(2)}]);
self.ltotsum = ko.computed(function(){
var total = 0;
ko.utils.arrayForEach(self.fields(), function(item) {
total += parseFloat(ko.utils.unwrapObservable(item.num));
});
return total;
});
};
ko.applyBindings(new vm());
Should work with the example above now.
The documentation says:
Key point: An observableArray tracks which objects are in the array, not the state of those objects
Simply putting an object into an observableArray doesn’t make all of
that object’s properties themselves observable. Of course, you can
make those properties observable if you wish, but that’s an
independent choice. An observableArray just tracks which objects it
holds, and notifies listeners when objects are added or
removed.
Your second example doesn't work because the value of the input fields is not bound to the values in the array. That values in the array are used only once, in the foreach binding, but when you type in the input boxes, nothing triggers KO.
Here is a working fiddle with a solution implemented. I used a helper ObsNumber
function vm(){
var self = this;
var ObsNumber = function(i) {
this.value = ko.observable(i);
}
self.fields = ko.observableArray([new ObsNumber(1) ,
new ObsNumber(2)]);
self.sum = ko.computed(function(){
var total = 0;
ko.utils.arrayForEach(self.fields(), function(item) {
total += parseFloat(item.value());
});
return total;
});
};
ko.applyBindings(new vm());
and the following markup
<div>
<p>
<!-- ko foreach: fields -->
<input data-bind="value: $data.value" />
<!-- /ko -->
<span data-bind="text: sum"></span>
</p>
</div>​

Categories

Resources