I'm relatively new to Backbone, and I've searched for a while, and didn't find an answer that satisfies my question (this one is close, but not sure it's exactly applicable Backbone.js Master-Detail scenario)
tl;dr: how to represent a subset (as in only some of the collection's model's fields, not a subset as in paging / filtering) of a collection? (e.g. a list view that shows only name and modified date of something, when the details includes not just more fields, but also nested child collections)
Now to the detailed version, here is the structure of the application:
Models
DocumentCollection
- a collection of documents, should only fetch name, and last modified date
Document
- when fetched as part of the collection fetch, should only fetch name, and modification date, when fetched "standalone" (e.g. in details view) should also fetch description and child articles
ArticleColletion
a nested collection that is part of the Document, should only be fetched when the document is fetched in a details view, but should be "lazy" and not fetched when the DocumentCollection
is fetched
Article
Views
DocumentCollectionView
(only shows document name, and last modified date)DocumentView
(includes the document name, description and articles)ArticlesView
and ArticleView
for showing a single or a collection of articles Goal
I would like to have the DocumentCollection
fetch
method call to only bring a subset of the Document
model (only name and modified time),
and when fetching the Document
directly, also to fetch the description field and child articles
The real world models are a bit more complex with more fields so my need is to cut some traffic on the wire and not load fields or sub collections before they are needed.
Possible Solutions
override the fetch method, and apply relevant logic (e.g. a flag for "full" or "partial" load)
forget about lazy fields, only have the nested collection lazy, cutting unused fields is premature and unneded optimization, and it's not the model's resposibility to decide what is needed and what is not as part of the view rendering, it should bring it all
Have a different collection and model for the "summary" view, e.g. DocumentSummaryCollection
and have DocumentCollection extend DocumentSummaryCollection
?
Question
What of the above (if any) is the "backbone" way of doing it? and are there any other approaches?
This is one of those common scenarios which I am not really sure there is a 'backbone' way to handle. Here is one possible solution...
When you fetch the documents collection (/api/documents
) you return some json like this:
[{id: 1, name: 'foo'}, {id: 2, name: 'bar'}]
When you fetch a document (/api/documents/1
), return some json like this:
{
id: 1, name: 'foo', description: 'some stuff about foo',
articles: [{id: 1, name: 'hello'}, {id: 2, name: 'hi again'}]
}
When the individual DocumentModel is fetched, there will be a new attribute articles
. You listen for the change:articles
event, and set
that new json into this.articles
which is an ArticlesCollection.
Now... some code:
var Article = Backbone.Model.extend({});
var ArticleCollection = Backbone.Collection.extend({
model: Article
});
var Document = Backbone.Model.extend({
constructor: function() {
// make sure articles collection is created.
this.articles = new ArticleCollection();
Backbone.Model.prototype.constructor.apply(this, arguments);
},
initialize: function(attributes, options) {
this.articlesChanged();
this.on('change:articles', this.articlesChanged, this);
},
// articles attribute changed.
// set it into the Collection.
// this way, existing models will be updated, new ones added.
articlesChanged: function(){
// if document was fetched and has articles json array,
// set that json into this.articles collection.
var articles = this.get('articles');
if(articles){
this.articles.set(articles);
// remove 'articles' attribute now that it is set into the collection.
this.unset('articles');
}
}
});
var DocumentCollection = Backbone.Collection.extend({
model: Document
});
Some more code:
var docs = new DocumentCollection();
docs.fetch().done(function() {
// docs now has documents with only name, date attributes,
// and empty articles collection.
var doc = docs.at(0);
var name = doc.get('name'); // foo
var articleCount = doc.articles.length; // 0
doc.fetch().done(function() {
// first doc is now full, with articles, description, etc.
doc.articles.each(function(article) {
console.log(article.get('name'));
}, this);
// re-fetch the collection later... to check if new documents exist.
docs.fetch().done(function() {
// new docs added, removed docs gone.
// existing doc models updated.
});
});
});
I think the main thing I like about this, is that it preserves the documents/articles collection instances, even when the document collection, or an individual document is re-fetched later.
So, for example, if you were showing a DocumentModel and its articles collection in the details view, and the entire DocumentCollection was re-fetched, the DocumentModel being shown would still be in the collection after the fetch (unless it was actually removed on the server). This is nice if you have some change add remove
type events hooked up to those instances.
With the Backbone 1.0 move towards the 'update' fetch (which is great), I am curious if there are any other (or better) solutions out there, as this really is a pretty common issue... and I really haven't used this exact solution much and I am not sure it is ideal. I used to use parse
more to grab the child json and reset
the child collection.. But I think I ran into some problems with that with the updating fetch, so started trying this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With