Joel Cochran
Joel Cochran

Reputation: 7768

Can you force KnockoutJS to reevaluate bindings?

I'm using KnockoutJS and MVVM in a page and most if it works, but I'm having trouble getting all the bindings to reevaluate. For instance, I have a button that I want to enable when certain criteria are met:

<input type="submit" value="Purchase" data-bind="{enable: IsPurchaseValid}" />

IsPurchaseValid is a computed function of my viewmodel:

viewModel.IsPurchaseValid = ko.computed(function() {
console.log("IsPurchaseValid: function entered...");

if (this.Duration() == null ||  this.Total() <= 0 || this.SelectedPackageId() < 0) {
    console.log("IsPurchaseValid: Something is missing...");
    return false;
}

return this.IsLocalityCountValid();
}, viewModel);

The button is correctly disabled when the page loads, but is never reevaluated. IsLocalityCountValid is another computed function and the console.log statements indicate it is returning true. Chrome's console shows no scripting errors.

How do I get the enable binding to reevaluate correctly? I also have a span with visible bound to !IsLocalityCountValid that never turns visible. I feel like I'm missing something basic but can't figure out what it may be.

Upvotes: 1

Views: 2845

Answers (5)

John Papa
John Papa

Reputation: 22338

I have a very basic repro for enable where it works. (see fiddle below)

I suggest you check your CSS. I ran into this problem once because I had somehow deleted my button:disabled style. I had a style for the button that changed the background color and the font color, but somehow I removed the button:disabled style .... so the net result was that when it was disabled, it looked exactly the same as when it was enabled.

Anyway, you can test the button enabling/disabling here: You can test this for yourself here: http://jsfiddle.net/johnpapa/wLKS6/

The problem did not happen to me when there is no CSS to interfere. So the key may be to make sure your CSS classes are behaving first.

Upvotes: 2

Joel Cochran
Joel Cochran

Reputation: 7768

So it appears that this is actually a bug. With the original code, if you bind it to an input type "submit" it does not work, but if you bind it to an input type "button" it does work. I submitted it to the KnockoutJS google group.

EDIT: I'm going to accept this as the answer, but in JSFiddle both button and submit work. My assumption is that there must be some cross-jquery library conflicts as I have several other libraries at play in the actual site as well.

Upvotes: 0

Judah Gabriel Himango
Judah Gabriel Himango

Reputation: 60051

I just noticed that your databinding statement is wrapped in curly braces:

data-bind="{enable: IsPurchaseValid}"

Try without the curly braces:

data-bind="enable: IsPurchaseValid"

Upvotes: 1

Jeffrey Yasskin
Jeffrey Yasskin

Reputation: 5732

You mention that IsLocalityCountValid has changed to true, but you don't mention whether Duration has changed to non-null, Total has changed to be >0, and SelectedPackageId has changed to >=0. If none of them change, Knockout won't re-evaluate the computed because it returned false before needing to evaluate IsLocalityCountValid. See http://knockoutjs.com/documentation/computedObservables.html#how_dependency_tracking_works for some more details.

Upvotes: 1

Judah Gabriel Himango
Judah Gabriel Himango

Reputation: 60051

This may be a case where KO's evaluation of your computed value doesn't see all the observables, and thus, doesn't know when to reevaluate the computed value.

For example, if KO sees that this.Duration is not null, it may not evaluate the other observables in your if block, and thus, may not know to reevaluate your computed value if those other observables change.

Try this on for size, see if it works:

viewModel.IsPurchaseValid = ko.computed(function() {
   console.log("IsPurchaseValid: function entered...");

   // Evaluate all dependent observables up front.
   // This will let KO know which observables this computed value is dependent on.
   var duration = this.Duration();
   var total = this.Total();
   var packageId = this.SelectedPackageId();
   var islocalityCountValid = this.IsLocalityCountValid();
   if (duration == null || total <= 0 || packageId < 0) {
       console.log("IsPurchaseValid: Something is missing...");
       return false;
   }

   return isLocalityCountValid;
}, viewModel);

Upvotes: 1

Related Questions