user2744722 user2744722 - 1 month ago 15
Javascript Question

read in knockout computable not always firing with extender

this might be easiest to describe with a fiddle http://jsfiddle.net/LkqTU/32190/

I have an observable called price which has an extender so that it rounds to two digits and does not allow non numeric.

I have a writable computed observable that puts a dollar sign in front of it.

however if the extender returns 0. the second time it happens $0 is not returned to the textbox (the computed observable) so if I type hello world in the textbox (computed observable) the price correctly reports 0 and the formatted price shows $0. however if I clear it out and type in hello world a second time. this time price is still $0 but the formatted price textbox is showing hello world (even though formatted price is reporting $0). must be missing a notification somewhere?

here is the code.

ko.extenders.numeric = function(target, precision) {
//create a writable computed observable to intercept writes to our observable
var result = ko.pureComputed({
read: target, //always return the original observables value
write: function(newValue) {
var current = target(),
roundingMultiplier = Math.pow(10, precision),
newValueAsNum = isNaN(newValue) ? 0 : parseFloat(+newValue),
valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier;

//only write if it changed
if (valueToWrite !== current) {
target(valueToWrite);
} else {
//if the rounded value is the same, but a different value was written, force a notification for the current field
if (newValue !== current) {
target.notifySubscribers(valueToWrite);
}
}
}
}).extend({
notify: 'always'
});

//initialize with current value to make sure it is rounded appropriately
result(target());

//return the new computed observable
return result;
};


function model() {
var self = this;

this.price = ko.observable('29.01').extend({
numeric: '2'
});
this.formattedPrice = ko.computed({
read: function() {
return '$' + this.price()
},
write: function(value) {
value = parseFloat(value.replace(/[^\.\d]/g, ""));
this.price(isNaN(value) ? 0 : value);
},
owner: this
});
}

var mymodel = new model();

$(document).ready(function() {
ko.applyBindings(mymodel);

});

Answer

What seemed to work for me was extending the formattedPrice computed variable to always notify and modify the write method to just pass in the value rather than checking for isNaN; your extender does this anyway and forcing a 0 makes it so that your if (newValue !== current) test never returns true as newValue and current will always be 0 in this scenario (NaN !== 0 will pass the check):

  this.formattedPrice = ko.computed({
    read: function() {
      return '$' + this.price()
    },
    write: function(value) {
      value = parseFloat(value.replace(/[^\.\d]/g, ""));
      this.price(value); //just pass in nan or the value so the extender can check/notify if newValue !== current
    },
    owner: this
  }).extend({
    notify: 'always'
  });

Updated fiddle: http://jsfiddle.net/LkqTU/32192/

Comments