前回はショッピングカートを作成を通じて、Ember.jsの汎用的な機能を解説しました。 今回は前回作成したショッピングカートを拡張しつつ、さらに他のEmber.jsの機能を紹介します。
今回取り扱うEmber.jsアプリケーションはこちらです。
前回からの差分は次のとおりです。
- カートの中身が保存される
- 注文画面で商品の個数を変更できる
- 注文を確定できる
前準備
さて、さっそくショッピングカートを拡張して……と進みたいところですが、まずは少しだけ準備をしてから進むことにしましょう。実はつい先日の2月7日にEmber.js 1.10.0がリリースされました。
このバージョンからテンプレート記述言語がHandlebarsからHTMLBarsに変更になりました。HTMLBarsはHnadlebarsを元にEmber.jsのために実装されたテンプレート記述言語で、より柔軟な記述とパフォーマンス改善を目的としています。とは言っても、まったく新しい文法になるわけではありません。Handlebarsの機能に追加して、今後段階的に新しい機能が追加されていく予定です。
詳しい変更点とEmber.js 1.9.1からのアップデート手順はEmber.js 1.10.0のリリースノートで解説されています。
本稿でもこの手順にしたがってEmber.js 1.10.0にアップデートすることにしましょう。
ということで、本稿の対象バージョンはこちらです。
Ember.js のバージョンアップ
特に重要な点は次の2つです。
handlebars.js
の代わりにember-template-compiler.js
を読み込む
今までhandlebars.js
をscriptタグで読み込んで利用していましたが、Ember.js 1.10.0からはHTMLBarsを同梱したember-template-compiler.js
を利用します[1]。
ember.js
の代わりにember.debug.js
を読み込む
開発用のデバッグ出力機能が充実したember.debug.js
と、本番用にデバッグ処理をなくしてパフォーマンスを向上させたember.prod.js
があります。Ember.js 1.9.1まで開発用のファイル名はember.js
だったのですが、開発用・本番用の区別をしやすくするために名前が変更されました。
さて、これらを踏まえて準備しましょう。第5回で作成したアプリケーションを次のように変更します。
- まずは、必要なファイルをダウンロードして
libs
ディレクトリに保存します。
index.html
のscriptタグを以下のように記述します。
以上で準備完了です。では、さっそくショッピングカートに機能を追加していきましょう。
カートの保存
ここまでのショッピングカートだとブラウザをリロードするとカートの中身が失われてしまっていました。これでは安心して買い物ができません。そこでカートの中身をlocalStorageに保存することで、ブラウザをリロードしてもカートの中身が失われないようにします。
方針は次のとおりです。
- 商品をカートに入れたタイミングで商品IDをlocalStorageに保存する
- 画面を表示したタイミングでlocalStorageから商品のIDを読み出しカートの中身を復元する
商品IDを保存する
まずはCartController
にカートの中身を保存するメソッドを追加します。
localStorageに保存できる値は文字列のみなので、JSON.stringify()
しているのがポイントです。
続いて、ProductsRoute
のaddCart
アクションでsave()
メソッドを呼びます。
これでカートに追加した商品のIDをlocalStorageに保存できるようになりました。まだカートの内容を復元する処理を入れていないので画面からの確認はできませんが、開発者コンソールからであれば確認できます。
いくつかの商品をカートに入れた状態で次のコードをコンソールに打ち込んでみてください。
選択した商品のIDが表示されたでしょうか?
さて、ここまでで商品IDを保存できるようになりましたが、このIDから商品を辿るにはどうすればよいでしょうか? 商品一覧はProductsRoute
の中にだけ存在しているので、今のままではCartController
から商品一覧を取得できません。
そこで、商品管理の仕組みを整理して必要に応じて参照できるようにしておきましょう。Ember.Object
というEmber.jsが提供するクラスの仕組みを利用して、商品オブジェクトを問い合わせるためのメソッドを持ったクラスを作成することにします[2]。
Ember.Object
「商品」をクラスとして表現し、IDから商品を取得するクラスメソッドを作成します。
今まで特に解説していませんでしたが、extend()
メソッドを使うことで既存のクラスを継承して新しいクラスを定義できます。その際、引数のオブジェクトに初期値とメソッドを指定します。
reopenClass()
メソッドを使うと、クラスを拡張できます。引数としてオブジェクトを渡すと、プロパティやメソッドがクラスに定義されます。
上記の例では、App.Product.data
というプロパティとApp.Product.all()
・App.Product.find()
というメソッドが定義されます。
商品データを用意します。ここで登場したpushObjects()
メソッドは、複数の要素をまとめて配列に追加するためのメソッドです。
また、create()
メソッドを使うことでインスタンスを生成できます。
これで商品データの準備と商品を問い合わせるメソッドが完成しました。App.Product.all()
を使うと商品全件を、App.Product.find(id)
を使うとIDを元に1件の商品を取得できます。
ProductsRoute
のmodel()
メソッドでもApp.Product.all()
メソッドを使うようにしましょう。
カートを復元する
次はカートを復元する処理を組み込みます。CartController
にカートの中身を復元するメソッドを追加します。
商品IDはJSON.stringify()
を使って文字列で保存してあるため、読み出すときにはJSON.parse()
を使って配列に変換しています。
あとは、restore()
メソッドを画面表示時に実行できればカートの中身を復元できそうですね。Ember.jsで「アプリケーション起動時に実行される処理」を設定するにはinitializer
を利用します。
このinitializerについて解説します。
name
initializerの名前です。複数のinitializer同士の間で実行順序を制御したいときに利用できます。
initialize
アプリケーション初期化時に実行される関数です。実行したい処理を記述します。
container
(initialize
の第一引数)
Ember.jsのDIコンテナです。lookup
メソッドに「タイプ:名前」の形式で問い合わせると指定のオブジェクト取得できます。ここではCartController
のインスタンスを取得しています。
app
(initialize
の第二引数)
Ember.jsアプリケーションのインスタンスです。App
と同じオブジェクトが取得できます。
以上、initializerの解説でした。
ここまでで、画面を表示した際にカートを復元できるようになりました。
まとめて表示する
カートの中身を保存できるようになりましたが、今のカートでは商品をひとつひとつ表示しているだけなので同じ商品を複数選択している場合の見通しがよくありません。そこで、「商品」という単位ではなく、商品+個数+小計という「明細」の単位でカートを表示してみましょう。さきほど登場したEmber.Object
を使って、「明細」を表すクラスを定義することにします。
クラスを定義する際には、Computed Propertyも一緒に定義できます。
続いてCartController
では、Product
を直接参照する代わりにこのOrderLine
を利用して選択した商品を管理することにします。CartController
のmodel
を変更することになるため、今までmapBy()
やpushObject()
など商品の配列に対して操作していた部分がそのままでは使えなくなります。CartController
全体を書き直すことにしましょう。
同様に、ProductsRoute
でCartController
のpushObject()
メソッドを呼んでいた部分もaddProduct()
メソッドを使うように変更しましょう。
cart
テンプレートも同様に変更します。
見やすさのために<li>
の代わりに<table>
を使うようにしました。次のCSSを追記してもう少しだけ見栄えを整えておきましょう。
これでカートが明細行で表示されるようになりました。
商品の個数変更
さて、せっかく明細を表示するようにしたので、カートの商品を確認している途中で購入予定の商品の個数を変更できるようにしてみましょう。
cart
テンプレートにボタンを追加します。
{{#each orderLine in model}}
~{{/each}}
中の<tr>
タグの中にボタンを追加してください。
これに対応するactions
をCartController
に定義します。
incrementProperty()
・decrementProperty()
メソッドはEmber.Object
が提供するメソッドです。指定したプロパティの値を増減できます。
さて、実はさきほどの機能追加が原因で今まで動いていた部分が一部動かなくなっていることに気がついたでしょうか? それが何かというと、カートの中身を保存する部分です。「商品の個数を変更する」という操作を追加しましたが、そのタイミングでカートの保存は行っていませんでした。
必要な箇所すべてでCartController
のsave()
を呼び出せば再びカートの中身が保存されるようになりますが、今回の例では「カートの中身が変更した時点で自動で保存される」ようにしたいだけです。それに、何かカートの操作を追加するたびにsave()
メソッドを呼び出すのは煩雑なうえに忘れがちになってしまいます。
Observer
Ember.jsにはObserver
という機能があり、これを使うと値が変更されたタイミングで自動で実行されるメソッドを定義できます。ここではこのObserver
を使って、カートの中身を保存することにしましょう。
CartController
のsave()
メソッドに次の記述を追加します。
このobserves()
メソッドがポイントです。これまでsave()
メソッドは必要になったタイミングで適宜実行していましたが、observes()
メソッドを使うとsave()
メソッドがオブザーバーとしてオブジェクトに設定されます。
observes()
メソッドの引数には、Computed Propertyと同じく監視するプロパティ名を指定します[3]。
ここでひとつ注意したいことがあります。カートの内容を復元する際にmodel
に明細を追加しているため、 このままではカート復元中にsave()
メソッドが実行されてしまいます。
これを防ぐため、カートの準備が整ったあとで自動保存するようにしましょう。
CartController
にisReady
というプロパティを追加し、ここでカートの準備が整ったたどうかを管理します。もしカートの準備が整ってなければ、自動保存は行いません。
購入
さて、ここまででカートの機能が充実してきました。次はいよいよ購入処理を実装します。
とは言っても、実際に何かを購入できるようにするわけではありません。「購入する」というリクエストを自分のアプリケーションに投げたつもりになってリクエストパラメータを組み立てるところまでを実装します。
まずはcart
テンプレートに購入ボタンを追加します。
このボタンに対応するactions
をCartController
に追記します。
alert()
を呼ぶ際に、組み立てたリクエストパラメータを表示してみています。ここでは、次の形でリクエストパラメータを組み立てました。
アプリケーションサーバを用意したとすると、この形のパラメータを受け取って実際に購入処理を行うことになるでしょう。もちろん、アプリケーションサーバの都合に合わせて組み立てるパラメータの形を変更してもかまいません。
ここまでで、商品の選択から商品の購入の雰囲気を感じることはできたでしょうか。本来であれば名前や住所の入力、クレジットカードによる決済などがさらに必要だと考えられますが、 いずれもEmber.jsの入門記事の枠を超えてしまうため本稿では扱いません。
今回のアプリケーションは最終的にこのようになりました。
まとめ
前回・今回を通じてEmber.jsの汎用的な機能を解説しました。Ember.jsには他にもまだまだ多くの機能がありますが、比較的に利用頻度が多いと筆者が感じているものを取り上げました。
またEmber.js公式ガイドの"THE OBJECT MODEL"にも今回取り上げた機能が解説されています。さらなる理解のために、ぜひこちらにも目を通してみてください。