Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically fill models with more detailed data in emberjs

I'm a bit stuck with implementing master-detail views in emberjs.

Most of my views have a master-view which is just a list of titles. Then, a user can click such title and he/she gets an overlay with the detail-view (somewhat like a typical newsitem page).

Now, I noticed that when ember asks the information for the master-view it calls urls like: /backend/newsitems. But these calls can get very heavy because the backend returns a list of all newsitems with all their detailed data. But this feels wrong because the user is only looking at the master-view and didn't request any detailed information yet.

Is there a way to make ember clear that the master-view only needs several attributes and that a request for detailed information should get the extra attributes of that particular item?

Just as an example, my model looks like this:

App.Newsitem = DS.Model.extend({
  slug: DS.attr('string'),
  type: DS.attr('string'),
  title: DS.attr('string'),
  summary: DS.attr('string'),
  text: DS.attr('string'),
  thumb: DS.attr('string'),
  date: DS.attr('date'),

  mediaitems: DS.hasMany('App.Mediaitem')
});

But my master-view only needs id type title to show a list of titles and an icon next to that title. Then, when a user request the detail of one newsitem all other attributes should be fetched.

like image 214
polyclick Avatar asked Mar 05 '13 11:03

polyclick


2 Answers

I solved this problem by having the server include a 'partial' property in the payload. 'partial' will be true if it's just a partial response, and it'll be false if it's a full response. I'll also offer a workaround if you're not able to change the server API (and therefore can't include a 'partial' property in the payload).

As others have suggested, in the model hook of the 'detail' route, call the model's reload() method if the currently loaded model is only a partial:

App.ItemRoute = Ember.Route.extend({

    model: function (params) {
        return this.store.find('item', params.item_id).then(function (item) {
            if (item.get('partial'))
                return item.reload();
            else
                return item;
        });
    }

});

This solved part of the problem, but if your user then navigates to the master route, then Ember Data will replace all fields with the server response and wipes out any property values that weren't returned from the server. To overcome this, override the store's push method like this:

App.ApplicationStore = DS.Store.extend({
    push: function (type, data, _partial) {
        if (data.partial && this.hasRecordForId(type, data.id))
            return this.getById(type, data.id);
        else
            return this._super(type, data, _partial);
    }
});

This ensures that your partial payload doesn't overwrite the full payload.

Here's a jsbin to demonstrate: http://jsbin.com/bejop/6/edit

If you're not able to include a 'partial' property in the response, then you can apply the same logic as above but look for a specific property in your model that will appear in your detail response, but not in your partial response. Then when you override the push method, you can do it like this:

    App.Partials = [
        { type: App.Item, propertyName: 'detailed_description' }
    ];

    App.ApplicationStore = DS.Store.extend({
        push: function (type, data, _partial) {

            var that = this;
            var partial = App.Partials.find(function (p) {
                return p.type === that.modelFor(type);
            });

            if (partial && !data[partial.propertyName])
                return this.getById(type, data.id);

            return this._super(type, data, _partial);
        }
    });
like image 98
Johnny Oshika Avatar answered Sep 30 '22 12:09

Johnny Oshika


Is there a way to make ember clear that the master-view only needs several attributes and that a request for detailed information should get the extra attributes of that particular item?

Sure, but perhaps not in the way you are expecting. Ember models are really lightweight, so there is no need to have a 1-1 relationship between them and your backend schema. In general I like the think of ember-models in terms of API endpoints. In this case your API is exposing 2 distinct sets of data, so the most straightforward solution is to create a separate ember model to represent the lightweight list of titles. This model will have it's own api endpoint at /backend/newsitem_listings.

App.NewsitemListing = DS.Model.extend({
  slug: DS.attr('string'),
  type: DS.attr('string'),
  title: DS.attr('string')
});

With this in place, you can use App.NewsitemListing.find() or App.NewsitemListing.find({query: q}) to fetch some/all of the listings, then App.Newsitem.find(id) to load details for an individual record. You might consider adding a relationship such as newsitem: DS.hasOne('App.Newsitem') to the model, or you could just use slug to generate links on the fly.

like image 32
Mike Grassotti Avatar answered Sep 30 '22 12:09

Mike Grassotti