Currently I'm bound to plain array. Values of the array can be changed by external components.
Is there any possibility to send notification that value has changed and re-render DOM tree?
I can't use observables, thus valueHasMutated
is not a solution, array is very large and contains lot of complex objects in it.
If I understand correctly, the array is a member of the ViewModel (which you have control over), but you cannot change it into an observableArray
because things outside of your control modify the array, using plain array syntax. Also, you can take some action after these black-box functions run, to notify the model that the array may have been mutated.
We can do this. Define an (internal) observableArray
and define a public property on the ViewModel that wraps it. That gives you access to the observableArray
using ordinary array syntax. However, changes to individual (non-observable
) elements of an array do not send notifications, so you will need to provide a call to the valueHasMutated
method of the internal observableArray
, to be called whenever you make changes.
var vm = (function () {
var arrayImpl = ['hi'];
var obsArray = ko.observableArray(arrayImpl);
var itemNumbers = [1,2,3,4,5,6,7,8,9];
var self = {
itemNumbers: itemNumbers,
selectedItemNumber: ko.observable(1),
newValue: ko.observable(),
arrayHasMutated:obsArray.valueHasMutated
};
Object.defineProperty(self, 'plainArray', {
get: obsArray,
set: obsArray
});
return self;
});
ko.applyBindings(vm);
Our app can use the plainArray
property just like a regular array. When updates are made, call arrayHasMutated
.
Item Number: <select data-bind="options:itemNumbers, value:selectedItemNumber"></select>
<br />
New Value: <input data-bind="value:newValue" />
<button data-bind="click:function () { var idx=selectedItemNumber()-1; plainArray[idx] = newValue(); arrayHasMutated(); }">Set it</button>
<ol data-bind="foreach:plainArray">
<li data-bind="text:$data"></li>
</ol>
<br />
Length: <span data-bind="text:plainArray.length"></span>
Try it out: http://jsfiddle.net/4jogh3k5/1/
If you can pass another object, instead of the original array, to the consumers then you can use defineProperty
to wrap mutations.
var original = new Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
var wrapper = {};
for (var idx in original) {
(function(prop) {
Object.defineProperty(wrapper, prop, {
get: function() { return original[prop]; },
set: function(v) {
console.log("changing index " + prop + " to " + v);
original[prop] = v;
}
});
})(idx);
}
Obviously this only works when you change the value at an index. If your array has objects and you change one of those objects directly, like original[0].property = "value"
then you would need to extend this technique to visit every object in the array and create an equivalent structure of wrapper objects.
I'm assuming you're using Knockout.js since you tagged it. Therefore, you can use ko.ObservableArray() and bind it to the DOM.
var originalArray = [1, 2, 3, 4];
var observableArray = ko.ObservableArray(originalArray);
See this link