Deep-wrapping of JSON response objects in Ember Objects - javascript

I am currently working on an Ember project where I fetch a complex JSON response in a Route's model function. In the corresponding template, I display attributes of the response. For some of them, there are certain actions available which lead to changes on this model.
I want these changes to be directly reflected in the UI using Ember's binding. Unfortunately, while accessing a top-level response property in the ObjectController with this.get('attributeFromJson') works well, trying the same (or a set) on one of the child properties doesn't work.
I have produced the following JSFiddle to demonstrate the problem: http://jsfiddle.net/KkD6U/
My understanding is that I would need to convert the response into a Ember.Object manually in order to benefit from its Ember.Observable mixin.
My question is: is there any plain Ember way to automatically "deep-wrap" a whole JSON response into a structure of Ember.Object to use get and set on the whole response? What would be the plain Ember way to do this?
Update:
In the meantime, I built a trivial function to convert plain JS objects to Ember objects, see jsFiddle here: http://jsfiddle.net/5vEcL/1/
Does it look feasible?

My understanding is that I would need to convert the response into a Ember.Object manually in order to benefit from its Ember.Observable mixin.
Basically yes, if you would wrap your child objects in a Ember.Object.create(...) it would work with ember's binding mechanism:
...
anArray: [
Ember.Object.create({
id: '1',
anotherAttribute: '123'
}),
Ember.Object.create({
id: '2',
anotherAttribute: '456'
})
]
...
See here your modified jsfiddle of the example above: http://jsfiddle.net/ZZFkA/
Hope it helps.

Related

Dojo JsonRestStore with array not at root-level of JSON response

Is there a way to configure a JsonRestStore to work with an existing web service that returns an array of objects which is not at the root-level of the JSON response?
My JSON response is currently similar to this:
{
message: "",
success: true,
data: [
{ name: "Bugs Bunny", id: 1 },
{ name: "Daffy Duck", id: 2 }
],
total: 2
}
I need to tell the JsonRestStore that it will find the rows under "data", but I can't see a way to do this from looking at the documentation. Schema seems like a possibility but I can't make sense of it through the docs (or what I find in Google).
My web services return data in a format expected by stores in Ext JS, but I can't refactor years worth of web services now (dealing with pagination via HTTP headers instead of query string values will probably be fun, too, but that's a problem for another day).
Thanks.
While it's only barely called out in the API docs, there is an internal method in dojox/data/JsonRestStore named _processResults that happens to be easily overridable for this purpose. It receives the data returned by the service and the original Deferred from the request, and is expected to return an object containing items and totalCount.
Based on your data above, something like this ought to work:
var CustomRestStore = declare(JsonRestStore, {
_processResults: function (results) {
return {
items: results.data,
totalCount: results.total
};
}
});
The idea with dojo/store is that reference stores are provided, but they are intended to be customized to match whatever data format you want. For example, https://github.com/sitepen/dojo-smore has a few additional stores (e.g. one that handles Csv data). These stores provide good examples for how to handle data that is offered under a different structure.
There's also the new dstore project, http://dstorejs.io/ , which is going to eventually replace dojo/store in Dojo 2, but works today against Dojo 1.x. This might be easier for creating a custom store to match your data structure.

Creating and updating nested objects in Ember

I got nested JSON data from the server like this:
{
name: "Alice",
profile: {
something: "abc"
}
}
and I have the following model:
App.User = Ember.Object.extend({
name: null,
profile: Ember.Object.extend({
something: null
})
})
If I simply do App.User.create(attrs) or user.setProperties(attrs), the profile object gets overwritten by plain JS object, so currently I'm doing this:
var profileAttr = attrs.profile;
delete attrs.profile
user.setProperties(attrs); // or user = App.User.create(attrs);
user.get('profile').setProperties(profileAttrs);
It works, but I've got it in a few places and in the real code I've got more than one nested object, so I was wondering if it's ok to override User#create and User#setProperties methods to do it automatically. Maybe there's some better way?
Based on your comment, you want the automatic merging behaviour you get with models (the sort of thing you get with .extend()). In that case, you could try registering a custom transformer, something like:
App.ObjectTransform = DS.Transform.extend({
deserialize: function(json){
return Ember.Object.create(json);
}
});
App.User = DS.Model.extend({
profile: DS.attr('object')
});
See: https://github.com/emberjs/data/blob/master/TRANSITION.md#json-transforms
If you are doing your server requests without an adapter you can use the model class method load() with either an array of json objects or a single object. This will refresh any known records already cached and stash away the JSON for future primary key based lookups. You can also call load() on a model instance with a JSON hash as well but it will only update that single model instance.
Its unclear why you are not using an adapter, you can extend one of the Ember Model adapters and override the the record loading there, eg. extend from the RESTAdapter and do any required transform on the JSON if required by overriding _loadRecordFromData
You can also override your models load function to transform data received if required as well. The Ember Model source is fairly easy to read so its not hard to extend to your requirements.

Breeze not returning full data model

Using Breeze with Entity Framework code first to return data from calls to a web service.
I have a data model that's several levels deep. In this instance I'm returning a "schedule" object, which has a number of child "DefaultItems", each one of which has a cost and a single "type" child with its own properties.
If you call the web service for one of these directly, you get something like this, which is as expected:
{
$id:"1",
$type:"Schedule_06B188AC55B213FE4B13EA5B77D9C039007E80E9DB6F6841C055777A028C5F95, EntityFrameworkDynamicProxies-Core",
DefaultItems:[
{
$id:"2",
$type:"DefaultItem, Core",
RowId:"d422af5d-d6ca-46a3-a142-1feb93348e1d",
Cost:1,
Type:{
$id:"3",
$type:"Type, Core",
RowId:"38ed6d1b-d0b7-43cb-b958-2b2424b97759",
Type:"Type1"
},
Schedule:{
$ref:"1"
}
},
//more DefaultItem objects
{},
{}
],
RowId:"627eb2f2-ec74-4646-b3d1-d6423f84a2cd",
Start:"2010-01-18T00:00:00.000",
End:"2019-01-18T00:00:00.000"
}
This then comes down to the browser, where knockout is used to bind it to data objects. The trouble is that at this point, the data only seems to be one level deep.
So I can get at Schedule.Start and Schedule.End without issue. I can also iterate through the DefaultItem objects inside my Schedule and get their Costs out. But the Type objects inside DefaultItem just aren't there.
It's not about using an incorrect name to bind them: if you pause in the browser debugger and drill down into the JSON that the browser has, there's no Type objects at all, not even empty objects where they should be.
How come they come out of the web service, but don't seem to be in the data that Breeze passes back to the browser?
Apparently in Breeze, relationships have to be defined both ways in order to propagate. So I had to ensure that my the primary key in my Type class was marked as a foreign key to the DefaultItem class.
I believe this is currently registered as a bug. It's certainly a bit annoying.

Backbonejs - use nested object as id attribute

Is it possible to use a nested object as the id attribute in Backbone?
For e.g. something like,
var MyModel = Backbone.Model.extend({
defaults : {
'info': {
'name': ""
},
},
idAttribute: "info.name"
}
BTW, the above doesn't work as an ID, I added it here just to give an idea of what I was trying to achieve.
TIA
I don't think you can directly assign a nested object as an idAttribute .
But you can directly set the id on the model when the response is served by the server in the parse method
parse: function(response) {
response.id = response.info.name;
return response;
}
as #Sushanth says, parse is for sure a good way to do this.
But generally using nested objects within Backbone Models is unsafe and not the best way to do it. When you change your response.info.name property, and you bind events to response.info, you will not get notified.
After a few years with Backbone, I would like to tell you, that parsing your received models from server into primitive objects is the best you can do.
If you want your models to go back to server exactly the same way they came in, you could override the toJSON function, which could transform it back. Of course, you need to implement those two... :/

Persisting & loading metadata in a backbone.js collection

I have a situation using backbone.js where I have a collection of models, and some additional information about the models. For example, imagine that I'm returning a list of amounts: they have a quantity associated with each model. Assume now that the unit for each of the amounts is always the same: say quarts. Then the json object I get back from my service might be something like:
{
dataPoints: [
{quantity: 5 },
{quantity: 10 },
...
],
unit : quarts
}
Now backbone collections have no real mechanism for natively associating this meta-data with the collection, but it was suggested to me in this question: Setting attributes on a collection - backbone js that I can extend the collection with a .meta(property, [value]) style function - which is a great solution. However, naturally it follows that we'd like to be able to cleanly retrieve this data from a json response like the one we have above.
Backbone.js gives us the parse(response) function, which allows us to specify where to extract the collection's list of models from in combination with the url attribute. There is no way that I'm aware of, however, to make a more intelligent function without overloading fetch() which would remove the partial functionality that is already available.
My question is this: is there a better option than overloading fetch() (and trying it to call it's superclass implementation) to achieve what I want to achieve?
Thanks
Personally, I would wrap the Collection inside another Model, and then override parse, like so:
var DataPointsCollection = Backbone.Collection.extend({ /* etc etc */ });
var CollectionContainer = Backbone.Model.extend({
defaults: {
dataPoints: new DataPointsCollection(),
unit: "quarts"
},
parse: function(obj) {
// update the inner collection
this.get("dataPoints").refresh(obj.dataPoints);
// this mightn't be necessary
delete obj.dataPoints;
return obj;
}
});
The Collection.refresh() call updates the model with new values. Passing in a custom meta value to the Collection as previously suggested might stop you from being able to bind to those meta values.
This meta data does not belong on the collection. It belongs in the name or some other descriptor of the code. Your code should declaratively know that the collection it has is only full of quartz elements. It already does since the url points to quartz elements.
var quartzCollection = new FooCollection();
quartzCollection.url = quartzurl;
quartzCollection.fetch();
If you really need to get this data why don't you just call
_.uniq(quartzCollecion.pluck("unit"))[0];

Categories