Pull to refresh

Введение в SproutCore, часть первая

JavaScript *
В последнее время появилось много статей о JavaScript MVC фреймворках. Видимо есть потребность в подобных инструментах. Мое внимание привлек продукт под названием SproutCore. Обнаружив на скудное наличие информации на русском языке я решил перевести для себя ряд руководств с официального сайта фреймворка. Поделюсь с сообществом первым из них.

После прочтения этого руководства, вы сможете:
  • Использовать шаблоны SproutCore, для описания внешнего вида приложения;
  • Обрабатывать события внутри представления;
  • Использовать привязки(bindings) для обновления представления, после изменения состояния модели.


Вы узнаете все это во время создания приложения Todo-лист.

Исходный код этого приложения доступен на Github. Также есть скринкаст.

1 Установка SproutCore

Предполагается что у Вас уже установлен SproutCore, в противном случае Вам нужно его установить.
Можно скачать инсталлятор с официального сайта SproutCore, а можно установить с помощью RubyGems командной gem install sproutcore.

Кстати SproutCore написан на ruby так что инсталятор все равно Вам установит интерпретатор ruby. После окончания разработки можно сделать билд приложения, тогда ruby будет уже не нужен, но об этом в другой раз.

Я устанавливал с помощью RubyGems, проблем не было.

2 Создание нового приложения

Для начала сгенерируем HTML-приложение командой:

$ sc-init todos --template

Данная утилита создаст несколько файлов в директории apps/todos.

apps/
&nbsp&nbsptodos/
&nbsp&nbsp&nbsp&nbsp todos.js – файл JavaScript, в котором определены ваши модели, представления и контроллеры.
&nbsp&nbsp&nbsp&nbsp resources/
&nbsp&nbsp&nbsp&nbsp templates/ – тут содержатся шаблоны приложения.
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsptodos.handlebars – основной шаблон приложения.
&nbsp&nbsp&nbsp&nbsp images/ – тут содержатся изображения.
&nbsp&nbsp&nbsp&nbsp stylesheets/ – а тут стили.
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsptodos.css – основная таблица стилей для приложения.
&nbsp&nbsptests/
Buildfile – Содержит команды для сборки приложения. В большинстве случаев можно использовать файл по умолчанию.
README – Описание проекта.

Если открыть файл apps/todos/todos.js то увидим следующее
Todos = SC.Application.create();
 
SC.ready(function() {
  Todos.mainPane = SC.TemplatePane.append({
    layerId: "todos",
    templateName: "todos"
  });
});


Этот код создает пространство имен для приложения (в данном случае «Todos» ), и добавляет новую панель.
Панель отвечает за делегирования событий и размещение ваших шаблонов в DOM, но об этом позже.

Если при генерации приложения имя содержит символы в верхнем регистре, например ToDos то пространство имен будет называться ToDos,
а имена директорий будут содержать нижние подчеркивания (в приведенном примере apps/to_dos).

3 Объявление модели

Мы создаем todo лист, соответственно пользователи должны иметь возможность добавлять задачи в список, а также отмечать выполненные задачи.
Определим модель в качестве нового подкласса SC.Object.
Теперь файл apps/todos/todos.js выглядит следующим образом:

Todos = SC.Application.create();
 
Todos.Todo = SC.Object.extend({
  title: null,
  isDone: false
});
 
SC.ready(function() {
  Todos.mainPane = SC.TemplatePane.append({
    layerId: 'todos',
    templateName: 'todos'
  });
});

Обратите внимание, что определение модели должно следовать после создания объекта Todos.
Теперь мы имеем класс модели с двумя свойствами title — string и isDone — boolean.

4 Управление моделью с помощью контроллера

Теперь когда данные описаны с помощью модели, создадим контроллер для управления ими. Так как нам необходим упорядоченный список задач, будем использовать экземпляр класса SC.ArrayController.

В конец файла apps/todos/todos.js добавим:
Todos.todoListController = SC.ArrayController.create({
  // Инициализация контроллера массива с пустым массивом.
  content: []
}); 

В MVC фреймворках, каким является SproutCore, контроллер представляет собой мост между моделью (отвечающей за работу с данными) и представлением (отвечающим за отображение данных).

Теперь у нас есть контроллер без данных, добавим к нему метод создания новой задачи. Обновим предыдущий код в файле apps/todos/todos.js:
Todos.todoListController = SC.ArrayController.create({
  // Инициализация контроллера массива с пустым массивом.
  content: [],
 
  // Создание новой задачи с переданным названием, и добавление ее в массив.
  createTodo: function(title) {
    var todo = Todos.Todo.create({ title: title });
    this.pushObject(todo);
  }
});

SC.ArrayController действует как прокси к содержимому массива. SproutCore будет применять все изменения внесенные в ArrayController непосредственно к содержимому массива.

5 Создание новой задачи при помощи текстового поля.

Для этого примера был подготовлен CSS файл, который можно скачать тут и
заменить им пустой файл, сгенерированный утилитой создания приложения, находящийся по адресу apps/todos/resources/stylesheets/todos.css.

У нас есть модель и настроенный на работу с ней контроллер, теперь приступим к самому интересному — создание пользовательского интерфейса. Для начала создадим текстовое поле, в которое пользователь сможет ввести название создаваемой задачи.

Для описания шаблонов пользовательского интерфейса в SproutCore используется Handlebars, который был несколько расширен для большего удобства работы.

Чтобы начать работать с представлениями откроем файл resources/templates/todos.handlebars.
В нем содержится следующее:
<h1>Welcome to SproutCore!</h1>

Заменим содержимое на:
<h1>Todos</h1>
<input id="new-todo" type="text" placeholder="что нужно сделать?" >

Получить дополнительную информацию об использовании Handlebars можно тут. Чтобы узнать больше об использовании Handlebars со SproutCore, почитайте тут.

Теперь когда мы имеем модель, контроллер и представление, самое время открыть приложение в браузере и посмотреть как оно выглядит. Для упрощения процесса разработки есть утилита sc-server. Эту команду нужно выполнить из директории в которой размещено приложение.
$ sc-server

Starting server at 0.0.0.0:4020 in debug mode
To quit sc-server, press Control-C
>> Thin web server (v1.2.1 codename Bat-Shit Crazy)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:4020, CTRL+C to stop

Откройте веб-браузер и перейдите к http://localhost:4020/todos. Полюбуйтесь на свое творение. После того как мы убедились что приложение работает, пришло время рассказать SproutCore как обрабатывать события тега . Когда пользователь введет название задачи и нажмет Enter, мы создадим новую задачу и добавим ее в массив задач.

В SproutCore представление отвечает за обновление DOM дерева и обработку событий. Кромое того имеется возможность буферизовать изменения в DOM для повышения производительности, и поддерживать универсальную кроссплатформенную обработку событий. Всякий раз когда необходимо отобразить
динамической контент, нужно использовать объект view.

Добавим следующий код в файл apps/todos/todos.js перед вызовом функции SC.ready:
Todos.CreateTodoView = SC.TextField.extend({
  insertNewline: function() {
    var value = this.get('value');
 
    if (value) {
      Todos.todoListController.createTodo(value);
      this.set('value', '');
    }
  }
});

Когда CreateTodoView содержит в себе текстовое поле, мы создаем подкласс SC.TextField, который предоставляет несколько удобств для работы с текстовыми полями. Например, Вы можете получить доступ к значениям свойств и отвечать на запросы событий более высокого уровня, таких как insertNewline,
когда пользователь нажмет Enter. Теперь, когда мы определили наше представление, мы должны включить его в шаблон.

внесем изменения в файл apps/todos/resources/templates/todos.handlebars:
<h1>Todos</h1>
{{#view Todos.CreateTodoView}}
<input id="new-todo" type="text" placeholder="что нужно сделать?" />
{{/view}}

#view это вспомогательный блок Handlebars (помощник или helper — кому как привычнее), который связывает SC.TemplateView и элементы HTML. Это означает, что поведение, описанное в представлении (например, обработка событий) будет ассоцировано с элементами HTML внутри блока #view.

Теперь, когда у нас есть интерфейс создания новых задач, создадим интерфейс для их отображения.
Будем использовать блок #collection для отображения списка. #collection создаст экземпляр SC.TemplateCollectionView, который отобразит каждый елемент списка в соответствии с HTML кодом внутри блока #collection.

Добавим в конец файла apps/todos/resources/templates/todos.handlebars следующий код:
{{#collection SC.TemplateCollectionView contentBinding="Todos.todoListController"}}
  {{content.title}}
{{/collection}}

Мы объявили привязку collection к содержимому todoListController.
Теперь каждый элемент массива будет отображен в соответствии с шаблоном {{content.title}}.

Обратите внимание, для того чтобы создать привязку(binding) необходимо объявить свойство имя которого заканчивется на Binding. В нашем случае свойство content будет привязано к Todos.todoListController и все изменения свойства content будут вызывать изменения в Todos.todoListController.

Отличное время посмотреть на наше приложение http://localhost:4020/todos в Вашем браузере. Оно выглядит так же, как и раньше, но если ввести в текстовое поле название задачи и нажать Enter, она автоматически добавится в список под текстовым полем.

Привязки или Bindings — это основной инструмент для работы данными в SproutCore. Они делают всю грязную работу по синхронизации данных и отображения.

6 Отметка о выполнении задачи

У нас есть возможность добавлять задачи, но нет возможность отмечать их выполнение. Давайте добавим эту возможность.

Первое что мы сделаем — это добавим чекбокс к каждому элементу списка задач. Как уже говорилось ранее, чтобы обрабатывать события (например пользовательский ввод) необходимо применить объект view к той части HTML в которой нужна эта обработка. В нашем случае мы добавляем чекбокс и хотим получать уведомления каждый раз, когда пользователь изменит его статус. Помните, что мы можем связать представление и HTML содержимое при помощи хелпера #view. В тоже время мы можем ссылаться
на конкретные объекты из элементов HTML, отвечающих за отображение данных с помощью хелпера view (обратите внимание на отсутствие #). Например можно создать сложное представление
которое будет использоваться несколько раз. В этом случае нет необходимости обновлять все шаблоны, когда один из элементов нуждается в обновлении.

Заменим предыдущий код в файле apps/todos/resources/templates/todos.handlebars на следующий:
{{#collection SC.TemplateCollectionView contentBinding="Todos.todoListController"}}
  {{view Todos.MarkDoneView}}
{{/collection}}

Теперь опишем Todos.MarkDoneView на который будем ссылаться в шаблоне. Когда наше представление содержит чекбокс, мы объявляем подкласс SC.Checkbox, которые передает в представление свойства value и title.

SproutCore связывает обработчик события change и обновляет значение когда обработчик завершит свою работу. В разных браузерах эта задача может реализовываться по разному, но SproutCore избавит Вас от этих проблем.

Для каждого элемента списка SC.TemplateCollectionView создадим дочернее представление, чье свойство content будет содержать объект view. В нашем случае это будет дочернее представление для каждой задачи, а значение свойства content будет привязано к содержимому конкретной задачи.

Это позволит нам легко связать свойства чекбокса и свойства объекта. В нашем случае мы привязем свойство value(чекбокса) со свойством isDone(объекта) и свойство title(чекбокса) со свойством title(объекта).

В файл apps/todos/todos.js добавим следующий код, перед вызовом функции SC.ready:
Todos.MarkDoneView = SC.Checkbox.extend({
  titleBinding: '.parentView.content.title',
  valueBinding: '.parentView.content.isDone'
});

Для придания списку задач уникального внешнего вида, в CSS файле содержатся разные классы для активной и выполненной задач. Давайте свяжем свойство class(чекбокса) и свойство isDone(объекта).

Заменим код отображения списка задач в файле apps/todos/resources/templates/todos.handlebars на следующий:
{{#collection SC.TemplateCollectionView contentBinding="Todos.todoListController" itemClassBinding="content.isDone"}}
  {{view Todos.MarkDoneView}}
{{/collection}}

Каждый элемент получит класс is-done, если свойство isDone ассоциированного объекта будет иметь значение true. SproutCore автоматически сгенерирует имя класса исходя из имени свойства.

Каждое представление имеет ряд свойств, таких как ID, class и classBinding. Хелпер collection позволяет добавить к элементам любое из этих свойств которые также будут применены ко всем дочерним представлениям. Например если вы определяете свойство itemClass в collection, то каждый из элементов будет иметь свойство class.

Теперь перезагрузите приложение в браузере и попробуйте отметить задачу как выполненную, текст задачи сразу станет перечеркнутым. При изменении свойства isDone любого элемента массива, соответсвующий элемент HTML изменит внешний вид. SproutCore сделает это автоматически.

7 Узнаем больше

Мы можем добавлять задачи и отмечать выполненные. А можем ли мы еще извлечь какую-либо информацию из списка? Давайте отобразим на вершине списка количество невыполненных задач.

Откроем apps/todos/resources/templates/todos.handlebars и добавим новый элемент view после Todos.CreateTodoView.
{{#view Todos.StatsView id="stats"}}
  {{displayRemaining}}
{{/view}}

Выражение {{displayRemaining}} позволит автоматически обновлять DOM когда свойство displayRemaining представления Todos.StatsView будет изменяться.

Добавим описание представления в файл apps/todos/todos.js перед вызовом SC.ready:
Todos.StatsView = SC.TemplateView.extend({
  remainingBinding: 'Todos.todoListController.remaining',
 
  displayRemaining: function() {
    var remaining = this.get('remaining');
    return (remaining === 1 ? remaining +" задача не выполнена" : "количество не выполненных задач: "+remaining);
  }.property('remaining')
});

displayRemaining содержит строку множественном числе, исходя из количества оставшихся задач. Вот еще один основной инструмент SproutCore в действии, он называется вычисляемое свойство. Вычисляемое свойство — это свойство, значение которого определяется с помощью функции.

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

в файле apps/todos/todos.js обновим код следующим образом:
Todos.todoListController = SC.ArrayController.create({
 
  // ...
 
  remaining: function() {
    return this.filterProperty('isDone', false).get('length');
  }.property('@each.isDone')
});

В данном случае мы используем ключ зависимости each. Таким образом мы объявляем зависимость от изменения свойства isDone любого элемента массива. Свойства с ключом each также обновляются при добавлении или удалении элементов. Объявлять ключи зависимости необходимо, т.к. SproutCore использует их для обновления привязок. Вот как все работает.

bindings

8 Удаление завершенных задач

При работе с приложением на понадобится периодически удалять выполненные задачи. Как Вы поняли, мы хотим внести изменения в todoListController и чтобы SproutCore автоматически отобразил эти изменения.
Для этого добавим метод clearCompletedTodos в контроллер.

в файле apps/todos/todos.js обновим код на следующий:
Todos.todoListController = SC.ArrayController.create({
 
  // ...
 
  clearCompletedTodos: function() {
    this.filterProperty('isDone', true).forEach(this.removeObject, this);
  }
});

Теперь добавим кнопку в шаблон.
Откроем apps/todos/resources/templates/todos.handlebars и добавим кнопку для StatsViews:
{{#view Todos.StatsView id="stats"}}
  {{#view SC.Button classBinding="isActive" target="Todos.todoListController" action="clearCompletedTodos"}}
    Удалить выполненные задачи
  {{/view}}
  {{displayRemaining}}.
{{/view}}

Мы определили экземпляр SC.Button, который вызывает метод объекта при нажатии. В нашем случае мы создали кнопку для вызова метода clearCompletedTodos объекта Todos.todoListController. Также мы объявили, что при нажатии кнопки необходимо добавить к ней класс is-active. Это позволит изменить вид кнопки на нажатую.

Теперь попробуйте загрузить приложение, добавить задачи, отметить ряд из них как завершенные и удалить их из списка. Как видите — все работает.

9 Отметить все

Допустим мы решили отметить все задачи как выполненные. Теперь это не сложно, т.к. основная работа уже сделана.

Добавим метод allAreDone в todoListController. Обновим код в файле apps/todos/todos.js на следующий:
Todos.todoListController = SC.ArrayController.create({
 
  // ...
 
  clearCompletedTodos: function() {
    this.filterProperty('isDone', true).forEach(this.removeObject, this);
  }
});

SproutCore имеет много перечислимых помощников. everyProperty(‘isDone’, true) возвращает true, если каждый элемент в массиве имеет свойство isDone равное true. Вы можете узнать больше прочитав руководство.

Далее, мы создадим флажок, чтобы отметить все задачи выполненными и связать его значение со свойством контроллера. В файле apps/todos/resources/templates/todos.handlebars сразу после Todos.StatsView добавим:
{{view SC.Checkbox class="mark-all-done" title="выполнить все" valueBinding="Todos.todoListController.allAreDone"}}

Если сейчас вы загрузите приложение и попробуете отметить все задачи как выполненные то увидите что флажок «выполнить все» автоматически изменил состояние, однако он не работает в обратном направлении. При нажатии на флажок «выполнить все», никаких изменений в списке задач не происходит. Чтобы заставить флажок работать как надо, перепишем метод allAreDone в todoListController.

Обновим код в файле apps/todos/todos.js на следующий:
Todos.todoListController = SC.ArrayController.create({
 
  // ...
 
  allAreDone: function(key, value) {
    if (value !== undefined) {
      this.setEach('isDone', value);
 
      return value;
    } else {
      return this.get('length') && this.everyProperty('isDone', true);
    }
  }.property('@each.isDone')
});

При попытке присвоить вычисляемому свойству значение, функция свойства вызвается с праметрами key и value. Это мы и используем в нашем примере, при щелчке на флажок, SproutCore пытается изменить свойство allAreDone соответственно мы можем перехватить этот вызов.

Вот теперь приложение работает так как мы этого хотели.

10 Стили для мобильных устройств

Наше приложение можно считать полнофункциональным на всех типах современных мобильных устройств. Давайте оптимизируем его для различных разрешений экрана. скачайте файл CSS тут и сохраните под именем
apps/todos/resources/stylesheets/todos_mobile.css. Теперь откройте приложение с помощью мобильного устройства и вы увидите тоже приложение только оформленное для мобильного устройства.

Успехов в освоении SproutCore.
Оригинал статьи.
Tags:
Hubs:
Total votes 28: ↑25 and ↓3 +22
Views 2K
Comments Comments 19