Knockout ObservableArray not getting updated in UI - javascript

I am trying to update the array value bound to ObservaleArray. However its not getting updated.
function AppViewModel() {
var self = this;
self.Title=ko.observable('Sample');
self.people = ko.observableArray([
new Person('Ajay'),
new Person('Kumar')
]);
self.updateAns=function(){
self.people()[0].Answered=false;
};
self.updateName=function(){
self.people()[0].Name('John');
};
self.updateTitle=function(){
self.Title('New Title');
};
}
var Person=function(name){
this.Name=name;
this.Answer=[{Id: 1, Answered:true},{Id:2, Answered:true}]
}
ko.applyBindings(new AppViewModel());
Full Demo
How to keep the array value sync?

The observable array will only listen to changes to the array itself, you also need the members onthe Person to be observables if you want them to update

Observable array tracks changes of a collection of things. Meaning it doesn't detect changes of array items, but only changes of the collection, like add/remove/replace elements. In order to keep Person name value in sync it should be observable.
var Person = function(name){
this.Name = ko.observable(name);
...
}
Updated demo

Related

Knockout JS - How to initialize a complex object as observable?

I have a ViewModel and have an observable property that will have a complex object after an edit link is clicked. This is a basic example of managing a set of Groups. User can click on the 'edit' link and I want to capture that Group in SelectedGroup property.
But I'm not sure how should I initialize the SelectedGroup and make every peoperty in this object as observable to begin with.
function ManageGroupsViewModel() {
var self = this;
self.Groups = ko.observableArray();
self.IsLoading = ko.observable(false);
self.SelectedGroup = ko.observable();
}
Typically you'd start out with SelectedGroup being null:
self.SelectedGroup = ko.observable(null);
...and then when you're ready to edit a group, just set it to a new instance; if that instance needs observable properties, you create them just like you did for ManageGroupsViewModel:
function GroupVM() {
this.name = ko.observable("");
this.members = ko.observableArray();
}
and
// Start editing a group
yourGroupsViewModel.SelectedGroup(new GroupVM());
This other answer of mine has a fairly thorough example of doing this.

Merging history state object with constructor using knockout merge

i'm having issues with merging my history state object with a constructor that i have, that gets saved in the same history state for later use.
Plugin example page: https://rawgit.com/grofit/knockout.merge/master/example.html
Using the example that is shown in knockout merge plugin page that uses a constructor like my own i've built my code but unfortunately since i'm relatively new to knockout i ran into issues.
This is the a piece of code shown inside knockout merge's example
function Person()
{
this.Firstname = ko.observable();
this.Surname = ko.observable();
}
function ViewModel()
{
this.SimpleExampleModel = new Person();
this.MergeSimpleExample = function() {
var personJson = { Firstname: "James", Surname: "Bond" };
ko.merge.fromJS(this.SimpleExampleModel, personJson);
};
};
ko.applyBindings(new ViewModel());
Now my code:
(The object that is pushed to the history is the constuctor's observables as an object)
Constructor:
var searchTerm = function () {
this.MinPrice = ko.observable();
};
lbx.vm = {
term: new searchTerm(),
injectHistory: function () {
// ko.merge.fromJS(this.term, history.state); Doesn't work
// var json = ko.toJSON(history.state) - Doesn't work
//var json = JSON.Parse(history.state) - Doesn't work
//var json = { MinPrice: 222 }; Works
var json = { "MinPrice": 222 }; // Works
ko.merge.fromJS(this.term, json);
console.log("injected");
}
};
As you can see according to my testing, whenever i try to turn my JS object into json it doesn't work, but it does if i build the json manually.
Fiddle with my problem: https://jsfiddle.net/Badzpeed/05zdLgxh/1/
As you will see in the fiddle when i popstate, nothing happens, the observable value is always the same and it doesn't throw any error.
Any help would be appreciated !
Thank you in Advance
Finally fixed my issue, turns out that i forgot to add the constructor to the merge and i also had a double call to the function that was passing the object to my history state, making two of them.
Fixed the issue by changing those two things.
Thank you for your time !

Save original values in Knockout

I read data into my viewmodel from JSON (from my server).
My user changes the viewmodel from some input fields, but I need to be able to undo it and bring back the original value from my original JSON.
Is it possible to store "attributes" on a viewmodel - an attribute like original value? So I can read it back?
Thanks
UPDATE...
Tried making a jsfiddle (which obviously doesn't work) to show what I would like to do:
var ViewModel = function(first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
};
ViewModel.firstName.attribute("fieldtype", "string");
ViewModel.firstName.attribute("fieldlength", "30");
ViewModel.firstName.attribute("org-value", "Jane");
ko.applyBindings(new ViewModel("John", "Doe"));
$("#cmd").clicked(function() {
ViewModel.firstName(ViewModel.firstName.attribute("org-value"));
});
http://jsfiddle.net/MojoDK/kaymX/
I like to attach "attributes" to observables by adding properties to it (it's just a JavaScript function after all). Each property is attached to the observable it belongs to rather than stored in another structure elsewhere. Another benefit is that the properties on observables are also not serialized if you call ko.toJSON() on the view model.
function VM (value) {
var self = this;
self.foo = ko.observable(value);
self.foo.original = value;
self.revert = function () {
self.foo(self.foo.original);
};
}
JsBin: http://jsbin.com/biguvoqe/1/edit?html,js,output
Adapting what you put in your fiddle, you could do something like this:
http://jsfiddle.net/kaymX/2/
var ViewModel = function(first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
this.firstName.attributes = {};
};
var myVM = new ViewModel("John", "Doe");
myVM.firstName.attributes["fieldtype"] = "string";
myVM.firstName.attributes["fieldlength"] = "30";
myVM.firstName.attributes["org-value"] = "Jane";
ko.applyBindings(myVM);
$("#cmd").click(function() {
myVM.firstName(myVM.firstName.attributes["org-value"]);
});
Although it might be easier to move setting of attributes inside the constructor for your view model. Also you could use the dot notation rather than the bracket notation, but I left it with accessing properties by strings since I assume you had a reason for having it that way in the first place.

knockout.js: No concat on observableArray

Only starting with knockout.js, but already running into some trouble when trying to make a computed method based on 2 different observableArrays
Using the documentation on knockout.js' website, I've created the following viewmodel:
var Cart = function() {
var self = this;
self.Products = ko.observableArray([]);
self.Products2 = ko.observableArray([]);
self.Messages = ko.observableArray([]);
self.TotalAmount = ko.computed(function() {
var result = 0;
ko.utils.arrayForEach(
this.Products().concat(this.Products2()),
function(item) {
result+=item.AmountIncludingVAT();
}
);
return result;
});
};
Doing this throws an error of "Uncaught TypeError: Object #<error> has no method 'concat'.
I know there is this function called arrayPushAll, but it's a destructive function which would alter the original observableArray. (I don't think this is something I want).
Is there any clean way to achieve what I'm trying to do? Or do I have to make 2 different calls to arrayForEach, one for each array?
Change:
this.Products().concat(this.Products2()),
to:
self.Products().concat(self.Products2()),
Inside your TotalAmount ko.computed function.
this in the context of your computed refers to the global object rather than the view model. So you need to use the self variable that is assigned the correct this value earlier.
Working Example - http://jsfiddle.net/55kZp/
concat didn't works for me .. I did push
self.Products.push.apply(
self.Products, self.Products2()
);

knockout.js accessing container model property in a contained viewModel

I have nested view models like below. I am trying to access value in container view model from the contained view model (child). I got undefined error when the modelA.prop1 trying to get mainVM.prop1 value. Thanks for your help.
function mainVM() {
var self = this;
//chain associated view models
self.modelA = new modelA();
self.modelB = new modelB();
self.prop1 = ko.observable("some value from mainVM.prop1");
}
function modelA(){
var self = this;
self.prop1 = ko.observable(mainVM.prop1); //I'd like to get value in containing view model above
}
function modelB(){....}
$(function () {
var viewModel = new mainVM();
ko.applyBindings(viewModel);
});
If you want to make sub-ViewModels dependent/aware of their parent you'll have to pass it to them. E.g.:
function mainVM() {
var self = this;
//chain associated view models
self.modelA = new modelA(self);
self.modelB = new modelB(self);
self.prop1 = ko.observable("some value from mainVM.prop1");
}
function modelA(parent){
var self = this;
self.prop1 = ko.observable(parent.prop1); //I'd like to get value in containing view model above
}
function modelB(parent){....}
$(function () {
var viewModel = new mainVM();
ko.applyBindings(viewModel);
});
Think carefully though if this dependency is something you want in your design.
An alternative (though arguably worse from a design standpoint) solution would be to give them access through the scope, e.g.:
$(function () {
function mainVM() {
var self = this;
//chain associated view models
self.modelA = new modelA();
self.modelB = new modelB();
self.prop1 = ko.observable("some value from mainVM.prop1");
}
function modelA(){
var self = this;
self.prop1 = ko.observable(viewModel.prop1); //I'd like to get value in containing view model above
}
function modelB(){....}
var viewModel = new mainVM();
ko.applyBindings(viewModel);
});
Some additional thoughts to #Jeroen answer
Having dependencies to parent from children is not only bad design it can create hard to find memory leaks
If you use the parent from a computed in the child KO will hook up a dependency, if you remove the child it's computed will still fire when the parent change state.
My general way of solving dependencies between models is to use a EventAggregator pattern, I have made one for this library
https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy
Its a signalR library, if you do not need singalR you can extract the event aggregation part
Demo
http://jsfiddle.net/jh8JV/
ViewModel = function() {
this.events = ko.observableArray();
this.subModel = new SubViewModel();
signalR.eventAggregator.subscribe(Event, this.onEvent, this);
};
ViewModel.prototype = {
onEvent: function(e) {
this.events.push(e);
}
};
I think you've got an "XY problem" here: you want to accomplish task X (which you haven't named here) and you think that implementation Y (in this case, a child VM having a dependency on its parent) is the way to do it, even though Y might not be the best (or even a good) way to do it.
What's the actual problem you're trying to solve? If you need to access the parent property from within a child binding, Knockout's binding context ($root, $parent, $parents[], etc.) will let you do it, e.g.
<div data-bind="with:modelA">
<p>prop2 is <span data-bind="text:prop2"></span>
and prop1 from the main model is
<span data-bind="text:$root.prop1"></span>
</p>
</div>
In this case you could use $parent in place of $root since there's only one level of nesting.

Categories

Resources