Knockout - How to reset Dynamic observable array with new value - javascript

I can't reset observable array with new value am using some lazy loading Technic.
I can clear but can't reset, but it not allowing me to add new dynamic value.
fiddle
http://jsfiddle.net/kspxa8as/
js
var i = 1;
optionsProvider = function(self) {
var self = self || {};
self.options = {};
self.get = function(name, initialValue) {
if (!self.options[name]) {
console.log("Called - " + name);
self.options[name] = ko.observableArray([initialValue]);
var requestHeader = '';
setTimeout(function() {
var aa = [{name: "plant 1" + i, selected: true}, {name: "palnt 2" + i, selected: false}];
self.options[name](aa);
i++;
}, 2000);
}
return self.options[name];
};
return self;
};
ViewModel = function() {
var self = this;
var k = 1;
var ob = new optionsProvider(self);
self.PlantSelected = ob.get("name" + k, '');
self.fillNewSelect = function() {
self.PlantSelected.removeAll();
self.PlantSelected().push(ob.get("name" + k, ''));
k++;
};
};
ko.applyBindings(new ViewModel());
HTML
<select class="n_imported_country"
data-bind="options: PlantSelected,
optionsText :'name'
"
>
</select>
<div data-bind="click: function(){
$root.fillNewSelect();
}">click to fill new select value</div>
I am a newbie to knockout, great welcome your answers.

I recommend the use of a promise library to handle the asynchronous Ajax load of new items. I've used jQuery's implementation in the sample below. Notice how optionsProvider no longer requires any dependency on the viewmodel.
var optionsProvider = function (name, initialValue) {
return function () {
return $.get("/target/url", {parameter: "value"})
.fail(function () {
console.log("request to get new items failed", arguments);
});
};
};
var ViewModel = function () {
var self = this,
k = 1,
ob = optionsProvider("name" + k, '');
self.PlantSelected = ko.observableArray([]);
self.fillNewSelect = function () {
ob().then(function (newData) {
var p = self.PlantSelected;
p.removeAll();
p.push.apply(p, newData);
});
};
// init
self.fillNewSelect();
};
ko.applyBindings(new ViewModel());
The second change to mention is the way to push new objects into an array. .push() supports an argument list:
arr.push('a', 'b', 'c')
If you have an array of items you want to push (for example a JSON result), you would use .apply(), otherwise you would push the array itself as the first item:
arr.push.apply(arr, ['a', 'b', 'c']);
Observable arrays in knockout support the same usage.
Compare: http://jsfiddle.net/kspxa8as/6/

Try something like this the trick here is passing the instance of the observableArray to the function and get our job done
Viewmodel:
var ViewModel = function() {
var self = this;
var k = 1;
var ob = new optionsProvider(self);
self.PlantSelected = ko.observableArray(); //Declare it as observableArray
ob.get("name" + k, '',self.PlantSelected) // call the function passing observableArray instance
self.fillNewSelect = function()
{
self.PlantSelected.removeAll();
ob.get("name", ''+ k,self.PlantSelected)
k++;
};
};
ko.applyBindings(new ViewModel());
function :
var i = 1;
optionsProvider = function(self) {
var self = self || {};
self.options = {};
self.get = function(name, initialValue,instance) { //pass observableArray instance here
if (!self.options[name] || self.options[name]()) {
var requestHeader = '';
setTimeout(function() {
var aa = [{name: "plant 1" + i, selected: true},
{name: "palnt 2" + i, selected: false}];
instance(aa); // assign data to instance
},2000);
i++;
}
return true;
};
return self;
};
working sample here

Related

How to create a simple web page that check dirty value with knockout js?

How to create a simple web page that check dirty value with knockout js?
ps:simple code written
This should be plenty to get you started, note the author is rniemeyer:
http://jsfiddle.net/rniemeyer/dtpfv/?utm_source=website&utm_medium=embed&utm_campaign=dtpfv
//not used in this example. one time flag, that drops its subscriptions after the first change.
ko.oneTimeDirtyFlag = function (root) {
var _initialized;
//one-time dirty flag that gives up its dependencies on first change
var result = ko.computed(function () {
if (!_initialized) {
//just for subscriptions
ko.toJS(root);
//next time return true and avoid ko.toJS
_initialized = true;
//on initialization this flag is not dirty
return false;
}
//on subsequent changes, flag is now dirty
return true;
});
return result;
};
ko.dirtyFlag = function(root, isInitiallyDirty) {
var result = function() {},
_initialState = ko.observable(ko.toJSON(root)),
_isInitiallyDirty = ko.observable(isInitiallyDirty);
result.isDirty = ko.computed(function() {
return _isInitiallyDirty() || _initialState() !== ko.toJSON(root);
});
result.reset = function() {
_initialState(ko.toJSON(root));
_isInitiallyDirty(false);
};
return result;
};
function Item(id, name) {
this.id = ko.observable(id);
this.name = ko.observable(name);
this.dirtyFlag = new ko.dirtyFlag(this);
}
var ViewModel = function(items) {
this.items = ko.observableArray([
new Item(1, "one"),
new Item(2, "two"),
new Item(3, "three")
]);
this.save = function() {
alert("Sending changes to server: " + ko.toJSON(this.dirtyItems));
};
this.dirtyItems = ko.computed(function() {
return ko.utils.arrayFilter(this.items(), function(item) {
return item.dirtyFlag.isDirty();
});
}, this);
this.isDirty = ko.computed(function() {
return this.dirtyItems().length > 0;
}, this);
};
ko.applyBindings(new ViewModel());

How do I append to a mapped observable array in Knockout.js?

How do you add a row to an editable table in Knockout.js?
var data = {
"Lines": [
{"Entries": [{"Hours": 5.5},{"Hours": 2.50},{"Hours": 3.75}]},
{"Entries": [{"Hours": 5.1},{"Hours": 2.00},{"Hours": 4.75}]},
{"Entries": [{"Hours": 1.2},{"Hours": 3.00},{"Hours": 2.12}]}
]
}
var data1 = {"Entries": [{"Hours": 0},{"Hours": 0},{"Hours": 0}],Total:0};
The table displays self.List() which is an observableArray mapped to data.Lines with self.List(ko.mapping.fromJS(data.Lines)())
[{"Entries":[{"Hours":"33.5"},{"Hours":2.5},{"Hours":3.75}],"Total":39.75},{"Entries":[{"Hours":5.1},{"Hours":2},{"Hours":4.75}],"Total":11.85},{"Entries":[{"Hours":1.2},{"Hours":3},{"Hours":2.12}],"Total":6.32}]
When I click the addRow button I am thinking I need to recompute self.List(). I have tried from why-can-not-i-concat-data-to-observable-array-in-knockout
self.addRow =function(){
self.List(self.List().concat(data1))
self.applyTotals();
}
applyTotoals works fine if I don't add a row.
self.applyTotals = function(){
ko.utils.arrayForEach(self.List(), function(vm){
vm.Total = ko.computed(function(){
var s = 0;
ko.utils.arrayForEach(this.Entries(), function(entry){
var p = parseFloat(entry.Hours(), 10);
if (!isNaN(p)) {
s += p;
}
});
return s;
}, vm);
});
}
but I get uncaught TypeError:this.Entries is not a function and the new row won't compute totals. So I have tried
self.addRow =function(){
self.List = ko.computed(function(){
var orig = self.List();
var os= ko.toJS(orig);
os.push(data1)
console.log(JSON.stringify(os))
var oa = ko.observableArray([]);
return oa(ko.mapping.fromJS(os)());
})
}
How do I modify a mapped observableArrray?
Here is the fiddle: http://jsfiddle.net/mckennatim/jngesuf2/
well #mcktimo you are not effectively using mapping plugin . you can make use of 2nd paramter Mapper in fromJS function and build you viewModel effectively .
viewModel:
function model(data) {
var self = this;
self.Entries = ko.observableArray();
self.Total = ko.computed(function () {
var sum = 0;
ko.utils.arrayForEach(self.Entries(), function (entry) {
var value = parseFloat(entry.Hours(), 10);
if (!isNaN(value)) {
sum += value;
}
});
return sum;
});
ko.mapping.fromJS(data, {}, self);
}
var mapping = { //everything goes through this point
create: function (options) {
return new model(options.data);
}
}
function ViewModel() {
var self = this
self.List = ko.observableArray([])
self.LoadData = function (data) {
ko.mapping.fromJS(data.Lines, mapping, self.List)
}
self.LoadData(data);
self.addRow = function () {
self.List.push(ko.mapping.fromJS(data1, mapping));
}
}
ko.applyBindings(new ViewModel(), document.getElementById('ko'))
working sample here
I suggest to take a deeper dive into the mapping documentation

JavaScript: Prevent Array.push()

I have a sealed object with an array member on which I want to prevent direct pushes.
var myModule = (function () {
"use strict";
var a = (function () {
var _b = {},
_c = _c = "",
_d = [];
Object.defineProperty(_b, "c", {
get: function () { return _c; }
});
Object.defineProperty(_b, "d", {
get { return _d; }
});
_b.addD = function (newD) {
_d.push(newD);
};
Object.seal(_b);
return _b;
}());
var _something = { B: _b };
return {
Something: _something,
AddD: _b.addD
};
}());
myModule.Something.c = "blah"; // doesn't update = WIN!!
myModule.AddD({}); // pushed = WIN!
myModule.Something.d.push({}); // pushed = sadness
How can I prevent the push?
UPDATE:
Thanks for all the thoughts. I eventually need the JSON to send to the server. It looks like I might need to use an object for the array then figure out a way to generate and return the JSON needed, or change _something to use .slice(). Will play and report.
you could override the push method:
var _d = [];
_d.__proto__.push = function() { return this.length; }
and when you need to use it in your module, call Array.prototype.push:
_b.addD = function (newD) {
Array.prototype.push.call(_d, newD);
};
I haven't done any performance tests on this, but this certainly helps to protect your array.
(function(undefined) {
var protectedArrays = [];
protectArray = function protectArray(arr) {
protectedArrays.push(arr);
return getPrivateUpdater(arr);
}
var isProtected = function(arr) {
return protectedArrays.indexOf(arr)>-1;
}
var getPrivateUpdater = function(arr) {
var ret = {};
Object.keys(funcBackups).forEach(function(funcName) {
ret[funcName] = funcBackups[funcName].bind(arr);
});
return ret;
}
var returnsNewArray = ['Array.prototype.splice'];
var returnsOriginalArray = ['Array.prototype.fill','Array.prototype.reverse','Array.prototype.copyWithin','Array.prototype.sort'];
var returnsLength = ['Array.prototype.push','Array.prototype.unshift'];
var returnsValue = ['Array.prototype.shift','Array.prototype.pop'];
var funcBackups = {};
overwriteFuncs(returnsNewArray, function() { return []; });
overwriteFuncs(returnsOriginalArray, function() { return this; });
overwriteFuncs(returnsLength, function() { return this.length; });
overwriteFuncs(returnsValue, function() { return undefined; });
function overwriteFuncs(funcs, ret) {
for(var i=0,c=funcs.length;i<c;i++)
{
var func = funcs[i];
var funcParts = func.split('.');
var obj = window;
for(var j=0,l=funcParts.length;j<l;j++)
{
(function() {
var part = funcParts[j];
if(j!=l-1) obj = obj[part];
else if(typeof obj[part] === "function")
{
var funcBk = obj[part];
funcBackups[funcBk.name] = funcBk;
obj[part] = renameFunction(funcBk.name, function() {
if(isProtected(this)) return ret.apply(this, arguments);
else return funcBk.apply(this,arguments);
});
}
})();
}
}
}
function renameFunction(name, fn) {
return (new Function("return function (call) { return function " + name +
" () { return call(this, arguments) }; };")())(Function.apply.bind(fn));
};
})();
You would use it like so:
var myArr = [];
var myArrInterface = protectArray(myArr);
myArr.push(5); //Doesn't work, but returns length as expected
myArrInterface.push(5); //Works as normal
This way, you can internally keep a copy of the interface that isn't made global to allow your helper funcs to modify the array as normal, but any attempt to use .push .splice etc will fail, either directly, or using the .bind(myArr,arg) method.
It's still not completely watertight, but a pretty good protector. You could potentially use the Object.defineProperty method to generate protected properties for the first 900 indexes, but I'm not sure of the implications of this. There is also the method Object.preventExtensions() but I'm unaware of a way to undo this effect when you need to change it yourself
Thank you, dandavis!
I used the slice method:
var myModule = (function () {
"use strict";
var a = (function () {
var _b = {},
_c = _c = "",
_d = [];
Object.defineProperty(_b, "c", {
get: function () { return _c; }
});
Object.defineProperty(_b, "d", {
get { return _d.slice(); } // UPDATED
});
_b.updateC = function (newValue) {
_c = newValue;
};
_b.addD = function (newD) {
_d.push(newD);
};
Object.seal(_b);
return _b;
}());
var _something = { B: _b };
return {
Something: _something,
AddD: _b.addD
};
}());
myModule.Something.c = "blah"; // doesn't update = WIN!!
myModule.AddD({}); // pushed = WIN!
myModule.Something.d.push({}); // no more update = happiness
This allows me to protect from direct push calls enforcing some logic.

How to json strinfigy a function instance in javascript?

If I have this code
var node = function(n) {
var name = n;
var children = [];
var finished = false;
var failed = false;
this.getName = function() {
return name
};
this.downloadData = function(obj) {
};
this.getChildren = function() {
return children;
};
this.setChildren = function(c) {
Array.prototype.push.apply(children, c);
};
this.isFinished = function() {
return finished;
};
this.setFinished = function() {
finished = true;
}
this.isFailed = function() {
return failed;
}
this.setFailed = function() {
failed = true;
}
};
How can I convert this into an object like:
var a = new node("a");
var j = JSON.stringify(a);
result
{"name":"a","children":[],"finished":false,"failed":false}
thanks
This could be done by implementing the toJSON function.
If an object being stringified has a property named toJSON whose value
is a function, then the toJSON() method customizes JSON
stringification behavior: instead of the object being serialized, the
value returned by the toJSON() method when called will be serialized.
- Mozilla
eg:
var node = function(n) {
var name = n;
var children = [];
var finished = false;
var failed = false;
this.toJson = function toJson() {
return {"name":name ... };
}
}
You need object properties instead of variables.
So, instead of declaring var name = n;, you would declare this.name = n;. Which would make it look something like
var node = function(n) {
this.name = n;
this.children = [];
this.finished = false;
this.failed = false;
///other functions here
}

ko foreach with ko.computed is coping the input value to next input

I have an observable string witch contains a list of options.
Every single option is separated by this symbol "-*!*-"
There is also a computed function called optionsSplitted which is responsible to return an array of options.
This array is used from the foreach binding.
There is also a button to add options.
Everything works fine on the model, I can edit my options, add a new one.
But when I add some options and then edit one, it will be copied to the next one. Why???
jsfiddle
function ViewModel(args) {
var self = this;
self.activeAttributes = ko.observable({
options: ko.observable('a-*!*-b-*!*-c')
});
self.activeAttributes.optionsSplitted = ko.computed(function(){
return self.activeAttributes().options().split("-*!*-");
});
self.changed = function (data) {
var options = "", optionsSize = $('.option').length;
$('.option').each(function(i){
if(i < optionsSize - 1)
options += $(this).val() + "-*!*-";
else
options += $(this).val();
});
self.activeAttributes().options(options);
alert("Options: " + options)
};
self.addOption = function(data) {
self.activeAttributes().options(self.activeAttributes().options() + "-*!*-");
};
}
var model = {
};
var viewModel = new ViewModel(model);
ko.applyBindings(viewModel);
Using the ko.utils.arrayMap utility is fine.
jsfiddle
function ViewModel(args) {
var self = this;
self.activeAttributes = ko.observable({
options: ko.observable('a-*!*-b-*!*-c')
});
self.activeAttributes.optionsSplitted = ko.computed(function(){
var options = self.activeAttributes().options().split("-*!*-");
return ko.utils.arrayMap(options, function (option) {
return {
value: ko.computed({
read: function () { return option; }
})
};
});
});
self.changed = function (data) {
var options = "", optionsSize = $('.option').length;
$('.option').each(function(i){
if(i < optionsSize - 1)
options += $(this).val() + "-*!*-";
else
options += $(this).val();
});
self.activeAttributes().options(options);
alert("Options: " + options)
};
self.addOption = function(data) {
self.activeAttributes().options(self.activeAttributes().options() + "-*!*-");
};
};
var model = {
};
var viewModel = new ViewModel(model);
ko.applyBindings(viewModel);

Categories