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
Related
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());
I want to create a factory that is responsible for generating a PlayerList, but I'm having problems accessing the variables I set in the initialize function. The code is
app.factory("PlayerList", function(){
// Define the PlayerList function
var PlayerList = function() {
this.initialize = function() {
// create an array for our players
var players = [];
};
this.add = function(player) {
this.players.push(player);
}
this.remove = function(player) {
if ( players.length > 0 )
{
this.players.splice(players.indexOf(player), 1);
}
}
this.initialize();
};
return (PlayerList);
});
I want to refer to the players array inside the add and remove methods but I'm getting back undefined.
Here var players = []; is local variable for initialize but your are expecting this.players.push(player); means players should be in PlayerList scope.
So your factory should be look like
app.factory("PlayerList", function () {
// Define the PlayerList function
var PlayerList = function () {
var self = this;
this.players = [];
this.initialize = function () {
self.players = [];
};
this.add = function (player) {
self.players.push(player);
console.log(self.players);
}
this.remove = function (player) {
if (self.players.length > 0) {
self.players.splice(self.players.indexOf(player), 1);
}
}
this.initialize();
};
return (PlayerList);
});
var playerList = (function (){
var playerLists = {};
playerList.playerList = function() {
this.initialize = function() {
// create an array for our players
var players = [];
};
this.add = function(player) {
this.players.push(player);
}
this.remove = function(player) {
if ( players.length > 0 )
{
this.players.splice(players.indexOf(player), 1);
}
}
this.initialize();
};
return playerLists;
})();
app.factory("PlayerList",playerList.playerList);
I have the following service:
app.service('ItemService', function() {
my_items = [];
var removeItem = function(newObj) {
var toRemove = newObj;
var indexOf = my_items.indexOf(toRemove);
my_items.splice(indexOf, 1);
};
var resetItems = function(){
my_items = [];
};
var getItems = function(){
return my_items;
};
return {
removeItem: removeItem,
resetItems: resetItems,
getItems: getItems
};
});
and in my controller i have:
$scope.my_items = ItemService.getItems();
$scope.myCheckPush = function(item) {
ItemService.addItem(item);
}
$scope.myCheckRemove = function(item) {
ItemService.removeItem(item);
};
$scope.reset = function () {
ItemService.resetItems();
};
Here my remove function works well. But the reset function is not working as expected. Any ideas what i might be doing wrong?
Both my_items and $scope.my_items point to the same array. When you do my_items = [], you just remove the reference from my items, but since $scope.my_items still holds the reference the array is not deleted.
To delete the array use my_items = 0, which will actually delete the array's contents effecting $scope.my_items as well.
var resetItems = function(){
my_items.length = 0;
};
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
Here is example:
http://jsfiddle.net/valin/W4ubQ/
As you can see array instantiated by function (this.features) is working. But array instantiated by ko.mapping (this.featuresFromJS) is working only for view, but not inside javascript function. How should I instantiate featuresFromJS or whatever to compute lowTotal?
Any help is appreciated.
Hope this will help:
function objFeatures(name, price) {
return {
name: ko.observable(name),
price: ko.observable(price)
}
}
var AppViewModel = function () {
var self = this;
self.featuresFromJS = ko.observableArray();
self.features = ko.observableArray([
new objFeatures("Feature1", 20),
new objFeatures("Feature2", 50)]);
var data = '[{"name":"Feature3","price":20},{"name":"Feature4","price":50}]';
ko.mapping.fromJSON(data, {}, self.featuresFromJS);
self.lowTotal = ko.computed(function () {
var total = 0;
ko.utils.arrayForEach(this.featuresFromJS(), function (item) {
alert("hooray!");
total += item.price();
});
return total;
}, self);
self.grandTotal = ko.computed(function () {
var total = 0;
ko.utils.arrayForEach(this.features(), function (item) {
total += item.price();
});
return total;
}, self);
};
ko.applyBindings(new AppViewModel());