Вчера я в общих чертах описал процесс подключения Backbone.js в приложение на «рельсах». Кто не читал, тому рекомендую сначала бегло пробежать по той статье.
Сегодня расскажу о, пожалуй, самой важной части Backbone.js: о роутерах. Почему я считаю роутеры самыми важными? Всё просто: чтобы что-то увидеть в окне браузера, пользователь сначала должен набрать URL, кликнуть ссылку или что-то в этом духе. Так вот роутеры и решают, что и в какой момент времени показывать пользователю, в зависимости от URL-а. Они являются как бы связующей частью между пользователем и серверной частью приложения. Впрочем, ничего сверхъестественного.
В моём приложении всего один роутер, этого вполне достаточно, думаю, для большинства приложений. При желании, можно для каждой части приложения делать отдельный файл — это уже дело личных предпочтений.
Для начала давайте рассмотрим файл app/assets/javascripts/app.js.coffee:
window.App =
Models: {}
Collections: {}
Views: {}
Routers: {}
init: ->
new App.Routers.Posts()
Backbone.history.start()
$(document).ready ->
App.init()
Обращаю внимание, что моё приложение называется App. Запуск приложения - App.init() — происходит, когда уже DOM полностью загружен. Функция init() — это своего рода конструктор, если кто-то знаком с другими языками программирования. И она выполняет всего две, но очень важные функции:
1. new App.Routers.Posts() — инициализирует наши роутеры
2. Backbone.history.start() — запускает механизм сохранения истории нашего перемещения по приложению. Вы же хотите пользоваться кнопками «Вперёд» и «Назад» своего браузера? Вот это именно для этого.
Теперь перейдём непосредственно к роутерам. Все файлы, содержащие роуты, находятся в одной директории: app/assets/javascripts/routers/. У меня там всего один файл: posts.js.coffee. В данном файле находится всего один класс App.Routers.Posts, который наследуется от Backbone.Router. Собственно, сегодня я буду рассказывать в основном про этот класс.
Начинается этот класс с перечня роутеров, которые допустимы в нашем приложении:
routes:
'' : 'index'
'posts/new' : 'add'
'posts/:id' : 'show'
'posts/:id/edit' : 'edit'
'users' : 'users'
'users/new' : 'newUser'
'users/:id' : 'showUser'
'users/:id/edit' : 'editUser'
'help' : 'help'
Думаю те, кто привыкли работать с роутами в «рельсах», найдут много общего. Слева указывается часть URL-а, который в адресной строке, справа — функция, которая обрабатывает данный путь. Ничего сложного и необычного.
Далее следует функция конструктор для данного класса: она будет вызываться каждый раз, когда кто-то будет пытаться создать экземпляр данного класса:
initialize: ->
@collection = new App.Collections.Posts()
@collection.reset($('#all_posts_data').data('posts'))
@users = new App.Collections.Users()
@users.reset($('#all_users_data').data('users'))
view = new App.Views.Menu()
$('#sidebar').html(view.render().el)
Что же делает наш конструктор?? Первые две строчки аналогичны третьей и четвёртой, разница лишь в разных данных. Сначала мы инициализируем коллекцию
@collection = new App.Collections.Posts()
а после этого загружаем в коллекцию данные
@collection.reset($('#all_posts_data').data('posts'))
Данный приём является примером хорошего тона, когда при загрузке и старте приложения не приходится делать ещё один или несколько запросов к серверу, чтобы получить данные. Кроме этого, не происходит задержки при отображении данных. Но позвольте, скажут некоторые, а откуда эти данные взялись на странице??? Ответ очень прост: стоит ознакомиться с содержимым файла app/views/main/index.html.erb. В самом конце есть две вот такие строчки:
<%= content_tag :div, '', id: "all_posts_data", data: {posts: Post.all} %>
<%= content_tag :div, '', id: "all_users_data", data: {users: User.all} %>
В данном случае мы создаём два div-а, в которые в поле data осуществляем загрузку данных из наших таблиц на сервере. Таким образом, на момент отрисовки странички пользователю, приложение имеет уже некоторый первоначальный набор данных для отображения и манипуляций ими, без необходимости обращения к серверу. По-моему, очень даже симпатично.
Но вернёмся к нашему конструктору класса. Остались не рассмотренными ещё две строчки:
view = new App.Views.Menu()
$('#sidebar').html(view.render().el)
Данные строчки выполняют всего одну функцию: отображают на экране меню с левой стороны. Только и всего.
В принципе, большинство остальных функций в данном классе выполняют одно и то же действие: формируют необходимую картинку на экране, в зависимости от URL-а. Рассмотрим на примере метода index.
index: ->
$('.current').removeClass('current')
$('#all_posts').addClass('current')
$('.active').removeClass('active')
$('#home_link').addClass('active')
view = new App.Views.PostsIndex(collection: @collection)
@showView('#content', view)
Первые 4-ре строки — это не что иное, как украшательства: осуществляют подсветку необходимого пункта меня слева и вверху. Не более.
Следующая строка view = new App.Views.PostsIndex(collection: @collection)
осуществляет создание экземпляра view класса App.Views.PostsIndex, ответственного за отображение всех постов приложения. Почему всех? Да потому что в качестве аргумента передаётся коллекция, в которую были загружены все данные, полученные с сервера. Впрочем, об отображениях (views), мы поговорим в следующих постах.
Сейчас же стоит обратить внимание на функцию showView(), которая, собственно, и осуществляет вывод на экран. Вот что представляет из себя эта функция:
showView: (selector, view) ->
@currentView.close() if @currentView
$(selector).html view.render().el
@currentView = view
view
Может возникнуть резонный вопрос: а почему так сложно? почему бы просто не заменить содержимое элемента новым содержимым? В принципе, так можно, и так делают. Только вот есть одно «но»: как только Ваше приложение начнёт разрастаться, когда у него появится большое количество обработок действий пользователя(events), от тогда начнут возникать всякие неожиданные результаты — нажимаете одну кнопку, а действие происходит для других данных, появляются ошибки и всё такое. Чтобы этого не происходило, необходимо текущее отображение корректно закрыть, прекратить (unbind) действие всех ранее объявленных событий(events), а вот на его место уже отобразить новые данные.
Закрытие текущего представления(view) происходит при помощи функции close(), объявленной в самом начале файла:
Backbone.View::close = ->
@beforeClose() if @beforeClose
@remove()
@unbind()
О действии функций remove() и unbind() рекомендую ознакомиться на сайте jQuery.
Вот, в принципе, и всё о роутерах. Теперь Вы должны знать, как происходит процесс обработки и отображения данных, в зависимости от того, какой URL набран в адресной строке.
В следующий раз поговорим о моделях и коллекциях.
Сентябрь 14th, 2012 by none | Комментариев нет