Продолжим писать приложение на Ember.js. В прошлый раз мы сгенерировали каркас приложения. Сегодня рассмотрим основы типичного ember-приложения.
Сначала немного теории. Ember придерживается принципа MVC - это значит что в приложении есть модели, контроллеры и слой представления (в ember это handlebars шаблоны). Это достаточно распространненый сегодня паттерн. Но в ember есть важные отличия, которые нужно сразу понять.
В приложении есть объект Router, который сопоставляет урлу соответствующий ему шаблон или набор вложенных шаблонов. Как в прошлый раз мы просто добавили роут /hello
и шаблон hello.hbs
. В результате при переходе на этот урл мы видели то, что написали в шаблоне. А где же контроллер, спросите вы? Контроллеры в приложении отвечают за состояния и действия. Если нужно выполнить какое-нибудь действие при нажатии на кнопку, то этот код должен быть в контроллере. Так как наше приложение состояний не меняет, то и контроллеров мы не делали. Хотя ember неявно создал его за нас, это видно по логам в консоли браузера:
generated -> controller:hello
Если router отвечает за то, какой шаблон должен быть выбран для текущего урла, то route отвечает за то, какую модель нужно передать в шаблон, чтобы тот ее отрендерил. У нас пока никаких моделей нет, поэтому и роут мы тоже не создавали.
Теперь попрактикуемся. Давайте сделаем так, чтобы в нашем приложении можно было просматривать список постов и каждый пост в отдельности.
Начнем с модели. Создадим модель Post. Для этого, находясь в папке приложения, выполните команду:
ember generate model post
Как видите ember-cli сгенерировал для нас два файла - post.js и post-test.js. Тестов пока касаться не будем, откроем файл post.js. Там мы увидим следующее:
import DS from 'ember-data';
export default DS.Model.extend({
});
Здесь мы видим довольно необычный синтаксис с импортом и экспортом. Если ES6 modules вам ни о чем не говорит, то почитайте статью http://frontender.info/es6-modules/, там все подробно рассказано. Благодаря ember-cli, который использует ES6 module transpiler для того чтобы преобразовать ES6 модули в require.js мы можем писать в стиле ES6 уже сейчас, даже когда браузеры еще их не поддерживают.
В качестве базового класса для наших моделей мы будем использовать DS.Model из пакета Ember Data. Давайте опишем поля:
export default DS.Model.extend({
title: DS.attr(),
body: DS.attr()
});
Давайте теперь добавим в приложение урл /posts
по которому будем показывать список постов.
Для этого в файле router.js
добавим строчку:
this.route('posts');
И выполним такую команду:
ember g route posts
Здесь мы воспользовались псевдонимом команды generate
и сгенерировали файлы для роута и шаблона.
По этому урлу уже можно зайти, но там пока ничего нет. Давайте обновим шаблон posts.hbs
:
<h3>List of posts</h3>
{{#each}}
<h5>{{title}}</h5>
<p>{{body}}</p>
{{/each}}
Чтобы этот шаблон заработал в него нужно передать список постов. Как упоминалось выше, это нужно делать в роуте. Давайте изменим роут в файле posts.js
в папке routes
:
export default Ember.Route.extend({
model: function() {
return this.store.find('post');
}
});
Здесь мы обращаемся к хранилищу данных с запросом найти все записи для модели post
.
Теперь перед нами встала проблема, по дефолту Ember Data использует REST-адаптер для загрузки данных, но мы еще не делали backend с которого можно было бы загружать данные. Эту проблему мы можем обойти изменив текущий адаптер на FixtureAdapter
. Этот адаптер предназначен для использования в тестах, но и нам сейчас пригодится. Для этого используем ember cli:
ember g adapter application
Он сгенерирует файл app/adapters/application.js
в котором мы и укажем нужный нам адаптер:
export default DS.Fixturedapter.extend({});
И немного изменим файл с моделью Post, чтобы адаптер мог найти данные:
import DS from 'ember-data';
var Post = DS.Model.extend({
title: DS.attr(),
body: DS.attr()
});
Post.reopenClass({
FIXTURES: [{
id: 1,
title: 'Post 1',
body: 'Lectus in pellentesque lorem...'
}, {
id: 2,
title: 'Post 2',
body: 'Porttitor facilisis ac...'
}]
});
export default Post;
Теперь если открыть страницу /posts
, то мы увидим список постов. Дальше нам нужна возможность открывать страницу конкретного поста. Для этого изменим код в router.js
:
Router.map(function() {
this.resource('posts', function () {
this.route('post', { path: '/:post_id' });
});
});
Роут posts
превратился в ресурс, а в ресурс мы добавили роут post
. Ресурс это просто набор роутов. Роуты объявленные внутри ресурса являются вложенными. Таким образом урл конкретного поста будет таким /posts/:post_id
.
Теперь сгенерируем шаблон для поста:
ember g route posts/post
Как видите ember cli создал папку posts
и в нее добавил роут post.js
. Теперь перенесем роут posts.js
в папку posts
и переименуем его в index.js
. То же самое сделаем с шаблоном и сделаем заголовок ссылкой:
{{#each}}
<h5>{{#link-to 'posts.post' this}}{{title}}{{/link-to}}</h5>
<p>{{body}}</p>
{{/each}}
Для этого мы использовали хэлепер link-to
, которому передали название роута и модель, из которой он сам достанет id.
Ну и добавим в шаблон post.hbs
вывод поста:
<h5>{{title}}</h5>
<p>{{body}}</p>
Теперь, если мы ни где не обшились, мы можем пощелкать по ссылкам и увидеть каждый пост на своей странице. При этом работают браузерные кнопки вперед и назад и можно сразу открыть страницу поста по прямой ссылке.
Кстати, как вы могли заметить, на не пришлось редактировать роут post
, чтобы указать какую модель нужно погрузить, так как Ember сам понял это по параметру :post_id
. И заметьте, как мало кода нам пришлось написать чтобы все это получить.
Исходный код приложения можно посмотреть на гитхабе.
На этом пока все.