前回はユーザのインタラクションを受け取る方法を解説しました。今回はこれまでの連載内容を踏まえつつ、
今回取り扱うEmber.
「商品を選択してカートに入れ、
前準備
本稿の対象バージョンはこちらです。
第3回の記事を参考にして必要なファイルを作成してください。
ファイルの作成が完了したら、
まずはルーティングとデータを準備します。
App = Ember.Application.create();
App.Router.map(function() {
this.resource('products', {path: '/'}, function() {
this.route('show', {path: 'products/:id'});
});
this.resource('cart');
});
App.ProductsRoute = Ember.Route.extend({
model: function() {
return [{
id: 1,
name: 'ステッカー',
price: 6.0,
url: 'http://devswag.com/products/ember-sticker'
}, {
id: 2,
name: 'Tシャツ',
price: 22.0,
url: 'http://devswag.com/products/ember-js-tshirt'
}, {
id: 3,
name: 'ぬいぐるみ',
price: 10.0,
url: 'http://devswag.com/products/ember-mascot-tomster'
}];
}
});
App.ProductsShowRoute = Ember.Route.extend({
model: function(params) {
return this.modelFor('products').filter(function(product) {
return product.id === Number(params.id);
})[0];
}
});
次はテンプレートを準備します。
<script type="text/x-handlebars">
<header>
{{link-to "商品一覧" "products"}}
{{link-to "カート" "cart"}}
</header>
<main>
{{outlet}}
</main>
</script>
<script type="text/x-handlebars" data-template-name="products/index">
<h1>注目の商品</h1>
<ul>
{{#each product in model}}
<li>{{link-to product.name "products.show" product}}</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="products/show">
<h2>{{model.name}}</h2>
<a target="_blank" {{bind-attr href=model.url}}>詳しく見る</a>
${{model.price}} USD
<button>カートに入れる</button>
</script>
<script type="text/x-handlebars" data-template-name="cart">
<h1>注文確認</h1>
カートは空です。
</script>
さて、
ではこれからカートを動くようにしていきましょう。
ArrayController
まずは、
App.CartController = Ember.ArrayController.extend({
});
ここで初めて登場したArrayController
について説明します。Ember.
はEmber.
の仲間ですが、
ArrayControllerには配列操作のメソッドが数多く定義されており、model
プロパティに設定された配列に対して様々な操作ができます
JavaScriptネイティブのArrayに定義されているメソッドと比較した時の大きな違いは、
今回、CartController
に保存することにします。
ProductsRoute
にactions
を追記してください。
App.ProductsRoute = Ember.Route.extend({
// ...
actions: {
addCart: function(product) {
this.controllerFor('cart').pushObject(product);
this.transitionTo('cart');
}
}
});
products/
テンプレート中のaction
を指定します。
<button {{action "addCart" model}}>カートに入れる</button>
action
ヘルパーは、
次はcart
のテンプレートを以下の内容で書き換えてください。
<h1>注文確認</h1>
{{model.length}}個の商品がカートに入っています。
<ul>
{{#each product in model}}
<li>{{product.name}}: ${{product.price}} USD</li>
{{/each}}
</ul>
ここまでで、
ではここで、
Route#controllerFor()
Route
に定義されているcontrollerFor
メソッドを使うことで、任意のコントローラのインスタンスを取得できます。ここではカートに商品を追加するために、 ProductsRoute
からCartController
にアクセスしています。ArrayController#pushObject
配列に要素を追加します。ネイティブの
Array
のpush
メソッドに相当します。実際はmodel
プロパティに設定された配列に要素が追加されます。
さて、ArrayController
のメソッドがでてきたところで、Array
について少し詳しく見てみることにしましょう。
Array
ArrayController
に定義されている配列操作のメソッドは、Array
のプロトタイプにも定義されています。
JavaScriptにおけるプロトタイプ拡張には賛否ありますが、
ここでは、pushObject
を含めEmber.
pushObject()
配列に要素を追加します。
var array = ['hi']; array.pushObject('yo'); array; //=> ['hi', 'yo']
addObject()
pushObject
によく似ていますが、追加しようとしている要素がすでに存在する場合には何も行いません。 var array = ['hi']; array.addObject('hi'); array; //=> ['hi']
findBy()
配列の中から特定の要素を探します。要素のプロパティに対して探索条件を指定できます。
var array = [{name: 'a'}, {name: 'b'}, {name: 'c'}]; array.findBy('name', 'b'); //=> {name: 'b'}
mapBy()
配列中の要素の特定のプロパティを取り出して新しい配列を返します。
var array = [{name: 'a'}, {name: 'b'}, {name: 'c'}]; array.mapBy('name'); //=> ['a', 'b', 'c']
この他にもたくさんのメソッドが提供されています。ぜひ公式リファレンスに目を通してみてください。
ちなみに、findBy
を使うと、App.
のmodel
メソッドの実装を簡素化できます。
App.ProductsShowRoute = Ember.Route.extend({
model: function(params) {
return this.modelFor('products').findBy('id', Number(params.id));
}
});
プロトタイプ拡張を禁止する
また、
<script>
window.EmberENV = {
EXTEND_PROTOTYPES: false
};
</script>
<script src="libs/ember.js"></script>
この状態でEmber.Ember.
メソッドを利用して配列を拡張する必要があります
var array = [];
typeof array.pushObject; //=> 'undefined'
array = Ember.A(array);
typeof array.pushObject; //=> 'function'
横道に逸れてしまいましたが、Array
の話になります。
ではカートの開発に戻ることにしましょう。
Computed Property
ここまでで商品をカートに追加できるようになりました。ただ、
そこで、
まずは
CartController
を次のように変更します。
App.CartController = Ember.ArrayController.extend({
uniqProductCount: function() {
return this.mapBy('id').uniq().get('length');
}.property('length')
});
cart
テンプレートの商品数を表示している部分を次のように変更します。
{{uniqProductCount}}種類({{model.length}}個)の商品がカートに入っています。
さて、
function() {
// ...
}.property()
ここではComputed Propertyと呼ばれるEmber.
Computed Propertyとは
Computed Propertyについて一言で説明すると
このFunction
のproperty()
メソッドは、Function
のプロトタイプを拡張して提供しているメソッドで、Ember.
オブジェクトを返します。property()
メソッドの引数には、
今回のCartController
のケースだと、uniqProductCount
)」はlength
)」から算出されます。
もし複数のプロパティから算出されるComputed Propertyを定義したい場合には、
fullName: function() {
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName')
この例では、fullName
を参照するとfirstName
とlastName
からfullName
が算出されます。一度算出された値はキャッシュされ、firstName
、lastName
のどちらかが変更されるまでfullName
の結果は再計算されません。
ここまで見ると、
メソッドとの違い
Ember.
Ember.
プロトタイプ拡張
Function.
のプロトタイプ拡張を禁止したい場合は、
その場合、Ember.
メソッドを経由してComputed Propertyを利用します。
Ember.computed([依存プロパティ名1, 依存プロパティ名2, ...], function() {
// ...
});
さて、
CartController
にComputed Propertyの定義を追加します。
App.CartController = Ember.ArrayController.extend({
// ...
totalPrice: function() {
return this.mapBy('price').reduce(function(total, price) {
return total + price;
}, 0);
}.property('@each.price')
});
cart
テンプレートに次の行を追記します。
合計金額: ${{totalPrice}} USD
property()
メソッドに指定している@each
は、
以上でComputed Propertyについての解説は終わりです。
最後に、
html, body {
margin: 20px;
color: #222;
}
header, main {
max-width: 800px;
margin: auto;
}
header {
border-bottom: #ddd solid 1px;
}
a {
color: #222;
}
a:hover {
color: #555;
}
header a {
display: inline-block;
text-decoration: none;
padding: 10px 2px;
width: 100px;
text-align: center;
border-radius: 4px 4px 0 0;
border: 1px solid #ddd;
border-bottom: none;
}
header a:hover {
background-color: #eee;
}
header a.active, header a.active:hover {
color: #222;
cursor: default;
background-color: white;
border-bottom: white solid 1px;
margin-bottom: -1px;
}
button {
border: 1px solid transparent;
border-radius: 4px;
background-color: white;
color: black;
border: #ddd solid 1px;
padding: 4px;
}
button:hover {
background-color: #eee;
}
ここまでで、
まとめ
今回はEmber.
- カートの中身が保存されない
- 一度選択した商品は取り消しできない
- 商品を購入できない
次回はこういったショッピングカートとしては当たり前の機能を作成しつつ、