Knockout checkbox change event sends old value
Asked Answered
C

3

36

I'm having a problem with knockout "checked" binding. It seems that "change" event at checkbox return old value, before it is updated(so if it was unchecked it will return false). I don't think that I can subscribe to the value since I have it inside object.

<tbody data-bind="foreach: Categories">
                <tr>
                    <td><input type="checkbox" data-bind="checked: ShowOpened, event: { change: $root.CategoryChange }" /></td>
                </tr>
            </tbody>
<script type="text/javascript">
var Category = function (Id, Name, Order, ShowOpened) {
    this.Id = Id;
    this.Name = Name;
    this.Order = Order;
    this.ShowOpened = ShowOpened;
    this.IsUpdated = ko.observable(false);

    this.OldOrder = Order;
    this.OldShowOpened = ShowOpened;
};
var ViewModel = {
    Categories: ko.observableArray([]),
    CategoryChange: function(pCategory) {
        if(pCategory.Order != pCategory.OldOrder || pCategory.ShowOpened != pCategory.OldShowOpened)
            pCategory.IsUpdated(true);
        else
            pCategory.IsUpdated(false);
    }
};
ko.applyBindings(ViewModel);
</script>

So in this example I have ShowOpened checkbox that can trigger CategoryChange method that will change a variable inside object(that I need later to know what object are updated). But when the chechbox is changed it always send out the old value, triggering method, and then changes the value. Is there any way to fix this?

Clarissa answered 2/7, 2012 at 13:46 Comment(3)
Something's wrong with your code. Neither ShowOpened nor IsUpdated are ko.observables (and probably that's why you always get the old value). How can this code even work? CategoryChange should throw an exception.Siracusa
@Siracusa you're wright, I've made a mistake when creating this post originally. But still the error you pointed out is not the problem.Clarissa
Oi, still missing this: this.ShowOpened = ko.observable(ShowOpened); and this: pCategory.ShowOpened(). I must say, you've made a lot of mistakes while writing this post...Siracusa
S
67

Since you persisted on stating that the lack of ko.observables is not an issue, I've looked at it closer. It seems, that you are correct! The change event is fired before the actual value is set. I'm afraid I do not know the reason for this.

But there is an easy way to fix this: just change change event to click event:

<input type="checkbox" data-bind="checked: ShowOpened, click: $root.CategoryChange" />

Remember, that you have to explicitly put return true; at the end of click event handler. Otherwise the new value won't be set to checkbox.

If you do not want to use click event, then you can do it the other way. Subscribe to changes of ShowOpened:

this.ShowOpened = ko.observable(ShowOpened);
this.ShowOpened.subscribe(function(newValue) {
    /* Do something when ShowOpened changes.
       newValue variable holds the new value, obviously. :) */
});
Siracusa answered 2/7, 2012 at 15:10 Comment(6)
Great, click event works. I tried it begore, just forgot to return true, and checkbox never got checked/unchecked. Thanks.Clarissa
I had exactly the same issue with IE not recognizing the change event. Grr.Analyzer
Works perfect! Thanks. The "return true;" is key for this to work properly!Intermediate
It's nice that the click event is triggered even when I press spacebar and the checkbox has focus.Quinton
I was using two checkboxes before reading this answer as I wasn't returning true. Thanks for the nice solution.Jaguar
I had forgotten about .subscribe(func). I like that approach quite a lot.Phyliciaphylis
H
7

Try using subscribe instead of event binding. This should work now

<table>
    <tbody data-bind="foreach: Categories">
        <tr>
            <td><input type="checkbox" data-bind="checked: ShowOpened" /></td>
        </tr>
    </tbody>
<table>

var Category = function (Id, Name, Order, ShowOpened) {
    this.Id = Id;
    this.Name = Name;
    this.Order = Order;
    this.ShowOpened = ko.observable(ShowOpened);
    this.IsUpdated = false;

    this.OldOrder = Order;
    this.OldShowOpened = ShowOpened;

    this.ShowOpened.subscribe(function (newShowOpened) {
        if(this.Order != this.OldOrder || this.ShowOpened() != this.OldShowOpened)
            this.IsUpdated = true;
        else
            this.IsUpdated = false;
    }, this);
};
var ViewModel = {
    Categories: ko.observableArray([])
};
ko.applyBindings(ViewModel);

Or as alternative (and in my opinion a better solution) you can use dependentObservable, now called computed. Here's how it would look like

<table>
    <tbody data-bind="foreach: Categories">
        <tr>
            <td>
                <input type="checkbox" data-bind="checked: ShowOpened" />
                <span data-bind="text: IsUpdated"></span>
            </td>
        </tr>
    </tbody>
</table>
var Category = function (Id, Name, Order, ShowOpened) {
    this.Id = Id;
    this.Name = Name;
    this.Order = Order;
    this.ShowOpened = ko.observable(ShowOpened);
    this.OldOrder = Order;
    this.OldShowOpened = ShowOpened;
    this.IsUpdated = ko.computed(function () {
        return this.Order != this.OldOrder || this.ShowOpened() != this.OldShowOpened;
    }, this);
};
var ViewModel = {
    Categories: ko.observableArray([])
};
ko.applyBindings(ViewModel);
Hamstring answered 2/7, 2012 at 14:37 Comment(0)
S
0

I also had this issue, but i couldn't use the subscribe solution because i was already subscribed to the same field with a ajax request that could reset the value. The code would then stay in a loop when you changed it. So i added the following workaround (its ugly but it works).

$("input[type='radio'], input[type='checkbox']", element).on("change", function (e, data) {
    setTimeout(function () {
        $(this).trigger("afterChange", data);
    }.bind(this), 10);
}); 

Then instead on listening to the change i would listen to the afterChange event.

<div data-bind="foreach: answerList">
    <input type="checkbox" data-bind="event: { afterChange: $root.sendSelectedAnswer($data) }"/>
</div>

I know its not the best solution, but in my case i had no other option.

Sontag answered 11/1, 2017 at 11:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.