1
votes

I'm using jQuery getJSON for fetching data in my ember.js app (ember 1.7.0). And have a small problem with rendering template while loading data.

Here is my first approach:

Parivar.Router.reopen({
    rootURL: '/account/'
});

Parivar.Router.map(function() {
    this.route('orders', { path: '/orders' });
    this.route('order', {path: '/orders/:number'});
});

Parivar.IndexRoute = Ember.Route.extend({
    beforeModel: function(){
        this.transitionTo('orders');
    }
});

Parivar.Order = Ember.Object.extend({});

Parivar.Order.reopenClass({
    findMine: function(params) {
        return $.getJSON('/api/orders/mine?page=' + params.page);
    },
    order: function(params) {
        return $.getJSON('/api/orders/' + params.number);
    }
});

Parivar.OrdersRoute = Ember.Route.extend({
    queryParams: {
        page: {
            refreshModel: true
        }
    },
    model: function(params) { return Parivar.Order.findMine(params); }
});

Parivar.OrderRoute = Ember.Route.extend({
    model: function(params) { return Parivar.Order.order(params); }
});

Parivar.ApplicationController = Ember.Controller.extend({
    renderHeader: function() {
        return this.get('currentRouteName') === 'orders';
    }.property('currentRouteName')
});

Parivar.OrdersController = Ember.ObjectController.extend({
    queryParams: ['page'],
    page: 1,
    needPaginate: function() {
        return this.get('model.pages') != 1;
    }.property('model.pages')
});

Parivar = Ember.Application.create({
    rootElement: '#ember_orders',
    LOG_TRANSITION: true,
    LOG_ACTIVE_GENERATION: true,
    LOG_VIEW_LOOKUPS: true,
    ready: function() {
        console.log("Ember.TEMPLATES: ", Ember.TEMPLATES);
    }
});

And i have needed templates (application, orders, order, header and loading) In application.hbs:

{{#if renderHeader}}
    {{partial 'header'}}
{{/if}}
{{outlet}}

All works fine (and i got a nice loading template!), except rendering header template. It showing only after all data is loaded and loading template is gone. But i want to show header immediately after loading application.

My second approach was to use beforeModel hook. I tried to render header template to named outlet there. But i got and error saying that there are no outlet with name header. Maybe there is no outlets when code in beforeModel executed?

In my third try i changed findMine in Order class to this:

findMine: function(params) {
    var orders = Parivar.Order.create();
    Ember.$.getJSON('/api/orders/mine?page=' + params.page).then(function(result) {
        orders.setProperties(result);
    });
    return orders;
}

So, i have empty orders object returned to app immediately, and then populated with data. All works fine, except i don't have my loading template rendered, and showing user "You have no orders" until data is populated in orders object. All this caused by returning empty orders object.

Here is the question: How can i render header template before data is loaded, while still have that awesome 'loading' template functionality?

Any help will be appreciated.

1

1 Answers

1
votes

First you need to know how loading substate works. For detail you can check http://emberjs.com/guides/routing/loading-and-error-substates/

Generally speaking, for a route which beforeModel, model, afterModel are not resolved immediately, Ember will try to find if there is a loading route in the same level. If the loading route exists Ember will enter that route until all model hooks are resolved. In your case if you want to show header before loading data, you need to put the header out of orders template.

There're several way to solve your problem.

The 1st way - put header into application template

Does the header need to be shown for all pages? If so, you can put header in the orders route's upper level route's template, which is application template.

In application.hbs

{{partial 'header'}}
{{outlet}}

Then in orders route model hook you can return a promise, then a loading route will be triggered.

The 2nd way - use nested routes

If your header is only rendered in orders route, you can load data in a sub-route. And orders.index route is a good place.

Parivar.Router.map(function() {
  // This will create index, loading, error sub-states under orders
  // Note the blank function is needed even in Ember 1.7.0
  this.resource('orders', function() {});
});

Parivar.OrdersIndexRoute = Em.Route.extend({
  model: function(params) {
    return Parivar.Order.findMine(params);
  }
});

And put header in the orders.hbs

{{#if renderHeader}}
  {{partial 'header'}}
{{/if}}
{{outlet}} {{! will render "orders.index" here }}

You also need to have a orders/loading.hbs to trigger the loading substate. Or you can overwrite route's loading action to implement your own loading logic.

A jsbin example can be found here: http://emberjs.jsbin.com/sawez/3/edit

The 3rd way - load data in setupController and use PromiseProxyMixin

This is not a good way for your case so I don't say very detail thing. The idea is loading data in setupController hook and mixin Em.PromiseProxyMixin into your controller, then you can use isFulfilled, isRejected and isPending properties in yoru controller to render different things. For how to use Em.PromiseProxyMixin you can see this API documentation