polyclick
polyclick

Reputation: 2703

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.

Upvotes: 7

Views: 2213

Answers (3)

Johnny Oshika
Johnny Oshika

Reputation: 57552

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);
        }
    });

Upvotes: 3

Pascal
Pascal

Reputation: 2747

I had a similar problem recently and I hope what I came up with can help you.

My API has a /books endpoint which returns an array of objects with several - but not all - fields. When I GET /books/:id though, I can every bit of information that there is for the book with the ID :id.

Then, in Ember I have this route setup for a single book page:

App.BookRoute = Ember.Route.extend
  model: (params) ->
    Book.find(params.book_id)

  setupController: (controller, model) ->
    controller.set("model", model)

    unless model.get('full') is true
      model.reload().then ->
        # We now have all the information.
        # Setting `full` to `true` to prevent loading it again.
        model.set('full', true)
    return

So when a User is just browsing the list, the model's fields are mostly empty, but when she then comes to a book detail view, the API is hit and the additional book data is inserted into the same model instance.

Upvotes: 2

Mike Grassotti
Mike Grassotti

Reputation: 19050

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.

Upvotes: 2

Related Questions