« Rails で Yahoo 検索 API + Widgets Tabnav でタブナビゲーション | メイン | Restletとjson-lib を使ったWebサービスへのアクセス »

楽天API+SWFAddress2.0を利用したストアのマッシュアップはてなブックマークに追加 livedoorクリップに追加 Yahoo!ブックマークに追加 del.icio.usに追加 イザ!ブックマーク ニフティクリップに追加

こんにちは、JavaScript担当の(株)アークウェブの竹村です。


前回の『SWFAddress2.0を利用した「状態」による機能制御』でSWFAddressのによるディープリンクの作り方と、通常のイベント駆動との違いについて理解いただけたと思います。

今回はこれを利用して、JavaScriptだけで楽天APIとマッシュアップしたストアのサイトを作ってみたいと思います。

レジュメは↓このようになっています。

  • Ajaxストア つくるぶ店
  • 楽天のAPI
  • 状態の設計
  • 初期化と状態制御部分
  • 楽天APIでJSONを取得するまで
  • 取得したJSONの出力はテンプレートを外部ファイルで
  • 詳細側も同じ要領で出力
  • ディープリンクにアクセスしてみる
  • まとめ

Ajaxストア つくるぶ店

どういうアプリか完成品を見てみてください。
名づけて『Ajaxストア つくるぶ店』ですっ!

▼Ajaxストア つくるぶ店
ajax_store.jpg
http://okra.ark-web.jp/~takemura/public/js/swfaddress2_store_demo/#/detail/?category=100317&product=wineya:664477

楽天のAPI

まずはマッシュアップする楽天のAPIについて紹介します。

まずは、トップページのメニューにある「ご利用方法」の「デベロッパーIDを取得」から指示された内容にしたがってデベロッパーIDを作ります。

これだけでAPIへのアクセスが可能になりました。
↓下記の[YOUR_developerID]を変更して、1行にしてブラウザのアドレスバーに入力して楽天APIへアクセスしてみてください。

http://api.rakuten.co.jp/rws/1.11/json?
developerId=[YOUR_developerID]
&operation=ItemSearch
&version=2007-10-25
&callBack=dummyFunction

↓このような画面が表示されたと思います。

楽天API実行サンプル

URLにcallBackを指定しているので、JSONPとして出力するように指示しています。
よって、dummyFunction({JSON}); という形式になっています。

APIの入力値と返されるJSON値については、楽天APIのサイトから確認してください。

状態の設計

APIで商品情報を取得する準備ができたところで、このWebサイトの設計をしましょう。

今回の肝は「ストアをSWFAddressによって状態管理する」ということですので、まずは状態の設計をします。

イメージとしては、↓こんな感じで考えていました。

layout.jpg

  • カテゴリ選択トップ
  • 商品一覧
  • 商品詳細

このそれぞれの段階でディープリンクが生成されるようにします。
※トップはディープリンクを外した状態にします

初期化と状態制御部分

まずは初期化とSWFAddressによるディープリンク変更が起こった時に状態を制御するcontrollerを見ていきましょう。

<script type="text/javascript" src="js/jsr_class.js"></script> <script type="text/javascript" src="js/prototype.js"></script> <script type="text/javascript" src="js/swfaddress.js"></script> <script type="text/javascript"> // グローバル変数 (...) // === 初期化 ===== document.observe("dom:loaded", initialize, false); function initialize() { // カテゴリ変更時のイベントハンドラの設定 $('form-categoryselect').onclick = function(){ setSWFAddress('listing', $F('form-category')); }; // SWFAddressのイベント設定 SWFAddress.addEventListener(SWFAddressEvent.CHANGE, controller); } function controller(event) { var iCategory = SWFAddress.getParameter('category'); giCategory = iCategory; var iProduct = SWFAddress.getParameter('product'); switch(SWFAddress.getPath()) { default: break; case 'listing': if (iCategory == '') { alert('カテゴリが選択されていません'); return; }; resetGlobalJson(); sendListing(iCategory); break; case 'detail': if (iCategory == '') { alert('カテゴリが選択されていません'); return; }; if (iProduct == '') { alert('商品が選択されていません'); return; }; sendDetail(iCategory, iProduct); sendListing(iCategory); break; } } (...) // === 共通関数 ===== function setSWFAddress(sPath, iCategory, iProduct) { switch (sPath) { case 'listing': SWFAddress.setValue('listing/?category='+iCategory); break; case 'detail': SWFAddress.setValue('detail/?category='+iCategory+'&product='+iProduct); break; default: SWFAddress.setValue(''); } } (...) </script> (...) <div id="category-select"> <p>■カテゴリ選択</p> <select id="form-category" name="category"> <option value="100227">食品</option> <option value="100317">ワイン</option> <option value="551167">スイーツ</option> </select> <input type="button" id="form-categoryselect" name="button" value="カテゴリ決定" />

initialize関数のイベントハンドラの設定はフォームの値を参照して、共通関数に記述したsetSWFAddress関数を呼んでいます。setSWFAddress関数では、次に取るべく状態に応じたディープリンクを SWFAddress.setValue で作成しています。

このディープリンクを利用できるように SWFAddress.addEventListener へcontrollerを指定してあります。

controllerでは SWFAddress.getPath で状態名を取得し、SWFAddress.getParameter でパラメータを取得してから、状態に応じた処理が書かれています。

楽天APIでJSONを取得するまで

つぎは、listingの状態の時に実行する sendListing関数から楽天APIでJSONを取得するまでをカンタンに見ていきます。

(...) // グローバル変数 var gsRakutenAPIListingURL = 'http://api.rakuten.co.jp/rws/1.11/json?developerId=9ad39aef6903b01ca205151220b7c47e&operation=ItemSearch&version=2007-10-25&sort=random'; (...) // === リスト表示 ===== function sendListing(iCategory) { (...) var sUrl = gsRakutenAPIListingURL +'&genreId='+ iCategory; sUrl += '&callBack=listingCallback&hits=5'; // script タグの発行 goJsr4Listing = new JSONscriptRequest(sUrl); goJsr4Listing.buildScriptTag(); goJsr4Listing.addScriptTag(); } function listingCallback(oJson) { // script タグの削除 goJsr4Listing.removeScriptTag(); (...) }

JSONPを利用したいので、毎度おなじみのjsr_class.jsを利用しています。

JSONPなので、データを受ける際に実行される関数は &callBack=listingCallback として楽天APIに送っています。

listingCallback関数の oJson に受信したデータが入っているので、このデータを組み立てて出力します。

次は、出力部分について追っていきましょう。

取得したJSONの出力はテンプレートを外部ファイルで

楽天APIで取得したJSONを出力する箇所を見ていきます。

function listingCallback(oJson) { (...) var aItem = oJson['Body']['ItemSearch']['Items']['Item']; // 楽天API形式から表示用に変換する aItem = convertRakutenAPIResult(aItem); // タイトルを変更する SWFAddress.setTitle(convCategoryId2Name(giCategory) +' | '+ gsTitleBase); // テンプレートから内容を生成して出力する makeContents($('listing'), 'listing', aItem); } (...) // === 共通関数 ===== function convertRakutenAPIResult(aItem) { for (var i = 0 ; i < aItem.length ; i++) { // 商品名が大抵やたら長いので途中で切ったものを作っておく aItem[i]['itemName_short'] = aItem[i]['itemName'].substr(0,20); if (aItem[i]['itemName'].length > 20) aItem[i]['itemName_short'] += '...'; // 親のカテゴリIDも取っておく aItem[i]['parent_genreId'] = giCategory; // 消費税フラグの数値化 aItem[i]['tax'] = aItem[i]['taxFlag'] == 0? '(税込)' : '(税別)'; // 送料フラグの数値化 aItem[i]['postage'] = aItem[i]['postageFlag'] == 0? '送料無料' : '送料別'; // クレジットカード利用可能フラグの数値化 aItem[i]['creditCard'] = aItem[i]['creditCardFlag'] == 0? 'カード利用不可' : 'カード利用可'; } return aItem; } function makeContents(oOutput, sPath, aItem) { new Ajax.Request( getTemplate(sPath), {onComplete:function(oResponse){ makeContentsCallback(oOutput, aItem, oResponse);} } ); } function getTemplate(sPath) { switch (sPath) { case 'listing': return 'templates/listing.html'; case 'detail': return 'templates/detail.html'; default: alert('sPath未指定 by getTemplate'); } } function makeContentsCallback(oOutput, aItem, oResponse) { var sTemplate = oResponse.responseText; var sContents = ''; for (var i = 0 ; i < aItem.length ; i++) { sContents += sTemplate.interpolate(aItem[i]); } oOutput.innerHTML = sContents; }

ちょっと長いですが、listingCallback関数を見ていただくと分かるようにHTMLとして出力するまでに3つのステップが記述してあります。

ステップ1) 楽天API形式から表示用に変換

楽天APIは、「消費税」「送料」「クレジットカード利用可能」という部分はフラグで渡ってくるので、このデータを出力する際に文言として変換しておきます。

リスト表示では、商品名を短く表示したかったのでJSONに itemName_short というデータを独自に作って入れています。

ステップ2) タイトルを変更

前回も書きましたが、カテゴリを変えたときや商品詳細で別商品を選択された状態に遷移したときは、タイトルを変更しておくと、ブラウザの「戻る」をリストさせたときにどこにもどれるのか一目瞭然となるのでユーザビリティがあがるのと、SEO的に有利になるでしょう。

ステップ3) テンプレートから内容を生成して出力

今回、テンプレートは外部ファイル化するようにしました。これによって、デザイナとの連携がしやすくなりますし、なによりJavaScriptの '(シンクグクォート)などで括らなくていいのが楽チンです。


makeContents関数で外部ファイルを読み込むようにして、Ajaxで受信完了したらmakeContentsCallback関数を実行するようにしています。

makeContentsCallback関数では、テンプレートに prototype.js のinterpolateでデータを置換しながら投入し、出力オブジェクトのinnerHTMLへ突っ込んでいます。

詳細側も同じ要領で出力

詳細側も同じ要領で出力できます。

// グローバル変数 var gsRakutenAPIDetailURL = 'http://api.rakuten.co.jp/rws/1.11/json?developerId=9ad39aef6903b01ca205151220b7c47e&operation=ItemCodeSearch&version=2007-04-11'; (...) // === 詳細表示 ===== function sendDetail(iCategory, iProduct) { var sUrl = gsRakutenAPIDetailURL +'&itemCode='+ iProduct; sUrl += '&callBack=detailCallback'; // script タグの発行 goJsr4Detail = new JSONscriptRequest(sUrl); goJsr4Detail.buildScriptTag(); goJsr4Detail.addScriptTag(); } function detailCallback(oJson) { // script タグの削除 goJsr4Detail.removeScriptTag(); var aItem = oJson['Body']['ItemCodeSearch']['Items']['Item']; // 楽天API形式から表示用に変換する aItem = convertRakutenAPIResult(aItem); // タイトルを変更する SWFAddress.setTitle(aItem[0]['itemName_short'] +' | '+ gsTitleBase); // テンプレートから内容を生成して出力する makeContents($('detail'), 'detail', aItem); }

sendDetail関数で、楽天APIにアクセスしてJSONPで detailCallback関数が実行されるように仕込んでおき、detailCallback関数で、リスト出力と同じ3つのステップで出力しているだけになっています。

ディープリンクにアクセスしてみる

前回同様にディープリンクを使ってアクセスしてみます。

  • トップ
    普通のトップ。これは何も変わらずです。
  • リスト表示
    スイーツカテゴリのリスト表示
  • 詳細表示
    『コーティングチョコレート(ミルク) 100g』という商品の詳細表示

一覧表示、詳細表示ともにアクセスすると楽天APIで取ってきた内容が表示されたと思います。

ローディングを端折っているので「○○を決めてください」的な文言が出てしまっていてちょっと見苦しいですが、とりあえずサンプルということでシンプルに書いてあります。

まとめ

2回に渡って紹介してきました『SWFAddress2.0』というライブラリについて、使い方と事例をご理解いただけたでしょうか?

AjaxやFlashを含めた RIA(リッチインターネットアプリケーション)を構築する上ではURLによる画面遷移が無いため、1画面を長く使うようなアプリケーションを構築する必要性が多くなってくると思います。その時に、このようなディープリンクを作るライブラリを思い出していただければと思います。

また、状態管理を使った設計は通常のイベント駆動とは異なり、イベントハンドラに直接動作を記述すのではなく、一旦イベントのコントローラに制御を渡してから、コントローラから動作するようにすることで、任意の状態を復元できるように設計する必要がある。ということを気をつけていただければと思います。