(この記事は、日本アイ・ビー・エム株式会社発行の「IBM developerWorks」から、日本アイ・ビー・エム株式会社の許可を得て転載したものです)
はじめに
IBM Mobile Foundationの一部となっているIBM Worklightは、さまざまな機器のプラットフォームで実行可能なWebベースの技術を使用して、迅速にモバイル・アプリケーションを作成するための堅牢なプラットフォームです。この記事では、第1回(あとでリンク)の続きとして、モバイル・ユーザーがタスクのToDoリストを作成して管理するために使用できる「Todo」という名前の完全に機能する自己完結型モバイル・アプリケーションの開発プロセスを進めます。このプロセスの中で、マルウェアによる攻撃や機器の盗難があった場合に機密情報を守るためのWorklightクライアント・ランタイムのセキュリティー対策機能である、暗号化オフライン・キャッシュ機能について説明します。
Worklightの基礎知識
無償で有効期限のないIBM Worklight Developer Edition 5.0を今すぐダウンロードしてください!
現時点で、EclipseIDEにはIBM Worklight Studioがセットアップされていて、皆さんは単純な「Hello World」タイプのアプリケーションをiOSとAndroidにデプロイする方法を十分理解できているはずです。今回は、第1回で開発を始めたTodoアプリケーションから取り掛かります。第1回で作成した初期アプリケーションをダウンロードして、Worklight Studio環境にインポートしてください(あるいは、この記事に付属のTodoアプリケーション・プロジェクト・ファイルをダウンロードすることもできます)。
アプリケーションの開発
この記事で説明するサンプル・アプリケーションは、例を示すことのみを目的とした演習として紹介しているものです。JavaScriptには大量のアプリケーション・ロジックが(特にjQueryを使用して) 取り込まれることになるため、読みやすく、信頼性が高く、矛盾のないような形でアプリケーション・ロジックを構造化するためのベスト・プラクティス、そして名前空間の使用方法に関するプラクティスなどを説明します。
図1に、Todoモバイル・アプリケーションを実行するユーザーの一般的な操作フローを示します。要約すると、ユーザーは以下の操作を行います。
- アプリケーションを開きます。
- オフライン・データをセキュアに保管するためのパスコードを入力し、「Start(スタート)」をタップして2番目のパネルに進みます。
- 最初のフィールドに新しい「Todo」項目を表すテキストを入力した後、「Add Item(項目の追加)」ボタンをタップして、その項目をリストに追加します。
- リスト内の項目をタップして「完了」のマークを付け、項目が選択された状態にします。「Remove Done(完了項目の削除)」をタップして、完了マークが付いたすべての項目を削除します。
- パネルに表示されている項目をフィルタリングする場合は、2番目のテキスト・フィールドに項目の名前またはその一部を入力し、「Filter Items...(項目のフィルタリング…)」をタップします。
以上の機能をアプリケーションに追加するために行う最初のステップは、コードをアプリケーション内で特定の役割を果たすモジュールに分割することです。Todoアプリケーションは、以下のモジュールで構成されることになります(図2を参照)。
- Constantモジュール: アプリケーションで使用するさまざまな定数の取得や設定を行います。
- Listモジュール: 現行セッションのリストを保管し、イベントを処理します。イベントには、リストへの新規項目の追加、完了項目へのマーク付け、完了マークが付いた項目の削除などがあります。
- Vaultモジュール: リストの暗号化および復号を行います。Worklightの暗号化オフライン・キャッシュ機能を利用するのは、このモジュールです。
コードを構造化するには、JavaScriptの世界で広く普及している「モジュール・パターン」を使用します(「参考文献」を参照)。リスト1に、モジュール・パターンのスケルトンを記載します。名前空間(MYAPP)に含まれるオブジェクト(Module1)には、自動実行関数 (即時関数) を割り当てます。このオブジェクトで使用するグローバル・オブジェクト (つまり、ウィンドウ。ほとんどのクライアント・サイドJavaScriptでは、ウィンドウがグローバル・オブジェクトの役目を持ちます) やベンダー・ライブラリー (つまり、jQueryなど。モジュールの外部でPrototype.jsや別のライブラリーが$を使用しているとしても、モジュール内で$を使用することができます) はこの関数に渡すようにするのが賢明です。また、依存関係をリストアップして、依存関係にローカル変数を割り当てるのも望ましいプラクティスです。それは、キーと値のペアを検索するよりも、ローカル変数にアクセスしたほうが早いからです。
以下はその一例です。
var eoc = WL.EncryptedCache; eoc.open()
いずれかのモジュールの中でopen関数を呼び出す必要が生じるたびに、JavaScriptでWLオブジェクトを検索し、続いてopen関数が含まれる別のオブジェクトを指すEncryptedCacheキーを検索するよりも、上記のように変数を割り当てるほうが賢い方法です。
MYAPP.Module1 = (function (global, $) { //List dependencies: jQuery 1.7.2 //List private variables //List private functions var _init = function () { console.log("I'm ready!"); } //List one time initialization procedures //public API return { init : _init }; }(window, jQuery)); //MYAPP.Module1
ここで、賢いコーディング・スタイルの規約を紹介しておきます。サンプル・コードでは、定数には大文字を使用し、プライベート関数の前にはアンダーバーを追加してプライベート変数と区別します。この規約を拡張し、変数を必要とするあらゆる関数の先頭で、1つのvarで複数の変数を宣言する「single var pattern」を使用することができます。このようにする理由としては、JavaScriptの関数内で宣言された変数はコードの実行時に「ホイスト」される (関数の先頭で宣言されたように扱われる)こと、また変数を使用し忘れると、望ましくない結果(同じ名前のグローバル変数が上書きされるなど)になる場合があることが挙げられます。
//Example of Hoisting myname = "global"; function func() { alert(myname); //returns: "undefined" var myname = "local"; alert(myname); //returns: "local"
モジュールを駆動するのは、クロージャーと呼ばれる基本構成概念です (「参考文献」を参照)。JavaScriptでは、クロージャーが参照する関数のローカル変数は、関数がリターンした後もそのまま保持されます。これは、JavaScriptで関数によって定義されるスコープに密接に関係します。リスト1に示されているクロージャーの使用例を見てください。ここで、_init() はプライベート関数であるため、MYAPP.Module1に割り当てられた自動実行関数の外部ではアクセスできませんが、このプライベート関数への参照を持つオブジェクトを返すことは可能です。したがって、MYAPP.Module1.init()とすれば、プライベート関数に簡単にアクセスすることができます。この仕組みを可能にするのが、クロージャーです。
JavaScript は、プログラミング・パターンについて書かれたほぼすべての本で説明されているObserverパターンと同様のイベント指向プログラミング言語です。JavaScriptのスクリプトでは、ユーザーがスクロールしたとき、HTMLタグをクリックしたとき、リンクにマウス・ポインターを重ねたときなどをはじめ、ユーザーが行うさまざまな操作のたびに、イベントが発生します。イベントはリッスンする対象とすることも、独自のイベントをトリガーすることもできます。
従うべき重要なプラクティスには、「関心の分離」もあります。これは、マークアップ、スタイル、アプリケーション・ロジックをそれぞれ切り離しておくことを意味します。HTMLコードは、アプリケーションの構造だけを記述していなければなりません。HTMLにインライン JavaScript関数を含めることは避けるべきです。それには、イベント駆動型のコーディング・スタイルを適用することができます。つまり、インライン呼び出しが必要な特定の要素をHTMLに含める代わりに、その要素に必要なアクションをトリガーする特定のイベントを観察します。こうすることにより、アプリケーション・ロジックをHTMLのあちこちに分散させるのではなく、JavaScript内だけに集めることができます。同様に、HTMLをJavaScriptの一部として作成して、テンプレートを代わりに使用することは避けてください (「参考文献」を参照)。
Constantモジュール
まずは、名前空間を定義するところから始めます (リスト3を参照)。このサンプルTodoアプリケーションで使用する名前空間には、「TD」という名前を付けます。名前空間は基本的に、アプリケーションに関係する他のすべてのオブジェクトを保持するオブジェクトです。アプリケーションの拡大に備え、TDをグローバル名前空間に定義しておくことも可能です。TDオブジェクトがまだ用意されていない場合は、「single var pattern」を使用してTDオブジェクトを作成してください。メソッド呼び出しの一貫性を確実にするのは、名前空間関数です。TD.namespace(TD.module1) を呼び出すと、module1という名前のオブジェクトがTDに追加されます。以下のリストを見ると、TD名前空間を宣言して、必要に応じてこのTD名前空間の下にオブジェクトを足していく方法がわかります。
/********************************************************************************** * TD NAMESPACE **********************************************************************************/ var TD = TD || {}; TD.namespace = function (ns_string) { var parts = ns_string.split('.'), parent = TD, i; // strip redundant leading global if (parts[0] === "TD") { parts = parts.slice(1); } for (i = 0; i < parts.length; i += 1) { // create a property if it doesn't exist if (typeof parent[parts[i]] === "undefined") { parent[parts[i]] = {}; } parent = parent[parts[i]]; } return parent; };
Worklight user documentationに記されているEncrypted Cache(暗号化キャッシュ)についての情報や「Worklight Developer's Reference Guide」も参照してください。
図3に示されているように、TD.Constant(このサンプル・アプリケーションのConstantモジュール)にはプライベート・メンバーがありません。このモジュールは、Public APIと名付けられたAPI(オブジェクト・リテラルを使用して作成された、単なるキーと値のペア) を介して値を返すだけです。同様に、Todo items keyは暗号化オフライン・キャッシュ機能に使用されます。encryptとdecryptの値は、単純なブール値です。これらの値によって、Vaultモジュールに暗号化または復号のどちらを行うかを指示します。
Listモジュール
図1とリスト4に示されているように、基本的にアプリケーションのメイン・ページとなるのは、このサンプル・アプリケーションの2番目のパネルです。このパネルには、以下の要素があります。
- ヘッダーに表示されるロゴ
- 新規項目を追加するときに使用するテキスト・フィールド(id="new-todo")
- 新規項目を追加するためのボタン(id="add")
- 完了マークが付けられたすべての項目を削除するためのボタン(id="delete")
- 項目リストを追加するための順不同リスト(id="todo-list")
- リストが空になっているときにテキストを表示するための div タグ (id="no_items")
また、data-filter=’true’ プロパティーを <ul> タグに追加すれば (「参考文献」を参照)、データをフィルタリングできることにも注意してください (http://jquerymobile.com/demos/1.1.0/docs/lists/index.html)。
<!-------------------------- MAIN PAGE --------------------------> <div data-role="page" id="main" > <!-- <div data-role="header"><h1>Todo App</h1></div> --> <img class="centerimg" src="images/logonobg.png"/> <div data-role="content" > <input type="text" name="new-todo" id="new-todo" placeholder="Write your tasks here..." /> <div class="controls" data-role="controlgroup" data-type="horizontal"> <a href="#" id="add" data-role="button" data-icon="plus">Add New</a> <a href="#" id="delete" data-role="button" data-icon="delete">Delete Done</a> </div> <ul id="todo-list" data-role="listview" data-filter="true" data-inset="true" > </ul><!-- /todo-list --> <div class="center" id="no_items"></div> </div> <!-- /content --> </div> <!-- /page -->
タスク・リストの項目は、以下の例に示すように、リスト配列に格納します。
[{content: “myTodo Item 1", done: false}, {content: “myTodo Item 2", done: true}].
これはキーと値のペアによる JavaScript オブジェクトで構成される単純な配列で、項目の名前を表す文字列と完了項目であるかどうかを追跡するためのブール値が値として設定されているものです。
図3に示されているように、TD.List(このサンプル・アプリケーションのListモジュール)には、_encrypt()というプライベート・メソッドが定義されています。このメソッドは Public APIからVaultモジュールのencrypt() メソッドを呼び出して、リスト(list という名前のプライベート配列)を暗号化します。TD.Listには、HTML文書内に定義された項目を表示するために、テンプレートから生成される_item_template()という関数もあります。この関数には、itemというキーと値としてリスト配列を持つオブジェクトを渡す必要があります。
演習として、より複雑な型チェック(例えば、item.doneがブール値を返すかどうかのチェック、nullに対するチェックなど) を実行することもできます。
プライベート関数_add()は、item.contentが空でないことや、’undefined’でないこと、そしてitem.doneを呼び出すと何らかの結果が返されることをチェックします。値が割り当てられていない変数は、デフォルトで‘undefined’を返すことに注意してください。
_add = function (item) { if (item.content !== 'undefined' && item.done !== 'undefined' && item.content.length > 0) { list.push(item); } },// add item
_refresh_list()関数は、リスト配列で動的にmap関数を実行して、その配列に格納されているすべてのオブジェクトに新しい要素を追加します。この関数の残りの部分では、リストをクリアし、リストを基に生成されたテンプレートの結果をHTML内の順不同リスト (ul) に追加した後、ビューを最新の表示に更新します。この最後のメソッド呼び出しは、リストのスタイルを最新の状態に更新するためのjQuery Mobile特有の呼び出しです。
_refresh_list = function () { $.map(list, function (item, i) { item.index = i; }); list_ul.empty(); list_ul.append(_item_template({item : list})); list_ul.listview('refresh'); if (list.length < 1) { no_items.text('No items.'); } else { no_items.text(''); } }; // refresh list
この手法を変更して、異なるデータ構造と異なるアルゴリズムを使用してIDの生成と追跡を行うようにすることもできます。その目標は、map関数を使用してリスト全体を繰り返し処理してIDを生成する必要をなくすこと、そして項目を追加または削除するたびに、リスト全体をクリアしてすべてを追加し直す必要をなくすことです。
次に、アクションをイベントにアタッチします。jQueryを使い慣れているとしたら、bind()、live()、delegate()などの呼び出しを見慣れていたり、使い慣れていたりすると思いますが、この例ではon()だけを使用します。jQuery1.7.0以降、これらの bind()、live()、delegate()といったメソッドは、単なる on() のラッパーになっているためです。また、$(this)は、$thisという名前のローカル変数に「キャッシュ」されます。これは、DOMの操作には非常にコストがかかるため、理想的には前述のjQuery関数はできるだけ使用しないようにして、DOM(Document Object Module)に入り込む時間を制限することが望まれるからです。この例では、完了項目であるかどうかを記述するブール値をトグルして、スタイルシート(.cssファイル)に定義されているdoneクラスを追加または削除していることに注意してください。最後に、項目をクリックして完了マークを付けた直後にアプリケーションを閉じる場合であっても、リストを encrypt()で暗号化してから永続化します。
add_btn.on('click', function () { _add({content : new_item.val(), done: false }); new_item.val(''); _refresh_list(); _encrypt(); }); // add btn list_ul.on('click', 'li', function () { var $this = $(this), $item = $this.find('a'), index = $item.attr('id'), current_item = list[index]; current_item.done = !(current_item.done); if (current_item.done) { $item.addClass('done'); } else { $item.removeClass('done'); } _encrypt(); }); // list item delete_btn.on('click', function () { var i = list.length; while (i--) { if (list[i].done) { list.remove(i); } } _refresh_list(); _encrypt(); });// delete btn
削除ボタンにも同様の処理が適用されます。そのためのコードは、Worklight Studioで表示することができます。
ListモジュールのPublic APIは極めて単純です。このAPIでは、これから作業する対象の最新のリストを取得するためのget()、新しいリストを設定して操作するためのset()、現在設定されているリストを最新の表示に更新するためのrefresh()を使用することができます。これらのメソッドを呼び出すには、TD名前空間の後にモジュールの名前を続け、最後にモジュールから返されるオブジェクトのキーのいずれかを追加して呼び出します。
以下の例を見てください。
TD.List.get(), TD.List.set([]), TD.List.refresh()
上記はすべて有効な関数です。次に、以下の例を見てください。
TD.List._refresh_list(), TD.List._add()
上記の関数は有効ではありません。これらの関数を呼び出せるのは、モジュール内から呼び出す場合(関数がスコープを作成することを思い出してください)、または特権メンバー(モジュール内で返されるオブジェクトなど)が呼び出す場合のみです。
//public API return { get : function () { return list; }, set : function (new_list) { list = new_list; }, refresh : function () { _refresh_list(); } };
Vaultモジュール
TD.Vaultモジュールは、Worklightの暗号化オフライン・キャッシュ(Encrypted Offline Cache:EOC)機能のラッパーです。EOCはローカル・ストレージ上にHTML5で作成され、cookieを使用しないで永続データを保管する方法となります。EOCには、ストレージをオープンおよびクローズするためのメソッド、そしてオープンされたストレージで値の読み取りおよび書き込みを行うためのメソッドが用意されています。EOCの背後にあるセキュリティーの詳細についてはこの記事では説明しませんが、要点を言えば、EOCはユーザー・パスワードを使用してデータを暗号化します。したがって、この特定のモジュールに関連するHTMLコードは、以下の要素が含まれるページ(図1の最初のパネル)となります。
- ロゴまたはアプリケーション名を表示するヘッダー・イメージ
- パスコードの入力フィールド (id=’passcode’)
- パスコードを送信するためのボタン (id=’send_passcode’)
<!-------------------------- PASSCODE PAGE --------------------------> <div data-role="page"> <img class="centerimg" src="images/logonobg.png"/> <div data-role="content"> <div class="center" id="invalid_passcode"></div> <ul data-role="listview" data-inset="true"> <li data-role="fieldcontain"> <label for="passcode">Enter Passcode:</label> <input type="text" name="passcode" id="passcode" value="" placeholder="Your passcode" /> </li> </ul> <a id="send_passcode" href="#" data-role="button" data-theme="a">Start</a> </div> <!-- /content --> </div> <!-- /page -->
JavaScriptコードでは、まず、グローバル変数を自動実行関数に渡します。この例で渡すグローバル変数は、jQuery$として再定義) とWL(WLとして再定義したWorklightの名前空間)です。次に、依存関係にローカル変数を割り当てることによって、依存関係を「キャッシュ」します。これにより、(WL名前空間をパラメーターとしてモジュールに渡すことなく)簡単にwl.EncryptedCache.openを呼び出して、eocという名前のローカル変数にwl.EncryptedCacheをキャッシュできるようになります。続いて、ユーザーが入力したパスフレーズをKEYというプライベート変数に格納し、パスコード・ボタン、パスコード・フィールド、そしてパスコードが無効な場合にエラー・メッセージを表示するdivに対応するDOM呼び出しを保存します(リスト10を参照)。
TD.namespace('TD.Vault'); TD.Vault = (function ($, wl) { //dependencies var eoc = wl.EncryptedCache, log = wl.Logger, list_obj = TD.List, CONST = TD.Constant, //private variables KEY = "", send_passcode_btn = $('#send_passcode'), passcode_fld = $('#passcode'), invalid_passcode_div = $('#invalid_passcode'),
Worklightの資料を調べて、onErrorHandler()コールバック関数から返されたステータス・コードに基づくエラー固有の処理を実装してください。
続いて、いくつかのプライベート関数を定義します。最初の_error()は、エラー・コールバックを受け取った場合にログに記録するだけの関数です。_setData()および_getData()メソッドは、それぞれ暗号化および復号するデータの取得方法と設定方法を指定します(リスト11を参照)。
//private functions _error = function () { log.debug("error"); }, _setData = function (new_list) { if (new_list) { list_obj.set(new_list); list_obj.refresh(); } }, _getData = function () { return list_obj.get(); },
イベント定義では、TD.Listと同様にsend_passcode_btnをクリック・イベントにアタッチし、ユーザーがアプリケーションの開始時に入力したパスコードの長さが1文字以上である場合、そのパスコードをプライベート変数KEYに割り当てます(リスト12を参照)。
send_passcode_btn.on('click', function () { var passcode = passcode_fld.val(); if (passcode.length > 0) { KEY = passcode; _decrypt(); $.mobile.changePage("#main", { transition: CONST.DEFAULT_TRANSITION }); } else { passcode_fld.val(''); invalid_passcode_div.text('Invalid Passcode.'); } });
次に、EOCの中身を復号するためにプライベート関数_decrypt()を呼び出します。この関数では、暗号化されたキャッシュをオープンした後に復号することになることを示す定数 (CONST.DECRYPT) を引数に指定して_open()を呼び出します。_open() 関数はeoc-openイベントをトリガーして該当するアクション(復号) を送信し、その結果として_read()が呼び出されます。_read()では、ユーザーが提供したキーを使用して、暗号化されたキャッシュの読み取りが試行されます。この時点で_setData()を呼び出し、parseJSON() によって構文解析されてJavaScript配列に戻されたデータを引数として渡します(この配列には、すべてのインデックスに対して1つの項目を表すキーと値のペアが含まれています)。キャッシュから何かが返された場合には、Listモジュールのset()を呼び出して、返されたリストを新しいリストとして設定します。そして最後に、TD.ListのPublic APIのrefresh()を呼び出して、HTMLのリスト要素を最新の表示に更新します。
_close = function () { var onCompleteHandler = function () { $.publish('eoc-closed'); }; //function(onCompleteHandler, onErrorHandler) eoc.close(onCompleteHandler, _error); }, _write = function () { var data = JSON.stringify(_getData()); //function(key, data, onCompleteHandler, onErrorHandler) eoc.write(CONST.TODO_ITEMS_KEY, data, _close, _error); }, _read = function () { var onCompleteHandler = function (data) { _setData($.parseJSON(data)); _close(); }; //function(key, onCompleteHandler, onErrorHandler) eoc.read(CONST.TODO_ITEMS_KEY, onCompleteHandler, _error); }, _encrypt = function () { _open(CONST.ENCRYPT); }, _decrypt = function () { _open(CONST.DECRYPT); }; $.subscribe("eoc-open", function (e, action) { if (action) { // == CONST.ENCRYPT _write(); } else { // == CONST.DECRYPT _read(); } });
暗号化もほとんど同じステップに従います。異なる点は、キャッシュをオープンした後のアクションが _read()ではなく_write()の呼び出しであることです。その後_getData()を実行して最新のリストを取得し、それをJSON.stringify()で文字列に変換した上で、そのストリングをキャッシュに格納します。暗号化および復号が完了するごとに、キャッシュをクローズしてください。
図4と図5に、完成したアプリケーションがiPhoneシミュレーター上で実行されている様子を示します。
まとめ
IBM Worklightを紹介するこの連載の第2回では、第1回でセットアップしたWorklight開発環境を使用して、機能を追加し、Todoサンプル・アプリケーションを作成しました。その過程で、コードを構造化することによって、アプリケーションの開発、表示、保守にどのようなメリットがもたらされるかを学びました。さらに、Worklightの暗号化オフライン・キャッシュ機能を使用してデータ(タスク・リスト)を機器に永続化する方法についても学びました。この連載の最終回では、アダプターによるサーバー・サイドの接続性を追加して、サンプル・アプリケーションを仕上げます。
ダウンロード
内容 | ファイル名 | サイズ | ダウンロード形式 |
Sample application project files | todo-app-part2.zip | 18.5MB | HTTP |
Worklightには、開発環境用に無償・無期限で使用できるDeveloper Editionが提供されています(WindowsおよびMacに対応)。ぜひ実際に触って使い勝手をお試しください。
参考文献
学ぶために
- Worklight製品情報
- IBM Worklightユーザー・マニュアル
- Apple 開発者向けサイト
- Worklightの開始手順
- ボイラープレート・コード
- IBM Worklight Studio製品情報
- IBM Worklight - モバイル・セキュリティー対策
- IBM developerWorks WebSphere