Backbone.js: Collection containing multiple Models with same ID - javascript

I've a merged collection in Backbone which contains photos and albums.
To distinguish between them, i've added a field type which is either photo or album. When I populate the collection, I create different models within the Collection#model method
model: (attrs, options) ->
switch attrs.type
when 'album' then new App.Models.Album(attrs, options)
when 'photo' then new App.Models.Photo(attrs, options)
Now I've discoverd a strange bug where adding a photo and an album with the same ID (let's say 2) results in a merge.
I've tracked this down to these LOC in the source code. It seems that it's undoable without creating a fork of Backbone itself. I've tried it but it also fails 35 tests.
I thought of 4 different ways of doing this, I don't know which of them is the better one:
I could add a prefix to the id. Let's say photo_2. This causes a change in the backend as well as some changes in the frontend to don't hit the server at /photos/photo_2
I could fork Backbone and change these LOC.
I could create two separate collections but have to deal with a merge and a sort in the view (which effects clients performance and requires a rewriting of the backend)
I could start with a photo ID of, let's say 1000000. This would extremely decrease the probability that a given user which has uploaded a photo with a given ID has also created an album with the same ID.

Since version 1.2 you can use Collection.modelId to specify how your collection will uniquely identify models. In your case, you can do the following to ensure that your types have different IDs.
var MyCollection = Backbone.Collection.extend({
modelId: function (attrs) {
return attrs.type + "-" + attrs.id;
}
// ...
})

I would suggest that on both Album and Photo, you add the following:
idAttribute: 'uniqueId'
parse: function(response) {
response.uniqueId = type+'_'+response.id
return response;
}

idAttribute:'uniqueId'
if this uniqueId is not known while declaring, try
idAttribute:'UUID'
I generated one from https://www.uuidgenerator.net/ and put it here, this attribute defined here need not be in model, so I just put in a UUID.

Related

Backbone - built-in REST capability

I am confused by Backbone's built-in REST capability. I was under the impression that Backbone models, upon a model.save(), would automatically append the value identified by idAttribute to the end of the urlRoot. But I see a lot of examples online, like the one below, where the application imperatively appends the the id to the url. Why is that? Which one is better?
An example using RequireJS:
define(function(require) {
var Backbone = require('Backbone');
return Backbone.Model.extend({
urlRoot: 'http://rest-service.guides.spring.io/greeting',
url: function() {
return this.urlRoot + '?name=' + this.id;
}
});
});
Backbone assumes that you were following some common REST practices while you were designing your REST API.
For instance, an API which updates a user should be exposed as:
PUT /users/:id
rather than
PUT /users?id=:id
Of course there are some edge cases when you must rewrite the default URL function but, in general, leaving it as is means you were following the best practices while designing your REST API.
One case I can think of when rewrites are necessary is when a sub-resource is identified by multiple IDs:
PUT /apps/:appId/schedules/:scheduleId
then the url function will be:
url: function () {
return this.urlRoot + '/' + this.appId + '/schedules/' + this.id
}
When applying REST, the id is typically the unique identifier of an item that is contained by a collection (expressed in plural).
The url /greeting/id doesn't seem to make a lot of sense to me.
Parameters sent via the query string (behind the question mark) serve as filters to the collection that is currently queried.

Backbone.js View, ParentView, Model, Collection and Template Best Practices

I am looking for some description of best practices for views and models/collections in Backbone. I know how to add models to collections, render views using templates and use views in parent views, however I'm looking for more context and perhaps some example links.
I've updated this question to be more specific.
Let's say you have a more grid layout with all kinds of variation, that gets pulled from the same collection. What would you do here to create this page? A simple child view repeated in a parent view won't work because the variation of the grid items is too great. Do you:
create tons of tiny views and collections and render all of these different views using the relevant collections into that one page?
create a complex template file that has a loop in it, that as you go through the loop, the loop outputs different markup?
Do people put multiple views inside a parent view, all from the same model?
Similarly, do people mix different models into the same parent view? For example movies and tv shows - these different models, can get they added to the same collection that renders that list?
Thanks!
You've asked good question. To answer it lets take a look to this from other angle:
On my exp i used to check first is there any logic on parent view, like sorting, validation, search and so on. Second - Collection with models or just model with array as property : is the model is independent and may exist without collection , for example you have navigation item, and there are no sense to make separate model for each item and navigation as collection as you will never use item itself. Another case you have user list. You may use user model in a lot of places and its better to make a separate model for user and collection to combine it.
Your case with UL may be resolved with single model and items properties with array of li, simple grid may have same approach as i don't see some special logic on wrap from your description.
But i should point out - i had close task to build mansory grid with collection parsed from server, items were models as it had different data structure, different templates and different events listener.
Taking decision i considered folowing:
item as independent tile, may be used as in grid and also independent.
item is model + template + view. different Models types helped to support different data structure, different Views types helped to support different events listeners and user interaction, different templates - diff looks.
collection as a tool to fetch initial data + load extra items + arrange mansonry view + create models according to fetched result.
UPDATE
Lets consider this pseudo code as masnonry implementation:
Models may looks like these:
var MansonryModel = Backbone.Model.extend({
/* Common methods and properties */
}),
MansonryVideoModel = MansonryModel.extend({
defaults: {
type: 'video',
videoUrl: '#',
thumbnail: 'no-cover.jpg'
}
}),
MansonryImageModel = MansonryModel.extend({
defaults: {
type: 'image',
pictureUrl: '#',
alt: 'no-pictire'
}
});
var MansonryCollection = Backbone.Collection.extend({
model: MansonryModel
});
Views could be like this:
var MansonryTileView = Marionette.ItemView.extend({
/* place to keep some common methods and properties */
}),
MansonryVideoTile = MansonryTileView.extend({
template: '#video-tile',
events: {
'click .play': 'playVideo'
},
playVideo: function(){}
}),
MansonryImageTile = MansonryTileView.extend({
template: '#image-tile',
events: {
'click .enlarge': 'enlargePicture'
},
enlargePicture: function(){}
});
var MansonryListView = Marionette.CollectionView.extend({
childView : MansonryItem
})
Hope this help

Optimal URL structure for Backbone.js and Backbone implementation

I'm developing a RESTful API for a Quiz app, which is going to be built with Backbone.js and Marionette. I'm quite new to backbone and was wondering what de best URL structure would be. I have the following resources:
Answer,
Question which contains Answers,
Question Group which contains Questions,
Quiz which contains Question Groups.
Two possible URL structures come to mind:
GET /quizzes/:id
GET /quizzes/:id/questiongroups
GET /quizzes/:id/questiongroups/:id
GET /quizzes/:id/questiongroups/:id/questions
GET /quizzes/:id/questiongroups/:id/questions/:id
GET /quizzes/:id/questiongroups/:id/questions/:id/answers
or:
GET /quizzes/:id
GET /quizzes/:id/questiongroups
GET /questiongroups/:id
GET /questiongroups/:id/questions
...
Now, I have been trying to use both of these options. With the first one, I can't figure out how to define the collections as a property of the parent models in Backbone so that I can use fetch() on them. The problem with the second option is a bit different: as I understand it, Backbone derives the url for a model from its collection, but the collection is a child of another resource, whereas the url for getting a single resource uses another collection, namely the global set of resources.
I'm pretty sure I'd have to override url() in both cases. I tried some things but didn't come up with anything useable at all. Also, I'd rather not override every single url()-model in the app, changing the API structure to suit the preferences of Backbone seems like a better option to me.
Any pointers as to what seems the right way to do it with Backbone would be great!
Thanks
If questiongroups can only appear in a single quiz, then the first option (the hierarchical one) is an obvious choice. To comply with RESTful conventions, you might want to consider using singular nouns instead: /quiz/:id/questiongroups/:id/question/:id/answer/:id
To solve your fetching problem, I would recommend using nested backbone models as per this answer: https://stackoverflow.com/a/9904874/1941552. I've also added a cheeky little parentModel attribute.
For example, your QuizModel could look something like this:
var Quiz = Backbone.Model.extend({
urlRoot: '/quiz/', // backbone appends the id automatically :)
defaults: {
title: 'My Quiz'
description: 'A quiz containing some question groups.'
},
model: {
questionGroups: QuestionGroups,
},
parse: function(response){
for(var key in this.model){
var embeddedClass = this.model[key];
var embeddedData = response[key];
response[key] = new embeddedClass(embeddedData, {
parse:true,
parentModel:this
});
}
return response;
}
});
Then, your QuestionGroups model could have the following url() function:
var QuestionGroups = Backbone.Model.extend({
// store metadata and each individual question group
url: function() {
return this.parentModel.url()+'/questiongroup/'+this.id;
}
});
Alternatively, if you don't need to store any metadata, you could use a Backbone.Collection:
var QuestionGroups = Backbone.Collection.extend({
model: QuestionGroup,
url: function() {
return this.parentModel.url()+'/questiongroup/'+this.id;
}
});
I'm afraid I haven't tested any of this, but I hope it can be useful anyway!

Fetching a single Backbone model from server

Say I have a route setup:
'photos/:id' : 'showPhoto'
and somebody shares the url: www.mysite.com/photos/12345 with a friend.
When their friend clicks on the shared link, showPhoto gets called back with 12345 passed as the id. I cant figure out how to fetch the model from the server, because even when setting its id property and calling fetch(), backbone thinks that the model isNew and so the ajax request url is just /photos instead of /photos/12345:
showPhoto: (id) ->
photo = new models.Photo _id:id
photo.fetch #does a GET to /photos, I would have expected it to request /photos/12345
success: () ->
render photo view etc...
Photo = Backbone.Model.extend
idAttribute: '_id'
urlRoot: '/photos'
The model Photo is usually part of a collection, but in this scenario someone is visiting the site directly and only expects to see data for one photo, so the collection is not instantiated in this state of the app.
Is the solution to load the entire collection of photos and then use collection.getById(id)? This just seems way too inefficient when I just want to load the properties for one model.
if you don't have the model as part of a collection, you have to tell the model the full url manually. it won't auto-append the id to the urlRoot that you've specified. you can specify a function as the urlRoot to do this:
Photo = Backbone.Model.extend({
urlRoot: function(){
if (this.isNew()){
return "/photos";
} else {
return "/photos/" + this.id;
}
}
});
Backbone uses the id of the model to determine if it's new or not, so once you set that, this code should work correctly. if it doesn't, you could always check for an id in the if-statement instead of checking isNew.
You do not need to tell backbone whether or not to append the id the url. Per the documentation: http://backbonejs.org/#Model-fetch, you may simply set the urlRoot to the equivalent of the url in a collection.
Backbone will automatically append the desired id to the url, provided you use one of the following methods:
model.set("id", 5); //After initialized
model = new Backbone.Model({id: 5}); //New model
If you manually set the id in the attributes hash or directly on the model, backbone won't be aware of it.
model.id = 5; //Don't do this!
there's already a similar question: "How do I fetch a single model in Backbone?"
my answer there should work for you (and it's in coffeescript)
also remember to check Backbone Model#url documentation, it's all explained there
I would bootstrap the collection (by rendering the following to the page) with just one model in it like this:
photos = new PhotoCollection();
photos.reset([ #Html.ToJson(Model) ]);
Note that the server side code that I use is ASP.Net MVC so use something specific to your server side architecture. Also note that the square brackets are important as they take your singular model and wrap it in an array.
Hope that's helpful.

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