Knockout observable change not detected if value c

2019-07-20 21:34发布

问题:

I have a list of text boxes bound to the ko.observableArray.

I have to make sure that text box values can't be blank, and I do it with jQuery by setting the value to 0 if it's blank on blur()

The problem is that the value change done with jQuery is not registered by knockout.

How do I observe the value change in my model?

See my simplified fiddle to get the point across - http://jsfiddle.net/k45gd/1/

HTML

<input type="number" data-bind="value: age" />
<span data-bind="text: age"></span>
<button data-bind="click: setAgeExternally">I want the label to change to 0</button>

JS

var model = function() {
    this.age = ko.observable(21);

    //this code is outside of the model, this is oversimplification
    this.setAgeExternally = function(){
        $('input').val(0);
    }
};

ko.applyBindings(new model());

回答1:

In the example you provide, you're updating the input box with this code:

this.setAgeExternally = function(){
    $('input').val(0);
}

Considering that the input is bound to the age property, it would be simpler to do this:

this.setAgeExternally = function(){
  this.age(0);
}

However, even that isn't really needed, as the age property is exposed on your viewmodel. So external code could just do this, and the setAgeExternally method isn't really needed:

model.age(0);

Let's jump back to your original problem - the one you describe but don't post code for. You mention that you have a list of input boxes bound to an observable array.

There is an interesting gotcha you need to be aware of when working with an observable array:

From the documentation at http://knockoutjs.com/documentation/observableArrays.html:

Key point: An observableArray tracks which objects are in the array, not the state of those objects

Simply putting an object into an observableArray doesn’t make all of that object’s properties themselves observable. Of course, you can make those properties observable if you wish, but that’s an independent choice. An observableArray just tracks which objects it holds, and notifies listeners when objects are added or removed.

Given the requirements you list, there isn't any need for jQuery at all. You can try this three-part solution:

  1. Make your observableArray contain observables. So you'd end up with something like:

    var model = function() {
      this.ages = ko.observableArray([
        {age: ko.observable(13)},
        {age: ko.observable(18)},
        {age: ko.observable(16)},
        {age: ko.observable(13)}
      ]);
    };
    
  2. Next, create a knockout extender that resets to 0 automatically if blank

    ko.extenders.defaultIfBlank = function(target, defaultValue) {
      var result = ko.computed({
        read: target,  //always return the original observables value
        write: function(newValue) {
            if (newValue == "") {
              target(defaultValue);
            } else {
               target(newValue);
            }
        }
      });
    
      //initialize with current value to make sure it is not blank
      result(target());
    
      //return the new computed observable
      return result;
    };
    
  3. Apply the extender to the observables in your array

    var model = function() {
      this.ages = ko.observableArray([
        ko.observable(13).extend({defaultIfBlank: "0"}),
        ko.observable(18).extend({defaultIfBlank: "0"}),
        ko.observable(16).extend({defaultIfBlank: "0"}),
        ko.observable(13).extend({defaultIfBlank: "0"})
      ]);
    };
    

Working fiddle: http://jsfiddle.net/tlarson/GF3Xe/



回答2:

You need two things:

  1. After changing the value of the element with jQuery, you need to let Knockout know to update the model. You can do this by triggering an change event:

    $('input').val(0).trigger('change');
    
  2. For Knockout (prior to 3.1.0) to respond to jQuery events, it needs to know that you're using jQuery. For that to happen, you must include jQuery in your document before Knockout.

Here's your fiddle with both updates: http://jsfiddle.net/mbest/k45gd/2/



回答3:

Used Michael Best suggestion:

This is the code that worked for me:

function insertControlsAtcaret(element, addControlText){
    if (element.selectionStart || element.selectionStart == '0') {
        //For browsers like Firefox and Webkit based
        var startPos = element.selectionStart;
        var endPos = element.selectionEnd;
        var scrollTop = element.scrollTop;
        element.value = element.value.substring(0, startPos) + addControlText + element.value.substring(endPos, element.value.length);
        $(element).trigger("change");
        element.focus();
        element.selectionStart = startPos + addControlText.length;
        element.selectionEnd = startPos + addControlText.length;
        element.scrollTop = scrollTop;
    } else {
        element.value += addControlText;
        $(element).trigger("change");
        element.focus();
    }
};

Thx MB, saved my time... :)