В этом уроке мы рассмотрим основные принципы популярной платформы 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
, и вскоре вы поймете, почему.
Давайте теперь перейдем к созданию представлений. Согласно документам:
Общая идея состоит в том, чтобы организовать ваш интерфейс в логические представления, поддерживаемые моделями, каждая из которых может обновляться независимо при изменении модели, без необходимости перерисовывать страницу.
Теперь подумайте о нашем приложении и подумайте, как мы можем сделать его в будущем. В этом уроке мы отображаем данные только один раз. Но что если в будущем мы захотим обновить свойства экземпляра модели? Мы определенно хотели бы иметь ссылку на каждый экземпляр модели и присоединить необходимую логику контроллера к этому конкретному экземпляру. Имея это в виду, нам понадобятся два представления для нашего приложения:
- Главное (или родительское) представление, которое содержит все вложенные представления. Давайте назовем это
SurfboardsView
. - Подвид, который отвечает за визуализацию одного экземпляра модели. Давайте назовем это
SurfboardView
.
Сначала мы создадим наш SurfboardsView
, расширив метод Backbone View , например:
var SurfboardsView = Backbone.View.extend({ });
Давайте теперь включим следующие 3 вещи:
- Элемент
el
или DOM . Представления должны всегда иметь элемент DOM. В случае основного вида мы будем использовать#table-body
. - Функция
initialize
, которая сразу же запускается при создании нового экземпляра представления. - Функция
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
. Но что именно нам нужно здесь делать? Вот шаги:
- На каждой итерации цикла нам нужно создать новое представление
SurfboardView
и убедиться, что в него передаются правильные данные (т.е. текущая модель). - Из созданного экземпляра представления нам нужно вызвать функцию отображения этого представления и вернуть заполненный элемент HTML.
- После извлечения визуализированного
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!