Reputation: 9603
If you've got a checkbox on a form, to which you bind a function on click using Knockout, it appears to override programming control of the checkbox status.
There's a fiddle demonstrating the problem here: http://jsfiddle.net/Y5Zk8/
Code:
<input type="checkbox" id="thisFails" data-bind="click: $root.Fails" />
<label for="thisFails">This Fails</label>
var SimpleModel = function() {
this.Fails = function() {
alert('clicked');
$('#thisFails').attr('checked', true);
}
};
ko.applyBindings(new SimpleModel());
Now, I'm well aware that if I return true or false from my function, it'll work. But imagine for a moment that I can't do that (there's reasons for this - it's complicated). Why can't I control the value of the box in JQuery?
Upvotes: 0
Views: 1768
Reputation: 1617
As per this other stackoverflow answer:
and summarised as:
Allowing the default action
By default, Knockout will prevent the event from taking any default action. For example if you use the event binding to capture the keypress event of an input tag, the browser will only call your handler function and will not add the value of the key to the input element’s value. A more common example is using the click binding, which internally uses this binding, where your handler function will be called, but the browser will not navigate to the link’s href. This is a useful default because when you use the click binding, it’s normally because you’re using the link as part of a UI that manipulates your view model, not as a regular hyperlink to another web page.
However, if you do want to let the default action proceed, just return true from your event handler function.
So the default is that unless you return true, the default event will be prevented.
If it's checked value you want to control you could the following, it's a bit long winded mind you and a definite hack, but it works.
Setup an observable on your viewmodel to hold whether the checkbox is checked or not
self.isChecked = ko.observable(false);
Setup a computed observable on your vm that will be used to bind to the checkbox checked value;
self.isCheckedCp = ko.computed(function(){
return self.isChecked();
});
Bind the computed to the checked attribute in the html:
<input type="checkbox" id="thisFails" data-bind="click: $root.Fails,checked:isCheckedCp" />
Change the Fails function to incorporate a timeout that will run immediately after the fails function completes, and in that function, set the value of the underlying isChecked observable (in the example it just toggles the current value)
self.Fails = function(e) {
console.log('e',e.isChecked());
alert('arse');
console.log( $('#thisFails'));
setTimeout(function(){
console.log('set now');
//this works
self.isChecked(!self.isChecked());
//still doesn't work with the set timeout
$('#thisFails').attr('checked', true);
console.log('e',e.isChecked());
}, 0);
}
If you step through the execution of it, you will see that the when it gets to the setTimeout console.log('set now'); that the checkbox has reverted to what it was when you clicked it.
Setting the self.isChecked then updates the observable and the computed is accessed and the checkbox display is updated.
Now the reason that works is quite a bit beyond my knowledge of browsers and their execution paths, but I think the setTimeout with the zero timeout value effectively adds this bit of code to run immediately after the current (click in this case) function, this link goes into some detail:
Why is setTimeout(fn, 0) sometimes useful?
I can't work out why setting checked attribute via your original jquery doesn't work when tried in that timeout function though.
I hope this actually helps you somehow!
Upvotes: 5