Knockout: copy values from one model instance into another model instance - javascript

I have two Knockout Model instances (ModelInstance1 and ModelInstance2). For both instances all bindings are already applied and page is loaded with two different forms.
One form is visible for user and allows user to do inline editing. FOr this form I use ModelInstance1.
Another form is modal form that appears on Edit button click. This form uses ModelInstance2. If user clicks Cancel, then no values are saved to database and modal form is closed.
Now I need on my custom event (imagine as it would be button click) to copy all values from one instance to another.
Of course, I can do like this manually:
ModelInstance2.FirstName(ModelInstance2.FirstName());
ModelInstance2.LastName(ModelInstance2.LastName());
etc.
but is there any other way to do this?
Take into account that my Model contains observables, observable arrays, computed and subscriptions.

Write the constructor function for your model so that it can take a plain object as an initializer. Then you can clone a model by doing
var modelInstance2=new Model(ko.utils.parseJSON(ko.toJSON(modelInstance1)));
You need to serialize to JSON and then parse the result to get a deep copy (properties that are reference types like objects and arrays are actual copies and not references to the original). If you did
var modelInstance2=new Model(ko.toJS(modelInstance1));
then you'd get a shallow copy and changing any reference-type property in modelInstance2 would also change it in modelInstance1.

How about...
var Model = function (firstName, lastName) {
var self = this;
self.FirstName = ko.observable(firstName);
self.LastName = ko.observable(lastName);
self.clone = function () {
return new Model(this.FirstName(), this.LastName());
};
}
var instance1 = new Model('Jon', 'Smith');
var instance2 = instance1.clone();
If you had the knockout mapping extension available you could always do this....
var temp = ko.toJS(instance1);
var instance2 = ko.mapping.fromJS(temp);
http://knockoutjs.com/documentation/plugins-mapping.html
If you dont want to maintain a method that clones the object then mapping is a good option, I can think of no other way to handle it. These seem to be your options.

Related

How to restrict data changes of a variable in multiple variables which using the same data?

I am facing an issue with object property changes in angular JS.
For example I have an array of objects.
$scope.ex1 = [{"name":"Ethel Price","gender":"female","company":"Enersol"},
{"name":"Claudine Neal","gender":"female","company":"Sealoud"},
{"name":"Beryl Rice","gender":"female","company":"Velity"},
{"name":"Wilder Gonzales","gender":"male","company":"Geekko"},
{"name":"Georgina Schultz","gender":"female","company":"Suretech"},
{"name":"Carroll Buchanan","gender":"male","company":"Ecosys"},
{"name":"Valarie Atkinson","gender":"female","company":"Hopeli"},
{"name":"Schroeder Mathews","gender":"male","company":"Polarium"},
{"name":"Lynda Mendoza","gender":"female","company":"Dogspa"},
{"name":"Sarah Massey","gender":"female","company":"Bisba"}]
I have a form where I show the data in a list format to display it am using angular-datatable.
When a user clicks on list items am storing that particular object in an array.
For example the user selects two items from the above list am keeping that two objects in other variable like below:
$scope.selected = [{"name":"Lynda Mendoza","gender":"female","company":"Dogspa"},
{"name":"Sarah Massey","gender":"female","company":"Bisba"}];
The issue is when I change some properties in $scope.selected the changes are reflecting automatically in $scope.ex1 object.
Like below:
$scope.selected[0].name = "Rakesh rekala"
The above change will reflect in $scope.ex1 array to that particular item.
How to restrict this scenario is there any way to solve this.
Javascript objects are mutable, use angular.copy to deep copy your source object array to a variable and then use the variable to push objects into the selected array
You should change your push line.
$scope.selected.push($scope.ex1[0]);
$scope.selected[0].name = "Test"; //ex1 will change
$scope.selected.push(angular.copy($scope.ex1[1]));
$scope.selected[1].name = "Test2"; //It's ok!
http://jsfiddle.net/ms403Ly8/124/

KnockoutJS - UI not updating with built-in observableArray methods except push and pop

When I do a push or pop operation on my observable array, it is reflected in the ui. However other operations on the array won't change anything in the UI. Here's an example of my case:
<ul data-bind="foreach: addresses">
<!-- ko template: {name: 'AddressItemTemplate', data: {address: $data, page: 'update-page'} }-->
<!-- /ko -->
</ul>
I use my template in two different pages and thats the reason I am using the template data like that.
<script type="text/html" id="AddressItemTemplate">
<p data-bind="text: (page == 'update-page') ? 'updating' : 'declined'"</p>
<p data-bind="text: address.title"></p>
</script>
Now on js side, ofc I declared the addresses as an observable array
this.addresses = ko.observableArray([addresObject1, addressObject2, ...])
Somewhere on the page, I edit the address values. To have UI reflecting the changes, I do the following:
//suppose we know that the first address is being edited
var tmp_addresses = addresses();
tmp_addresses[0].title = 'blabla';
addresses(tmp_addresses);
And there it is, in the viewModel, I can see that the content of the addresses has been updated, but not in the UI??
addresses.push(someAddressObject);
or
addresses.pop();
works (updates the UI with the new/removed element). But addresses.splice(0, 1, newAddressObject) does not do anything in the UI again.
What am I missing here? How can push pop work and not the others??
Am I experiencing a bug in knockout framework?
UPDATE
I found out a way to do it, but there's something wrong. I'll come to that but first:
I am well aware that if I use observable objects in the observable array, the changes would be reflected in UI. However that is exactly the thing I want to avoid. It is an overkill.
Observable properties should be required in cases where properties are really exposed to user interaction. For example, if you have a UI for setting each of the fields of an object, then yes, observable property would be the right call.
However in my case, I dont even have a UI for updating the address field. Moreover, I dont need tinkering and constantly watching all the properties of all the addresses. In my case, every now and then an update occurs from the server and that changes only a single field in a single address field.
On another perspective the way I suggest should work. I simply update the whole array at once, not every element individually. It's the exactly the same logic with:
someObservableObject({newObject: withNewFields, ...});
Thats why I dont need my objects as observables. I simply want to re-declare the array and be done with the change. For example, it is advised that if you are going to make lots of pushes into the observable array, dont use array.push(...) multiple times, instead re-declare the larger array on to the observable array variable in a similar way I do it in my question. Otherwise, I am telling knockout to track every single object and every single field in them, which is hardly what I want.
Now, I finally got it working but the way I do suggests that there is a cleaner way to do it.
I found out that, the items in the observable array are somehow tracked and not updated when you re-declare the array with them. For example the code I gave in the question would not work. However the code below works:
var tmp_addresses = addresses();
var tmp_addr = tmp_addresses[0];
var new_addr = {};
Object.keys(tmp_addr).forEach(function(key){
new_addr[key] = tmp_addr[key];
});
new_addr.title = 'Hey this is something new!'
addresses.splice(0, 1, new_addr);
Not satisfied? The code below is going to work as well, because we are re-defining the array:
var newAddressObject1 = {...}, newAddressObject2 = {...};
addresses([newAddressObject1, newAddressObject2]);
But the following would not work!
var tmp_addresses = addresses();
var tmp_addr = tmp_addresses[0];
tmp_addr.title = 'Hey this address wont update';
addresses.splice(0, 1, tmp_addr);
How come? I think knockout adds an internal property to his items in observableArrays and when I try to reinsert one, it will not update.
My problem has now morphed into creating a new object with the same properties of the desired item in the observable array. The way I coded above is simply very dirty-looking. There's gotta be a better way to do that
You are wrongly assigning value to observable title that is the reason why UI not reflecting its changes (2 way binding broken).
Thumb rule is always use () notation while assigning a value to observable (keeps two way binding intact)
viewModel:
var ViewModel = function () {
var self = this;
self.addresses = ko.observableArray([{
'title': ko.observable('one')
}, {
'title': ko.observable('two')
}])
setTimeout(function () {
var tmp_addresses = self.addresses();
tmp_addresses[0].title('blabla'); //assigning data to observable
self.addresses(tmp_addresses);
}, 2000)
};
ko.applyBindings(new ViewModel());
working sample here
PS: Don't get deceived by seeing the value change in viewModel the moment you done assigning using = two binding is broken UI wont reflect VM'S changes .
when you splice up your observableArray UI takes it changes check here
The problem was exactly as #jason9187 pointed out in the comments: The references of the objects in the observable array does not change when I edit a field of them. Therefore, KO would not interpret my array as changed. If the observableArray had contained simple data types, then the way I suggested could work without a problem. However, I have an Object in the array, therefore although I edit the Object, it's reference (pointer) remains the same, and KO thinks that all Objects are the same as before.
In order to achieve what I wanted, we have to solve the deep cloning problem in javascript like in this post.
Now there's a trade-off there, deep cloning is very simple in vanilla if you don't have a circular architecture or functions in your objects. In my case, there's nothing like that. The data comes from a restful API. If anybody in the future gets hold of this problem, they need to deep-clone their 'hard-to-clone' objects.
Here's my solution:
var tmp_addresses = JSON.parse(JSON.stringify(addresses())); //Creates a new array with new references and data
tmp_addresses[0].title = 'my new title';
addresses(tmp_addresses);
Or, if you can create address objects, following will work as well:
var tmp_addresses = addresses();
tmp_addresses[0] = new randomAddressObject();
addresses(tmp_addresses);
Here is a fiddle that I demonstrate both of the methods in a single example

Deep copying objects in angular?

I wonder if there is away to avoid copying references to objects when you need to create a simple object which has an array of embedded objects.The situation is as follow: I have server which accepts a JSON and applies some logic then stores the object in DB. lets say my form is for saving teams in DB. The server accepts team as json. the team has an array of TeamMember objects, my form has a simple field to enter team member info and add it to team teamMembers array. Now here is the problem, when I add a team member to array list and want to add another team member when I type into the field the added member is changed also !. I know the reason
$scope.addTeamMember=function(teamMember){
$scope.team.teamMembers.push(teamMember);
}
and it is because I put same reference into the teamMembers array so I have same object added several times.
to avoid this I should create a new team member object, copy all teamMember properties and and add it the array.
$scope.addTeamMember=function(teamMember){
var newTeamMember; /*<--- copy teamMember */
$scope.team.teamMembers.push(newTeamMember); /*and add newTeamMember*/
}
Your question says you want to "avoid deep copy", but I'm not sure that's accurate. It sounds like you just want to use angular.copy, because you need to create a copy of the team member and add that to the array:
$scope.addTeamMember = function(teamMember) {
var newTeamMember = angular.copy(teamMember);
$scope.team.teamMembers.push(newTeamMember);
};
This is the best documentation available
https://docs.angularjs.org/api/ng/function/angular.copy
there is a live example as well on the page which is self illustrative.
I personally use this:
function copyObjToObj(source, destination) {
if(!angular.equals(source,destination)){
if (!!destination)
angular.copy(source, destination);
else
destination = angular.copy(source);
}
return destination;
}
var destination = copyObjToObj(sourceObj, destination);

Knockout.js - How to change the viewmodel

I'm trying to change the view model which is bound to some part of a DOM template (instead of changing the values of the view model) but I just can't figure out how or if it's even possible
Here's the scenario:
Create a new View Model object
Bind it (e.g. applyBindings(myViewModel)
Create another View Model object
Bind the new object to the same part of the DOM so all elements are now bound to the new object.
I want to do the equivalent of changing the value of DataContext of a control in WPF (on which KO's MVVM pattern is based)
The reason for this is that I'm trying to use the same view model for both the representation of the object in a list and the representation of the object in its own view, so I already have a view model for all objects being shown in the list.
There are other workarounds but I think this would be the most elegant way to do it.
There are two way of working with multiple viewmodel. The first way is to do multiple binding like #nathan gonzalez said. You should do binding up your viewmodels. However this complicates things a bit. Therefore difficult to manage.
The second way is to use master viewmodel. I would recommend this.
http://jsfiddle.net/sinanakyazici/e29EZ/10/
<div data-bind="with: mainvm">
<span data-bind="text: prop, click : action"></span>
</div>
var vm = function(value)
{
this.prop = ko.observable(value);
var self = this;
this.action = function() {
console.log("clicked: " + self.prop());
}
}
var master = {
mainvm : ko.observable(null)
}
master.mainvm(new vm('viewmodel 1'));
master.mainvm(new vm('viewmodel 2'));
ko.applyBindings(master);
so ko.applyBindings() should cover this for you. you can pass in a 2nd parameter that tells which top level element to apply the bindings to, like so:
ko.applyBindings(myExistingViewModel, $('#someElementId')[0]);
you may want to clean the elements first though, like this:
ko.cleanNode($('#someElementId')[0]);
this completely removes the bindings and clears the in memory data for that element and its children bindings.

Should I use methods or subclasses? If that makes sense

A Little Background...
I have an an object called SineMacula which houses many methods for creating form elements and making these form elements do things on a page.
Firstly, when the page loads, a method called setFields() is called which loops through all the fields on a page and sets them appropriately i.e. autocomplete, checkbox etc...
The code for setFields() looks like this:
/**
* Set Fields
* This function will set all fields
*
* The options:
* - fields: the fields selector to loop through
*
* #param object options The field options
*/
SineMacula.prototype.setFields = function (options){
// Set the defaults for the fields
var options = $.extend({
fields: '.field', // Define the default field selector
},options);
// Loop through the fields and set the events
$(options.fields).each(function(){
// Set the field events
SineMacula.setBlur($(this));
SineMacula.setFocus($(this));
SineMacula.setToggleLabel($(this));
// If the field is a checkbox then set it
if($(this).parent().hasClass('checkbox')){
SineMacula.setCheckbox($(this).parent());
}
// If the field is an autocomplete then set it
if($(this).parent().hasClass('autocomplete')){
SineMacula.setDropdown($(this).parent(),{source:$(this).attr('data-source')});
}
// etc...
});
};
Most of the code above can be ignored, but I have inserted all of it so that you can see exactly what I am doing.
My Question
I have quite a few methods of the SineMacula object such as setCheckbox(), setDropdown()...
What I would like to know is, should I be treating these methods as objects in themselves?
So should my code look like this instead:
if($(this).parent().hasClass('autocomplete')){
new SineMacula.dropdown($(this).parent(),{source:$(this).attr('data-source')});
}
Notice the new keyword before calling the dropdown() method.
Is this a better method of working things? Will be use less memory etc?
There is no reason to create an instance of an object only to call the constructor and then throw the object away. By doing the work in the constructor you are just using it as a regular function, but with the overhead of creating an unused object.
(In fact you don't seem to use the SineMacula instance for anything either, other than as a namespace for the methods.)
As a general rule of thumb a new object appears when you need to delegate some responsibility to it. So if you later do something like sineMaculaInstance.setCheckboxValue(checkbox, true) it definitely looks like that should be checkbox responsibility.
Another way of looking at it is to analize the SineMacula object by the Single Responsibility Principle. In short, if you can describe what your object does in one or two lines you are generally ok. If you have to write an entire paragraph to state what SineMacula does, then it looks like you should refactor that object an split concrete responsibilities to other objects.
HTH
It seems to me, that since you are housing all of your methods within this SineMacula namespace / module, there is no purpose in re-instantiating another whole new SineMacula object.
Unless you were going to be adding different / specific prototypes/methods that you don't want attached to the original object, and are specific to a certain section or form element on your page.
var newThing = new SineMacula('doDifferentStuff');
newThing.dropdown = '''do something different''';
The whole reason for instantiating the class would also be to set the new this to be whatever you are invoking it from. And it seems like everything you have is already tied in together, and simply uses the SineMacula.setBlahblah of calling itself.
Hope that doesn't sound too garbled!

Categories

Resources