Статьи

Основы Backbone.js: модели, представления, коллекции и шаблоны

В этом уроке мы рассмотрим основные принципы популярной платформы MV *, Backbone.js . Мы рассмотрим модели, представления, коллекции и шаблоны, а также увидим, как каждая из них строится на основе друг друга при создании приложения. Мы также коснемся ответственности и разделения проблем, что в конечном итоге позволит нам создать масштабируемое приложение с разумной базой кода.

Backbone имеет только одну жесткую зависимость — Underscore.js. Мы также будем использовать jQuery для легкого манипулирования DOM. Ваша типичная структура документа должна выглядеть следующим образом:

<html> <body> app content here <script src="path/to/jquery.js"> <script src="path/to/underscore.js"> <script src="path/to/backbone.js"> <script src="path/to/app.js"> </body> </html> 

Все скрипты приложения должны быть включены после различных библиотек. Кроме этого, мы готовы погрузиться!

Backbone.js и шаблон MV *

Магистраль подпадает под эгиду фреймворков MV *, что означает, что она в основном состоит из M odels и V iews. Традиционные платформы MVC содержат тот дополнительный C , который обозначает контроллер. Исторически, контроллер реагировал на какой-то ввод данных пользователем и передавал этот ввод в модель или обратно в представление. В средах MV *, таких как Backbone, логика контроллера обрабатывается внутри самого представления. Чтобы узнать немного больше об анатомии JavaScript MV * Framework, прочитайте этот пост.

Мы немного подпрыгнули здесь, так как мы не смотрели, какие модели и виды. Ну, это не так сложно! Я пытаюсь представить модели как «модель данных», точно так же, как у архитектора может быть модель дома, или у инженера может быть модель крыла. Имея это в виду, легче понять, как конкретная модель представляет собой определенный набор данных. В приложении промышленного уровня эти данные, скорее всего, хранятся где-то в базе данных. Таким образом, модели могут связываться с этой базой данных и выполнять определенные действия, такие как операции CRUD.

А как насчет просмотров? Учитывая название «вид», довольно легко сделать предположения об их ответственности. Если вы думали, что это должно было сделать данные для конечного пользователя, то вы в основном правы. Мнения действительно ответственны за это. В Backbone, однако, они выполняют еще одну важную функцию, как я упоминал ранее. Они обрабатывают логику контроллера. Во второй части этой серии статей я рассмотрю обработку событий внутри представления, установление связи с моделью и отправку обновлений обратно в представление. Сейчас же важно понять, что эта логика действительно существует, но находится внутри представления. Давайте перейдем к моделям и получим лучшее понимание их.

Копаться в моделях

Вот небольшая выдержка из документации по моделям Backbone :

Модели — это сердце любого приложения JavaScript, содержащего интерактивные данные, а также большую часть логики, окружающей их: преобразования, проверки, вычисленные свойства и контроль доступа.

Имея это в виду, давайте составим небольшой пример, который мы будем использовать в дальнейшем. Представьте, что у нас есть магазин серфинга, и мы хотим создать базу данных досок для серфинга, которые мы несем Таким образом, если клиент спрашивает, есть ли у нас плата от определенного производителя, или точная модель платы, или и то, и другое, мы можем быстро взглянуть вверх. Давайте также предположим, что мы хотим отслеживать акции. Наша модель иерархии будет выглядеть так:

 Surfboard |__manufacturer |__model |__stock для Surfboard |__manufacturer |__model |__stock 

В Backbone мы создаем новую модель, расширяя Backbone.Model следующим образом:

 var Surfboard = Backbone.Model.extend({ }); 

Теперь мы можем создать новый экземпляр этой модели, используя конструктор следующим образом:

 var board1 = new Surfboard({ manufacturer: 'Channel Islands', model: 'Whip', stock: 12 }); 

Переменная board1 теперь ссылается на новый экземпляр нашей модели board1 и содержит свой собственный набор значений. Тем не менее, мы можем передать любые значения. Давайте использовать функцию по defaults чтобы добавить некоторые атрибуты по умолчанию в нашу модель. Теперь это должно выглядеть так:

 var Surfboard = Backbone.Model.extend({ defaults: { manufacturer: '', model: '', stock: 0 } }); 

Если бы мы хотели получить некоторые данные из этого экземпляра, мы бы использовали метод get , который получает текущее значение атрибута из модели. Представьте, что у нас есть эта разметка в нашем документе:

 <table class="table"> <tr> <th>Manufacturer</th> <th>Model</th> <th>Stock</th> </tr> <tr> <td id="board1-manufacturer"></td> <td id="board1-model"></td> <td id="board1-stock"></td> </tr> </table> 

Мы можем заполнить эти поля следующим образом:

 $('#board1-manufacturer').html(board1.get('manufacturer')); $('#board1-model').html(board1.get('model')); $('#board1-stock').html(board1.get('stock')); 

Вот результат со вторым экземпляром модели:

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

Помните также, что в производственном приложении мы, скорее всего, будем генерировать экземпляры модели путем чтения из базы данных. Это также добавляет к необходимости группировки моделей в коллекции. Без дальнейших церемоний, давайте посмотрим на это.

Создание коллекции

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

Теперь в нашем приложении мы хотим вывести все данные в табличном виде. Ранее мы вручную выводили атрибуты каждого экземпляра модели, что крайне непрактично. Однако на этот раз давайте создадим коллекцию Backbone (упорядоченный набор моделей) и добавим в нее наши модели. Мы начнем с создания нашей коллекции под названием Surfboards :

 var SurfboardsCollection = Backbone.Collection.extend({ }); 

Давайте установим модель коллекции в нашу модель Surfboard , которая позволит нам указать класс модели, который содержит коллекция:

 var SurfboardsCollection = Backbone.Collection.extend({ model: Surfboard }); 

Теперь, когда наша коллекция настроена, мы можем использовать метод add для добавления моделей в коллекцию . Давайте добавим наши три доски для серфинга, сначала создав новый экземпляр коллекции, а затем добавляя экземпляры доски по одному:

 var Surfboards = new SurfboardsCollection; Surfboards.add(board1); Surfboards.add(board2); Surfboards.add(board3); 

Все наши модели теперь сгруппированы и готовы к визуализации. В нашем HTML-коде давайте удалим строки таблицы, которые у нас были раньше, и добавим просто простое тело таблицы. Теперь наша таблица должна выглядеть так:

 <table class="table"> <thead> <tr> <th>Manufacturer</th> <th>Model</th> <th>Stock</th> </tr> </thead> <tbody id="table-body"></tbody> </table> 

Теперь мы можем использовать один из доступных нам методов Underscore для циклического сбора и вывода данных. Мы будем использовать each , который мы можем применить непосредственно к нашей коллекции и который также принимает один аргумент, представляющий модель. Поэтому на каждой итерации мы имеем доступ к текущей модели и можем выполнять методы модели. Имея это в виду, рендеринг таблицы результатов прост:

 Surfboards.each(function(surfboard) { $('#table-body').append( '<tr>' + '<td>' + surfboard.get('manufacturer') + '</td>' + '<td>' + surfboard.get('model') + '</td>' + '<td>' + surfboard.get('stock') + '</td>' + '</tr>' ); }); 

Вот демонстрация этого в действии:

До сих пор он выглядел великолепно, и в некоторых отношениях мы вручную визуализировали представление. Делать это таким образом очень ограниченно, и не позволяет нам использовать полный набор методов Backbone. Это также не позволяет нам выполнять любую логику контроллера на лету. Нам нужно правильно визуализировать наши данные, используя представления Backbone, поэтому давайте перейдем к этому.

Рендеринг данных с представлениями и шаблонами

Прежде чем приступить к построению наших представлений, давайте коснемся аккуратной темы — шаблонов. Шаблоны позволяют нам легко визуализировать пользовательский интерфейс как альтернативу прямому манипулированию DOM. Хотя нам разрешено использовать все, что мы хотим, Underscore поставляется с собственным механизмом шаблонов , поэтому мы воспользуемся этим.

В нашем HTML-коде давайте создадим шаблон, который будет отвечать за визуализацию данных модели. Это должно выглядеть так:

 <script type="text/template" id="surfboard-template"> <td><%= manufacturer %></td> <td><%= model %></td> <td><%= stock %></td> </script> 

Теперь наш шаблон готов к вызову внутри представления для упрощения рендеринга данных. Обратите внимание, что я не включил обертывающий элемент tr , и вскоре вы поймете, почему.

Давайте теперь перейдем к созданию представлений. Согласно документам:

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

Теперь подумайте о нашем приложении и подумайте, как мы можем сделать его в будущем. В этом уроке мы отображаем данные только один раз. Но что если в будущем мы захотим обновить свойства экземпляра модели? Мы определенно хотели бы иметь ссылку на каждый экземпляр модели и присоединить необходимую логику контроллера к этому конкретному экземпляру. Имея это в виду, нам понадобятся два представления для нашего приложения:

  1. Главное (или родительское) представление, которое содержит все вложенные представления. Давайте назовем это SurfboardsView .
  2. Подвид, который отвечает за визуализацию одного экземпляра модели. Давайте назовем это SurfboardView .

Сначала мы создадим наш SurfboardsView , расширив метод Backbone View , например:

 var SurfboardsView = Backbone.View.extend({ }); 

Давайте теперь включим следующие 3 вещи:

  1. Элемент el или DOM . Представления должны всегда иметь элемент DOM. В случае основного вида мы будем использовать #table-body .
  2. Функция initialize , которая сразу же запускается при создании нового экземпляра представления.
  3. Функция render , которая визуализирует шаблон представления из данных модели.

Вот наш обновленный вид:

 var SurfboardsView = Backbone.View.extend({ el: '#table-body', initialize: function() { }, render: function() { } }); 

Поскольку мы вручную вставляем экземпляры модели в нашу коллекцию, нам не нужно ждать запуска каких-либо событий, сигнализирующих о завершении выборки данных. Это означает, что мы можем немедленно вызвать функцию render в нашем представлении. Наша функция initialize теперь выглядит так:

 initialize: function() { this.render(); } 

Внутри нашей функции render давайте начнем с нескольких вещей. Во-первых, мы убедимся, что у нашего элемента нет HTML. Во-вторых, давайте повторим нашу коллекцию. В-третьих, давайте вернемся к this , что разрешает цепочки вызовов. Это дает нам:

 render: function() { this.$el.html(''); Surfboards.each(function(model) { // do something... }); return this; } 

Все идет нормально. Нам просто нужно закончить функцию render . Но что именно нам нужно здесь делать? Вот шаги:

  1. На каждой итерации цикла нам нужно создать новое представление SurfboardView и убедиться, что в него передаются правильные данные (т.е. текущая модель).
  2. Из созданного экземпляра представления нам нужно вызвать функцию отображения этого представления и вернуть заполненный элемент HTML.
  3. После извлечения визуализированного SurfboardView нам нужно добавить его к элементу тела таблицы, который мы установили.

Имейте в виду, что элемент представления, обозначенный как el , может быть получен в любой точке. С этим знанием, вот как выглядит функция render для SurfboardsView :

 render: function() { this.$el.html(''); Surfboards.each(function(model) { var surfboard = new SurfboardView({ model: model }); this.$el.append(surfboard.render().el); }.bind(this)); return this; } 

Обратите внимание на пару вещей здесь. Во-первых, мы передаем текущую модель каждому новому экземпляру представления SurfboardView, чтобы к его атрибутам можно было обращаться из каждого из этих экземпляров. Во-вторых, нам нужно привязать this к each циклу в связи с созданием нового контекстного контекста.

Прямо сейчас, ничего не должно произойти, если вы запустите скрипт. Это потому, что мы еще не определили и не настроили наш SurfboardView . Давайте сделаем это. Мы начнем еще раз с расширения представления Backbone:

 var SurfboardView = Backbone.View.extend({ }); 

На этот раз мы хотим определить tagName которое будет заполнено атрибутами модели. Если вы помните наш шаблон ранее, вы также помните, что мы никогда не включали обертывающий элемент tr . Это потому, что именно здесь мы определяем это, в конечном итоге создавая новый элемент.

Мы также хотим создать ссылку на наш шаблон представления , который мы будем использовать в нашей render function . Наконец, внутри функции render мы заполняем элемент представления, используя шаблон и атрибуты модели, и возвращаем this для создания цепочки, как и раньше. Вот последний скрипт для SurfboardView :

 var SurfboardView = Backbone.View.extend({ tagName: 'tr', template: _.template($('#surfboard-template').html()), render: function() { this.$el.html(this.template(this.model.attributes)); return this; } }); 

Ницца! Теперь наши различные экземпляры внутри SurfboardsView имеют доступ к соответствующей функции и элементу render .

Наконец, что не менее важно, нам нужно запустить приложение, создав экземпляр SurfboardsView , например так:

 var app = new SurfboardsView; 

Круто, вы должны увидеть таблицу, заполненную нашими моделями для серфинга! Вот демонстрация, чтобы увидеть это в действии:

Заворачивать

Это как раз и будет сделано для этого сегмента исследования Backbone.js. Сегодня мы рассмотрели здесь много основ, а также обсудили некоторые замечательные методы для масштабируемости и движения вперед. В следующий раз мы рассмотрим реализацию некоторой логики контроллера, а также рассмотрим взаимодействие между представлениями и моделями и просмотр обновлений. Если у вас есть какие-либо вопросы или комментарии, я был бы рад услышать их в обсуждении ниже. Спасибо за прочтение!

Хотите узнать больше о Backbone?

У SitePoint Premium появился новый курс по Backbone.js. Присоединитесь к Premium, чтобы получить к нему доступ, и ко всей библиотеке ресурсов SitePoint!