Backbone JS complex model fetch - javascript

I have two backbone models, loaded from server:
var Model = Backbone.Model.extend({});
var SubModel = Backbone.Model.extend({});
var SubCollection = Backbone.Collection.extend({
model: SubModel
});
var m = new Model();
m.fetch({success: function(model)
{
model.submodels = new SubCollection();
model.submodels.url = "/sub/" + model.get("id");
model.submodels.fetch();
}});
So, the server has to send two separate responses. For example:
{ name: "Model1", id: 1 } // For Model fetch
and
[{ name: "Submodel1", id: 1 }, { name: "Submodel2", id: 2 }] // For Submodel collection fetch
Is there a way to fetch a Model instance with Submodel collection at once, like:
{
name: "Model1",
id: 1,
submodels: [{ name: "Submodel1", id: 2 }, { name: "Submodel1", id: 2 }]
}

To be able to do that is up to your back-end - it doesn't really have anything to do with Backbone.
Can you configure your back-end technology to return related models as nested resources?
If your back-end is Rails, for instance, and your models are related in ActiveRecord, one way of doing this is something like
respond_to do |format|
format.json { render :json => #model.to_json(:include => [:submodels])}
end
What back-end technology are you using?
Edit:
Sorry, misunderstood the gist of your question, once you've got your back-end returning the JSON in the proper format, yeah, there are things you need to do in Backbone to be able to handle it.
Backbone-Relational
One way to deal with it is to use Backbone-Relational, a plugin for handling related models.
You define related models through a 'relations' property:
SubModel = Backbone.RelationalModel.extend({});
SubCollection = Backbone.Collection.extend({
model: SubModel
});
Model = Backbone.RelationalModel.extend({
relations: [
{
type: 'HasMany',
key: 'submodels',
relatedModel: 'SubModel',
collectionType: 'SubCollection'
}
]
});
When your Model fetches the JSON, it will automatically create a SubCollection under the 'submodels' property and populate it with SubModels - one for each JSON object in the array.
jsfiddle for backbone-relational: http://jsfiddle.net/4Zx5X/12/
By Hand
You can do this by hand if you want as well. In involves overriding the parse function for your Model class (forgive me if my JS is not 100% correct - been doing CoffeeScript so much lately its hardwired in my brain)
var Model = Backbone.Model.extend({
parse: function(response) {
this.submodels = new SubCollection();
// Populate your submodels with the data from the response.
// Could also use .add() if you wanted events for each one.
this.submodels.reset(response.submodels);
// now that we've handled that data, delete it
delete response.submodels;
// return the rest of the data to be handled by Backbone normally.
return response;
}
});
parse() runs before initialize() and before the attributes hash is set up, so you can't access model.attributes, and model.set() fails, so we have to set the collection as a direct property of the model, and not as a "property" that you would access with get/set.
Depending on what you want to happen on "save()" you may have to override `toJSON' to get your serialized version of the model to look like what your API expects.
jsfiddle:
http://jsfiddle.net/QEdmB/44/

Related

Backbone.js fetch() returning object instead of child in server instance but in local instance retrieves child

I have 2 instances. My local with OS Win7, and a Server instance with OS Linux.
I am fetching the JSON data and setting it to a model using following code.
var RModel = Backbone.Model.extend({
idAttribute: 'name',
parse: function (response) {
return {
'name': response.name,
'title': response.title,
'description': response.description,
'parameters': new ParamsList(response.parameters)
};
}
});
that.model = new RModel();
that.model.url = "url/" + '?limited=false';
that.model.fetch({
cache: false
}).done(function() {
that.headerTemplate = that.headerTemplateEdit;
that.bodyTemplate = that.bodyTemplateEdit;
that.footerTemplate = footerTemplate;
that.load({});
});
In my local instance the result of following code in console.
this.model
child
_changing:false
_pending:false
_previousAttributes:Object
attributes:Object
changed:Object
cid:"c217"
id:"testUndefinedParam"
url:"/url?limited=false"
__proto__:
Backbone.Model
In server instance
this.model
i
_changing: false
_pending: false
_previousAttributes: Object
attributes: Object
changed: Object
cid: "c25920"
id: "testDateError2"
url: "/url?limited=false"
__proto__: t.Model
If anyone came across this issue please show some way retrieve JSON data properly.
My guess is that the code in your server is processed (minify/uglify) and the code in your local is not. So the processor just renamed child to i, Backbone to t etc. You should be concerned about differences in actual data rather than the constructor names the console outputs. As far as I know there is no standard and it can vary between browsers

Rally Query in the SDK 1 vs Store in the SDK 2

I am trying to create an exportable to CSV grid app for Rally to show dependencies. but i am not getting the results i would expected for the SDK 2 using a store. I found a legacy SDK 1 app that is returning what i am looking for.
I not sure what i need to do the get the predecessors to show. all i am getting is a count from the store in the data object. I have been through the docs a bunch of times and i am stuck.
var dependencydata = Ext.create('Rally.data.wsapi.Store', {
model: 'hierarchicalrequirement',
autoLoad: true,
listeners: {
load: function(dependencydata, data, success) {
//process data
console.log("Woot Data !",dependencydata, data, success);
this.loadgrid(dependencydata);
},
scope: this
},
fetch: ['Rank','FormattedID','Name','Predecessors','Successors','Project','ScheduleState']
});//end Store
The Result is
data
FormatId 12345
Blah
Predecessor
Count 2
What I need is
data
FormatId 12345
Blah
Predecessor
object
Array
FormatId 45637
In SDK 1 it would have been done like this I guess
var queryConfig = {
type : 'hierarchicalrequirement',
key : 'stories',
fetch: 'Rank,FormattedID,Name,Predecessors,Successors,Project,ScheduleState',
query: '(Release.Name = "' + relDropdown.getSelectedName() + '")',
order: 'Rank'
};
rallyDataSource.findAll(queryConfig, showUserStoriesTable);
}
v2.0 of WSAPI which is used by SDK 2 does not allow hydrating collections in one request like v1.0 did.
Luckily, there's a guide in the docs dealing with this exact topic:
https://help.rallydev.com/apps/2.0/doc/#!/guide/collections_in_v2
Your case is slightly more complicated since you'll need to load 2 collections for each story (Predecessors and Successors)
So something like this should work:
//keep track of all the pending collection loads
var promises = [];
Ext.create('Rally.data.wsapi.Store', {
model: 'UserStory',
fetch: ['Rank','FormattedID','Name','Predecessors','Successors','Project','ScheduleState'],
autoLoad: true,
listeners: {
load: function(store, records) {
_.each(records, function(story) {
//create the stores to load the collections
story.predecessorStore = story.getCollection('Predecessors');
story.successorStore = story.getCollection('Successors');
//load the stores and keep track of the load operations
promises.push(predecessorStore.load({fetch: ['FormattedID']}));
promises.push(successorStore.load({fetch: ['FormattedID']}));
});
//wait for all promises to finish
Deft.Promise.all(promises).then({
success: function() {
//all data loaded.
console.log(records[0].predecessorStore.getRange());
console.log(records[0].successorStore.getRange());
}
});
}
}
});
Note that this will generate a ton of requests, so it is best to limit the total number of stories in the root store through filters.

Can we set multiple values to modle in backbone

I am new to backbone and nodejs, I have made a demo which used backbone and nodejs for updating and inserting data. I'm able to send put request with single data at a time.
this.model.set({
user_id:Session.get('userid'),
seat_id:seatId
});
this.model.save({
success: function() {
// do some stuff here
alert("a")
},
error: function() {
// do other stuff here
alert("b")
}
})
The above code post single row info to server. I want to send multiple info to server at a time. Can we set model something like below
this.model.set([{
user_id:Session.get('userid'),
seat_id:2
},{
user_id:Session.get('userid'),
seat_id:3
}]);
Thanks
You can do that:
arr = [{
user_id:Session.get('userid'),
seat_id:2
},{
user_id:Session.get('userid'),
seat_id:3
}];
this.model.set(sessions, arr);

Looking for design pattern to create multiple models/collections out of single JSON responses

I have a Backbone application where the JSON I get from the server isn't exactly 1 on 1 with how I want my models to look. I use custom parse functions for my models, ex:
parse: function(response) {
var content = {};
content.id = response.mediaId;
content.image = response.image.url;
return content;
}
This works. But, in some cases I have an API call where I get lots of information at once, for instance, information about an image with its user and comments:
{
"mediaId": "1",
"image": {
"title": "myImage",
"url": "http://image.com/234.jpg"
},
"user": {
"username": "John"
},
"comments": [
{
"title": "Nice pic!"
},
{
"title": "Great stuff."
}
]
}
How would I go about creating a new User model and a Comments collection from here? This is an option:
parse: function(response) {
var content = {};
content.id = response.mediaId;
content.image = response.image.url;
content.user = new User(response.user);
content.comments = new Comments(response.comments);
return content;
}
The trouble here is, by creating a new User or new Comments with raw JSON as input, Backbone will just add the JSON properties as attributes. Instead, I'd like to have an intermediate parse-like method to gain control over the objects' structure. The following is an option:
parse: function(response) {
// ...
content.user = new User({
username: response.user.username
});
// ...
}
...but that's not very DRY-proof.
So, my question is: what would be a nice pattern to create several models/collections out of 1 JSON response, with control over the models/collections attributes?
Thanks!
It may not be the nicest way possible, but this is how I do it:
content.user = new User(User.prototype.parse(response.user));
The only problem is that the this context in User.parse will be wrong. If you don't have any specific code in the User constructor, you can also do:
content.user = new User();
content.user.set(user.parse(response.user));
I also noticed an interesting note in the Backbone version 0.9.9 change log:
The parse function is now always run if defined, for both collections and models — not only after an Ajax call.
And looking at the source code of Model and Collection constructor, they do it like so:
if (options && options.parse) attrs = this.parse(attrs);
Maybe upgrading to 0.9.9 will give you what you need? If upgrade is not an option, you can of course implement the same in your own constructor.

Backbone-Relational related models not being created

I'm trying to create a nested, relational backbone project but I'm really struggling. The rough idea of what I'm trying to do is shown below but I was under the impression upon calling fetch() on Client, a number of bookings would automatically be created based on the bookings being returned as JSON.
The format of my JSON can be seen beneath the outline of the MVC:
/****************************************************
/* CLIENT MODEL - Logically the top of the tree
/* has a BookingsCollection containing numerous Booking(s)
/* Client
/* -Bookings [BookingsCollection]
/* -Booking [Booking]
/* -Booking [Booking]
/*****************************************************/
var Client = Backbone.RelationalModel.extend({
urlRoot: '/URL-THAT-RETURNS-JSON/',
relations: [
{
type: Backbone.HasMany,
key: 'Booking',
relatedModel: 'Booking',
collectionType: 'BookingsCollection'
}
],
parse: function (response) {
},
initialize: function (options) {
console.log(this, 'Initialized');
}
});
var Booking = Backbone.RelationalModel.extend({
initialize: function (options) {
console.log(this, 'Initialized');
}
});
var BookingsCollection = Backbone.Collection.extend({
model: Booking
});
Any help outlining what I'm doing wrong would be massively appreciated.
Thanks
EDIT
Thanks for taking the time to post the feedback, it's exactly what I was hoping for.
Is it the case that the JSON physically defines the actual attributes of models if you don't go to the effort of setting attributes manually?
In other words, if the JSON I get back is as you have suggested above, would Backbone simply create a Client object (with the 4 attributes id, title, firstname & surname) as well as 2 Booking objects (each with 4 attributes and presumably each members of the BookingsCollection)?
If this is the case, what is the format for referencing the attributes of each object? When I set up a non-backbone-relational mini-app, I ended up in a situation whereby I could just reference the attributes using Client.Attribute or Booking[0].EventDate for example. I don't seem to be able to do this with the format you have outlined above.
Thanks again.
The JSON being returned is not what Backbone or Backbone-Relational is expecting by default.
The expectation of Backbone and Backbone-Relational is:
{
"id": "123456",
"Title":"Mr",
"FirstName":"Joe",
"Surname":"Bloggs",
"Bookings": [
{
"id": "585462542",
"EventName": "Bla",
"Location":"Dee Bla",
"EventDate":"November 1, 2012"
},
{
"id": "585462543",
"EventName": "Bla",
"Location":"Dee Bla",
"EventDate":"November 1, 2012"
}
]
}
To use your response, you need to create a parse function on the Client model that returns the structure I've posted above.
A jsFiddle example of your model definitions working with my example JSON: http://jsfiddle.net/edwardmsmith/jVJHq/4/
Other notes
Backbone expects ID fields to be named "id" by default. To use another field as the ID for a model, use Model.idAttribute
The "key" for the Bookings Collection I changed to "Bookings"
Sample Code:
Client = Backbone.RelationalModel.extend({
urlRoot: '/URL-THAT-RETURNS-JSON/',
relations: [
{
type: Backbone.HasMany,
key: 'Bookings',
relatedModel: 'Booking',
collectionType: 'BookingsCollection'
}
],
parse: function (response) {
},
initialize: function (options) {
console.log(this, 'Initialized');
}
});
Booking = Backbone.RelationalModel.extend({
initialize: function (options) {
console.log(this, 'Initialized');
}
});
BookingsCollection = Backbone.Collection.extend({
model: Booking
});
myClient = new Client( {
"id": "123456",
"Title":"Mr",
"FirstName":"Joe",
"Surname":"Bloggs",
"Bookings": [
{
"id": "585462542",
"EventName": "Bla",
"Location":"Dee Bla",
"EventDate":"November 1, 2012"
},
{
"id": "585462543",
"EventName": "Bla",
"Location":"Dee Bla",
"EventDate":"November 1, 2012"
}
]
});
console.log(myClient);​
Post Edit
Yes, the JSON pretty much defines the attributes of the model. You can use a combination of parse(), defaults, and validate() to better control what attributes are valid and allowed.
The canonical way of reading and setting properties on a Backbone Model is through the get(), escape(), and set() functions.
set is especially important as this does a bunch of housekeeping, such as validating the attribute and value against your validate function (if any), and triggering change events for the model that your views would be listening for.
In the specific case of the situation in this answer, you might
myClient.get('Title'); // => "Mr"
myClient.get('Bookings'); //=> an instance of a BookingsCollection with 2 models.
myClient.get('Bookings').first().get('Location'); //=> "Dee Bla"
myClient.get('Bookings').last().get('Location'); //=> "Dee Bla"
myClient.get('Bookings').first().set({location:"Bora Bora"});
myClient.get('Bookings').first().get('Location'); //=> "Bora Bora"
myClient.get('Bookings').last().get('Location'); //=> "Dee Bla"

Categories

Resources