Backbone - небольшая библиотека на javascript, которая структурирует клиентский код. Чаще всего, Backbone используется для создания одностраничных приложений.
Backbone.js имеет жесткую зависимость от библиотеки underscore.js и слабую от JQuery. Он состоит из следующих модулей:
- Views (Представления)
- Events (События)
- Models (Модели)
- Collections (Коллекции)
- Routers (Маршрутизаторы)
Мы разберем все эти модули на простых примерах и первое, с чего мы начнем, - это приложение, которое выводит Привет, мир.
Для начала работы скачайте этот простой HTML файл. Этот файл содержит ссылки на библиотеки, которые нам понадобятся (JQuery, Underscore.js, Backbone.js и Backbone-localStorage.js) и обозначенные места для вашего HTML и JS кода. Не беспокойтесь о библиотеках, мы обязательно объясним для чего они нужны.
После загрузки вышеупомянутого файла, обратите внимание на HTML, где вся ваша страница будет построена с использованием класса Backbone.Views!
Ваше JS приложение будет загружено вот сюда:
<div id="container">Загрузка...</div>
Views (Представления)
Backbone-представления являются эквивалентом «контроллеров» MVC-фреймворков (например, Ruby On Rails). Представления в Backbone.js отвечают за отображение данных приложения, а также могут реагировать на события, которые возникают в моделях, коллекциях или реагировать на события DOM-элементов.
Для создания представления нужно всего-навсего расширить класс Backbone.View
. Изучите представленный ниже код с комментариями, а затем вставьте его в яваскрипт-блок загруженного HTML файла.
var AppView = Backbone.View.extend({
// el - ссылка на DOM-элемент, в который представление
// вставляет сформированное содержимое
el: '#container',
// Функция initialize вызывается при создании
// экземпляра представления
initialize: function(){
this.render();
},
// Для вызова jQuery функций представление Backbone
// имеет свойство $el
render: function(){
this.$el.html("Привет, мир");
}
});
После копирования кода, откройте файл в браузере, после чего вы должны увидеть текст 'Привет, мир' вместо слова 'Загрузка...'. Однако этого не происходит. Дело в том, что нам нужно инициализировать представление. Для этого добавьте в конец нашего кода следующую строку:
var appView = new AppView();
Отлично, мы получили надпись 'Привет, мир' и познакомились с Представлениями (см. полный код здесь)
Backbone и шаблоны
Поскольку Backbone тесно работает с библиотекой под названием underscore.js (или просто _.js), вы можете воспользоваться доступным в ней шаблонизатором. Также, вы можете воспользоваться любым другим шаблонизатором, который вам нравится, например mustache, или handlebars. Давайте для простоты придерживаться _.js.
Шаблоны в Underscore имеют следующий синтаксис:
_.template(templateString, [data], [settings])
Здесь templateString
- настройки шаблонизатора. Вы можете использовать в нем заглушку вида <%= %>
и <%- %>
для динамически вставляемых данных. Последняя экранирует спец. символы, делая HTML код безопасным, в то время как первая - нет. Кроме того, вы можете использовать <% %>
для запуска любого javascript кода.
[data]
- объект с данными, необязательный патаметр.
С помощью необязательного параметра [settings]
мы можем переопределить любые настройки шаблонизатора, заданые в templateString
Давайте посмотрим его в действии и перепишем наш 'Привет, мир' используя шаблонизатор в Underscore.
var AppView = Backbone.View.extend({
el: $('#container'),
// Шаблон, который имеет заглушку 'who', которая
// потом заменится неким значением
template: _.template("<h3>Привет, <%= who %></h3>"),
initialize: function(){
this.render();
},
render: function(){
// Рендерим, подставляя в заглушку 'who'
// значение, равное 'мир'
this.$el.html(this.template({who: 'мир'}));
}
});
var appView = new AppView();
Запустите приложение снова и убедитесь, что оно работает с шаблоном.
Начнем снова с HTML болванкой, использованной ранее. Теперь, вместо div#container давайте добавим следующий код:
<section id="todoapp">
<header id="header">
<h1>Todos</h1>
<input id="new-todo" placeholder="Что должно быть сделано?">
</header>
<section id="main">
<ul id="todo-list"></ul>
</section>
</section>
Мы собираемся реализовать неупорядоченный список ul элементов с чекбоксами.
Models (Модели)
Модели - сердце каждого приложения. Они содержит интерактивные данные и логику, с ними связанную, такую как получение, установка и проверка данных, значения по умолчанию, инициализации данных, преобразования и д.р. Для нашего примера, мы собираемся создать модель под названием Todo
, которая будет хранить строку текста (title) и была ли задача была завершена, или нет (completed).
// создаем пространство имен для нашего приложения
var app = {};
app.Todo = Backbone.Model.extend({
defaults: {
title: '',
completed: false
}
});
Заметьте, что для удобства сделано так, что названия классов выделены заглавной буквой, а переменные и объекты - нет. Еще один важный аспект моделей - это то, что их свойства являются динамическими; они могут быть созданы на лету и не иметь никаких особенностей.
Протестируем наш код. Для этого откройте консоль браузера (в Хроме: ctrl
+shift
+i
, или ⌘
+alt
+i
) и попробуйте ввести следующий код, чтобы ознакомиться с моделями:
var todo = new app.Todo({title: 'Изучаем Backbone.js', completed: false}); // создаем объект с атрибутами
todo.get('title'); // "Изучаем Backbone.js"
todo.get('completed'); // false
todo.get('created_at'); // undefined
todo.set('created_at', Date());
todo.get('created_at'); // "Wed Sep 12 2012 12:51:17 GMT-0400 (EDT)"
Коллекции в Backbone (Backbone.Collection)
Коллекции - это упорядоченные множества моделей, где вы можете получить и установить модели в коллекции, прослушивать события, когда любой элемент в коллекции изменяется и извлекать данные модели с сервера. Например: todoList.fetch();
.
Коллекции позволяют сохранять данные (в базе данных, файлах, памяти) и это требует ссылку на нее. Поэтому, необходимо указать параметр url
с относительнным URL, где ресурс модели будет расположен на сервере. В противном случае, вы получите такую ошибку: A "url" property or function must be specified (Свойство "url", или функция должна быть указана).
Для упрощения, мы не будем импользовать сервер (я сделаю отдельный урок для этого); вместо этого мы будем использовать локальное хранилище (local storage) HTML5 для persistence через Backbone плагин. Таким образом, мы должны определить свойство LocalStorage вместо URL. Необходимо подключить backbone-localstorage.js с остальными вашими библиотеками как в примере.
app.TodoList = Backbone.Collection.extend({
model: app.Todo,
localStorage: new Store("backbone-todo")
});
// экземпляр коллекции
app.todoList = new app.TodoList();
Протестируем наш код. Для этого снова откройте консоль браузера и попробуйте ввести следующий код:
var todoList = new app.TodoList()
todoList.create({title: 'Изучаем коллекции'}); // заметьте что 'completed' будет установлено в false по умолчанию
var lmodel = new app.Todo({title: 'Learn Models', completed: true});
todoList.add(lmodel);
todoList.pluck('title'); // ["Изучаем коллекции", "Изучаем Модели"]
todoList.pluck('completed'); // [false, true]
JSON.stringify(todoList); // "[{"title":"Изучаем коллекции","completed":false,"id":"d9763e99-2267-75f5-62c3-9d7e40742aa6"},{"title":"Изучаем Модели","completed":true}]"
Backbone.View
Как уже упоминалось выше, Представления в Backbone не имеют HTML-меток для нашего приложения, зато они (как контроллеры в MVC-фреймворке) обрабатывают данные и связывают их с шаблонами и рендерят HTML-код, основываясь на событиях и изменении данных.
Представления имеют 4 основных свойства: el, initialize, render и events. Мы уже видели в деле первые три, а совсем скоро познакомимся с четвертым. Вы ведь не забыли еще наш "Привет, мир":
var AppView = Backbone.View.extend({
el: '#container',
initialize: function(){
this.render();
},
render: function(){
this.$el.html("Привет, мир");
}
});
Каждое представление должно ссылаться на DOM-элемент и el
- это и есть та самая ссылка. this.el
создается из таких свойств Представления, как el, tagName, className, id, или attributes. Если ни один из них не указан, то this.el
является пустым div. view.$el
- это закешированный jQuery объект элемента представления (view.el).
С помощью функции initialize у вас есть возможность передать параметры, которые будут прикреплены к модели, коллекции, или view.el.
Функция render вставляет отметки в элементы. Не все Представления требуют наличие функции render, как вы можете увидеть в примере, они просто вызывают другие рендерные функции.
События (events) записываются в следующем формате:
{"<EVENT_TYPE> <ELEMENT_ID>": "<CALLBACK_FUNTION>"}
Например:
events: {'keypress #new-todo': 'createTodoOnEnter'}
В jQuery мы бы написали:
$('#new-todo').keypress(createTodoOnEnter);
Теперь вернемся к нашему To-Do приложению: Нам нужно Представление, которое отрендерит каждый объект из модели Todo на страницу. Шаблон item-template
и Представление app.TodoView
сделают то, что нам нужно.
<script type="text/template" id="item-template">
<div class="view">
<input class="toggle" type="checkbox">
<label><%- title %></label>
</div>
</script>
В следующем блоке кода мы описываем Представление, используя заданный выше шаблон (#item-template
) чтобы вывести заголовок из model.
// рендерим индивидуальный todo-элемент списка (li)
app.TodoView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#item-template').html()),
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this; // включить цепочку вызовов
}
});
Когда мы создаем Представления, они могут получить любой параметр, который нам нужен. В нашем случае мы называем его model
, так как то, что нам нужно создается с помощью модели (т.е. todo):
var view = new app.TodoView({model: todo});
Также обратите внимание, что он использует tagName: li
вместо el
. Это означает, что новые отрендеренные элементы будут помещены в <li></li>
Backbone.Events
Этот модуль может быть смешан с каким-либо объектом, чтобы придать ему поведение издатель-подписчик. События предоставляют несколько методов, из которых мы будем обсуждать следующие: on
, off
и trigger
. Если это вы знакомы с JQuery, то имеете представление как они работают.
Метод on
привязывает функцию обратного вызова к объекту. Формат записи:
object.on(event, callback, [context])
Здесь event - событие, callback - вызываемая функция, context - контекст вызова (необязательный параметр)
Также вы можете использовать bind. Эта функция связывает объект с событием и функцией обратного вызова. Когда запускается событие, она запускает функцию обратного вызова. Например:
todoList.on('add', this.addAll, this);
Каждый раз, когда добавляется новая единица в Backbone.Collection, запускается событие add
. В вышеуказанном примере после запуска события, запускается функция обратного вызова addAll()
и текущий объект передается с this
в качестве контекста.
События можно также установить на произвольные объекты, используя underscore.js с помощью функции extend
:
var object = {},
callback = function(msg) {
console.log("Сработало " + msg);
};
_.extend(object, Backbone.Events);
object.on("my_event", callback);
object.trigger("my_event", "Мое пользовательское событие");
Теперь нам нужно другое Представление, чтобы взять коллекцию и отрендерить каждый пункт задачи. Мы будем называть его 'AppView'. Взгляните на этот код и попытайтесь идетитфицировать каждый из элементов (мы уже описали их в предыдущих разделах):
// рендер полного списка, вызывающего TodoView для каждого события
app.AppView = Backbone.View.extend({
el: '#todoapp',
initialize: function () {
this.input = this.$('#new-todo');
// когда новые элементы добавляются в коллекцию,
// мы рендерим их с помощью addOne
app.todoList.on('add', this.addOne, this);
app.todoList.on('reset', this.addAll, this);
app.todoList.fetch(); // Загружаем список из local storage
},
events: {
'keypress #new-todo': 'createTodoOnEnter'
},
createTodoOnEnter: function(e){// Код клавиши ENTER = 13
if ( e.which !== 13 || !this.input.val().trim() ) {
return;
}
app.todoList.create(this.newAttributes());
this.input.val(''); // Очищаем input
},
addOne: function(todo){
var view = new app.TodoView({model: todo});
$('#todo-list').append(view.render().el);
},
addAll: function(){
this.$('#todo-list').html(''); // Очищаем список
app.todoList.each(this.addOne, this);
},
newAttributes: function(){
return {
title: this.input.val().trim(),
completed: false
}
}
});
//--------------
// Инициализация
//--------------
app.appView = new app.AppView();
В заключительной части нашего урока по Ускоренному изучению Backbone мы узнаем, как реализовать создание, чтение, обновление и удаление для моделей!