この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
前回のエントリーではViewを紹介しましたが、今回はComponentを紹介します。
Componentとは?
非常にざっくりとした言い方をするならば、Ember.jsのComponentは独自のタグを定義する機能です。ComponentはViewによく似ています。それもそのはずで、Ember.Componentクラスは、Ember.Viewクラスのサブクラスです。Componentは、Viewよりも抽象度と独立性が高く設計されています。
メンバーを表示するComponent
Viewと同様にリスト表示をComponent化してみます。
<script type="text/x-handlebars">
<ul>
{{#each model}}
<li><span>{{name}}({{color}})</span></li>
{{/each}}
</ul>
</script>
前回と同様に、「<span>{{name}}({{color}})</span>」の部分をComponent化しましょう。
Componentの定義
Componentを定義するには、Viewと同様にComponent用のクラスとテンプレートを定義します。
App.MemberComponent = Ember.Component.extend({
});
<script type="text/x-handlebars" id="components/member">
<span>{{name}}({{color}})</span>
</script>
Viewの時と異なるのは、テンプレート名を「components/」ではじめることと、Ember.Viewクラスの代わりにEmber.Componentを継承する点です。なお、Viewでもそうですが、この手のクラスは暗黙に作成されるため、拡張が不要な場合は定義する必要がありません(自分は暗黙が嫌いので明示的に宣言します)。
Component名は、components/の後ろに続けますが、複数単語の時は「components/member-info」のようにハイフンで区切る必要があります。
Componentの利用
Componentを利用する場合は、ヘルパーは不要です。次のように独自のタグを使うように指定します。
<script type="text/x-handlebars">
<h2 id="toc-component2">Component</h2>
<ul>
{{#each model}}
<li>{{member name=name color=color}}</li>
{{/each}}
</ul>
</script>
Viewと最も異なる点は、外側のコンテキストがComponentに継承されないことです。必要な情報は、パラメータとして渡さなければなりません。ここでは、memberという独自タグの属性でnameパラメータとcolorパラメータを指定しています。
Componentの拡張
続けてComponentを拡張していきましょう。
状態とイベント
ComponentはViewのサブクラスであるため、基本的にはViewと同様の拡張ができます。つまり、Viewで解説したように状態とイベントを定義することができます。
App.MemberComponent = Ember.Component.extend({
count: 1,
click: function(evt) {
var current = this.get('count');
this.set('count', current + 1);
}
});
<script type="text/x-handlebars">
<h2 id="toc-component4">Component</h2>
<ul>
{{#each model}}
<li>{{member name=name color=color}}</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" id="components/member">
<span>{{name}}({{color}}) - {{view.count}}</span>
</script>
コンテキストを継承しない点を除けば、Viewと同じです。
actionを定義する
Componentではactionを定義する事ができます。actionとは抽象化したイベントです。先ほどのサンプルコードに現れるclickイベントはユーザインターフェイスに直接関連しています。これを抽象化し、countUpというactionとして定義すると次のようになります。
App.MemberComponent = Ember.Component.extend({
count: 1,
actions: {
countUp: function(evt) {
var current = this.get('count');
this.set('count', current + 1);
}
}
});
イベントハンドラ(関数)が、actionsプロパティのcountUp要素としてが登録されているか、clickプロパティに登録されているかの違いでしかありません。actionを設定するには、actionヘルパーを利用します。
<script type="text/x-handlebars" id="components/member">
<span>{{name}}({{color}}) <span {{action 'countUp'}}>UP</span> - {{view.count}}</span>
</script>
ここでは、Component全体でイベントを発生させず、あえて一部でイベントを発生させてみました。このように、Componentではactionを細かく設定することが可能です。勿論、複数のactionを指定する事もできます。
actionでは、clickなどと違い意味が明確になり、「クリック」という具体的な操作から、「カウントアップ」という抽象化された操作として定義できます。
yieldを使いComponentに表示させる
Componentはコンテキストを継承しませんが、外部から表示ブロックを受け取り内部展開することができます。テンプレートでは、外から受け取ったブロックを表示する部分をyieldヘルパーで置き換えます。
<script type="text/x-handlebars" id="components/member">
<span>{{yield}} <span {{action 'countUp'}}>UP</span> - {{view.count}}</span>
</script>
Componentを利用する時は、次のようにComponentを開始タグと閉タグで宣言し、内部に表示したい情報を定義します。
<script type="text/x-handlebars">
<h2 id="toc-component5">Component</h2>
<ul>
{{#each model}}
<li>{{#member}}
{{name}}({{color}})
{{/member}}</li>
{{/each}}
</ul>
</script>
これで外部からメンバー名の部分を受け取って表示できるようになりました。
こうなるとこのComponentはカウントアップ可能な独立した部品(独自タグ)として大きく抽象化されたと言えます。名前を変えておきましょう。
App.CounterComponent = Ember.Component.extend({
count: 1,
actions: {
up: function(evt) {
var current = this.get('count');
this.set('count', current + 1);
}
}
});
<script type="text/x-handlebars">
<h2 id="toc-component6">Component</h2>
<ul>
{{#each model}}
<li>{{#counter}}{{name}}({{color}}){{/counter}}</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" id="components/counter">
<span>{{yield}} <span {{action 'up'}}>UP</span> - {{view.count}}</span>
</script>
まとめ
ComponentはViewをより抽象化し、コンテキストを持たせない代わりに外部からのコンテキストに依存した表示を展開できるようにした表示用の部品です。どの辺で使い分けるかについての判断は難しいかもしれませんが、使いこなせばカッコイイ機能ですね。
最後にコード全体を示します。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Component - Ember.js</title>
</head>
<body>
<script type="text/x-handlebars">
<h2 id="toc-component7">Component</h2>
<ul>
{{#each model}}
<li>{{#counter}}{{name}}({{color}}){{/counter}}</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" id="components/counter">
<span>{{yield}} <span {{action 'up'}}>UP</span> - {{view.count}}</span>
</script>
<script type="text/javascript" src="js/libs/jquery-1.9.1.js"></script>
<script type="text/javascript" src="js/libs/handlebars-1.0.0.js"></script>
<script type="text/javascript" src="js/libs/ember-1.0.0.js"></script>
<script type="text/javascript">
window.App = Ember.Application.create();
App.ApplicationRoute = Ember.Route.extend({
model: function(params) {
return [
{name: '百田夏菜子', color:'レッド' },
{name: '玉井詩織', color:'イエロー' },
{name: '佐々木彩夏', color:'ピンク' },
{name: '有安杏果', color:'グリーン' },
{name: '高城れに', color:'パープル' },
];
},
});
App.CounterComponent = Ember.Component.extend({
count: 1,
actions: {
up: function(evt) {
var current = this.get('count');
this.set('count', current + 1);
}
}
});
</script>
</body>
</html>