Как убедиться, что Backbone полностью отрисовал страницу?

Я работаю над большим корпоративным приложением с BackboneJS. Одна страница в приложении создается с использованием нескольких вызовов подсистемы через REST. Как мне убедиться, что все службы, необходимые для загрузки страницы, были вызваны, а привязка шаблонов завершена?

Например, у меня есть MasterView, который заботится о collection.fetch() каждого дочернего представления, вот так.

myApp.views.MasterView = Backbone.View.extend({
    initialize: function(params) {
        var self = this;
        this.collection.fetch({
            success: function(resp) {
                self.collection.bind("reset", self.render(), self);
            },
            error: function(xhr, xhrStatus) {
                // push error message, in case of fetch fails.
            }
        });
    },
    render: function() {
        var self = this;
        this.collection.each(function(model) {
            if (model.get('authorize') && model.get('status') === "success" && model.get('data').length > 0) {
                self.bindTemplate(model.get('data')[0]);
            }
        });
    }
});

У меня есть набор представлений для страницы, который отвечает за отображение двух других представлений CustomerInfo и CustomerAccounts. Вид идет так.

myApp.views.CustomerView = Backbone.View.extend({
        initialize: function() {
            var customerInfo = new myApp.collection.CustomerInfo();
            new myApp.views.CustomerInfo({el: $("#infoContainer"), collection: customerInfo});

            var customerAccount = new myApp.collection.CustomerAccount();
            new myApp.views.CustomerAccount({el: $("#accountContainer"), collection: customerAccount});
        }
});

Представления CustomerInfo и CustomerAccount выглядят так:

myApp.views.CustomerInfo = myApp.views.MasterView.extend({
    initialize: function() {
        var self = this;
        myApp.views.MasterView.prototype.initialize.call(self, {
            qParam1: "qparam1",
            qParam2: "qparam2"
        });
    },
    render: function() {
        var self = this;
        self.template = _.template(myApp.Templates.get("customer-page/customer-info"));
        myApp.views.MasterView.prototype.render.apply(self);
    },
    bindTemplate: function(data) {
        var self = this;
        $(self.el).html(self.template({"info": data}));
    }
});
 
myApp.views.CustomerAccounts = myApp.views.MasterView.extend({
    initialize: function() {
        var self = this;
        myApp.views.MasterView.prototype.initialize.call(self, {
            qParam1: "qparam1"
        });
    },
    render: function() {
        var self = this;
        self.template = _.template(myApp.Templates.get("customer-page/customer-accounts"));
        myApp.views.MasterView.prototype.render.apply(self);
    },
    bindTemplate: function(data) {
        var self = this;
        $(self.el).html(self.template({"accounts": data}));
    }
});

Я хотел бы знать, есть ли способ узнать из myApp.views.CustomerView, что представления CustomerInfo и CustomerAccounts завершили рендеринг? Основная проблема заключается в том, что представление CustomerInfo загружается быстро, а представление CustomerAccount загружается некоторое время. Следовательно, мне нужно отобразить страницу за один раз, когда оба представления готовы в DOM.


person Dipin Krishnan    schedule 09.05.2013    source источник
comment
Просто наблюдение: ваши дочерние представления действительно не должны иметь ссылок на родительское представление (я бы даже сказал, что ни одно представление не должно иметь ссылку на другое представление, но это совсем другая тема). Я также рекомендую прочитать несколько руководств/примеров о том, как более эффективно использовать Backbone; ваши взгляды должны реагировать на изменения в моделях/коллекциях; в нынешнем виде ваши взгляды делают НАМНОГО слишком много.   -  person Pierreten    schedule 09.05.2013


Ответы (3)


Когда вы создаете свои представления, добавьте слушателя к событию view_ready. Когда представление завершает выборку данных и рендеринг, запускает его само по себе. В конце метода рендеринга родительского представления.

self.trigger('view_ready');

И в представлении Main добавьте что-то вроде:

this.listenTo(CustomerInfoView, 'view_ready', this.customer_info_ready);
this.listenTo(CustomerAccountsView, 'view_ready', this.customer_account_ready);

Затем в вашем основном представлении или основной модели добавьте 2 свойства: info_ready и customer_ready и инициализируйте их значением 0. Каждый раз, когда запускается одно из двух ранее упомянутых событий, делайте что-то вроде следующего:

customer_info_ready : function(){
    this.model.set('info_ready',true);
    if (this.model.get('account_ready') === true) {
         this.trigger('both_elements_ready'); 
     }
}
customer_account_ready : function(){
    this.model.set('account_ready',true);
    if (this.model.get('info_ready') === true) {
         this.trigger('both_elements_ready'); 
     }
}

Затем добавьте прослушиватель к «both_elements_ready» в главном представлении:

initialize: function() { 
 //your code
 this.on('both_elements_ready',this.page_ready); }

РЕДАКТИРОВАТЬ: добавлена ​​информация, чтобы сделать ответ более подходящим для вопроса и более подробным.

person Rakan Nimer    schedule 09.05.2013
comment
Спасибо за ваш ответ, но извините, что я не смог точно выполнить свое требование, публикуя его в первый раз. Согласно вашему предложению, я бы знал, когда будут готовы CustomerInfoView и CustomerAccountsView, но мне нужно знать, когда они оба будут готовы. - person Dipin Krishnan; 09.05.2013
comment
Вау, это еще более запутанно, чем вопрос. Какой смысл использовать фреймворк, если вы планируете обойти его и сделать его более сложным, чем если бы фреймворка вообще не было? - person Pierreten; 09.05.2013
comment
@RakanNimer: Ваше решение может быть хорошим, если есть только 2 представления, как указано в вопросе. Но что, если внутри myApp.views.CustomerView просмотров будет еще больше? - person Dipin Krishnan; 09.05.2013
comment
@Pierreten Что ты предлагаешь делать? - person Rakan Nimer; 09.05.2013
comment
Хорошо, поэтому использование ChaplinJS решит эту проблему очень элегантно! - person Rakan Nimer; 10.05.2013

Изменить: этот ответ основан на том, что я узнал из примера «Бранч с Чаплином» здесь. Chaplin — это фреймворк, построенный поверх магистрали.


Итак, я собираюсь предложить решение на основе coffeescript (у меня просто есть решение в coffee :/! Попробуйте использовать js2coffee если вы хотите конвертировать обратно в js)

Мастер-класс

Идея состоит в том, чтобы иметь мастер-класс, а не представление, которое будет держать приложение в руках, а не главное представление.

module.exports = class Application
  initialize: (finished_init) =>
    @collection = new Collection()
    @main_view = new View()
    @collection.fetch(
      success: (collection) =>
        @main_view.collection = @collection
        finished_init()
    )
  render: (targetLocation, params) ->
    switch targetLocation
      when "main" then (
        @main_view.render()
      )

В методе initialize мы извлекаем данные коллекции. В случае успеха вызывается finshed_init(). Вы можете заменить его на @render (кстати, @ == это :) )

Инициализация

Вот как я инициализирую свое приложение:

$ ->
  app = new Application()

  app.initialize( ->
    #----------------------------------#
    # Create the router and its routes
    # This is called at the finished_init() of the Application class
    #----------------------------------#
    app_router = new Router
  )

Запуск более одного fetche асинхронно

У вас может быть функция, которая отслеживает завершение выборки, или вы можете попробовать использовать async. . У него есть хорошая функция `parallel, которая делает именно это.

@collection1 = new Collection()
@collection = new Collection()
@main_view1 = new View()
@main_view2 = new View()

async.parallel [ (callback) ->
  setTimeout (->
    @collection1.fetch(
      success: (collection) =>
        @main_view1.collection = @collection
        callback null, "one"
  ), 200
, (callback) ->
  setTimeout (->
    @collection2.fetch(
      success: (collection) =>
        @main_view2.collection = @collection
        callback null, "two"
  ), 100
 ], (err, results) ->
   if not err
    @render()  # Render what you want
person tUrG0n    schedule 09.05.2013

Некоторое время ломая голову и выполняя поиск в Google, я нашел эта ссылка.

Поэтому я внес несколько изменений в свой MasterView, и он заработал, и проблема, с которой я столкнулась, была решена. Изменение, которое я сделал в MasterView, заключается в следующем.

var activeConnections=0;

myApp.views.MasterView = Backbone.View.extend({
    initialize: function(params) {

        activeConnections++;

        var self = this;
        this.collection.fetch({
            success: function(resp) {

                activeConnections--;

                self.collection.bind("reset", self.render(), self);

                if(activeConnections===0){
                    // trigger the page has finished rendering
                }
            },
            error: function(xhr, xhrStatus) {
                // push error message, in case of fetch fails.
                if(activeConnections===0){
                    // trigger the page has finished rendering
                }
            }
        });
    },
    render: function() {
        var self = this;
        this.collection.each(function(model) {
            if (model.get('authorize') && model.get('status') === "success" && model.get('data').length > 0) {
                self.bindTemplate(model.get('data')[0]);
            }
        });
    }
});

Спасибо всем, кто помог мне решить эту проблему.

person Dipin Krishnan    schedule 16.05.2013