4

I have been struggling for a few days with knockout data binding. The simple yet very annoying example of problem that I struggle with is down below.

I have a simple ViewModel class with a method that changes boolean from false to true. Knockout does bind with HTML on page load, but there seems to be a problem with data bind on click event.

Observable does change when I debug the code, but View stays the same.

Whole code and (not)working example are down below.

function ViewModel (data) {
 
 var self = this;
 
 this.textFlag = ko.observable(false);
 
 this.changeText = function (eventID, panelStatus) {
  this.textFlag = ko.observable(false);  
  
  if(eventID == 1) this.textFlag(panelStatus);
 }
}

var viewModel = new ViewModel();
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h1 data-bind="text: textFlag() === true"></h1>
<button data-bind="click: changeText(1, true)">Button</button>

See example on jsfiddle

Durga
  • 15,263
  • 2
  • 28
  • 52
sarac
  • 43
  • 5
  • See Durga's edit, you can make your live example **right here on site**, instead of offsite in a fiddle, most of the time. – T.J. Crowder Aug 25 '17 at 11:46
  • Please explain what you want to do in a bigger context. It's obvious where your mistake is and what to do to resolve it, but that's still not using knockout properly. There likely is a better way to achieve what you want to do, if you explain what this is for. – Tomalak Aug 25 '17 at 11:50

3 Answers3

2

Two issues:

  1. You're recreating your observable in changeText. You need to remove the this.textFlag = ko.observable(false); line. You've already done that bit.

  2. Your click handler is defined incorrectly. Remember that KO takes your bindings and effectively creates an object initializer out of them. Let's look at the one that would create:

    {
        click: changeText(1, true)
    }
    

    See the problem? That calls changeText(1, true) and assigns the result of the call to click. Instead, you want to provide a function reference, so click can call the function.

    (Obviously, what KO actually does is more complicated than that, with lots of with wrappers, but eventually that's what it does.)

Once you remove the errant line from changeText, you could do this:

<button data-bind="click: changeText.bind($data, 1, true)">Button</button>

Live Example:

function ViewModel (data) {
 
 var self = this;
 
 this.textFlag = ko.observable(false);
 
 this.changeText = function (eventID, panelStatus) {
     
  if(eventID == 1) this.textFlag(panelStatus);
 }
  }

var viewModel = new ViewModel();
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h1 data-bind="text: textFlag() === true"></h1>
<button data-bind="click: changeText.bind($data, 1, true)">Button</button>

...but it may be better to define a function on the viewmodel that uses 1, true:

this.changeText1True = function() { return this.changeText(1, true); };

...and call that:

<button data-bind="click: changeText1True">Button</button>

(Obviously, use a better name.)

Live Example:

function ViewModel (data) {
 
 var self = this;
 
 this.textFlag = ko.observable(false);
 
 this.changeText = function (eventID, panelStatus) {
     
  if(eventID == 1) this.textFlag(panelStatus);
 }
  
  this.changeText1True = function() {
    return this.changeText(1, true);
  }
}

var viewModel = new ViewModel();
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h1 data-bind="text: textFlag() === true"></h1>
<button data-bind="click: changeText1True">Button</button>
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
2

You are redefining this.textFlag variable, thus breaking the binding. All you have to do is update its value using this.textFlag(panelStatus); like this :

function ViewModel(data) {

  var self = this;

  this.textFlag = ko.observable(false);

  this.changeText = function(eventID, panelStatus) {
    if (eventID == 1) {
      this.textFlag(panelStatus);
    }
  }
}

var viewModel = new ViewModel();
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h1 data-bind="text: textFlag() === true"></h1>
<button data-bind="click: function () { changeText(1, true) }">Button</button>

As stated in TJ Crowder answer you also have to either wrap your call to your function inside another anonymous function as seen in docs or create another function using bind like suggested.

Serge K.
  • 5,303
  • 1
  • 20
  • 27
  • (I'm very surprised that anonymous function works, I would have thought `this` would get lost. Been a while since I did significant KO work, and clearly it *does* work as your snippet works just fine. Huh.) – T.J. Crowder Aug 25 '17 at 11:53
  • @T.J.Crowder I guess it was because we posted at the same time and showed both different issues, and yours seemed more relevant than mine to the downvoter. About the anonymous function, I mostly used KO like that, and as I said it is the way they show us in the docs. – Serge K. Aug 25 '17 at 11:54
  • 1
    (@T.J.Crowder I have to say I'm honored to surprise you :D) – Serge K. Aug 25 '17 at 11:57
  • 1
    Yeah. I have to admit I didn't catch the `textFlag` thing until I went back to add snippets (seeing that Durgo had made that so easy) and I was there saying "Um....why isn't it changing?" (And then eventually seeing it.) Anyway, good answer. – T.J. Crowder Aug 25 '17 at 12:01
  • Thank you very much. @NathanP. – sarac Aug 25 '17 at 12:01
-1

function ViewModel (data) {
 
 var self = this;
 
 self.textFlag = ko.observable(false);
 
 self.changeText = function (eventID, panelStatus) {
    
  if(eventID === 1) {
   self.textFlag(panelStatus);
  }
 }
}

var viewModel = new ViewModel();
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h1 data-bind="text: textFlag"></h1>
<button data-bind="click: changeText.bind($data, 1, true)">Button</button>

Made some change to your code.

Phael
  • 99
  • 5