Backbone parse server response to model - javascript

I'm trying to deal with a server response, and am a little confused how to turn the json response into Backbone Models.
My Backbone model looks like so:
Entities.Recipe = Backbone.Model.extend({
defaults: {
id: '',
name: '',
introduction: ''
},
parse: function (response)
{
if(._isObject(response.results)){
return response.results
else {
return response
}
})
Entities.RecipeCollection = Backbone.Collection.extend({
url: 'recipes',
model: Entities.Recipe
)}
var API = {
getRecipeEntities: function (){
var recipes = new Entities.RecipeCollection()
var defer = $.Deferred()
recipes.fetch({
url: 'http://3rdpartyApilocation.com/recipes'
success: function (data) {
defer.resolve(data)
}
})
var promise = defer.promise()
$.when(promise).done(function (fetchedData)
{})
return promise
}
RecipeManager.reqres.setHandler('recipe:entities', function()
{
return API.getRecipeEntities()
}
And the response.results is an Array of objects - with each object having an id key, a name key and an introduction key. But because I am so inexperienced with Backbone I have no idea how to map those results to the model?
I have installed Chromes Marionette inspector and when I look at the entire array of results seems to be passed to the model, rather than each individual object within each response.result being set to each individual model. Sorry if I can't be more clear - I'm very new to Backbone...

Perhaps your confusion is because you're in fact able to use parse on a model or on a collection. And from your explanation it looks like the response.results object returns a list of objects that you want to become models in your application. But because you're catching that object in a model, the model doesn't know what to do with that array.
Let's say you have a response like this:
{
"status": "ok",
"results": [
{
"id": 1,
"name": "Pie"
}, {
"id": 2,
"name": "Rice"
}, {
"id": 3,
"name": "Meatballs"
}
]
}
Then you would just use parse on your Collection to let it know the response isn't array itself, and help it find it in the results property.
Here's a working example:
var Recipe = Backbone.Model.extend();
var Recipes = Backbone.Collection.extend({
model: Recipe,
url: 'http://www.mocky.io/v2/56390090120000fa08a61a57',
parse: function(response){
return response.results;
}
});
var recipes = new Recipes();
recipes.fetch().done(function(){
recipes.each(function(recipe){
/** Just demo of the fetched data */
$(document.body).append('<p>'+ recipe.get('name') +'</p>');
});
});
<script src='http://code.jquery.com/jquery.js'></script>
<script src='http://underscorejs.org/underscore.js'></script>
<script src='http://backbonejs.org/backbone.js'></script>

Related

Parsing data in Backbone.js

I've got a number of backbone models which have a number of nested sub-models. My solution looks like this:
Models.Base = Backbone.Model.extend ({
relatedModels: {},
/**
* Parses data sent according to the list of related models.
*
* #since Version 1
* #param {Object} response Response
* #return {Object} Parsed data
*/
parse: function (response) {
var key,
embeddedClass,
embeddedData;
for (key in this.relatedModels) {
embeddedClass = this.relatedModels[key];
embeddedData = response[key];
response[key] = new embeddedClass (embeddedData, { parse: true });
}
return response;
}
});
(using stuff gleaned from this post - Nested Models in Backbone.js, how to approach)
This works fine whilst I'm getting stuff from the server:
Models.Individual = Models.Base.extend({
idAttribute: "idInd",
urlRoot: "data/individuals/save",
relatedModels: {
details: Collections.DetailList,
relationships: Collections.RelationshipList
}
});
... but when I try and initialise a model from a plain bit of JSON, for example if I were to do this:
var Ind = new Models.Individual ({
idInd: 1,
name: "Bob Holness",
details: [
{ option: "I'd like an 'e' please, bob" },
{ option: "Can I have a 'p' please, bob" }
],
relationships: []
});
... it doesn't seem to want to parse "details". I'd guess that was because it's not running the Parse function, but anyway - how can I get it to parse the data in both instances?
The easiest way to do it would be to pass parse: true to the constructor, like so:
var Ind = new Models.Individual ({
idInd: 1,
...
}, { parse: true });
If you do this a lot you can override the constructor in your base class and make it pass parse: true every time you create a new model instance:
Models.Base = Backbone.Model.extend({
constructor: function(attributes, options) {
var opts = $.extend({}, options || {});
if (_.isUndefined(opts.parse)) {
opts.parse = true;
}
Backbone.Model.call(this, attributes, opts);
},
...
});

Setting Backbone model through JSON file

I'm new to backbone and I'm trying to set a collection of models with data loaded from a json-file. But it won't get set. Whats the problem here? Anyone having an idea? When i fetch the collections it seems that its empty.
JSON
{
"items": [
{
"id": "0",
"media": "",
"desc": "lorem",
"img": "ipsum"
},
{
"id": "1",
"media": "",
"desc": "lorem",
"img": "ipsum"
}
]
}
Javscript
var Card = Backbone.Model.extend({
defaults: function() {
return {
items: {
id: "lorem",
media: "lorem",
desc:"lorem",
img: "lorem"
}
};
},
clear: function() {
this.destroy();
}
});
var CardCollection = Backbone.Collection.extend({
model: Card,
url: "file.json",
parse: function (response) {
for (var i = 0; i<response.items.length; i++) {
}
}
});
var cards = new CardCollection();
cards.fetch();
console.log(cards)
fetch is asynchronous, it returns immediately before the data has been loaded.
Try this
var cards = new CardCollection();
cards.fetch({
success: function () {
console.log(cards);
}
});
The CardCollection.parse method is called with the response. If no transformation is required to the response from the server, you can remove the parse method of the CardCollection. The default implementation is pass thru and the response is used as-is.
Basically parse should return the array to be added to the collection. The current implementation does not do anything. return is missing for the implementation.
Just return something from your parse method and you are good to go. It is expected to return an array of objects and each object will fit into your model. So finally you will have a collection of models corresponding to array of objects being returned from parse method.
Try this
var CardCollection = Backbone.Collection.extend({
model: Card,
url: "file.json",
parse: function (response) {
return response.items;
}
});
Read this Documentation on Parse Method for more information.

Converting JSON data to Backbone Model with child Collection

I am working with a Playlist object which has some properties defining itself as well as a PlaylistItem collection.
When I receive data from my server, I get its JSON response in my client-side success method:
success: function (data) {
console.log("JSON data:", data);
playlists = _.map(data, function (playlistConfig) {
return new Playlist(playlistConfig);
});
...
}
Here, I convert my JSON data into Playlist objects. Each Playlist object is a Backbone.Model.
Here's how my data looks:
And here's what the Playlist constructor looks like:
return function(config) {
var playlist = new Playlist(config);
return playlist;
};
var Playlist = Backbone.Model.extend({
defaults: function() {
return {
id: null,
userId: null,
title: 'New Playlist',
selected: false,
position: 0,
shuffledItems: [],
history: [],
items: Backbone.Collection.extend({
model: PlaylistItem
})
};
},
...
}
My problem:
If I create a Playlist object with defaults, it initializes with an empty Backbone.Collection for PlaylistItem. However, if I create a Playlist object with an already-defined collection, I get a basic array and not a Backbone.Collection. This is because I am working with JSON data from the server which has not been converted to Backbone entities yet. That data is extended over the Playlist's defaults and overwrites the Backbone.Collection entity.
What is a proper way to initialize with a populated Backbone.Collection? I could write code in Initializes which checks the type of my items array and if it is not a Backbone.Collection I could create a new Backbone.Collection and add the items to it and then replace the old array with the new one, but that seems really hoakey.
Don't define your PlalistItems Collection inside defaults, but beforehand.
Then, create an initialize method on your Playlist Model like so:
var PlaylistItems = Backbone.Collection.extend({
...
});
var Playlist = Backbone.Model.extend({
initialize: function() {
this.set('items', new PlaylistItems(this.items));
},
defaults: function() {
return {
id: null,
userId: null,
title: 'New Playlist',
selected: false,
position: 0,
shuffledItems: [],
history: [],
items: [] // don't define your PlaylistItems Collection here
};
}
});
Check out the fiddle here: http://jsfiddle.net/georgedyer/r2XKb/
(you'll need to open the console to see the collection)
Another issue I ran into was after you save your model to the server you will get back a response that will change your embedded collection into a regular javascript array. To remedy this I had to override the parse function on my model class like so:
var model = backbone.Model.extend({
urlRoot : "/rest/model",
initialize: function(){
this.set("myCollection", new MyCollection(this.myArray));
},
defaults: {
myArray: []
},
parse: function(response){
this.set(response);
this.set("myArray", new MyCollection(response.myArray));
}
});

how to override Backbone's parse function?

I try to use backbone in my project. But I have met problem when try to overide parse method of Backbone. The server has send back more data than I want.For example:
What I want is:
[{
id: "123",
name: "david"
},{
id: "456",
name: "kevin"
}]
but the server's result is :
{
total: 1000,
items:[{
id: "123",
name: "david"
},{
id: "456",
name: "kevin"
}]
}
so I want process result in parse method and return only the array. How can I do this? When I try I got error. How should I do?
In your backbone model, define the parse function the way you would like:
Model = Backbone.Model.extend({
parse: function () {
return {
id: this.get("id"),
name: this.get("name")
}
}
});
But, it would be better to handle and set the data in the model initializer, like so:
Model = Backbone.Model.extend({
initialize: function (attrs) {
try {
//TODO HERE: test for correct values and throw errors
// set object variables here
this.set({
name: attrs.name,
id: attrs.id
});
} catch (e) {
console.log(e);
}
}
});
No need to overwrite the parse function now. This way you know the data that your model is handling is good, and you set what variables it contains. This avoids many errors from invalid data.
Each item in the array should really be a submodel, which is what I have written above. Your parent model should look like:
Model = Backbone.Model.extend({
initialize: function (items) {
this.subModels = [];
items.forEach(function (item) {
this.subModels.push( new SubModel(item) )
});
}
});
Or as a collection:
Collection = Backbone.Collection.extend({
model: ItemModel,
});
To which you would pass response.items
From Backbone Parse docs
Collection = Backbone.Collection.extend({
model: YourModel,
parse: function(response){
return response.items;
}
});

Backbone.js model with collection

I have 2 models and one collection. JobSummary is a model, JobSummaryList is a collection of JobSummary items, and then I have a JobSummarySnapshot model that contains a JobSummaryList:
JobSummary = Backbone.Model.extend({});
JobSummaryList = Backbone.Collection.extend({
model: JobSummary
});
JobSummarySnapshot = Backbone.Model.extend({
url: '/JobSummaryList',
defaults: {
pageNumber: 1,
summaryList: new JobSummaryList()
}
});
When I call fetch on the JobSummarySnapshot object, it gets everything... Except when I move through the summaryList collection they are all of type object and not JobSummary.
I suppose this makes sense since other than the defaults object, it doesn't know that the summaryList should be of type JobSummaryList. I can go through each item and convert it to a JobSummary object, but I was hoping there was a way to do it without having to do it manually.
Here's my test code (working jsfiddle here):
var returnData = {
pageNumber: 3,
summaryList: [
{
id: 5,
name: 'name1'},
{
id: 6,
name: 'name2'}
]
};
var fakeserver = sinon.fakeServer.create();
fakeserver.respondWith('GET', '/JobSummaryList', [200,
{
'Content-Type': 'application/json'},
JSON.stringify(returnData)]);
var callback = sinon.spy();
var summarySnapshot = new JobSummarySnapshot();
summarySnapshot.bind('change', callback);
summarySnapshot.fetch();
fakeserver.respond();
var theReturnedList = callback.getCall(0).args[0].attributes.summaryList;
_.each(theReturnedList, function(item) {
console.log('Original Item: ');
console.log(item instanceof JobSummary); // IS FALSE
var convertedItem = new JobSummary(item);
console.log('converted item: ');
console.log(convertedItem instanceof JobSummary); // IS TRUE
});
UPDATE:
It occurred to me that I could override the parse function and set it that way... I have this now:
JobSummarySnapshot = Backbone.Model.extend({
url: '/JobSummaryList',
defaults: {
pageNumber: 1,
summaryList: new JobSummaryList()
},
parse: function(response) {
this.set({pageNumber: response.pageNumber});
var summaryList = new JobSummaryList();
summaryList.add(response.summaryList);
this.set({summaryList: summaryList});
}
});
This works so far. Leaving the question open in case someone has comment on it....
Your parse() function shouldn't set() anything, its a better practice to just return the attributes, Backbone will take care of setting it. e.g.
parse: function(response) {
response.summaryList = new JobSummaryList(response.summaryList);
return response;
}
Whatever you return from parse() is passed to set().
Not returning anything (which is like returning undefined) is the same as calling set(undefined), which could cause it not to pass validation, or some other unexpected results if your custom validate()/set() methods expects to get an object. If your validation or set() method fails because of that, the options.success callback passed to Backbone.Model#fetch() won't be called.
Also, to make this more generic, so that set()ing to a plain object from other places (and not only from the server response) also effects it, you might want to override set() instead:
set: function(attributes, options) {
if (attributes.summaryList !== undefined && !(attributes.summaryList instanceof JobSummaryList)) {
attributes.summaryList = new JobSummaryList(attributes.summaryList);
}
return Backbone.Model.prototype.set.call(this, attributes, options);
}
You might also find Backbone-relational interesting - it makes it much easier to deal with collections/models nested inside models.
edit I forgot to return from the set() method, the code is now updated

Categories

Resources