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 набран в адресной строке.

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

Сентябрь 14th, 2012 by none | Комментариев нет

Backbone.js + Coffeescript + Rails

Не так давно я написал статью о создании SPA на основе Spine.js в качестве front-end-а и «рельсов» в качестве back-end-а. Spine.js очень интересная штука, очень простая и понятная, но для создания не очень сложного приложения. Как только приложение начинает разрастаться, начинаются проблемы. Как по мне, то главная проблема — это роуты, разбросанные по разным файлам, малопонятный substack. Остальное уже как-то по мелочи.

Поигравшись со Spine.js, я переключил свой взор на более обкатанную и несколько более распространённую библиотеку: Backbone.js. Как оказалось, он не на много сложнее Spine.js. Основное неудобство было в том, что все туториалы написаны для javascript-a, а мне уже давно как-то по душе Coffeescript. Именно поэтому я решил написать несколько постов о том, как их подружить друг с другом: Backbone.js, CoffeeScript, Twitter Bootstrap и «рельсы».

Как обычно, весь исходный код доступен на Github-е, а работающее приложение можно увидеть на Heroku.

Данная статья будет вводная, много кода в ней не будет. Все остальные программные изыскания будут несколько позже. На странице работающего примера в разделе «Resources» можно найти несколько полезных ссылок, которые помогут Вам заполнить некоторые пробелы в знаниях по данной теме.

Итак, начнём. Просто создайте новое, пустое приложение «рельсов»:

$ rails new backbone_app --skip-bundle
$ cd backbone_app

После этого в Gemfile добавляем следующее:

1. вместо gem ‘sqlite3′ :

group :development do
    gem 'sqlite3'
end

group :production do
    gem 'pg'
end

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

2. в группу :assets добавляем

gem 'twitter-bootstrap-rails'

3. а в конец файла добавляем самый важный gem для этого приложения:

gem 'backbone-on-rails'

После всех этих добавлений можно смело установить все необходимые gem-ы:

$ bundle install

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

$ rails generate backbone:install

По умолчанию, генератор создаст файлы с расширением *.js.coffee, для использования синтаксиса CoffeeScript. Если Вам более привычно и удобно работать с javascript-ом, то укажите от этом генератору, задав опцию --javascript

Данный генератор создаст несколько директорий в /app:

app/assets/
├── javascripts
│ ├── application.js
│ ├── backbone_app.js.coffee
│ ├── collections
│ ├── models
│ ├── routers
│ └── views
└── templates

Лично я сразу переименовываю файлы, типа backbone_app.js.coffee, на app.js.coffee. Мне так удобнее. Хотя… лукавлю (-: Это у меня после прочтения вот этой статьи. Кстати, тоже рекомендуется к прочтению.

Рекомендую ознакомиться с содержимым файлов application.js и app.js.coffee после выполнения предыдущей команды.

В принципе, наше приложение готово для дальнейшей работы с Backbone.js. В последующих частях я расскажу у роутерах, моделях, коллекциях и отображениях(views). Как, что и с чем едят.

Всем удачи и работающего кода! (-:

Сентябрь 13th, 2012 by none | Комментариев нет

Spine.js и Rails

Как говорят у нас в Одессе, и снова здрасте! Именно так, а не здравствуйте (-:

Сегодня я решил предоставить на суд общественности совсем небольшое приложение. Так называемое SPA — Single Page Application. Самым ярким и известным представителем SPA является, пожалуй, Gmail. Ну на такую функциональность никто пока не замахивается, а вот что-то типа системы управления для сайта — а почему бы и нет?! Как это может выглядеть, Вы можете посмотреть на Heroku. Строго не судите, это всего лишь некоторые зарисовки о том, как это может быть.

Я же хочу лишь рассказать, что к чему. Во-первых, как не сложно догадаться, я использовал связку Spine.js и Rails. Spine.js — это отличный маленький фрейморк для создания SPA. Почему пал выбор именно на него, а не на, например, Backbone.js? Ну, во-первых, он написан на Coffeescript. Во-вторых, легко и просто интегрируется с рельсами, следует их философии. В-третьих, простая и понятная документация, которой совсем не много. В общем, когда разберёшься, что к чему, то писать на нём одно удовольствие.

В использовании Spine.js ничего необычного, разве что введено понятие SubStack для использования нескольких Stacks. Чтобы разобраться, что к чему, следует ознакомиться с вот этой статьей(Eng.). Ещё я там использовал так называемую auto download paggination, иными словами, — это автоматическая подгрузка необходимого контента, когда скроллер div-а приближается к нижней части. Это реализовано только на статьях и картинках: когда их достаточно много, то во время  первоначальной загрузки они не все загружаются, а только часть, потом порциями подгружаются при прокрутке.

Для загрузки картинок и музыки я использовал Uploadify и File Ajax Upload соответственно. Работу с ними я уже описывал раньше.

Работа рельс ничем не отличается от указанных выше статей. Единственное, на что стоит обратить внимание, — обмен между Spine.js и рельсами осуществляется при помощи JSON. Никакой разметки, стилей по сети не гоняется, только данные.

Ну вот и всё, если вкратце. Как обычно, исходники можно найти на Github-e.

Единственное, на что хочу обратить внимание, — это то, что на Heroku, как я думаю, не разрешают хранить картинки и музыку, так что не удивляйтесь битым ссылкам, просто удаляйте и пытайтесь загрузить что-то своё. В общем-то, это и не удивительно на бесплатном аккаунте (-:

Ах, да, забыл сказать, что я не стал убирать из кода команды @log(), что все желающие могли в консоли браузера посмотреть, что и когда выполняется.

Июль 16th, 2012 by none | Один комментарий

Ckeditor на Heroku и вообще на production

В прошлой статье я рассказал о том, как подружить Ckeditor и elFinder. Всё работало хорошо, пока я запускал у себя на локальной машине. Проблемы начались, когда я попытался сделать тестовое приложение на Heroku. Скажу прямо: ничего не заработало (-: Пришлось нырнуть в глубины Интернета и найти следующее решение.

1. Перенёс всю директорию Ckeditor в  vendor/assets/javascripts/ckeditor.

2. В файле production.rb добавил следующее: config.assets.precompile += ['ckeditor/*']

3. В application.html.erb добавил var CKEDITOR_BASEPATH = ‘/assets/ckeditor/’; до того, как будет осуществлена загрузка application.js.

4. В application.js добавил *= require ckeditor/ckeditor

После этого всё заработало и у меня на компе, и на сервере Heroku. Единственное, что стоит ещё сделать перед развёртыванием на Heroku, — это поменять sqlite3 на pg в Gemfile.

Результат можно увидеть на http://ckeditor-elfinder.herokuapp.com/

Все благодарности вот сюда.

Июнь 19th, 2012 by none | Комментариев нет

Установка Ckeditor и elFinder в Rails 3.2.6

Как-то давно уже была запись, рассказывающая об установке Ckeditor-а в Rails. Правда в тот раз рассказывалось, как установить его при помощи gem-а. Да, всё прекрасно до тех пор, пока нам не нужно внести какие-то настройки, так сказать, кастомизировать редактор. Слово-то какое (-: Сколько ни читал сайт данного гема — ничего так и не заработало. После этого плюнул, скачал просто архив с официального сайта и добавил в приложение. Это оказалось значительно проще. А вот тут уже как хочу, так и верчу редактор (-:

Впрочем, работающий пример приложения можно посмотреть опять же на Github-e. Вернее исходники приложения (-:

Смысл всего приложения сводится к тому, что когда Вы хотите добавить картинку в поле редактора, то для просмотра доступных файлов на сервере вызывается elFinder. Поэтому всё выглядит вполне прилично.

Честно признаюсь: большинство настроек найдено в безграничных просторах Интернета. Касаются они в основном того, как подружить Ckeditor и elFinder. Вряд ли имеет смысл приводить весь код — это Вы сами можете посмотреть в исходниках. Если будут вопросы — спрашивайте в комментах.

Работающий вариант можно посмотреть на Heroku.

Июнь 19th, 2012 by none | Комментариев нет