Как стать автором
Обновить

Оптимизируем работу с шаблонами в Backbone

Время на прочтение3 мин
Количество просмотров7.3K
Знакомство с javascript-фреймворком Backbone я, как и многие, начинал с todo-туториала, на базе которого строилось дальнейшее использование фреймворка в своих проектах.

Но туториалы заканчиваются, и начинаются рабочие будни.


Думаю, многим знаком такой участок кода (из вышеупомянутого туториала):

window.AppView = Backbone.View.extend({

    // Instead of generating a new element, bind to the existing skeleton of
    // the App already present in the HTML.
    el: $("#todoapp"),

    // Our template for the line of statistics at the bottom of the app.
    statsTemplate: _.template($('#stats-template').html()),
 
    ...


Давайте разберемся подробнее:
  • декларацию вида AppView необходимо производить после готовности DOM-дерева, то есть оборачивать в jQuery(function() {...}), что не всегда удобно.
  • шаблон statsTemplate подгружается на этапе декларации вида, чтобы экземпляры могли использовать его без дополнительной загрузки и обработки
  • шаблон находится внизу html-страницы и подгружается в DOM при загрузке страницы


Конечно, для небольших приложений вопросов нет. А если приложение большое и содержит десятки шаблонов?
Зачем загружать и инициализировать все шаблоны сразу? Нам может и не понадобиться большинство из них.

Загрузка шаблонов



Хорошо бы подгружать шаблоны только тогда, когда они нам нужны и кешировать их.

Исходя из этого, напишем загрузчик:
    Core.Template = {
        cache: {}, // здесь хранятся загруженные шаблоны
        pending: {}, // очередь callback-ов, которые необходимо вызвать после загрузки шаблона
        load: function(url, callback) {
            callback = callback || function() {};

            if (this.cache[url]) { //если шаблон уже загружен, просто вызовем callback
                callback(this.cache[url]);
                return;
            }

            // добавляем callback в очередь
            if (this.pending[url]) { 
                this.pending[url].push(callback);
                return;
            }

            this.pending[url] = [callback];

            jQuery.ajax({ //загружаем шаблон
                url : url,
                dataType: 'text',
                complete: function(resp) {
                    var cache = 
                          this.cache[url] = _.template(resp.responseText); // парсим и заносим в кеш

                    _.each(this.pending[url], function(cb) { // обрабатываем очередь
                        cb(cache);
                    });

                    delete this.pending[url]; // очищаем очередь
                }.bind(this),
                error: function() {
                    throw new Error("Could not load " + url);
                }
            });
        }
    };


Загружая шаблон по ajax, имеем дополнительную возможность кешировать его средствами браузера.
Очередь сделана для того, чтобы загрузчик не грузил шаблон для каждого вида модели из нашей коллекции, а сделал это один раз и передал готовый для работы шаблон в каждый вид.

Надеюсь, что комментариев к коду достаточно, чтобы понять, как все работает.
Далее нам необходимо научить наши views работе с шаблонами по-новому.

Базовый класс вида для работы с шаблонами



Напишем базовый класс вида, чтобы он умел работать с нашим загрузчиком шаблонов.

    Core.View = Backbone.View.extend({
        renderQueue: false, // очередь запросов на рендеринг

        initialize: function() {
            if (this.templateUrl) {
                Core.Template.load(this.templateUrl, function(data) {
                    this.template = data; // запоминаем шаблон
                    if (this.renderQueue !== false) { // обработаем очередь на рендер
                        _.each(this.renderQueue, function(item) {
                            this._render.apply(this, item);
                        }.bind(this));
                        this.renderQueue = false;
                    }
                }.bind(this));
            }
        },

        _render: function() {}, // эта функция будет переопределяться в видах вместо render

        render: function() {
            if (!this.template) { // если шаблон не загружен - дождемся загрузки и отрендерим после
                this.renderQueue = this.renderQueue || [];
                this.renderQueue.push(arguments);
                return this;
            }
            return this._render.apply(this, arguments); //вызываем метод рендеринга
        }
    });


Таким образом, если у нас в параметрах вида передается templateUrl, то загружаем шаблон, иначе работаем по обычной логике.
Единственное отличие от стандартного поведения, функция непосредственного рендеринга выносится в метод _render. В принципе, ничто не мешает переписать код, оборачивающий все в метод render, но мне так показалось удобнее и проще.

Использование



Что на практике?
Вот так будет выглядеть измененная часть кода, показанного в начале статьи:

window.AppView = Core.View.extend({
    el: $("#todoapp"),

    // Загружаем шаблон отсюда
    templateUrl: '/templates/app.html',

    initialize: function() {
      AppView.__super__.initialize.apply(this); // вызываем родительский инициализатор

      ...
    }, 
    // Переименовываем render в _render и statsTemplate в template
    _render: function() {
      this.$('#todo-stats').html(this.template({
        total:      Todos.length,
        done:       Todos.done().length,
        remaining:  Todos.remaining().length
      }));
    },

    ...


Готово!

Код загрузчика доступен здесь.
Пример изменненного todo-туториала можно посмотреть здесь.
Если интересно, что поменялось в todo: diff

Спасибо за внимание!
Теги:
Хабы:
Всего голосов 20: ↑20 и ↓0+20
Комментарии5

Публикации