Ember.js the Second Step
- 1. Ember.js
The Second Step (Router, Route and etc)
https://github.com/dopin/ember-tokyo-reborn/
https://github.com/dopin/ember-tokyo-reborn/blob/master/
presentation.md
- 3. お品書き(2/)
• Router Route Template Componentの雑な説明
• シンプルなRouteの例
• Nested Routeの例
• サンプルアプリでRouter/Route/Templateを説明
• Rails developerが混乱するところもピックアップ
- 4. お品書き(3/)
• Router Route Templateのまとめ
• Convention of model hook
• ajaxはどこでやるべきか
• その他のComponentを雑に説明
• Ember.Objext Misin Service
• Store Model Adapter Serializer Deserializer
• Addon etc...
- 6. 自己紹介
• Murasaki-san / dopin / whatever
• New Organizer Help me!
• Freelance / Ember.js Rails developer
• I and , but
• I wanna be a good guy in Splatoon always. =>
- 7. Ember.js Tokyo
• Reborn!!
• Next March 22nd 2017 at Sakura Internet
• Next Next late in May 2017 at Sakura Internet
• We're looking for companies which provide us venue.
• Stay tuned
• https://ember-japan-community-slackin.herokuapp.com/
- 13. Emberのコンポーネント
• Router Route Template Controller Component
• Ember.Object
• Model Serializer Adapter Store
• Service Mixin
• Initializer
• etc... a lot!!
- 16. さらにその次は
• Ember-DATA, Model, Store, Serializer, Adapter...
• Service, Initializer
• Ember.Object, Ember.RSVP.*, etc...
ひとまず、 最初に知っておくべきもの からやっつけていきましょ
う
- 19. Router: Route Name
• routeを宣言すると使える
• link-to transitionTo modelForなどで使う
• さりげなく出てくるので、わからない時は質問してください
{{link-to 'Go to hello world' 'hello-world'}}
^^^^^^^^^^^
{{link-to 'Post index' 'posts'}}
^^^^^
- 25. Route hook methods (events)
• beforeModel
• model
• afterModel
• setupController
• renderTemplate
• willTransition... etc
- 26. Route hook methods (events)
• Route#model(フック) と EmberDATAのModel(クラス)とを混同し
ないようにしましょう
• Modelは必須ではありません(条件あり)
• storeを使わない(個人的には使用すべきだと思う)
• link-to transitionToなどでinteger/string以外を渡さない
- 29. Route#modelを理解する
Promise: 解決したら次へ
model() {
return new Ember.RSVP.Promise( (resolve, reject) => {
resolve( [ { "data": [] }]);
// reject('error'); // エラー処理へ
});
}
Other: 即次へ
model() {
return [ { "data": [] }];
}
• 次 = afterModel -> setupController -> renderTemplate
- 30. Route#model Promise
// parent
model() {
this.get('ajax').request('/repositories');
}
// child
model (params) {
this.modelFor('parent').findBy('id', params.id);
}
• 子Route#modelは親Route#modelが解決してから実行される
- 33. Router, Route & Template
• シンプルな画面は、Routerでrouteの定義とtemplateさえあれば
OK
• 1画面、1route, 1template, 1controllerと捉えておいてOK
• ただしネストがある
• 慣れるまではEmber InspectorのView Tree、Routeを活用しよ
う
- 38. behind the scenes
こんなコードが裏で動いています(正確ではありません)
// app/routes/application.js
Ember.Route.extend({
renderTemplate() {
this.render('application');
},
});
// app/routes/index.js
Ember.Route.extend({
renderTemplate() {
this.render('index', {
into: 'application',
outlet: 'main',
controller: 'index',
});
},
});
- 39. behind the scenes
{{!-- app/templates/application.hbs --}}
<section>
{{outlet}}
</section>
{{!-- app/templates/index.hbs --}}
<h1>Index</h1>
<!-- 出力: output -->
<section>
<h1>Index</h1>
</section>
• {{outlet}} と {{outlet 'main'}} は同じ
- 40. 例2 Routeを1つ定義してみる
ember g route hello-World
Ember.Router.map(function() {
this.route('hello-world');
});
• アンダーバー(hello_wolrd)も使えますが、ファイル名はダッ
シュ(hello-world)なので注意しましょう
- 46. Rails's CoC
path controller view
/repositories repositories#index repositories/index.html.erb
/repositories/new repositories#new repositories/new.html.erb
/repositories/1 repositories#show repositories/edit.html.erb /
repositories/repository/index.hbs
/repositories/1/edit repositories#edit repositories/repository/edit.hbs
• Railsは app/views/layouts/application.html.erb の内に
yield で各テンプレートを描画する
- 47. Ember's CoC
path route controller template
/repositories RepositoriesRoute RepositoriesController repositories.hbs
/repositories RepositoriesIndexRoute RepositoriesIndexController repositories/index.hbs
/repositories/new RepositoriesNewRoute RepositoriesNewController repositories/new.hbs
/repositories/1 RepositoriesRepositoryRoute RepositoriesRepositoryControl
ler
repositories/repository.hbs
/repositories/1 RepositoriesRepositoryIndexR
oute
RepositoriesRepositoryIndexC
ontroller
repositories/repository/
index.hbs
/repositories/1/edit RepositoriesRepositoryEditRo
ute
RepositoriesRepositoryEditCo
ntroller
repositories/repository/
edit.hbs
- 48. Ember's CoC
• Emberは、親Routeのテンプレート内の {{outlet}} にテンプ
レートを描画する
• 親Routeに {{outlet}} がないとそれ以降の子Routeのテンプ
レートは表示されない
• IndexRouteはRailsでいう show にあたる
• HogeIndexRouteはネストしているRouteは自動で作られる
- 49. Route Family
path route parent
/repositories RepositoriesRoute ApplicationRoute
/repositories/new RepositoriesNewRoute RepositoriesRoute
/repositories/1 RepositoriesRepositoryRoute RepositoriesRoute
/repositories/1 RepositoriesRepositoryIndexRou
te
RepositoriesRepositoryRoute
/repositories/1/edit RepositoriesRepositoryEditRoute RepositoriesRepositoryRoute
- 61. Rails
resources :repository, only: [:index, :show] do
get 'contributors', on: :member
end
class RepositoriesController < ApplicationController
before_action :set_repository, only: [:show, :contributors]
def index
@repositories = Repository.where(org: 'emberjs').all
end
private
def set_repository
@repository = Repository.find_by(name: params[:name])
end
end
class Repository < ApplicationRecord
has_many :commits
has_many :contributors, class_name: User
end
- 76. リポジトリ一覧
{{!-- repositories/index.hbs --}}
<h1 class="page-header">Repositories</h1>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Created At</th>
<th>Updated At</th>
<th>Issues</th>
<th>Language</th>
</tr>
</thead>
<tbody>
{{#each model as |repo|}}
<tr>
<td>{{link-to repo.name 'repositories.repository' repo.name}}</td>
<td>{{repo.created_at}}</td>
<td>{{repo.updated_at}}</td>
<td>{{repo.open_issues_count}}</td>
<td>{{repo.language}}</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
- 83. リポジトリ一覧(左側) outlet追加
{{!-- application.hbs --}}
<ul class="nav nav-sidebar">
{{#active-link}}
{{link-to 'Dashboard' 'index'}}
{{/active-link}}
{{#active-link}}
{{link-to 'Repositories' 'repositories'}}
{{outlet 'repository-list'}}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 追加!
{{/active-link}}
</ul>
- 85. リポジトリ一覧(左側)
// app/routes/repositories.js
export default Ember.Route.extend({
ajax: Ember.inject.service(),
model() {
return this.get('ajax').request('https://api.github.com/orgs/emberjs/repos');
},
renderTemplate() {
this.render();
this.render('repositories/-repository-list', {
outlet: 'repository-list',
into: 'application',
});
}
});
これでリポジトリ一覧ページは完成!
- 91. リポジトリ詳細
{{!-- app/templates/repositories/repository.hbs --}}
<h1 class="page-header">{{link-to model.name 'repositories.repository' model.name}}</h1>
<ul class="list-inline">
{{#active-link}}
{{link-to 'Collaborator' 'repositories.repository.contributors' model.name class="btn btn-default"}}
{{/active-link}}
{{#active-link}}
{{link-to 'Edit' 'repositories.repository.edit' model.name class="btn btn-default"}}
{{/active-link}}
</ul>
{{outlet}}
次はindex
- 95. リポジトリ詳細 (index)Route
// app/routes/repositories/repository/index.js
export default Ember.Route.extend({
ajax: Ember.inject.service(),
model() {
let repo = this.modelFor('repositories.repository');
return new Ember.RSVP.Promise((resolve, reject) => {
let commitsUrl = `${repo.url}/commits`;
let recentCommits = [];
this.get('ajax').request(commitsUrl).then((commits) => {
Ember.RSVP.all(commits.slice(0, 5).map((commit) => {
return this.get('ajax').request(`${commitsUrl}/${commit.sha}`).then((data) => {
recentCommits.push(data);
});
})).then(() =>{
resolve({repo, commits, recentCommits});
}).catch((error) => reject(error));
}).catch((error) => reject(error));
});
}
});
- 96. リポジトリ詳細 (index)
{{!-- app/repositories/repository/index.hbs --}}
<h2 class="sub-header">Info</h2>
<div class="form-horizontal form-striped">
<div class="form-group">
<label class="col-sm-2 control-label">Watch</label>
<div class="col-sm-10">
<div class="checkbox">
{{model.repo.subscribers_count}}
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Stars</label>
<div class="col-sm-10">
<div class="checkbox">
{{model.repo.stargazers_count}}
</div>
</div>
</div>
</div>
<h2 class="sub-header">Recent Commits</h2>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Author</th>
<th>Message</th>
</tr>
</thead>
<tbody>
{{#each model.recentCommits as |commit|}}
<tr>
<td>{{commit.author.login}}</td>
<td>{{commit.commit.message}}</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
- 108. リポジトリ編集
{{!-- app/repositories/respository/edit.hbs --}}
<form>
<div class="form-group">
<label class="control-label">Name</label>
<input class="form-control" value={{model.name}} disabled>
</div>
<div class="form-group">
<label class="control-label">Description</label>
<textarea class="form-control"disabled></textarea>
</div>
<button type="button" class="btn btn-primary" disabled>Save</button>
<button type="button" class="btn btn-default" {{action "cancel"}}>Cancel</button>
</form>
- 111. Convention of model hook
model hook will not be executed every time.
モデルフックは毎回実行されるわけではありません。
In some cases, your overriding code will be ignored.
上書きしたコードが無視されることがあります。
- 112. Convention of model hook
// app/routes/repositories.js
Ember.Route.extend({
model() { [{name: 'a'}, {name: 'b'}] }
});
// app/routes/repositories/repository.js
Ember.Route.extend({
model(params) {
let repo = this.modelFor('repositories').findBy('name', params.name);
// here's the important part; ここ重要!
return this.ajax.request(`${api}/repos/${repo.name}/`);
}
});
// app/routes/repositories/edit.js
Ember.Route.extend({
model() {
return this.modelFor('repository');
}
});
- 113. Convention of model hook
Passing neither integer nor string
this.transitionTo('repositories.repository', repo);
^^^^
{{link-to 'Show' 'repositories.repository' repo}}
^^^^
// The executed model hook will be like this.
// The ajax request will not be executed.
// app/routes/repositories/repository.js
Ember.Route.extend({
model(params) {
return this.modelFor('repositories').findBy('name', params.name);
}
});
- 114. Convention of model hook
Passing integer or string...
this.transitionTo('repositories.repository', repo.name);
^^^^^^^^^
{{link-to 'Show' 'repositories.repository' repo.name}}
^^^^^^^^^
// The executed model hook will be as-is.
// app/routes/repositories/repository.js
Ember.Route.extend({
model(params) {
let repo = this.modelFor('repositories').findBy('name', params.name);
return this.ajax.request(`${api}/repos/${repo.name}/`);
}
});
- 115. Convention of model hook
• link-to transitionToのパラメータがintかstringを渡すとmodelフッ
クは書いたコードの通り実行される
• それ以外の時はEmberのconventionによって解決される
• 単純にoverrideしたつもりでも、挙動が呼び出し方で変わるので
注意が必要
• http://emberjs.com/api/classes/Ember.Route.html#method_model
- 116. Convention of Route definition
// app/router.js
this.route('posts', function() {
this.route('post', { path: '/:post_id' });
^^^^^^^
});
• model_id
• app/models/post.jsがないとエラーになります
- 122. setupController
Ember.Route.extend({
model(params) {
return { post: this.get('ajax').request(`/posts/${params.id}`) };
},
setupController(controller, model) {
this._super(...arguments);
this.get('ajax').request(model.post.commentUrl)
.then((comments) => {
controller.set('model', { 'post': model.post, comments});
})
.catch((error)=> {
this.transitionToRoute('error'); // ? no good
// You don't want to pass errors as a parameter.
});
}
});
- 131. Model
• Post Modelの定義の例
import DS from 'ember-data';
export default DS.Model.extend({
// idは必須、ただし宣言は不要、文字列扱いなのでソート時は注意
author: DS.belongsTo('user'),
title: DS.attr('string'),
comments: DS.hasMany('comment'),
});
- 132. Model
• Modelの機能
let post = this.store.find('post', 1) // Post.find(1)
post.set('title', 'Hey!'); // @post.title = 'hey'
post.get('hasDirtyAttributes'); // @post.changed? # => true
post.save(); // @post.save
- 134. JSON API (補足)
• http://jsonapi.org/
• Ember DATA公式サポート
• RDBMSで管理されたデータをJSONで表せる設計されている
• Relationship (Railsでいうassociation)
• 後方互換維持指向
- 143. Addon
• Less is more
• https://emberobserver.com/
• https://www.emberaddons.com/
ember install ember-bootstrap
ember install ember-cli-active-link-wrapper