Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to load only a subset of a collection's fields / have lazy model fields / lazy nested collections in a master detail scenario?

Tags:

backbone.js

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

  1. override the fetch method, and apply relevant logic (e.g. a flag for "full" or "partial" load)

  2. 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

  3. 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?

like image 468
Eran Medan Avatar asked Feb 17 '23 22:02

Eran Medan


1 Answers

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.

like image 140
Paul Hoenecke Avatar answered Feb 24 '23 23:02

Paul Hoenecke