С ростом сложности JavaScript-приложений фреймворки абсолютно необходимы, если вам необходимо соответствовать реальным срокам. В этой статье мы рассмотрим новую платформу Spine Mobile, которую вы можете использовать для создания великолепных мобильных приложений в CoffeeScript и HTML, не жертвуя при этом отличным пользовательским интерфейсом собственных приложений.
Заинтересованы? Давайте начнем!
Что такое позвоночник?
Spine — это облегченная JavaScript MVC-инфраструктура, которую можно использовать для создания великолепных клиентских веб-приложений. Spine Mobile — это расширение Spine, специально разработанное для создания мобильных веб-приложений.
Списки задач и менеджеры контактов составляют десятки, поэтому давайте сделаем что-то другое в этом уроке и создадим регистратор тренировок. Пользователи смогут записывать тренировки, в том числе их тип, время и продолжительность. Затем у нас будет простой список, показывающий все записанные тренировки. Также есть много возможностей для дальнейшего развития, таких как социальные функции и графики.
Вы можете посмотреть живую демонстрацию готового приложения здесь , а также весь исходный код примера на GitHub . Я настоятельно рекомендую вам следовать этому руководству, используя исходный код, по крайней мере на начальном этапе, поскольку это поможет вам начать работу, если вы новичок в Spine .
Если вам когда-нибудь понадобится более подробная информация о Spine Mobile , обратитесь к подробным документам или списку рассылки . Для краткого введения в CoffeeScript, посмотрите Небольшую Книгу на CoffeeScript .
Шаг 1: Настройка
Перво- spine.app
, нам нужно установить некоторые модули npm, а именно spine.app
и hem
. Первый генерирует приложения Spine, а второй действует как менеджер зависимостей. Если они еще не установлены, вам нужно скачать Node и npm (оба сайта имеют отличные руководства по установке). Затем запустите:
1
|
npm install -g spine.app hem
|
Теперь для создания нашего мобильного приложения Spine:
1
2
|
spine mobile spine.workout
cd spine.workout
|
Просмотрите структуру каталогов и исходные файлы, которые Spine создал для вас.
1
2
3
4
5
6
7
8
|
$ ls -la
.gitignore
Procfile
app
css
package.json
public
slug.json
|
Каталог app
— это место, где живет вся логика приложения, например его модели и контроллеры. public
каталог просто полон статических ресурсов, и именно здесь наше приложение будет скомпилировано. Это public
каталог, который используется в качестве нашего мобильного приложения.
Наше новое приложение также имеет некоторые локальные зависимости (указанные в package.json
), поэтому давайте продолжим и установим их сейчас:
1
|
npm install .
|
Они загрузят и установят локальные зависимости в папку с именем node_modules
(которой не должно быть в вашем контроле исходного кода).
Последнее, что нам нужно сделать, это запустить сервер разработки Spine, Hem .
1
|
hem server
|
Hem компилирует файлы CoffeeScript, разрешает зависимости, упаковывает исходный код в модули CommonJS и объединяет все в один файл JavaScript,
application.js
.
Теперь, когда сервер работает, мы можем перейти к нашему начальному приложению по адресу http: // localhost: 9294 .
Шаг 2: Модели
В средах MVC модели хранят данные вашего приложения и любую логику, связанную с этими данными. Вот и все — модели не должны больше ничего знать об остальной части вашего приложения; они должны быть полностью отделены друг от друга.
Наше приложение должно отслеживать тренировки, записывать тип тренировки, сколько времени это заняло и когда это произошло.
Итак, давайте продолжим и создадим новую модель, выполнив следующее:
1
|
spine model workout
|
Это создаст модель с именем: app/models/workout.coffee
. Давайте откроем этот файл и реализуем нашу модель Workout
, заменив содержимое следующим:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
Spine = require(‘spine’)
class Workout extends Spine.Model
@configure ‘Workout’, ‘type’, ‘minutes’, ‘date’
@extend Spine.Model.Local
load: ->
super
@date = new Date(Date.parse(@date))
validate: ->
return ‘type required’ unless @type
return ‘minutes required’ unless @minutes
return ‘date required’ unless @date
module.exports = Workout
|
Итак, это много кода без каких-либо объяснений; давайте углубимся в это и посмотрим на детали.
Прежде всего, мы создаем класс Workout
унаследованный от Spine.Model
, и вызываем @configure()
для установки имени и атрибутов модели:
1
2
|
class Workout extends Spine.Model
@configure ‘Workout’, ‘type’, ‘minutes’, ‘date’
|
Все идет нормально. Теперь мы собираемся расширить модель с помощью модуля с именем Spine.Model.Local
. Это гарантирует, что данные модели сохраняются между перезагрузками страницы с использованием локального хранилища HTML5.
1
|
@extend Spine.Model.Local
|
Теперь следующая функция, load()
, нуждается в небольшом объяснении. load()
вызывается несколько раз внутри Spine, особенно когда записи сериализуются и десериализуются. Наша проблема заключается в том, что мы сериализуем записи в JSON, сохраняя их в локальном хранилище HTML5. Однако JSON не имеет собственного типа Date, а просто сериализует его в строку. Это проблема, поскольку мы хотим, чтобы атрибут date
всегда был датой JavaScript. Переопределение load()
, гарантирующее, что атрибут date является датой JavaScript, решит эту проблему.
1
2
3
|
load: ->
super
@date = new Date(Date.parse(@date))
|
Наконец, у нас есть довольно простая функция validate()
. В Spine проверка модели завершается неудачей, если функция validate()
возвращает что-либо «правдивое» — то есть строку. Здесь мы возвращаем "type required"
если атрибут type
существует. Другими словами, мы проверяем наличие атрибутов type
, minutes
и date
.
1
2
3
4
|
validate: ->
return ‘type required’ unless @type
return ‘minutes required’ unless @minutes
return ‘date required’ unless @date
|
Вы заметите, что последняя строка в модели — это присвоение module.exports
. Это предоставляет класс Workout
, так что другие файлы могут требовать его. Приложения Spine используют модули CommonJS , для чего требуется явный модуль и экспорт свойств.
Модель WorkoutType
Единственная другая модель, которая нам понадобится, — это модель WorkoutType
. Это просто базовый класс, содержащий список допустимых типов тренировок. Как и прежде, нам нужно сначала сгенерировать модель:
1
|
spine model workout_type
|
И тогда его содержимое представляет собой простой класс, содержащий массив допустимых типов тренировки:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
class WorkoutType
@types: [
‘running’
‘jogging’
‘walking’
‘swimming’
‘tennis’
‘squash’
‘handstands’
‘skipping’
‘aerobics’
‘biking’
‘weights’
]
@all: ->
@types
module.exports = WorkoutType
|
Для получения дополнительной информации о моделях см. Руководство по моделям позвоночника .
Шаг 3: Основные контроллеры
В приложениях Spine контроллеры являются связующим звеном между моделями и представлениями. Они добавляют прослушиватели событий в представление, извлекают данные из модели и отображают шаблоны JavaScript.
Главное, что вам нужно знать о контроллерах Spine, это то, что все они ограничены одним элементом, свойством el
. Все, что делает контроллер за время своего существования, ограничено этим элементом; будь то добавление прослушивателей событий, реагирование на обратные вызовы событий, обновление HTML-элемента или извлечение данных формы.
Приложения Spine Mobile имеют один глобальный контроллер Stage
, который охватывает весь экран. Наше сгенерированное приложение уже содержит Stage
в app/index.coffee
, заменим его следующим:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
require(‘lib/setup’)
Spine = require(‘spine’)
{Stage} = require(‘spine.mobile’)
Workouts = require(‘controllers/workouts’)
class App extends Stage.Global
constructor: ->
super
# Instantiate our Workouts controller
new Workouts
# Setup some Route stuff
Spine.Route.setup(shim: true)
@navigate ‘/workouts’
module.exports = App
|
App
стадия App
станет первым экземпляром контроллера, отвечающим за настройку остальной части приложения. Как видите, он требует еще не определенного контроллера с именем Workouts
и создания экземпляров Workouts
в функции constructor
класса.
Другими словами, когда наше приложение запускается впервые, App
этап App
. Это, в свою очередь, создаст экземпляр нашего контроллера Workouts
, где будут все действия. Вы можете игнорировать все вещи маршрута в настоящее время.
Теперь давайте настроим вышеупомянутый контроллер Workouts
:
1
|
spine controller workouts
|
Новый контроллер Workouts
находится в app/controllers/workouts.coffee
. Этот контроллер будет находиться там, где живет большая часть нашего приложения, поэтому давайте начнем заполнять его, заменив его содержимое следующим:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
Spine = require(‘spine’)
{Panel} = require(‘spine.mobile’)
# Require models
Workout = require(‘models/workout’)
WorkoutType = require(‘models/workout_type’)
# To be implemented:
class WorkoutsList extends Panel
class WorkoutsCreate extends Panel
class Workouts extends Spine.Controller
constructor: ->
super
# Our application’s two Panels
@list = new WorkoutsList
@create = new WorkoutsCreate
# Setup some route stuff
@routes
‘/workouts’: (params) -> @list.active(params)
‘/workouts/create’: (params) -> @create.active(params)
# Fetch the initial workouts from local storage
Workout.fetch()
module.exports = Workouts
|
Опять же, давайте углубимся в это и объясним, что происходит. Во-первых, нам WorkoutType
две модели нашего приложения, Workout
и WorkoutType
:
1
2
3
|
# Require models
Workout = require(‘models/workout’)
WorkoutType = require(‘models/workout_type’)
|
Затем конструктор Workouts
настраивает несколько пока не реализованных Panel
, а затем несколько маршрутов, которые мы пока можем игнорировать. Наконец, Workout.fetch()
, Workout.fetch()
все сохраненные данные из локального хранилища .
Шаг 4: Список тренировок
Хорошо, теперь мы немного поработали с нашими контроллерами App
и Workouts
, но теперь самое интересное — панели.
Итак, наше приложение имеет два контроллера Panel
, представление списка и представление создания. Эти две панели относятся к основной сцене, которая обеспечивает их правильный вход и выход, показывая только одну панель за один раз.
Итак, давайте сначала определим наш контроллер WorkoutsList
в app/controllers/workouts.coffee
, который, как вы уже догадались, выведет список тренировок. Добавьте следующий код после операторов require
в workouts.coffee
перед определением контроллера Workouts
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class WorkoutsList extends Panel
title: ‘Workouts’
constructor: ->
super
# Add a button to the header
@addButton(‘Add’, @add)
# Bind the model to the view
Workout.bind(‘refresh change’, @render)
render: =>
# Fetch all workout records from the model
workouts = Workout.all()
# Render a template with the workout array
template = require(‘views/workouts’)(workouts)
# Replace the current element’s HTML with the template
@html(template)
add: ->
# Navigate to the ‘create’ controller, with a
# swipe transition out to the left
@navigate(‘/workouts/create’, trans: ‘right’)
|
Первое, что вы заметите, это то, что WorkoutsList
расширяет Panel
, класс, определенный в пакете spine.mobile
. Это гарантирует, что он наследует свойства Panel
, поэтому Stage
приложения может работать с ним.
Шаблон использует отличную библиотеку под названием Eco . Проверьте руководство просмотра для получения дополнительной информации о его синтаксисе. Достаточно сказать, что это синтаксис CoffeeScript, используя
нотация для отображения переменных шаблона на странице.
Тогда у нас есть свойство с названием title
. Это необязательный параметр, и он будет названием нашей панели.
В функции конструктора мы добавляем кнопку в заголовок панели, вызывая @addButton(title, callback)
. При касании это вызовет функцию класса add()
.
Наконец, мы добавляем привязку к двум событиям: обновление и изменение в модели Workout
. Всякий раз, когда модель изменяется, эти события запускаются, и вызывается наша функция callback render()
. render()
сначала извлекает все записи Workout
из базы данных, затем визуализирует шаблон, заменяя содержимое панели результатом.
Так что этот шаблон просто действует как функция. Все, что мы делаем, это выполняем эту функцию, передавая контекст шаблона, в результате получается визуализированный элемент DOM. Для получения дополнительной информации о том, как это работает, см. Руководство по просмотрам , в противном случае давайте нажмем и определим шаблон.
В app/views
создайте папку с названием workouts
которая будет содержать все наши шаблоны, связанные с контроллером Workouts
. Затем давайте создадим файл в app/views/workouts/index.jeco
содержащий:
1
2
3
4
5
|
<div class=»item»>
<span class=»type»><%= @type %>
<span class=»minutes»>for <%= @minutes %> mins
<span class=»date»>on <%= @date.toDateString() %>
</div>
|
Расширение .jeco
шаблона не является опечаткой, это расширение jQuery для библиотеки шаблонов Eco, предоставляемой Hem. Среди прочего, это позволяет нам связывать элементы с исходными данными шаблона, что будет полезно позже.
Конечным результатом является список тренировок, который выглядит следующим образом:
Очевидно, что если вы не создали никаких тренировок, список будет пустым. Мы можем создать тренировку программно, используя командную строку внутри консоли Web Inspector:
1
2
|
var Workout = require(‘models/workout’);
Workout.create({type: ‘handstands’, minutes: 5, date: Date.now()});
|
Шаг 5: Создание новых тренировок
Теперь последняя панель для определения — WorkoutsCreate
, которая будет содержать форму для создания новых тренировок. Это будет наш самый большой контроллер, но он должен быть довольно простым, теперь вы знакомы с API и терминологией.
Единственное новое добавление здесь — это добавление свойства elements
, которое является вспомогательным средством для сопоставления элементов DOM с переменными экземпляра. В приведенном ниже примере для элемента elements установлено значение {'form': 'form'}
, которое отображает
@form
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
class WorkoutsCreate extends Panel
title: ‘Add Workout’
elements:
‘form’: ‘form’
constructor: ->
super
@addButton(‘Back’, @back)
@addButton(‘Create’, @create)
# Render the view whenever this panel is activated,
# resetting the form
@bind ‘active’, @render()
render: ->
# Fetch all workout types
types = WorkoutType.all()
# Render the template, replacing the HTML
@html require(‘views/workouts/form’)(types: types)
create: ->
# Create new workout from form data
item = Workout.create(@formData())
# Navigate back to the list, if validation passed
@back() if item
# Navigate back to the list
back: ->
@form.blur()
@navigate(‘/workouts’, trans: ‘left’)
# Retrive form data as a object literal
formData: ->
type = @form.find(‘[name=type]’).val()
minutes = parseInt(@form.find(‘[name=minutes]’).val())
date = @form.find(‘[name=date]’)[0].valueAsDate
{type: type, minutes: minutes, date: date}
|
Итак, давайте разберем это по частям. Во-первых, в конструкторе WorkoutsCreate
мы добавляем на панель две кнопки: «Создать» и «Назад». Вы, вероятно, можете догадаться, что они собираются делать.
Далее мы привязываемся к активному событию панели, которое запускается всякий раз, когда панель отображается. Когда событие инициируется, вызывается функция render()
, заменяющая HTML элемента контроллера отображаемым шаблоном. Прикрепляя вызов render()
к активному событию, а не непосредственно в конструкторе, мы гарантируем, что форма сбрасывается при каждом переходе на панель.
Последняя часть панели — это функция create()
, в которой фактически создается наша запись Workout
. Мы используем formData()
для получения ввода пользователя, передавая его в Workout.create()
.
Теперь app/views/workouts/form.eco
шаблона app/views/workouts/form.eco
используемого в функции render()
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<form>
<label>
<span>Select type
<select name=»type» size=»1″ required>
<% for type in @types: %>
<option value=»<%= type %>»><%= type %></option>
<% end %>
</select>
</label>
<label>
<span>Select minutes
<select name=»minutes» size=»1″ required>
<option value=»5″>5 minutes</option>
<!— … —>
</select>
</label>
<label>
<span>Select date
<input name=»date» type=»date» required>
</label>
</form>
|
Вот и все для нашего приложения. Дайте ему крутиться и создайте несколько тренировок.
Шаг 6: Построить и развернуть
Последний шаг — создать приложение на диске и развернуть его. Мы можем сделать это используя Hem:
1
|
hem build
|
Это позволит сериализовать весь ваш JavaScript / CoffeeScript в один файл ( public/application.js
) и все ваши CSS / Stylus ( public/application.css
). Это необходимо сделать перед отправкой сайта на удаленный сервер, чтобы он мог обслуживаться статически.
Мы собираемся использовать Heroku для обслуживания нашего приложения, отличный вариант для обслуживания приложений Node.js и Rails, и у них есть щедрый бесплатный план. Вам нужно будет зарегистрировать учетную запись, если у вас ее еще нет, а также установить гем Heroku.
Теперь все, что нам нужно для развертывания нашего приложения, — это запустить несколько команд Heroku, чтобы развернуть наше приложение.
1
2
3
4
5
|
heroku create my-spine-app —stack cedar
git add .
git commit -m «first commit»
git push heroku master
heroku open
|
Вуаля! Теперь у вас есть привлекательное мобильное приложение, написанное на CoffeeScript, HTML5 и CSS3. Теперь у нас есть множество возможностей, таких как обертывание PhoneGap для доступа к API-интерфейсам телефона, настройка темы для телефонов Android или добавление автономной поддержки .
Следующие шаги
Может показаться, что многому научиться, но на самом деле мы рассмотрели большую часть API Spine в этом уроке. Почему бы не проверить обширную документацию и узнать немного больше о фреймворке?
Я уверен, что у вас есть много вопросов, поэтому не стесняйтесь задавать их в комментариях и большое спасибо за чтение! В противном случае, не забудьте обратиться к нашему дочернему сайту Mobiletuts + , чтобы найти лучшие учебники для мобильных устройств в Интернете!