Partial mapping to existing object with Knockout JS - javascript

I have got a complex model which is contains a load of ko.observable, ko.observableArray and nested objects which contain even more of these observables.
Now originally I had to make methods within each of the models which in composition make up the larger model to take Json data and then populate its observables. Now I tried the ko.mapping plugin but when I use that doing:
ko.mapping.fromJS(jsonData, {}, existingModel);
Which appears to work for most objects, however I have noticed that when I do this it seems to completely overwrite the object, and as I am using knockout js validation i.e:
this.Name = ko.observable().extend({ required: true });
this.Age = ko.observable().extend({ required: true, digits: true });
Problem is that these validation attributes seem to be removed when using the mapping module, so is there a way to get the mapping plugin to just update the values, rather than tampering with the object schema...
I am more than happy to use a different mechanism other than ko.mapping if there is a better way to apply Json data to the models.

From http://knockoutjs.com/documentation/plugins-mapping.html (section Advanced usage)
Sometimes it may be necessary to have more control over how the mapping is performed. This is accomplished using mapping options. They can be specified during the ko.mapping.fromJS call. In subsequent calls you don’t need to specify them again.
So, for example, you can try something like this:
var self = this;
var mapping = {
update: function(arg) {
var data = arg.data;
return {
Name: data.Name && self.Name(data.Name),
Age: data.Age && self.Age(data.Age)
}
}
};
ko.mapping.fromJS(data, mapping);

I ended up having to stick with my approach although I did manage to reduce a LARGE amount of the code written to populate the existing fields by writing the simple plugin below:
https://github.com/grofit/knockout.mapping.merge
It is very simple and doesn't really do much other than just match up objects with the same name to their existing fields. I was hoping to manage child object creation, so it could populate the entire object tree, but alas I ran out of time.
Hopefully it would at least show a possible solution without having to write lots of verbose code to update a model.

If your needs are modest, you can do this with a simple vanilla JS for loop. This example assumes that you have an existing model called 'myViewModel', and data for a partial refresh called 'jsonData':
for (var prop in jsonData) {
if (myViewModel.hasOwnProperty(prop)) {
myViewModel[prop](jsonData[prop]);
}
}
As it's only updating the value, any additional attributes you've attached to the observables should remain in place.

Related

Avoiding duplication of key/data

I have a design annoyance with some existing code in JS. The code is working, so I have no desperate hurry to change it, but the duplication shown below does annoy me. What is the usual/recommended/official way of avoiding this situation?
The actual system is a large/complex financial system, so I have simplified it to the most basic example which demonstrates the problem:
var colours={
red:{id:"red", vals:[1,0,0]},
green:{id:"green", vals:[0,1,0]},
grey:{id:"grey", vals:[0.5,0.5,0.5]}
// ...etc
};
// id needs to be known internally within the object - thus it is defined as a property.
// e.g:
colour.prototype.identify(console.log(this.id));
// id also needs to be used externally to find an object quickly.
// e.g:
function getcolour(s){return colours[s];}
// Although this works. It does mean duplicating data, with the theoretical possibility of a mismatch:
var colours={//...
blue:{id:"green", // oh dear...
How would this normally be handled by the experts?
This question is somewhat subjective.
When creating my applications I typically try do do the following:
never define same data in multiple places. source should always be unambiguous
if I need to create any indices for faster/easier access, I use utility methods to do it. Those methods should be properly unit-tested, so that I would have little doubts on them doing the wrong thing
use third party libraries as much as possible (such as already suggested lodash or underscore) to minimize the amount of code to be written/maintained.
If your algorithms and utilities are properly unit-tested you should not worry (too much) about getting the data into inconsistent state. However, if those are critically important systems/interfaces, you may add some validation on output. And it is generally a good practice to have data validation and marshaling on input.
Explanation on the utility methods:
if you have data array, say
var data = [{"id":"i_1", ...}, {"id":"i_2", ...},{"id":"i_3",....}];
Then and you have to create an index out of that or create more data sets based on the original array, then you create yourself a library of utility methods that do the modification on the array, create derivative data sets, or iterate on the array and create a resulting item on the fly. For example:
var createIndex = function( arr ){
// do something that converts the data array with expected structure to object
// {
// i_1: {"id":"i_1", ...},
// i_2: {"id":"i_2", ...},
// i_3: {"id":"i_3", ...}
return newObj;
}
This method will create a hash-map to access your data, which is faster then to iterate over the original array all the time. But now, this method you can easily unit-test and be sure that when you use it on the source data to get your intended dataset, there will be no inconsistency.
I wouldn't change the colours[key] direct access with other method to avoid duplication.
Any other attempt will lead to processing and you have mentioned that you have a large amount of data.
I assume that the duplication is over the incoming data that is a waste.
An example of processing over the network data consuming could be, going over the map object and set the id dynamically according to the key. (processing vs traffic)
colours[key].id = key
You can filter your object converting it to an array of objects and then filtering unique values. Converting it to an array would allow you to perform a lot of operations quicker and easier.
So you can map your object to an array:
var coloursArray = myObj.map(function(value, index) {
return [value];
});
Remove duplicates:
function removeDuplicates() {
return coloursArray.filter((obj, pos, arr) => {
return arr.map(mapObj => mapObj[id]).indexOf(obj[id]) === pos;
});
}
You can remove duplicates from an array using for example underscore.js through the .uniq method:
var uniqueColoursArray = _.uniq(coloursArray , function(c){ return c.id; });
Moreover, this function is pretty useless because you can access your element directly:
function getcolour(s){return colours[s];}
Calling colours[s] it is also shorter than getcolour(s). Your function would make sense if you pass also the array because it is not accessible in some other scope.
Then I can't understand why you do pass a console.log as parameter here:
colour.prototype.identify(console.log(this.id));
maybe you would like to pass just the this.id

How to traverse JS object and all arrays and objects inside to compare it with its copy?

I have a selectedItem object in Angular, it contains other objects and arrays. I create a deep copy using a JSON trick:
$scope.editableItem = JSON.parse(JSON.stringify($scope.selectedItem))
Then I use editableItem model in inputs, change some values inside. selectedItem doesn't change. Then I want to send via PATCH all the changes made, but not the fields which were not changed. So I need to strip the editableItem from all fields that are the same in unchanged selectedItem.
How to do this efficiently? I was thinking about traversing object recursively using Underscore, but I'd really like to know if it's a good way of thinking before I tackle it.
Alternatively I could probably create third object which would only contain touched fields from the second one, added dynamically, but I'm not sure how to approach this.
EDITED:
To be clear, I expect the answer to be generic and assume the most complicated object structure possible. For example no answers from this question are applicable here as they either assume the object has only simple fields or they need to have Angular watcher explicitly set for every field separately.
I do something similar with a function like this:
function getUpdateObject(orig, current) {
varChanges = {};
for (var prop in orig) {
if (prop.indexOf("$") != 0 && orig[prop] !== current[prop]) {
varChanges[prop] = current[prop];
}
}
return varChanges ;
};
I don't think this will get you all the way there. I'm not using it in any scenarios where the objects have member objects or arrays, but you should be able to test if "prop" is an object or array and call it recursively. The biggest caveat I see to that approach is if you have a deep, nested structure, you may not detect a change until you're down several levels. You'd probably have to keep the full potential hierarchy for a changed property in memory, then when you detect a change at a lower, level, write the whole hierarchy to the output object.
This is what I ended up with. Maybe it'll help someone. I used DeepDiff library. Code is in CoffeScript, should be easy to translate to JavaScript if anyone needs it.
$scope.getChangesObject = () ->
selected = $scope.selectedItem
editable = $scope.editableItem
changes = {}
differences = DeepDiff(selected, editable)
for diff in differences
formattedPath = ""
for pathPart, index in diff.path
if index isnt diff.path.length - 1
formattedPath += pathPart + "."
else
formattedPath += pathPart
changes[formattedPath] = editable[formattedPath]
changes

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

Ember GroupableMixin by using groupProperty

I am trying to implement GroupableMixin (https://github.com/ahawkins/ember.js/blob/groupable-mixin/packages/ember-runtime/lib/mixins/groupable.js) on EmberJS but I have trouble using it.
I want to specify a groupProperty in my ArrayController (just like how we have sortProperties) and I want my groupings to be calculated based on that property.
I did try http://emberjs.jsbin.com/cawaq/3/edit and this is what I got:
App.UsersIndexController = Ember.ArrayController.extend({
sortProperties: ['created_at'],
groupProperty: 'role',
groupedContent: groupBy('role'), //How do it reference groupProperty in groupBy?
//How can groupedContent be updated whenever groupProperty is changed?
});
I have seen http://discuss.emberjs.com/t/ember-enumerable-no-group-by/3594/6 and http://jsbin.com/ukagip/2/edit but still cannot quite figure out how to make that work properly.
The intent and behavior of Mr. Hawkins' GroupableMixin is very different from the one that's used discussed in Ember's Discourse forum. It seems that you're confusing the two methods.
Mixin Approach (a la Mr. Hawkins)
Note this is the approach I'd recommend
The GroupableMixin is an instance of Ember.Mixin, which is used to extend an object's prototype. For a more in depth explanation of mix-in's see Coding Value's explanation.
We can determine the mix-ins requirements and behavior from reading the tests for the mix-in:
the class must behave like an ArrayProxy (so ArrayController's are fine)
a groupBy property must be set for the class
So we can include this mix-in in an array controller as follows:
App.UsersIndexController = Ember.ArrayController.extend(Ember.GroupableMixin, {
groupBy: 'role'
});
Now that we've satisfied the mix-in's requirements, we'll have access to a few new computed properties and functions such as groupedContent for free. Without adding anything else to the controller, we could write something like this in the template to access the groups:
{{#each group in groupedContent}}
<h1>{{group.name}}</h1>
<ul>
{{#each user in content}}
<li>{{user.name}}</li>
{{/each}}
</ul>
{{/each}}
Here's an example that groups words in an array controller by first letter.
Computed Helper Approach (a la Ember Sherpa)
This method creates a helper which defines a computed property based on the key provided to the function. We can create a similar function which maps to the group interface of the mix-in:
Sherpa = {};
Sherpa.groupBy = function (groupBy) {
var dependentKey = 'content.#each.' + groupBy;
return Ember.computed(dependentKey, function(){
var result = [];
this.get('content').forEach(function(item){
var hasGroup = !!result.findBy('name', get(item, groupBy));
if (!hasGroup) {
result.pushObject(Ember.Object.create({
name: get(item, groupBy),
content: []
}));
}
result.findBy('name', get(item, groupBy)).get('content').pushObject(item);
});
return result;
});
};
Now we can use this function to create a groupedContent computed property: groupedContent: Sherpa.groupBy('role')
Here's an example which uses the same template as the previous example and has only swapped the mix-in with this method.
Recommendation
Use the GroupableMixin, it's far more robust than the method discussed in the forum:
The method discussed in the forum...
is simple and easy to understand
is inefficient
Recomputes the entire groups array whenever a change is made to the array or relevant property
Doesn't implement map structures for lookups and instead searches iteratively through the array
Nukes all itemControllers on any computed change: If you check some boxes in the second example and then add a new word, all of the checkboxes will be cleared
The mix-in...
is a much more robust, albeit complex, implementation
handles inserts and removals to groups instead of recomputing the entire map
backs the groups with a map for better performance and efficiency
behaves as expected when using an itemController: just tick of checkboxes and add some words in the first example, and you'll notice that the checkboxes are never cleared

Knockout mapping plugin [create, update]: objects created, cannot update

I've posted my code here: http://jsfiddle.net/HYDU6/6/
It's a pretty stripped-down version of what I'm actually working with, but captures the essence of my problem. My view model is like so:
var viewModel = {
objects: {
foo: [
{ text: "Foo's initial" },
],
bar: [
{ text: "Bar's initial" },
]
}
}
I'm using the ko.mapping plugin and my create handler for objects instantiates Obj from objects.foo and then objects.bar, returning the resulting two items in an array. This part works fine; I use
var view = {};
ko.mapping.fromJS(viewModel, mapping, view);
My issue is updating based on new data. (i.e., getting data from the server). I have an object of new data and I attempt
ko.mapping.fromJS(new_model, mapping, view);
I suspect this is incorrect but I have not been able to get it working despite extensive searching. (Trust me, it's been days. ): Anyway, thanks for any help.
EDIT: So I've mostly figured it out - I was depending too heavily on mapping.fromJS and certain things were not being wrapped into observables. I also realized that I didn't need the create(), only the update(), as it is called after create() anyway. If you have a similar problem let me know!
John,
When updating your data using ko.mapping be sure you don't create a new item. Your UI is already bound to the existing items, so you just want to update the values of the existing item properties; not create new ones. For the example you posted, you'll want to adjust your "update" method of your map to insert the new values into the correct ko.observable property, rather than creating a new object in it's place. The ko.mapping "update" method has a few different parameter lists depending on usage, with the third parameter being the target object of the map. You would want to update that object's properties.
obj.target[label].items[0].text(obj.data[label][0].text);
But, that's a bit of a mess. You'll probably want to create a second level of mappings (create / update) to handle "deep" object hierarchies like in your fiddle. For example one map for objects at the "foo/bar" level, and another call to ko.fromJS from within "update" with another map for the child Obj() objects.
After fixing that, you'll run into a couple simple binding errors that you can fix using another "with" binding, or a "foreach" binding for the child arrays.
Overall, you've just run into a couple common pitfalls, but nothing too severe. You can learn a bit more about a few of these pitfalls on my blog here : http://ryanrahlf.com/getting-started-with-knockout-js-3-things-to-know-on-day-one/
I hope this helps!

Categories

Resources