Backbone.js Routers

Вчера я в общих чертах описал процесс подключения 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 набран в адресной строке.

В следующий раз поговорим о моделях и коллекциях.

Недавние записи

Оставить комментарий