Статьи

Начало работы с Spine Mobile

С ростом сложности JavaScript-приложений фреймворки абсолютно необходимы, если вам необходимо соответствовать реальным срокам. В этой статье мы рассмотрим новую платформу Spine Mobile, которую вы можете использовать для создания великолепных мобильных приложений в CoffeeScript и HTML, не жертвуя при этом отличным пользовательским интерфейсом собственных приложений.

Заинтересованы? Давайте начнем!


Spine — это облегченная JavaScript MVC-инфраструктура, которую можно использовать для создания великолепных клиентских веб-приложений. Spine Mobile — это расширение Spine, специально разработанное для создания мобильных веб-приложений.

Списки задач и менеджеры контактов составляют десятки, поэтому давайте сделаем что-то другое в этом уроке и создадим регистратор тренировок. Пользователи смогут записывать тренировки, в том числе их тип, время и продолжительность. Затем у нас будет простой список, показывающий все записанные тренировки. Также есть много возможностей для дальнейшего развития, таких как социальные функции и графики.

Вы можете посмотреть живую демонстрацию готового приложения здесь , а также весь исходный код примера на GitHub . Я настоятельно рекомендую вам следовать этому руководству, используя исходный код, по крайней мере на начальном этапе, поскольку это поможет вам начать работу, если вы новичок в Spine .

Если вам когда-нибудь понадобится более подробная информация о Spine Mobile , обратитесь к подробным документам или списку рассылки . Для краткого введения в CoffeeScript, посмотрите Небольшую Книгу на CoffeeScript .


Перво- 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 .


В средах 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 . Это просто базовый класс, содержащий список допустимых типов тренировок. Как и прежде, нам нужно сначала сгенерировать модель:

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

Для получения дополнительной информации о моделях см. Руководство по моделям позвоночника .


В приложениях 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() все сохраненные данные из локального хранилища .


Хорошо, теперь мы немного поработали с нашими контроллерами 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. Среди прочего, это позволяет нам связывать элементы с исходными данными шаблона, что будет полезно позже.

Конечным результатом является список тренировок, который выглядит следующим образом:

List

Очевидно, что если вы не создали никаких тренировок, список будет пустым. Мы можем создать тренировку программно, используя командную строку внутри консоли Web Inspector:

1
2
var Workout = require(‘models/workout’);
Workout.create({type: ‘handstands’, minutes: 5, date: Date.now()});

Теперь последняя панель для определения — 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>

Вот и все для нашего приложения. Дайте ему крутиться и создайте несколько тренировок.


Последний шаг — создать приложение на диске и развернуть его. Мы можем сделать это используя 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 + , чтобы найти лучшие учебники для мобильных устройств в Интернете!