Checkbox onclick in knockout is not updating viewmodel - javascript

I have list of check boxes, on click of any checkbox i need to get all checked check boxes. To do that I am calling the java script function "ChangeColumnSelection".
The issue is, the clicked check box is not updating view model immediately. When i click next text box, I am seeing that the previously check box value got updated in the view model.
<ul class="list-group" data-bind="foreach: SelectionColumnList">
<li class="list-group-item">
<input type="checkbox" data-bind="attr: {onclick: 'javascript:ChangeColumnSelection(\'' + ColumnID + '\')'}, checked: IsSelected"
class="pull-xs-left push-down rightmargin" />
<span data-bind="text: ColumnName"></span>
</li>
</ul>
Update:
My view model is
var dynamicGridViewModel = {
SelectionColumnList: ko.observableArray([])
};
selectionInfo.ColumnID = columnInfo.ColumnID;
selectionInfo.ColumnName = columnInfo.ColumnName;
selectionInfo.DisplayOrder = columnInfo.DisplayOrder;
selectionInfo.SortType = 'None';
selectionInfo.IsSelected = true;
dynamicGridViewModel.SelectionColumnList.push(selectionInfo);

You don't need onclick events. You can achieve this just with checked binding:
var array = [{
ColumnID: 1,
ColumnName: "ColumnName 1"
}, {
ColumnID: 2,
ColumnName: "ColumnName 2"
}]
var viewModel = function() {
var self = this;
self.SelectionColumnList = ko.observableArray(array);
// no need to populate the array manually. Knockout will take care of it
self.chosenItems = ko.observableArray();
// every time chosenItems array changes, subscribe callback function gets triggered
self.chosenItems.subscribe(function() {
console.log(self.chosenItems());
})
}
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul class="list-group" data-bind="foreach: SelectionColumnList">
<li class="list-group-item">
<input type="checkbox" data-bind="checkedValue: ColumnID, checked: $parent.chosenItems" />
<span data-bind="text: ColumnName"></span>
</li>
</ul>
Here, ColumnId is set as the value of checkbox input. Hence, the chosenItems array will be an array of selected ColumnIds.
The great thing about Knockout is that, it allows not only primitive types like string, number or bool for checkedValue, but objects too. If you want the entire Column object to be populated in chosenItems, then you can set the checkedValue like this:
<input type="checkbox" data-bind="checkedValue: $data, checked: $parent.chosenItems" />
If you want to perform some operation upon changing of any checkbox's state, you can perform that inside the subscribe callback. This function gets triggered every time the array changes.
(Also, the proper way to add a click binding is data-bind="click: clickFunction")
UPDATE:
You're using an object literal as your viewModel. I suggest you create viewModel function and use the new operator. If you want to bind checked to a boolean property of Column, then you can create a computed property and subscribe to that computed property:
var columns = [{
ColumnID: 1,
ColumnName: "ColumnName 1",
IsSelected: false
}, {
ColumnID: 2,
ColumnName: "ColumnName 2",
IsSelected: true
}];
var viewModel = function() {
var self = this;
self.SelectionColumnList = ko.observableArray([]);
// this property has the selected ColumnIds
self.selectedItems = ko.computed(() => {
// If you're using ES6 systax
// return self.SelectionColumnList()
// .filter(column => column.IsSelected())
// .map(column => column.ColumnID);
// out of the columns, get the ColumnIds with IsSelected as true
return self.SelectionColumnList()
.filter(function(column) {
return column.IsSelected();
})
.map(function(column) {
return column.ColumnID
});
});
// gets triggered everytime checkbox is checked/unchecked
self.selectedItems.subscribe(function() {
console.log(self.selectedItems());
});
}
// create a new instance of the viewmodel
var dynamicGridViewModel = new viewModel();
// loop through the columns and populate the observableArray
columns.forEach(function(columnInfo) {
var selectionInfo = {};
selectionInfo.ColumnID = columnInfo.ColumnID;
selectionInfo.ColumnName = columnInfo.ColumnName;
// this property must be an observable
selectionInfo.IsSelected = ko.observable(columnInfo.IsSelected);
dynamicGridViewModel.SelectionColumnList.push(selectionInfo);
})
ko.applyBindings(dynamicGridViewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul class="list-group" data-bind="foreach: SelectionColumnList">
<li class="list-group-item">
<input type="checkbox" data-bind="checked: IsSelected" />
<span data-bind="text: ColumnName"></span>
</li>
</ul>
Here's a fiddle for testing

Related

Knockout checkedValue

I've got a question regarding checkedValue of checked binding in Knockout (http://knockoutjs.com/documentation/checked-binding.html)
Basically, look this fiddle http://jsfiddle.net/jo9dfykt/1/.
Why if I click the button "Add Item" the right choise is selected but if I click the second button "Add Item Two" this not succed?
What are the differences between addItem and addItem2 method in viewModel?
And then, why the with selectedValue shows nothing?
My goal is to bind a list of radio button to an observableArray and save the entire object into an observable. But I want to set the inital value of radio button without search it on observableArray.
Javascript
var viewModel = {
items: ko.observableArray([
{ itemName: 'Choice 1' },
{ itemName: 'Choice 2' }
]),
chosenItem: ko.observable(),
addItem: function() {
this.chosenItem(this.items()[0]);
},
addItem2: function() {
this.chosenItem({itemName: 'Choice 2'});
}
};
viewModel.chosenItem.subscribe(function(newValue){
console.log(newValue.itemName);
}),
ko.applyBindings(viewModel);
HTML
<!-- ko foreach: items -->
<input type="radio" data-bind="checkedValue: $data, checked: $root.chosenItem" />
<span data-bind="text: itemName"></span>
<!-- /ko -->
<input type="button" data-bind="click: addItem" value="Add Item"/>
<input type="button" data-bind="click: addItem2" value="Add Item Two"/>
<div>Selected Value: <span data-bind="text: chosenItem.itemName"></span></div>
Knockout computes the checked state by doing a reference check. I.e.:
checkedValue = $data; // from checkedValue: $data
checked = checkedValue === chosenItem() // from checked: $root.chosenItem
You'll probably know that, in javascript, two objects that look similar are not equal:
{ a: 1 } === { a: 1 } // returns false
That's why you need to rewrite your second button to:
this.chosenItem({itemName: this.items()[0]});
to make sure the reference check passes.
Also, to correctly show the current state, use:
<div data-bind="with: chosenItem">
Selected Value: <span data-bind="text: itemName"></span>
</div>
If you want to circumvent having to point to the elements in your items array, you can use checkedValue: itemName and addItem() { this.chosenItem("Choice 1"); } but this forces you to use unique names.
A better option would be to make viewmodels for your items that have their own add method:
var Item = function(name, selection) {
var self = this;
this.itemName = name;
this.add = function() {
selection(self);
};
};
var items = ["Choice 1", "Choice 2"].map(function(name) {
return new Item(name, vm.chosenItem);
});
In the first example you're providing a reference to the actual item you want selected. In the second you're providing an object which has the same property & value, but its not the actual item in the list merely one which looks like it.
But I want to set the inital value of radio button without search it on observableArray.
Perhaps make chosentItem a computed observable, which relies on a string observable with just the key of the item you want selected. This would allow you to say something like:
self.chosenItemName('Choice2');

Knock out Drop Down - Using Bool Values

I am working on a drop down control that is powered by Boolean values. I have a viewmodel that consists of the lookup dataset array (values and text attributes) and also a dataset that contains a Boolean value that needs to be aligned with that value chosen in the array. So data in the viewmodel represents an actual data item, while lookupdata represents essentially a mapping of value pairs. These two items really need to stay independent of eachother for the purposes of each data item containing only information relevant to it.
I have been able to get a sample working where I brought IsActive outside of the data object as it exists in viewmodel and placed it in viewmodel, however I can't get it to work while it is inside of viewmodel. I have created a JSFiddle that demonstrates functionally what I want the user to see, as well as one where I have the data where it belongs, but the drop down does not work as expected.
Active maps to true and Inactive maps to false. Switching between those should also affect the word true/false on the screen.
http://jsfiddle.net/rodearly/xF78A/11/
<div data-bind="with: data">
<select data-bind="options: $root.lookupData.status, optionsText: 'text', optionsValue: 'value', value: IsActive"></select>
<label>Value: </label>
<span data-bind="text: IsActive"></span>
</div>
<div>Alternative</div>
<div>
<select data-bind="options: lookupData.status, optionsText: 'text', optionsValue: 'value', value: IsActive"></select>
<label>Value: </label>
<span data-bind="text: IsActive"></span>
</div>
function Item(id, name) {
this.id = ko.observable(id);
this.name = ko.observable(name);
}
function getLookupData() {
var lookupData = {};
lookupData.status = [{
text: "Active",
value: true
}, {
text: "Inactive",
value: false
}];
return lookupData;
}
CreateImplantEditViewModel = function (data, lookupData) {
var vm = {};
vm.data = ko.observable(data);
vm.IsActive = ko.observable(false);
vm.lookupData = {};
vm.lookupData.status = ko.observableArray(lookupData.status);
return vm;
};
debugger;
var editImplantVm = CreateImplantEditViewModel({
IsActive: false
},
getLookupData());
ko.applyBindings(editImplantVm);
Thanks for any help,
~David
If I understand what you are attempting to do, your problem lies in the data that you are passing in to the 'CreateImplantEditViewModel' constructor. You need to create IsActive as an observable, otherwise it will never update:
var editImplantVm = CreateImplantEditViewModel({
IsActive: ko.observable(false)
},
getLookupData());
I've updated your jsfiddle, and both dropdowns now update their associated text binding when you change the selected option.
The problem here actually is that the Boolean values are being converted into string values. To get this to work, value needs to be either a string or a number.

Knockout js: Code to bind an object rather than individual values

This puts a checkbox next to each item of a list where changing the checked status adds/removes that value from the SelectedItems array:
<script type="text/x-jquery-tmpl" id="tmpl">
<input name="cSelect"
type="checkbox"
value="${ ID }"
data-bind="checked: VM.SelectedItems" />
<!-- Add other item content here -->
</script>
VM.SelectedItems = ko.observeableArray([]);
At any point, SelectedItems contains the ids of the checked items.
What if I wanted the checkbox to add and remove an object to SelectedItems? For example, I want to store an actual object of { id : 3, checked : true } instead of serializing it for the value attribute?
When using the checked binding against an array, it will only work with an array of primitives.
One option is to create a computed observable that builds your array of objects from the selected ids.
var ViewModel = function() {
var self = this;
this.items = [{id: 1, name: "one"}, {id: 2, name: "two"}, {id: 3, name: "three"}];
this.selectedIds = ko.observableArray();
this.selectedItems = ko.computed(function() {
return ko.utils.arrayMap(self.selectedIds(), function(id) {
return ko.utils.arrayFirst(self.items, function(item) {
return item.id == id; //selected id will be a string
});
});
});
};
ko.applyBindings(new ViewModel());
If you are dealing with a large number of items, then you might want to build an object that is an index of the items by key, so that you can loop through selectedIds and directly grab each object to reduce the amount of looping.
Here is a sample: http://jsfiddle.net/rniemeyer/pQQsY/
With KnockoutJS 3.0.0 you can use the checkedValue parameter:
<input name="cSelect" type="checkbox" value="${ ID }" data-bind="checkedValue: $data, checked: VM.SelectedItems" />
If your binding also includes checkedValue, this defines the value
used by the checked binding instead of the element’s value attribute.
This is useful if you want the value to be something other than a
string (such as an integer or object), or you want the value set
dynamically.
More details in the documentation
We can use like
<input type="checkbox" data-bind="attr: {value: JSON.parse($data).Id}, checked: $root.selectedIds"/>
and write a click event in the checkbox to get a selected data or subscripe method for selectedIds and get the selected id entire details as a JSON and we have to use JSON.parse to get the data.
but I don't how to store entire object with out JSON.

How to create an observable array with undo?

I am trying to add knockout JS to a search page on our website. Currently you open up a jQuery dialog box, which has a number of checkboxes of criteria that you can select.
There are multiple dialogs with multiple types of criteria. When you open the dialog, the checkboxes do not take effect until you hit an "Update" button, if you click cancel or just close the window, the changes you made get reverted and the dialog is set to its former state.
I read this and a few other posts. However this seems to only work with ko.observable, and I cannot seem to get it to work with ko.observableArray.
Has anyone accomplished this or have any ideas?
An example of what I want to do:
Html:
<form>
<div>
<div>
<label><input type="checkbox" data-bind="checked: genders" value="1" />Male</label>
<label><input type="checkbox" data-bind="checked: genders" value="2" />Female</label>
</div>
</div>
<a id="buttonCancel">Cancel</a>
<a id="buttonUpdate">Update</a>
</form>
<div data-bind="text: ko.toJSON(viewModel)"></div>
Javascript:
var viewModel = {
genders: ko.observableArrayWithUndo([])
};
ko.applyBindings(viewModel);
$('#buttonCancel').click(function(){
viewModel.genders.resetChange();
});
$('#buttonUpdate').click(function(){
viewModel.genders.commit();
return false;
});
Here would be one way to approach it:
//wrapper to an observableArray of primitive types that has commit/reset
ko.observableArrayWithUndo = function(initialArray) {
var _tempValue = ko.observableArray(initialArray.slice(0)),
result = ko.observableArray(initialArray);
//expose temp value for binding
result.temp = _tempValue;
//commit temp value
result.commit = function() {
result(_tempValue.slice(0));
};
//reset temp value
result.reset = function() {
_tempValue(result.slice(0));
};
return result;
};
You would bind your checkboxes to yourName.temp and the other part of your UI to just yourName.
Here is a sample: http://jsfiddle.net/rniemeyer/YrfyW/
The slice(0) is one way to get a shallow copy of an array (or even just slice()). Otherwise, you would be performing operations on a reference to the same array.
Given HTML similar to:
<div>
<button data-bind="click: function() { undo(); }">Undo</button>
<input data-bind="value: firstName" />
<input data-bind="value: lastName" />
<textarea data-bind="value: text"></textarea>
</div>
You could use some Knockout code similar to this, basically saving the undo stack as a JSON string representation of the state after every change. Basically you create a fake dependent observable to subscribe to all the properties in the view, alternatively you could manually iterate and subscribe to each property.
//current state would probably come from the server, hard coded here for example
var currentState = JSON.stringify({
firstName: 'Paul',
lastName: 'Tyng',
text: 'Text'
})
, undoStack = [] //this represents all the previous states of the data in JSON format
, performingUndo = false //flag indicating in the middle of an undo, to skip pushing to undoStack when resetting properties
, viewModel = ko.mapping.fromJSON(currentState); //enriching of state with observables
//this creates a dependent observable subscribed to all observables
//in the view (toJS is just a shorthand to traverse all the properties)
//the dependent observable is then subscribed to for pushing state history
ko.dependentObservable(function() {
ko.toJS(viewModel); //subscribe to all properties
}, viewModel).subscribe(function() {
if(!performingUndo) {
undoStack.push(currentState);
currentState = ko.mapping.toJSON(viewModel);
}
});
//pops state history from undoStack, if its the first entry, just retrieve it
window.undo = function() {
performingUndo = true;
if(undoStack.length > 1)
{
currentState = undoStack.pop();
ko.mapping.fromJSON(currentState, {}, viewModel);
}
else {
currentState = undoStack[0];
ko.mapping.fromJSON(undoStack[0], {}, viewModel);
}
performingUndo = false;
};
ko.applyBindings(viewModel);
I have a sample of N-Level undo with knockout here:
http://jsfiddle.net/paultyng/TmvCs/22/
You may be able to adapt for your uses.

Working with a list of checkboxes in knockoutjs

I'm trying to get my head around Knockout.js and I'm quite stuck when it comes to checkboxes.
Server side I'm populating a set of checkboxes with their corresponding values. Now, when any of the unchecked checkboxes are checked, I need to store it's value in a comma-seperated string. When they're unchecked, the value needs to be deleted from the string.
Have anyone got a hint on how to achieve this with knockoutjs?
I have the following code so far:
ViewModel:
$().ready(function() {
function classPreValue(preValue)
{
return {
preValue : ko.observable(preValue)
}
}
var editOfferViewModel = {
maxNumOfVisitors : ko.observable(""),
goals : ko.observable(""),
description : ko.observable(""),
contact : ko.observable(""),
comments : ko.observable(""),
classPreValues : ko.observableArray([]),
addPreValue : function(element) {
alert($(element).val());
this.classPreValues.push(new classPreValue(element.val()));
}
};
ko.applyBindings(editOfferViewModel);
});
And my checkboxes are populated with a foreach loop:
<input data-bind="checked: function() { editOfferViewModel.addPreValue(this) }"
type="checkbox" checked="yes" value='#s'>
#s
</input>
I try to pass the checkbox element as the parameter to my addPreValue() function, but nothing seems to happen when I check the checkbox?
Any help/hints on this is greatly appreciated!
The checked binding expects to be passed a structure that it can read/write against. This could be a variable, an observable, or a writable dependentObservable.
When passed an array or observableArray, the checked binding does know how to add and remove simple values from the array.
Here is a sample that also includes a computed observable that contains the array as comma delimited values. http://jsfiddle.net/rniemeyer/Jm2Mh/
var viewModel = {
choices: ["one", "two", "three", "four", "five"],
selectedChoices: ko.observableArray(["two", "four"])
};
viewModel.selectedChoicesDelimited = ko.computed(function() {
return this.selectedChoices().join(",");
}, viewModel);
ko.applyBindings(viewModel);
HTML:
<ul data-bind="template: { name: 'choiceTmpl', foreach: choices, templateOptions: { selections: selectedChoices } }"></ul>
<script id="choiceTmpl" type="text/html">
<li>
<input type="checkbox" data-bind="attr: { value: $data }, checked: $item.selections" />
<span data-bind="text: $data"></span>
</li>
</script>
Why isn't there a Mutually exclusive checkboxes example Online somewhere
Since this link came up first whilst I was searching for mutually exclusive checkboxes I will share my answer here. I was banging my head against the wall with all my attempts. By the way, when you handle the click event in a binding in-line knockoutjs it seems to disconnect the bindings(maybe only because I tried to call my resetIllnesses function as defined below) even if you return true from the function. Maybe there is a better way but until then follow my lead.
Here is the type I needed to bind.
var IllnessType = function (name,title) {
this.Title = ko.observable(title);
this.Name = ko.observable(name);
this.IsSelected = ko.observable(false);
};
The array to bind with.
model.IllnessTypes = ko.observableArray(
[new IllnessType('IsSkinDisorder', 'Skin Disorder'),
new IllnessType('IsRespiratoryProblem', 'Respiratory Problem'),
new IllnessType('IsPoisoning', 'Poisoning'),
new IllnessType('IsHearingLoss', 'Hearing Loss'),
new IllnessType('IsOtherIllness', 'All Other Illness')]
);
The reset illness function to clear them all.
model.resetIllnesses = function () {
ko.utils.arrayForEach(model.IllnessTypes(), function (type) {
type.IsSelected(false);
});
};
The markup
<ul data-bind="foreach:IllnessTypes,visible: model.IsIllness()">
<li><label data-bind="html: Title"></label></li>
<li><input class="checkgroup2" type="checkbox"
data-bind="attr:{name: Name },checked:IsSelected" /></li>
</ul>
This just doesn't work
If you have been struggling with trying to call the resetIllness function as I below, you will feel my pain.
<input type='checkbox' data-bind="checked:IsSelected,
click: function() { model.resetIllnesses(); return true; }" />
you have been sharing my pain. Well, it works! when you call it from following example.
Notice that there is a class that I added above so that I can add the click function.
The script that makes all your problems go away.
<script type="text/javascript">
$(function() {
$(".checkgroup2").on('click', function() {
model.resetIllnesses();
var data = ko.dataFor(this);
data.IsSelected(true);
});
});
</script>
Send info to the server
Also, in my case I had to send the information up to the server differently than the default html format so I changed the inputs a little.
<input class="checkgroup2" type="checkbox" data-bind="checked:IsSelected" />
<input type="hidden" data-bind="attr:{name: Name },value:IsSelected" />

Categories