Overriding backbone's parse function - javascript

I'm trying to use Backbone with an API.
The default API response format is:
{
somemetadatas:xxx ,
results:yyy
}
Whether it's a fetch for a single model or a collection.
So as far as I know I can override the Backbone parse function with:
parse: function (response) {
return response.results;
},
But I've seen in the documentation:
parse collection.parse(response)
parse is called by Backbone whenever
a collection's models are returned by the server, in fetch. The
function is passed the raw response object, and should return the
array of model attributes to be added to the collection. The default
implementation is a no-op, simply passing through the JSON response.
Override this if you need to work with a preexisting API, or better
namespace your responses. Note that afterwards, if your model class
already has a parse function, it will be run against each fetched
model.
So if I have a response for a collection fetch like that:
{
somemetadatas:xxx ,
results:[user1,user2]
}
The first parse function on the collection will extract [user1,user2].
But the doc says:
Note that afterwards, if your model class
already has a parse function, it will be run against each fetched
model.
So it will try to find response.results; on user1 and user2
I need both parse functions on the model and collection because both model and collection datas will be under the result attribute.
But if i fetch on a collection, I don't want the model parse function to be used againt a single array element.
So is there a solution to this problem?
I think of a solution where my collection parse function will transform something like this:
{
somemetadatas:xxx ,
results:[user1,user2]
}
into something like this:
[ {results.user1} , {results.user2} ]
So that the model parse function will not fail on a collection fetch.
But it's a bit hacky... is there any elegant solution to this problem?
By the way, as my API will always produce results of this form, is it possible to override by default the parse function of all my models and collections? (Sorry i'm a JS noob...)

You could test if the data you receive is wrapped by a results member and react accordingly. For example,
var M = Backbone.Model.extend({
parse: function (data) {
if (_.isObject(data.results)) {
return data.results;
} else {
return data;
}
}
});
And a Fiddle http://jsfiddle.net/9rCH3/
If you want to generalize this behavior, either derive all your model classes from this base class or modify Backbone's prototype to provide this function :
Backbone.Model.prototype.parse = function (data) {
if (_.isObject(data.results)) {
return data.results;
} else {
return data;
}
};
http://jsfiddle.net/9rCH3/1/

Parse also must be implemented in the Collection.
var EgCollection = Backbone.Collection.extend({
parse: function (data) {
if (_.isObject(data.results)) {
return data.results;
} else {
return data;
}
}
});
http://backbonejs.org/#Collection-parse

Related

Finding data in a belongs to relationship in ember

I am trying to find the account associated with the current user in an ember project. I am able to get the users id and pass it to a handlebars script via {{account.user.id}}. All my attempts to find an account with this user ID in my model hook, however, have been unsuccessful.
My current model hook in routes/my-account.js:
model (params) {
let accountID = this.store.query('account', { filter: { user: { id:currentUser} } });
console.log(accountID.id);
return this.get('store').findRecord('account', accountID);
}
accountID is returning as an ember class, but I cannot seem to parse any data from it. How would I go about getting the ID from the returned ember class in order to pass it to my get request?
To get and set properties from and to Ember objects, you have to use get and set, e.g.:
console.log(account.get('id'));
More to the point, though, your .query will (or should, at least) return an array of account models matching the filter. It will be wrapped in a promise—because it's an asynchronous network call—so you'll need to .then it. And you probably just want to grab the first account:
model() {
return this.store.query('account', { filter: { user: { id: currentUser } } })
.then(function(accounts) {
return accounts.get('firstObject');
});
}
If you have a proper {json:api}, you can just get the user, and then get its account relationship from e.g. /api/users/:id/account. Your model hook would look something like:
model() {
return this.store.findRecord('user', currentUser)
.then(function(user) {
return user.get('account');
});
}

How can you manipulate db content thats returned from findAll function in model hook of ember?

The return value of findAll is an unknown mixin. eg . in router xyz.js
model(){
a = this.store.findAll('food-track');
return a
}
How do we manipulate a or extract data from a in model itself like
model(){
a = this.store.findAll('food-track');
some_data = a['_id'];
some_more_data = a['name'];
return some_more_data
}
But then a isn't in a format that I expected it to be in and I wasn't able to perform any function like this? Btw the data is taken from couch/pouchdb.
I agree with kumkanillam answer: findAll will return an array so you'll have to iterate over it.
Also, depending on what your needs are, you can use computed properties to manipulate your model property as well. Here are the docs from Ember
findAll returns Promise which will be resolved to RecordArray which extends ArrayProxy so you can use all the methods available in ArrayProxy.
model(){
return this.store.findAll('food-track').then((result) => {
//here you can forEach method to iterate and to filter use filterBy
//Use objectAt(index) to retrieve object and use get and set
//dont forget to the return transformed result.
return result;
});
}

Wait for property in EmberJS on "server" side

In Ember, you can reference a property in a template, and the template will wait for that property to be populated before rendering.
This works great for obtaining a list of entries, which are populated by an external REST endpoint:
App.ItemsListRoute = Ember.Route.extend({
model: function() {
return {
client: App.Client.create()
}
}
});
Where the Client constructor looks like:
App.Client = Ember.Object.extend({
init: function() {
var _this = this; // For referencing in AJAX callback
$.ajax({
url: MY_API_URL,
type: 'GET'
}).done(function(res) {
_this.set('itemsList', parsedDataFromRes);
});
},
});
For my template that relies on the itemsList, this works great:
...
{{#each item in model.client.itemsList}}
<tr>
...
However, I have another route for a statistics, in which I would like to do some calculations on the results of the request, and return those values to the template:
App.StatsPageRoute = Ember.Route.extend({
model: function() {
var itemCount = getClient().get('itemsList').length;
return {
numItems: itemCount
}
})
I realize this is a contrived example - I could query the length on the template and it would work fine - but you'll have to humor me.
The issue with the above example is that the get('itemsList') is likely to return an undefined value, based on the data race of rendering the template, and the AJAX response and property setter being called.
How can I "wait" for a property to become available in my JS (not template code) so that it can be used to provide a model for the template?
Is converting the 'itemsList' property to a function returning a promise the most "Ember-Like" way of doing things? Would this heavily complicate my template logic?
You can use a promise and do additional operation in the then function call.
For example you can do
App.StatsPageRoute = Ember.Route.extend({
model: function() {
var itemCount = getClient().then(function(promiseResult){
var itemCount = promiseResult.get('itemsList').length;
return {
numItems: itemCount
}
})
})
To use this however, you need to make sure that getClient returns a promise. I suggested you use ic-ajax (included in Ember). It's an abstraction over jquery ajax that returns promises instead of expecting success and error callbacks
Furthermore, I strongly suggest that you look into ember-data and try to build a backend compliant with the spec. This way, you have a nice abstraction for your data and it increase development velocity tremendously, since you don't have to worry about interaction with the backend.
Edit: Returning a promise instead of data will not impact your template logic. Ember will resolve the promise and the Ember run loop will update the template automatically when the value changes. What you might want to have though, is perhaps a default value or a spinner of some kind. However, this is probably something you would have done regardless of if your model was returning a promise or not.

Keep somewhere additional data after backbone's collection fetched

I receive next data from server after get request:
{ data: { items: [...], itemsCount: Number } }
I'm saving items in backbone collection in next way:
parse: function (response) {
return response.data.items;
}
How do I keep somehow in collection or outer itemsCount?
If I understand your question correctly, the answer is straight forward. You're not limited to simply saving data in a collection or in the model.attributes hash. Backbone objects are traditional javascript objects and you can create any custom property you'd like.
So, in your parse function, you could do...
parse: function (response) {
this.itemsCount = response.itemsCount
return response.data.items
}
Note that I am assuming that your parse function is scoped to the collection. If it's not, then I need to see more code to demonstrate how to properly scope the function.

Ember.js REST Adapter without JSON root

The Ember.js REST Adapter expects the JSON to be returned as:
{
"person": {
"first_name": "Barack",
"last_name": "Obama",
"is_person_of_the_year": true
}
}
But my API returns the data without a root element:
{
"first_name": "Barack",
"last_name": "Obama",
"is_person_of_the_year": true
}
Is it possible to customize the REST Adapter so that it accepts my JSON data? Right now it's showing "Assertion failed: Your server returned a hash with the key 0 but you have no mapping for it"
UPDATE:
Based on Sherwin Yu's answer below, this is what I came up with, seems to work so far: https://gist.github.com/richardkall/5910875
You could also normalize it into something ember would expect.
App.PersonSerializer = DS.RESTSerializer.extend({
normalizePayload: function(type, payload) {
var typeKey = type.typeKey;
return {
typeKey: payload
}
}
});
Yes, you can write your own custom REST adapter. Take a look at the source code in the JSONSerializer, RESTSerializer (which extends the JSONSerializer), and the REST adapter.
Basically, the you need to override the extract* methods from the JSONSerializer.
Currently, it looks something like this:
extract: function(loader, json, type, record) {
var root = this.rootForType(type);
this.sideload(loader, type, json, root);
this.extractMeta(loader, type, json);
if (json[root]) {
if (record) { loader.updateId(record, json[root]); }
this.extractRecordRepresentation(loader, type, json[root]);
}
},
Notice how it checks json[root] -- you'd have to write your custom method based on your expected API response.
Another approach would be to "preprocess" the json from the API to use a root element. You could do this by finding out what methods call extract* (which passes it the json) and before it does so, modify the json to contain the root element.
Hope this helps, please let me know if it's unclear.
I solved this by extending DS.RESTSerializer. extractArray method needs to be overloaded when server response is array type.
App.PersonSerializer = DS.RESTSerializer.extend({
extractSingle: function (store, type, payload, id) {
var wrappedObj = {};
wrappedObj[type.typeKey] = payload;
return this._super(store, type, wrappedObj, id);
}});
The easiest way is to not use the RESTSerializer but the much simpler JSONSerializer, which does not expect a root element.
Good resources on understanding which serializer to use for a given API can be found in these two blog posts:
http://thejsguy.com/2015/12/05/which-ember-data-serializer-should-i-use.html
http://emberigniter.com/fit-any-backend-into-ember-custom-adapters-serializers/

Categories

Resources