I am new to Backbone-relational, I am not sure what is the right way to use HasMany.
I have a Parent model which have many children (by "many" I mean thousands of children). In order to avoid performance issue, I query children by their foreign key: /child/?parent=1, instead of create a huge list of child_ids in Parent. But seems this is not the way Backbone-relational work.
So I am wondering what is the right way to handle this.
1, Change my json api to include list of child id in parent, then send thousands of ids as Backbone-relational recommend:
url = function(models) {
return '/child/' + ( models ? 'set/' + _.pluck( models, 'id' ).join(';') + '/' : '');
}
// this will end up with a really long url: /child/set/1;2;3;4;...;9998;9999
2, override many method in Backbone-relational, let it handle this situation. My first thought is :
relations: [{
collectionOptions: function(model){
// I am not sure if I should use `this` to access my relation object
var relation = this;
return {
model: relation.relatedModel,
url: function(){
return relation.relatedModel.urlRoot + '?' + relation.collectionKey + '=' + model.id;
}
}
}
}]
// This seems work, but it can not be inherent by other model
// And in this case parent will have am empty children list at beginning.
// So parent.fetchRelated() won't fetch anything, I need call this url my self.
3, Only use Backbone-relational as a Store, then use Collection to manage relations.
4, Some other magic way or pattern or backbone framework
Thanks for help.
Here's the solution that I have going on my current project. Note that Project hasMany comments, events, files, and videos. Those relations and their reverse relations are defined on those models:
Entities.Project = Backbone.RelationalModel.extend({
updateRelation: function(relation) {
var id = this.get('id'),
collection = this.get(relation);
return collection.fetch({ data: $.param({ project_id: id }) });
}
});
I have the REST endpoint configured to take parameters that act as successive "WHERE" clauses. So project.updateRelation('comments') will send a request to /comments?project_id=4 I have some further logic on the server-side to filter out stuff the user has no right to see. (Laravel back-end, btw)
Related
I'm using a LayoutView to display a collection in table form. When a user clicks on a tr I swap the CompositeView for an ItemView that shows the details using the same region. It all works except the functionality of the back button is broken. Is there a way to trap the back event and switch views?
Or should I use two Views and pass the model id and then refetch the model? The problem with that though is the extra request and I lose the filter and sort values of the table unless I use local storage.
Including more code would be better, but in any case I will try to give some guidance for your problem.
To avoid fetching the data twice, you can keep a common object in a "parent" component, for example in the Router.
var theObject;
var router = Marionette.AppRouter.extend({
routes: {
"routeA/:id": "goToRouteA",
"routeB/:id": "goToRouteB"
},
goToRouteA: function(id) {
MyRegion.show(new myLayout({
model: this._getCommonObject(id)
}));
},
goToRouteB: function(id) {
MyRegion.show(new myLayout({
model: this._getCommonObject(id)
}));
},
/*Return the common object for the views*/
_getCommonObject: function(id) {
theObject = (theObject && theObject.get('id') == id) ? theObject : MyApp.request('getTheObject');
return theObject;
}
});
In this way, you can keep the reference to the same object without loosing information.
You just have to make sure to delete the object when it is not needed to avoid keeping old information, for example on the Region close event.
I want to display a list of images and their respective comments. Like:
Image url | Format | Comments
http://example.com/img.jpg | 1280x420 | [Comment 1], [Comment 2] ...show all ...show all
http://example.com/img2.jpg | 630x590 | [Comment 1], [Comment 2] ...show all
I have two resouces: /images and /comments/{image_id}
What is the recommended way to fetch the comments for each image to be able to display them on the same row? Does Marionette have a helper for this?
In my opinion, these look like a good place to use relational models. Backbone doesn't support these out of the box, so you'll need a plugin. Have a look at Backbone-Relational or supermodel.js. These projects provide better forms of model nesting than the default implementation. From there, use nested composite views to render the collections.
From what I know Marionette does not have such helper. I think you can use something simple like:
var ImageComments = Backbone.Collection.extend({
initialize: function(models, options) {
options || (options = {});
this.imageId = options.imageId;
Backbone.Collection.prototype.initialize.apply(this, arguments);
},
urlRoot: function() {
return 'comments/' + this.imageId;
}
});
var id = 1,
image = new Image({ id: id }),
comments = new ImageComments(null, { imageId: id });
$.when(image.fetch(), comments.fetch()).done(function() {
// .. do your things with image & comments
});
This describes simple case, if that's commonly used in your application you might want to implement your own fetch method (e.g. for image, that will also fetch comments) or use plugins like Backbone-relational or Backbone-associations
You can use nested composite views.
http://davidsulc.com/blog/2013/02/03/tutorial-nested-views-using-backbone-marionettes-compositeview/
You can also do old fashioned in template loops for the comments
http://www.headspring.com/an-underscore-templates-primer/
var items=[{"endsAt": "2013-05-26T07:00:00Z","id": 1,"name": "Niuniu1"},
{"endsAt": "2013-05-26T07:00:00Z","id": 2,"name": "Niuniu2"}]
ItemModel=Backbone.Model.extend({});
ItemCollection=Backbone.Collection.extend({
model:ItemModel,
url: '...',
parse: function(response) {
return response.items;
}
})
If I have a series of data like items, when I build model, for each model, it's endAt will be "2013-05-26T07:00:00Z". Where can I modify the model or data process so it will actually be "2013-05-26"?
I could do a foreach loop inside collection to process the date, but I'm wondering if there is a better pracitce like to do a parse inside the model?
Thanks!
The practice I use is the one you said you've thought about - implementing a custom parse on the model. As the documentation states, it will be called for you after a sync. See here: http://backbonejs.org/#Model-parse
ItemModel = Backbone.Model.extend({
parse: function(response,options) {
//perform your work on 'response',
// return the attributes this model should have.
};
})
As far as I know, you have 2 options here
Implement a custom parse method inside your model
Implement the initialize method inside your model
Both of them don't have any problems, I did 2 ways in several projects, and they work well
I'm using a composite key in my model and generate the ID based on my composite key:
app.Assignment = Backbone.Model.extend({
idAttribute : [ 'personId', 'jobId' ],
parse : function(resp) {
resp.id = resp.personId + "_" + resp.jobId;
return resp;
}
});
but Backbone still thinks that all instances of Assignment are new, allthough I'm setting the id in the parse method when fetching them from the API. As a result Backbone does no DELETEs and does a POST instead of PUT on updates. How can I work around this or what is the "right way" to do it?
Update:
Looks like replacing resp.id with this.id solves the issue.
The results of the parse method of a Backbone.Model are passed to the set method, which sets the attributes of the model. The point of confusion for you I think is that the model's ID isn't one of it's attributes; it's one of its properties.
So, what happens is this:
Your raw data comes back from the server and is passed to parse
That same raw data, now augmented with a id attribute, is passed to set
set looks and your idAttribute ([ 'personId', 'jobId' ]) and all of the keys in the raw data
Since none of those keys match the idAttribute, none of them are used as the model's ID, and thus you get your problem.
Your solution of setting this.id inside parse works, but it might cause problems down the road because parse is generally designed to operate on it's input (the raw data), not to modify the model itself; that part is supposed to happen next when set is called. A cleaner solution would instead be to do something like the following:
app.Assignment = Backbone.Model.extend({
// note that no idAttribute is specified, leaving it as the default "id"
parse : function(resp) {
resp.id = resp.personId + "_" + resp.jobId;
return resp;
}
}
Or, if you want a different ID attribute ...
app.Assignment = Backbone.Model.extend({
idAttribute: 'personAndJobId',
parse : function(resp) {
resp.personAndJobId = resp.personId + "_" + resp.jobId;
return resp;
}
}
Aside from the idAttribute issues here, you can always force Backbone to use a certain HTTP method via the type options passed to save().
model.save(null, { type: 'put' })
I've never work with composite ID in Backbone, but I think this could be an easy answer to your problem:
initialize: function() {
this.set("id", this.generateID());
},
generateID = function () {
return this.personId + + "_" + this.jobId;
}
With this code in you Backbone model definition you are creating a unique ID for each model and you shouldn't have problems for update and save it (and you don't need to set any idAttribute).
I'm making a Grocery list app, which is very similar with the todo list. I have several years of Rails dev experience, but am having trouble figuring out from all the examples what to put into a collection, and what to make a model.
I mocked up the app with Sinatra and Redis as the backend. My goal is to make Sinatra just the simple API and have backbone manage all the view.
Right now, a Grocery list is just a complex ID, which has a Set of string items. So something like:
/lists/asdfasdf34asdf => ["eggs", "bacon", "milk"]
Moving to backbone, would I make the model an "Item" and then the collection would be the "List", or would it be something else?
I guess my routes aren't classic Rest so maybe that's why i'm having trouble sorting out what to do where.
If there's only one grocery list, a Collection of item Models is probably appropriate. Backbone isn't too prescriptive about how things are organized, but you will definitely want to set the url property of each model/collection in a logical fashion. You might do something like this:
var app = {
item: Backbone.Model.extend({
// define an item model to go in the itemCollection
}),
itemCollection: Backbone.Collection.extend({
initialize: function (key) {
this.key = key;
},
model: app.item,
url: function () {
return 'lists/' + this.key + '/items/'
}
})
}
and then instantiate each version of the application along these lines:
var userListKey = 'foobar',
userCollection = new app.itemCollection(foobar);
// proceed with app. Requests for userCollection will now be
// directed to /lists/foobar/items
There are many other ways to do this, but hopefully this is a start.