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());
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);
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
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
In the name of keeping things DRY, I'd like to ask what the typical approach is when trying to avoid declaring duplicate properties. I have two viewModels: set and folder. Here they are:
Folder:
var folderViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
self.isHovering = ko.observable(false);
self.showCheckbox = function () {
self.isHovering(true);
};
self.hideCheckbox = function () {
self.isHovering(false);
};
self.checkboxIsVisible = ko.computed(function () {
return selectedItemsCount() > 0 || self.isHovering();
}, self);
self.softCheckboxIsVisible = ko.computed(function () {
return selectedItemsCount() > 0 && self.isHovering() == false;
}, self);
self.canDrag = ko.computed(function () {
if (selectedItemsCount() == 0 && !isAddingNewContent()) {
return true;
} else {
return false;
}
}, self);
self.isSelected = ko.observable(false);
self.toggleSelected = function () {
self.isSelected(!self.isSelected());
};
self.textSelected = ko.observable(false);
self.toggleTextSelected = function () {
self.textSelected(!self.textSelected());
};
self.isSet = ko.observable(false);
self.isDeleting = ko.observable(false);
self.isNew = ko.observable(false);
// If the folder hasn't been created yet, it won't have a folderId
if (typeof self.folderId === 'undefined') {
self.isNew(true);
}
self.isEditing = ko.observable(false).publishOn("IS_EDITING_CONTENT");
// monitor for clicks
// temp title
self.oldTitle = ko.observable();
};
Set:
var setViewModel = function (data) {
var self = this;
// Checkbox controls
self.isHovering = ko.observable(false);
self.showCheckbox = function () {
self.isHovering(true);
};
self.hideCheckbox = function () {
self.isHovering(false);
};
self.checkboxIsVisible = ko.computed(function () {
return selectedItemsCount() > 0 || this.isHovering();
}, self);
self.softCheckboxIsVisible = ko.computed(function () {
return selectedItemsCount() > 0 && this.isHovering() == false;
}, self);
self.canDrag = ko.computed(function () {
if (selectedItemsCount() == 0 && !isAddingNewContent()) {
return true;
} else {
return false;
}
}, self);
self.isSelected = ko.observable(false);
self.toggleSelected = function () {
self.isSelected(!self.isSelected());
};
self.textSelected = ko.observable(false);
self.toggleTextSelected = function () {
self.textSelected(!self.textSelected());
};
self.isSet = ko.observable(true);
ko.mapping.fromJS(data, {}, self);
self.isDeleting = ko.observable(false);
self.isNew = ko.observable(false);
// If the folder hasn't been created yet, it won't have a folderId
if (typeof self.setId === 'undefined') {
self.isNew(true);
}
self.isEditing = ko.observable(false).publishOn("IS_EDITING_CONTENT");
// temp title
self.oldTitle = ko.observable();
};
A lot of these properties are duplicated between the viewModels. Should I just keep them as is, or is there a nice way to condense this code?
Create a helper method that both viewmodel constructors call to add all of the common properties...
var helper = function (self, data) {
self.isHovering = ko.observable(false);
// ...
return self;
};
var setViewModel = function (data) {
var self = helper(this, data);
// extra stuff
};
var folderViewModel = function (data) {
var self = helper(this, data);
// extra stuff
};
What about trying inheritance? You could program a prototype viewModel with the properties and functions that both (set and folder) have and then define new "classes" for setViewModel and folderViewModel that have the same prototype as viewModel, just added the properties and functions that only they have.
Introduction to inheritance in javascript can be found here...