Я рекомендую следующую структуру для одного модуля
views/
base.coffee
models/
base.coffee
collections/
items.coffee
router/
router.coffee
templates/
base.html
module_name.coffee
module_name.coffee— основной файл вызова модуля, который возвращает функцию-конструкторviews/base.coffee— основная вьюшка, внутри которой инициализируются подмодулиmodels/base.coffee— основная модель. Нужна, чтобы получить данные с сервера и передать подмодулям данные и сохранять данные всех подмодулей.
Файлы модуля находятся в одном пространстве имен.
Этот объект хранится в window, так легче брать нужные части из любого места в приложении (например, компиляция шаблона window.ModuleName.Templates.Base()).
window.ModuleName = ModuleName =
Views:
Base: BaseView
Models:
Base: BaseModel
Collections: {}
Templates:
Base: Handlebars.compile BaseTmpl
Я считаю это анти-паттерном, но другой вариант в requirejs я не пробовал.
App - функция-конструктор, запускающая модуль (инициализирует основную вью и данные).
Возвращает вьюшку/модель/коллекцию и метод close.
class ModuleName.App
constructor: ->
@model = new ModuleName.Models.Base()
@view = new ModuleName.Views.Base
model: @model
return {
# data
model: @model
view: @view
# methods
close: @close
}
close: ->
@view.close?()
@view.remove?()Настоятельно рекомендую инициализировать и добавлять модуль на страницу следующий способом.
module = new ModuleName.App()
$( module.view.render().el ).appendTo( @$el )Метод render внутри модуля не должен добавлять html-код куда-либо на страницу самостоятельно.
Самый минимальный набор для initialize метода.
initialize: (options) ->
Cocktail.mixin( @, Mixins.Views.Base, Mixins.Common ) # add Mixins
@setOptions(options)
@initEventsListeners()
@initStickitBindings()
@addValidation()- Подключили миксины
- Начали слушать события
- Привязали модель
- Добавили валидацию
Старайтесь не вызывать render при инициализации вьюшки, а только в месте инициализации модуля.
Метод для инициализации прослушивания событий.
Раньше я использовал названия addEventListeners и addListeners, но initEventsListeners подходит больше.
initEventsListeners: ->
@listenTo @, 'rendered', =>
@cacheDOM()
@attachDOMEvents()
@stickit()Раньше использовал addStickit, рекомендую initStickitBindings.
Внутри только объект @bindings
initStickitBindings: ->
@bindings =
'[name="title"]': 'title'
@Внутри метода render происходит только рендер шаблона @$el и вызов события о завершении.
Так вьюшка узнает, что ей можно связывать модель и кешировать DOM.
Хорошо
render: ->
@$el.html ModuleName.Templates.Base()
@trigger 'rendered'
@Плохо
Метод вызывает побочные методы, что усложняет чтение кода.
render: ->
@$el.html ModuleName.Templates.Base(response.data)
@stickit()
@cacheDOM()
@Плохо
Запросы к серверу нужно делать внутри модели или коллекции и потом вызывать рендеринг или перерендеринг.
render: ->
$.when( @getData() )
.then
(response) =>
@$el.html ModuleName.Templates.Base(response.data)
@stickit()
@cacheDOM()
@Кэшируем jquery-объекты в переменные для быстрого доступа.
Я использую @$el в качестве пространсва имен (например, @$el.$list).
cacheDOM: ->
@$el.$list = @$('.module--list')
@$el.$button = @$('module--button')
@Разделяем события приложения и события DOM.
attachDOMEvents: ->
@$el.$button.on "click", (e) =>
console.log e.targetОсновные методы работы с данными (fetch, save и тд.) я переписывал. С самого начала бекенд не был заточен под архитектуру бэкбона, но сейчас ситуация стала лучше и я бы рекомендовал использовать подход бэкбона.
Вместо стандартного метода fetch я использовал свой метод getData. Cейчас я считаю, что лучше переписывать поведение fetch, так в любой момент можно будет отказаться от своей реализации в пользу нативной без замены названия.
Часто, внутри большого модуля есть подмодули. Каждый подмодуль независим от родителя и имеет свои методы получения данных с сервера. Чтобы избежать большого количества запросов, я рекомендую делать 1 запрос у модуля родителя и передавать данные в подмодули.
Иницализация и передача данных
module = new Module.App({
data: @model.toJSON()
})Потом эти данные передаем в модель и уже там вызываем fetch с ними.
# переписанный метод fetch, который вызывается при инициализации модели/коллекции
fetch: (data) ->
if data?
# записываем данные в модель или коллекцию
else
# делаем аякс запрос или вызываем стандартный fetchОт модуля к модулю используются одни и те же функции: удалить вьюшку с подвьюшками, сделать опции "глобальными", инициализировать валидацию и тд. Чтобы все это не копировать от файла к файлу, нужно использовать миксины. Миксин — класс/объект с набором методов, которые добавляются в модуль для расширения его возможостей.
Для работы с миксинами в Backbone я использую плагин Cocktail. В использовании он до безобразия прост:
Cocktail.mixin(@, Mixins.Common, Mixins.Views.Base)Я разбил файл на несколько частей: Common, Model, View. Нет смысла подключать в View методы для Model и наоборот. Общий набор методов я вынес в Common.
Сейчас в common у меня только 1 метод setOptions. Нужен для того, чтобы все опции, который были переданы в объекте стали доступны через this
Инициализирую я его в самом начале модуля, в initialize.
Пример набора опций:
options = {
model: new Backbone.Model(),
parentView: parentView
}После @setOptions(options) мы получим @model и @parentView.
Views по идее должен делится на несколько подкатегорий, сейчас это только Base — обязательный набор любой вью.
Массив в который добавляем все подвью, нужен для того, чтобы в будущем легко убрать все сущности.
Метод, добавляющий валидацию во вьюшку. Для валидации я использую плагин Backbone.Validation
Метод принимает объект options, в котором можно передать:
-
selector- для поиска элемента по заданному аттрибуту. По умолчанию этоname($('name="model_field"')).
Например,selector: 'data-selector'будет искать так$('data-selector="model_field"').
Поиск элемента расчитан максимум на 1 вложенность'one.two'=name="one[two]" -
valid/invalid— переписывают поведение стандартных методов для поиска и вывода ошибок.
Метод, который отвечает за удаление подвьюшек в массиве subViews (для поддержки старого кода он обходит массив instances), удаление текущей вью и модели.
Добавляет возможность удалить конкретную вью из определенного массива.
Методы для показа/чистки ошибок серверной валидации.
showErrors работает также как backbone.validation. В аргументах принимает errors и options. options используется только для задания кастомного селектора как и в addValidation.
- Я бы разобрался с рекурсивными зависимостями в require.js. Это позволит избавиться от захламления
windowмодулями. - Подстроить бекенд под бэкбон и пользоваться родными методами на полную катушку
- Улучшил архитектуру приложения с применением техник и паттернов из книги Эдди Османи
- Unit-тестирование
- Перечитал бы книгу внимательнее и до конца, многие вещи я придумывал сам или вычитывал откуда-то
- Не забывать про Backbone zombie views