ember.js the second step

147
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

Upload: eisuke-murasaki

Post on 09-Feb-2017

81 views

Category:

Software


2 download

TRANSCRIPT

Page 1: Ember.js the Second Step

Ember.jsThe Second Step (Router, Route and etc)

https://github.com/dopin/ember-tokyo-reborn/https://github.com/dopin/ember-tokyo-reborn/blob/master/presentation.md

Page 2: Ember.js the Second Step

お品書き(1/)

• 自己紹介• Ember.js Tokyoについて• このスライドの対象者• 免責事項• Emberについて• Ember.jsの各コンポーネントの役割と関連するCoCの説明

Page 3: Ember.js the Second Step

お品書き(2/)

• Router Route Template Componentの雑な説明• シンプルなRouteの例• Nested Routeの例• サンプルアプリでRouter/Route/Templateを説明

• Rails developerが混乱するところもピックアップ

Page 4: Ember.js the Second Step

お品書き(3/)

• Router Route Templateのまとめ• Convention of model hook

• ajaxはどこでやるべきか• その他のComponentを雑に説明

• Ember.Objext Misin Service• Store Model Adapter Serializer Deserializer• Addon etc...

Page 5: Ember.js the Second Step

お品書き(4/4)

• モデルのソートはどこですべきか?• モデルのフィルターはどこですべきか?• � 本日のデザート(ありません)

お腹いっぱいで帰ってください。

Page 6: Ember.js the Second Step

自己紹介• 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. =>

Page 7: Ember.js the Second Step

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/

Page 8: Ember.js the Second Step

余談• 私の個人的な思い• 昔:Railsを始めたけど、学ぶことが多い(Ruby、SQL、セキュリティ...)

• 今:Emberも似ている(ES6、SPA、非同期プログラミング...)

• Railsを学んで良かった使って良かった• Emberもそう、だから手助けになれば幸い• 楽な道じゃないけど頑張ろうぜ

Page 9: Ember.js the Second Step

このスライドの対象者• Ember.jsでWebアプリをEmber-CLIを使って開発したことがあり、

• Emberのファイル構成を知っているが、• いまいちEmberでのやり方がわからないという方

• Railsでいう resources :post をEmberでどうやるのかわからない方

Page 10: Ember.js the Second Step

免責事項�

• ところどころRailsとの違いを説明しますが、RailsのMVCは忘れてください(違うものです)

• ディレクトリ構造やファイル名の規約については省きます• 今回は主にRouter/Route/Template(outlet)に焦点を当てています• I'm not an Ember.js committer but just a user �

• 嘘言ってたらごめんなさい �

Page 11: Ember.js the Second Step

免責事項�

• 以下の話はしません �

• Route transition• queryParams

大事な機能なのであえて触れておきました �

Page 12: Ember.js the Second Step

Ember• CoCがあるフレームワーク• RailsのようにURLから使われいるものがどこにあるのかが分かる

• ディレクトリ構造とファイル名に決まりがあるので迷わない• 逆にCoCを知らないと迷ったり分からなくなる• 今回はEmberのCoCと各コンポーネント(MVCなど)を説明していきます

Page 13: Ember.js the Second Step

Emberのコンポーネント• Router Route Template Controller Component

• Ember.Object• Model Serializer Adapter Store• Service Mixin• Initializer• etc... a lot!!

Page 14: Ember.js the Second Step

最初に知っておくべきもの• Router• Route• Template• Controller

• これらの役割とCoC(Ember-CLIのファイルの命名規約も)

• Componentの使い方と活用方法• これはTemplateとControllerを知っていれば割と簡単

Page 15: Ember.js the Second Step

その次は• Railsでいう resources :posts (CRUD) をEmberで実現する方法• Routeの使い方と各Routeとの関係今回のメイントピックです!

Page 16: Ember.js the Second Step

さらにその次は• Ember-DATA, Model, Store, Serializer, Adapter...• Service, Initializer• Ember.Object, Ember.RSVP.*, etc...

ひとまず、 最初に知っておくべきもの からやっつけていきましょう

Page 17: Ember.js the Second Step

Router• 各URL(Route)の定義を行う

• Railsでいう config/routes.rb に相当• Routeの定義をすると、Route/Controllerも作られる

• Railsとは違いRouteとControllerはコード存在しなくても良い• ない場合はデフォルトのものが使用される

Page 18: Ember.js the Second Step

Router// app/router.jsEmber.Router.map(function() { this.route('hello-world');});

{{!-- app/templates/hello-world.hbs --}}<h1>Hello World</h1>

• http://localhost:4200/hello-world

• CoCに沿って、app/templates/hello-world.hbs が参照され、ブラウザにHello Worldと表示される

Page 19: Ember.js the Second Step

Router: Route Name

• routeを宣言すると使える• link-to transitionTo modelForなどで使う• さりげなく出てくるので、わからない時は質問してください �

{{link-to 'Go to hello world' 'hello-world'}} ^^^^^^^^^^^{{link-to 'Post index' 'posts'}} ^^^^^

Page 20: Ember.js the Second Step

Template• Handlebars

• Controllerのプロパティを表示したり• HelperやComponentを使ったり

• {{action}} を使ってDOMイベントをController/Routeに処理を任せる

Page 21: Ember.js the Second Step

Actions

{{!-- app/templates/application.hbs --}}<form {{action "save" on="submit"}}>{{input value=model.name}}</form>

Page 22: Ember.js the Second Step

Route• 各URL毎に存在する• 暗黙的に作られるRouteがある(index)

• Emberを理解するには、まずRouteをしっかり理解する必要がある

Page 23: Ember.js the Second Step

Routeの役割• データの取得(ajax)

• テンプレートの描画

• テンプレート内の {{action}} を実行するなど• これらの画面の遷移に応じたフックメソッドがある• イベントや状態によって別画面に遷移させたり• RailsのController部分を担当しているとも言える(かな?)

Page 24: Ember.js the Second Step

Handling Actions{{!-- app/templates/application.hbs --}}<form {{action "save" on="submit"}}>{{input value=model.name}}</form>

// app/routes/application.jsEmber.Route.extend({ actions: { save() { this.get('controller.model').save(); } }});

Page 25: Ember.js the Second Step

Route hook methods (events)

• beforeModel• model• afterModel• setupController• renderTemplate• willTransition... etc

Page 26: Ember.js the Second Step

Route hook methods (events)�• Route#model(フック) と EmberDATAのModel(クラス)とを混同しないようにしましょう

• Modelは必須ではありません(条件あり)

• storeを使わない(個人的には使用すべきだと思う)

• link-to transitionToなどでinteger/string以外を渡さない

Page 27: Ember.js the Second Step

Routeがやっていること(正確なコードではありません)export default Ember.Route.extend({ model() { return null; },

setupController(controller, model) { controller.set('model', model); // null },

renderTemplate() { this.render(); }});

Page 28: Ember.js the Second Step

Route#modelを理解する• 画面に必要なデータは基本modelで行う• Promiseとそれ以外では挙動が違う• 大事なので詳しく

Page 29: Ember.js the Second Step

Route#modelを理解するPromise: 解決したら次へmodel() { return new Ember.RSVP.Promise( (resolve, reject) => { resolve( [ { "data": [] }]); // reject('error'); // エラー処理へ });}

Other: 即次へmodel() { return [ { "data": [] }];}

• 次 = afterModel -> setupController -> renderTemplate

Page 30: Ember.js the Second Step

Route#model Promise

// parentmodel() { this.get('ajax').request('/repositories');}

// childmodel (params) { this.modelFor('parent').findBy('id', params.id);}

• 子Route#modelは親Route#modelが解決してから実行される

Page 31: Ember.js the Second Step

Controller• RouteとTemplateの間に存在しているイメージ

• Template内で {{foo}} のように値を参照している場合、Controllerのプロパティが参照される

• Routeの setupController フックによって、model フックで取得したデータはControllerの model プロパティに格納される

Page 32: Ember.js the Second Step

Controller• Route同様Template内のアクションをハンドリングする

• RouteよりControllerが優先される• Controllerにない場合はRouteに伝達される

• Controllerを使わなくて済むなら使わない方が良い(かな?)

Page 33: Ember.js the Second Step

Router, Route & Template

• シンプルな画面は、Routerでrouteの定義とtemplateさえあればOK

• 1画面、1route, 1template, 1controllerと捉えておいてOK

• ただしネストがある• 慣れるまではEmber InspectorのView Tree、Routeを活用しよう

Page 34: Ember.js the Second Step

Component

• ControllerとTemplateがRouteと切り離されて統合され再利用しやすくなったもの(Ember流WebComponent)

• 画面上の小さな部品から少し大きめの複雑なものがある• Data Down Actions Up / Smart component

• input link-to などEmberのビルトインコンポーネント• Component名はダッシュ(-)で区切られた2つ以上単語が必須

• my-component

Page 35: Ember.js the Second Step

裏側に潜入一旦ここで、Emberが裏側で何をしているのか、先ほどのHello World例も含めもう少し詳しく見ていきます。

Page 36: Ember.js the Second Step

例1 テンプレート描画の流れ• ember new $app_name

• ember s

• open http://localhost:4200/

Page 37: Ember.js the Second Step

• トップページにアクセスすると以下のように実行される

Page 38: Ember.js the Second Step

behind the scenes

こんなコードが裏で動いています(正確ではありません)// app/routes/application.jsEmber.Route.extend({ renderTemplate() { this.render('application'); },});

// app/routes/index.jsEmber.Route.extend({ renderTemplate() { this.render('index', { into: 'application', outlet: 'main', controller: 'index', }); },});

Page 39: Ember.js the Second Step

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'}} は同じ

Page 40: Ember.js the Second Step

例2 Routeを1つ定義してみるember g route hello-World

Ember.Router.map(function() { this.route('hello-world');});

• � アンダーバー(hello_wolrd)も使えますが、ファイル名はダッシュ(hello-world)なので注意しましょう

Page 41: Ember.js the Second Step

• /hello-world にアクセスすると以下のように実行される

Page 42: Ember.js the Second Step

基本がわかったところで、さらに実践的なRouteを定義してみましょうわからん�という方はEmber Inspectorとにらめっこしてください �

あと ember g --dry-run ... も �

Page 43: Ember.js the Second Step

Nested Routeリポジトリの一覧と詳細、編集ページの構成例(あるある)

path description

/repositories リポジトリ一覧

/repositories/new リポジトリ新規作成

/repositories/:id リポジトリ詳細

/repositories/:id/edit リポジトリ編集

Page 44: Ember.js the Second Step

Railsだとこう?# config/routes.rbresources :repository, only: [:index, :show, :edit, :new]

Page 45: Ember.js the Second Step

Emberだとこう// app/router.jsEmber.Router.map(function() { this.route('repositories', function() { this.route('new'); this.route('repository', { path: '/:id' }, function() {

this.route('edit'); }; });});

Page 46: Ember.js the Second Step

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 で各テンプレートを描画する

Page 47: Ember.js the Second Step

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 RepositoriesRepositoryController

repositories/repository.hbs

/repositories/1 RepositoriesRepositoryIndexRoute

RepositoriesRepositoryIndexController

repositories/repository/index.hbs

/repositories/1/edit RepositoriesRepositoryEditRoute

RepositoriesRepositoryEditController

repositories/repository/edit.hbs

Page 48: Ember.js the Second Step

Ember's CoC

• Emberは、親Routeのテンプレート内の {{outlet}} にテンプレートを描画する

• 親Routeに {{outlet}} がないとそれ以降の子Routeのテンプレートは表示されない

• IndexRouteはRailsでいう show にあたる• HogeIndexRouteはネストしているRouteは自動で作られる

Page 49: Ember.js the Second Step

Route Family

path route parent

/repositories RepositoriesRoute ApplicationRoute

/repositories/new RepositoriesNewRoute RepositoriesRoute

/repositories/1 RepositoriesRepositoryRoute RepositoriesRoute

/repositories/1 RepositoriesRepositoryIndexRoute

RepositoriesRepositoryRoute

/repositories/1/edit RepositoriesRepositoryEditRoute RepositoriesRepositoryRoute

Page 50: Ember.js the Second Step

Tree

Page 51: Ember.js the Second Step

RepositoriesRepositoryEditRouteまでの流れ• /repositories/1/edit

• ブラウザのリフレッシュか直接URLを叩いた場合、ApplicationRouteからスタートする

Page 52: Ember.js the Second Step

RepositoriesRepositoryEditRouteまでの流れ

• 親Routeのフックメソッドは実行される

Page 53: Ember.js the Second Step

RepositoriesIndexRouteからRepositoriesRepositoryEditRouteまでの流れ{{!-- app/templates/repositories/index.hbs --}}{{link-to '変更' 'reposotories.repository.edit'}}

• /repositories => /repositories/:id/edit

Page 54: Ember.js the Second Step

RepositoriesIndexRouteからRepositoriesRepositoryEditRouteまでの流れ

1. RepositoriesRepositoryRoute

2. RepositoriesRepositoryEditRoute

Page 55: Ember.js the Second Step

Routeの理解を深める• Nested Routeの説明をしました• うまく説明ができないので、実際に作ったサンプルアプリを例に、どのように実装していくか見ていきましょう

Page 56: Ember.js the Second Step

サンプルアプリ• GitHubにあるemberjs orgのリポジトリ閲覧アプリ• SPA(Ember)

• 各ページは直リンクできるようにする• 一覧を画面左、画面右に詳細で表示されるmaster & detail形式

Page 57: Ember.js the Second Step

サンプルアプリ(完成見本)

Page 58: Ember.js the Second Step

サンプルアプリ• Railsのコードについて �

• SPAと従来のWebアプリの比較用• データはGitHub APIからではなくDBから取得する• turoblinksやajaxを使わない• コードは想像で書いたものです

Page 59: Ember.js the Second Step

Task• GitHub APIから以下の情報を取得し画面に表示する• emberjs orgのリポジトリ一覧ページ• リポジトリの詳細情報ページ• 最近のコミットを5件詳細ページに表示• contributorsを別ページに表示

Page 60: Ember.js the Second Step

Railsの処理の流れ

Page 61: Ember.js the Second Step

Railsresources :repository, only: [:index, :show] do get 'contributors', on: :memberend

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]) endend

class Repository < ApplicationRecord has_many :commits has_many :contributors, class_name: Userend

Page 62: Ember.js the Second Step

Rails

repositories/show.html.erb

<%= render 'header' %><h2>Recent Commits</h2><table> <thead> <tr> <th>Author</th> <th>Message</th>

repositories/contributors.html.erb

<%= render 'header' %><ul> <% @repository.contributors.each do |user| %>

Page 63: Ember.js the Second Step

Emberではどうする?• SPA

• 提供されているAPIからデータを取得する必要がある• ページごとに必要なデータを毎回全部取得してHTMLを全置き換え? �

• 画面に必要なデータだけ取得して、必要な部分だけ置き換える �

Page 64: Ember.js the Second Step

Let's get started!

Page 65: Ember.js the Second Step

APIからデータを取得するGitHub APIのendpoints

Path Description

/orgs/:owner/repos an org's repositories

/repos/:owner/:repo a repository detail

/repos/:owner/:repo/commits a repository's recent commits

/repos/:owner/:repo/commits/:sha a repository's commit detail

Page 66: Ember.js the Second Step

App URLsPath Description

/repositories emberjs org repository list

/repositories/:name repository detail

/repositories/:name/contributors repository contributor list

Page 67: Ember.js the Second Step

Router

Router.map(function() { this.route('repositories', function() { this.route('repository', { path: '/:name' }, function() { this.route('contributors'); this.route('edit'); }); });});

Page 68: Ember.js the Second Step

トップページ

Page 69: Ember.js the Second Step

トップページ outlet

• application outletにindex.hbsが描画されている

Page 70: Ember.js the Second Step

リポジトリ一覧の実装

Page 71: Ember.js the Second Step

リポジトリ一覧• APIからリポジトリ一覧情報を取得し表示する• 各リポジトリの詳細ページにリンクする• RepositoriesRepositoryRoute(詳細)の親• 親(祖先)Routeのmodelは子Routeから参照可

• 有効活用することで無駄なajaxが減る• 兄弟や子孫Routeは不可

Page 72: Ember.js the Second Step

リポジトリ一覧 outlet

Page 73: Ember.js the Second Step

リポジトリ一覧 (index)

• RepositoriesRouteIndexはRepositoriesRouteの子Route

• RepositoriesRouteIndexから兄弟Routeや兄弟の子孫Routeに遷移した時に、切り替えたいコンテンツはRepositoriesIndexRouteに置く

• Railsのcontroller#indexとは違うので注意• この後もう一度indexが出てくるので、ここで理解できなくても�

• 複数 = index, 単一 = show、のようなRailsの考え方は忘れてね

Page 74: Ember.js the Second Step

Routeの流れ

Page 75: Ember.js the Second Step

リポジトリ一覧// app/routes/repositories.jsexport default Ember.Route.extend({ ajax: Ember.inject.service(),

model() { return this.get('ajax').request('https://api.github.com/orgs/emberjs/repos'); },});

Page 76: Ember.js the Second Step

リポジトリ一覧{{!-- 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>

Page 77: Ember.js the Second Step

リポジトリ一覧完成! � と言いたいけど...

Page 78: Ember.js the Second Step

リポジトリ一覧これだと詳細に移った後、他のリポジトリに移動するのが面倒。左側にも一覧が欲しい...

Page 79: Ember.js the Second Step

リポジトリ一覧(左側)

• master & detail

• 左のナビゲーションと右の画面は同じデータを利用できる• しかし問題が...

Page 80: Ember.js the Second Step

リポジトリ一覧(左側)

Page 81: Ember.js the Second Step

リポジトリ一覧(左側)

• 問題は左側のリストはrepoisotires outletの外• application.hbs

• データが子孫Routeにある... 参照できない• どうする?

• application.hbsにoutletを追加してrenderTemplateで指定する

Page 82: Ember.js the Second Step

リポジトリ一覧(左側) outlet

Page 83: Ember.js the Second Step

リポジトリ一覧(左側) 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>

Page 84: Ember.js the Second Step

リポジトリ一覧(左側)

部分テンプレートを用意{{!-- repositories/-repositories-list.hbs --}}<ul class="list-unstyled repository-list-nav"> {{#each model as |repo| }} {{#active-link}} {{link-to repo.name 'repositories.repository' repo.name}} {{/active-link}} {{/each}}</ul>

Page 85: Ember.js the Second Step

リポジトリ一覧(左側)// app/routes/repositories.jsexport 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', }); }});

これでリポジトリ一覧ページは完成! �

Page 86: Ember.js the Second Step

リポジトリ詳細の実装

Page 87: Ember.js the Second Step

リポジトリ詳細• リポジトリのデータは親Routeから引き継いでフィルターする• リポジトリ一覧のデータに含まれていないデータがあるので詳細情報をAPIから取得する(画面上indexでもOK)

• Index以外の子Routeではコミット情報は画面に表示しないので、ここやると無駄になるので取得しない

• 最新のコミットを5件表示したいが、これもIndexで行う• ページ上部はindexやコントリビュータページでも表示したい(共通部分)

Page 88: Ember.js the Second Step

リポジトリ詳細 outlet

Page 89: Ember.js the Second Step

Routeの流れ

Page 90: Ember.js the Second Step

リポジトリ詳細// app/routes/repositories/repository.jsexport default Ember.Route.extend({ ajax: Ember.inject.service(),

model(params) { let parentModel = this.modelFor('repositories'); let repo = parentModel.findBy('name', params.name); return this.get('ajax').request(repo.url); },});

Page 91: Ember.js the Second Step

リポジトリ詳細{{!-- 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

Page 92: Ember.js the Second Step

リポジトリ詳細 (index)の実装

Page 93: Ember.js the Second Step

リポジトリ詳細 (index)

• 親Routeか単一リポジトリのデータを持っているのでそれを利用する

• modelでコミットを取得する• コミットのメッセージも表示したいので、各コミットの詳細を取得する

Page 94: Ember.js the Second Step

リポジトリ詳細 (index) Routeの流れ

Page 95: Ember.js the Second Step

リポジトリ詳細 (index)Route// app/routes/repositories/repository/index.jsexport 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)); }); }});

Page 96: Ember.js the Second Step

リポジトリ詳細 (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>

Page 97: Ember.js the Second Step

リポジトリ詳細完成 �

次はコントリビュータのページを作成しましょう

Page 98: Ember.js the Second Step

リポジトリのコントリビューターの実装

Page 99: Ember.js the Second Step

リポジトリのコントリビューター

Page 100: Ember.js the Second Step

リポジトリのコントリビューター Routeの流れ

Page 101: Ember.js the Second Step

リポジトリのコントリビューター Route

// app/repositories/repository/contributors.jsexport default Ember.Route.extend({ ajax: Ember.inject.service(),

model() { let repo = this.modelFor('repositories.repository'); let url = `${repo.url}/contributors`; return this.get('ajax').request(url); }});

Page 102: Ember.js the Second Step

リポジトリのコントリビューター{{!-- app/templates/repository/contributors.js --}}<h2>Contributors</h2>

<div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>Name</th> <th>Contributions</th> </tr> </thead> <tbody> {{#each model as |user|}} <tr> <td> <img src={{user.avatar_url}}alt={{user.login}} height="50px">

{{user.login}} </td> <td> {{user.contributions}} </td> </tr> {{/each}} </tbody> </table></div>

Page 103: Ember.js the Second Step

リポジトリのコントリビューター�

Page 104: Ember.js the Second Step

おまけ• 編集画面• ただし権限がないのでREAD Onlyで

Page 105: Ember.js the Second Step

リポジトリ編集画面の実装

Page 106: Ember.js the Second Step

リポジトリ編集• データは親Routeのデータをそのまま利用できるのでajax不要• modelフックを書く必要なし• templateとcancelによる詳細画面への遷移処理を書けば�

Page 107: Ember.js the Second Step

リポジトリ編集// app/routes/repositories/repository/edit.jsexport default Ember.Route.extend({ actions: { cancel() { this.transitionTo( 'repositories.repository', this.get('controller.model.name') ); } }});

Page 108: Ember.js the Second Step

リポジトリ編集{{!-- 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>

Page 109: Ember.js the Second Step

サンプルアプリ

Page 110: Ember.js the Second Step

Router Route Templateのまとめ• この3つのコンポーネントが基本• Nested Route

• outlet• model

Page 111: Ember.js the Second Step

Convention of model hook

model hook will not be executed every time.モデルフックは毎回実行されるわけではありません。

� In some cases, your overriding code will be ignored. 上書きしたコードが無視されることがあります。

Page 112: Ember.js the Second Step

Convention of model hook// app/routes/repositories.jsEmber.Route.extend({ model() { [{name: 'a'}, {name: 'b'}] }});

// app/routes/repositories/repository.jsEmber.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.jsEmber.Route.extend({ model() { return this.modelFor('repository'); }});

Page 113: Ember.js the Second Step

Convention of model hook

Passing neither integer nor stringthis.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.jsEmber.Route.extend({ model(params) { return this.modelFor('repositories').findBy('name', params.name); }});

Page 114: Ember.js the Second Step

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.jsEmber.Route.extend({ model(params) { let repo = this.modelFor('repositories').findBy('name', params.name); return this.ajax.request(`${api}/repos/${repo.name}/`); }});

Page 115: Ember.js the Second Step

Convention of model hook

• link-to transitionToのパラメータがintかstringを渡すとmodelフックは書いたコードの通り実行される

• それ以外の時はEmberのconventionによって解決される• 単純にoverrideしたつもりでも、挙動が呼び出し方で変わるので注意が必要 � � �

• http://emberjs.com/api/classes/Ember.Route.html#method_model

Page 116: Ember.js the Second Step

Convention of Route definition

// app/router.jsthis.route('posts', function() { this.route('post', { path: '/:post_id' }); ^^^^^^^});

• model_id

• app/models/post.jsがないとエラーになります �

Page 117: Ember.js the Second Step

Route#error

• ajaxリクエスト失敗時の対処• errorのtemplate/outletは別

• beforeModel、 model、afterModel で返したPromise が reject されると、errorアクションが呼ばれ、テンプレートが存在すれば描画する

• この辺は詳しくないので説明できません �

Page 118: Ember.js the Second Step

その他複数のリソースを取得する例

Page 119: Ember.js the Second Step

RSVP.hash �

• ダッシュボードのように複数のデータを取ってくる例Ember.Route.extend({ model() { return Ember.RSVP.hash({ posts: this.get('ajax').request(`/posts`), users: this.get('ajax').request(`/users`), }); },});

Page 120: Ember.js the Second Step

RSVP.Promise �

• 1つのリソースとそれに紐づくデータを取得する例• EmberDATAを使うと書かなくて済む場合が多いEmber.Route.extend({ model(params) { return new Ember.RSVP.Promise((resolve, reject) => { this .get('ajax').request(`/posts/${params.id}`) .then((post) => { this.get('ajax')(post.commentUrl) .then((comments) => resolve({post, comments})) .catch((error) => reject(error)); }) .catch((error) => reject(error)); }); },});

Page 121: Ember.js the Second Step

afterModel �Ember.Route.extend({ model(params) { return this.get('ajax').request(`/posts/${params.id}`); },

afterModel(model) { /* afterModelでajaxはおすすめできない */

this.get('ajax')(model.commentUrl) .then((comments) => { /* ここではcontrollerはまだ参照できない */

}); }});

• controller.modelとして使いたい時はmodelフックで取得• routeにセットする時はafterModel

Page 122: Ember.js the Second Step

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. }); }});

Page 123: Ember.js the Second Step

ajaxはどこでやるべき?• 基本的に...

• 画面に必要なデータはRoute#model

• 保存処理などはRouteかControllerのaction内

Page 124: Ember.js the Second Step

ajaxはどこでやるべき?• なぜ?

• ユーザのアクション次第でいつでも画面遷移できるから• コンポーネントでajaxして、その間に他の画面に遷移した場合、ajaxが終わってコールバック処理が走りajaxを処理していたComponentが画面から削除されていたら、どうなりますか? thisとか..

• ����������

Page 125: Ember.js the Second Step

その他のコンポーネント• Ember.Object Mixin Service• Ember DATA = Store, Model, Adapter, Serializer, Deserializer• Initializer• Addon• etc...

Page 126: Ember.js the Second Step

Ember.Object

• EmberではあらゆるところでEmber.Objectが使われている• Vanilla.jsでは機能が足りない• Ember.***.extend, Mixin はEmber.Objectの機能• 基本なのでガイドは読んでおきましょう• https://guides.emberjs.com/v2.11.0/object-model/

Page 127: Ember.js the Second Step

Service

• Ember.Service、Ember.Objectの拡張• Emberアプリが使われている間ずっと単一で存在するオブジェクト

• Session, Geolocation, Websockets, サーバから送られてくる通知や• Ember DATAの仕様にマッチしていないAPIとのやりとりに使う• https://guides.emberjs.com/v2.11.0/applications/services/

Page 128: Ember.js the Second Step

Store

• 実はサンプルアプリには問題があります• 仮にリポジトリ名を変更できたとします• 先ほどの実装では左側のリストは古い名前のままで更新されません �

• ここでStore、Ember DATAの出番です

Page 129: Ember.js the Second Step

Store

• Storeはブラウザ上のRDBMSのようなものです• データ(モデル)を一元管理します• データを変更するとobserverに伝播されます• データはブラウザのメモリ上に保管されるので永続的ではありません

• データを永続化するにはajaxでサーバと通信するか、LocalStorageなどを使う必要があります

Page 130: Ember.js the Second Step

Model

• モデルはAPIが提供するデータの型定義• db/schema.rb + ActiveRecord

DS.Model RDBMS

Model Table

attribute column

Computed Property ?

Page 131: Ember.js the Second Step

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'),});

Page 132: Ember.js the Second Step

Model

• Modelの機能let post = this.store.find('post', 1) // Post.find(1)post.set('title', 'Hey!'); // @post.title = 'hey'post.get('hasDirtyAttributes'); // @post.changed? # => truepost.save(); // @post.save

Page 133: Ember.js the Second Step

Adapter

• MySQLやPostgreSQLのようにRDBMSにも種類がある• BackendのAPIに合わせて適切なadapterを選ぶ• ただし共通の規格が少ない �

• http://jsonapi.org/ �

• 独自RESTful API �

• http://graphql.org/ �

Page 134: Ember.js the Second Step

JSON API (補足)

• http://jsonapi.org/

• Ember DATA公式サポート• RDBMSで管理されたデータをJSONで表せる設計されている• Relationship (Railsでいうassociation)

• 後方互換維持指向

Page 135: Ember.js the Second Step

Adapterの役割• URLのビルド• ajax

this.store.findAll('post'); // => GET /poststhis.store.find('post', 1); // => GET /posts/1post.save(); // PUT /posts/1post.destroyRecord(); // DELETE /post/1

Page 136: Ember.js the Second Step

Serializer

• サーバから取得したJSONを• Storeが取扱えるJSONに変換する• ResponseBody => Serializer => JSONAPI format

Page 137: Ember.js the Second Step

Deserializer

• Store/Modelからサーバにリクエストする際に• サーバ側が取扱えるJSONに変換する• Model => Deserializer => RequsetBody

Page 138: Ember.js the Second Step

Adapter Serializer Deserializer

• JSONAPI.ORG => DS.JSONAPIAdapter ���

• Rails => DS.RESTAdapter �

• Other => Good luck �

Page 139: Ember.js the Second Step

Initializer

• initializer• instance-initializer

Page 140: Ember.js the Second Step

initializer

• アプリケーション起動時に実行される• injectの宣言を書いていく• 各コンポーネントがインスタンス化される前に設定を書く感じapp.inject('controller', 'service:session');

Page 141: Ember.js the Second Step

instance-initializer

• アプリケーション起動後に実行される• Routeなどがインスタンス化した後

• lookupが使える• lookupして何か設定する時はこっち// インスタンスとはPresentation = DS.Model.extend(...);instance = new Presentation();^^^^^^^^instance.set('status', "そろそろ終わります");

Page 142: Ember.js the Second Step

instance-initializer

• RouteやControllerはアプリケーション起動時にインスタンス化されます

• クラスの設定ではなくインスタンスの設定になるので、instance-initializerで設定します

import ENV from 'app/config/environment';

export function initialize(application) { let pusherService = application.lookup('service:pusher'); pusherService.setup(ENV.PUSHER_KEY, ENV.PUSHER_CONNECTION);}

export default { name: 'pusher-service', initialize: initialize};

Page 143: Ember.js the Second Step

Addon

• Less is more �• https://emberobserver.com/• https://www.emberaddons.com/

ember install ember-bootstrapember install ember-cli-active-link-wrapper

Page 144: Ember.js the Second Step

etc...

�Maybe next time? �

Page 145: Ember.js the Second Step

モデルのソートはどこですべきか?• ember-composable-helpers

{{#each (sort-by "lastName" "firstName" users) as |user|}} {{user.lastName}}, {{user.firstName}}{{/each}}

Page 146: Ember.js the Second Step

モデルのフィルターはどこですべきか?• 見た目の問題であれば ember-composable-helper

• Ajaxが絡むならRoute

Page 147: Ember.js the Second Step

Thank you �Questions?