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());
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:
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)}
]);
};
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;
};
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/
You need two things:
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');
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/
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... :)