![]() ![]() ![]() ![]() AJAXとDojoとStrutsを組み合わせWebページを高速化するこの記事のURLhttp://japan.internet.com/developer/20060926/26.html
著者:Doug Tillman
海外internet.com発の記事
はじめにWebページの開発に携わっている人なら、きっとAJAX(Asynchronous JavaScript and XML)について聞いたことがあるでしょう。AJAXコールを慎重に利用すると、サイトの操作性を向上させ、人目を引くようなブラウザクライアントアプリケーションを作成することができます。本稿では、AJAXを使ってStrutsアクションを呼び出す方法と、Tilesを使って呼び出し側ページの更新応答を簡単に作成する方法について説明します。StrutsやTiles、JavaScriptを使用した経験がない人は、本稿の最後で紹介しているチュートリアルや文献に目を通して、背景知識を把握しておくとよいでしょう。JavaScriptの書き方を十分に理解していて、StrutsやTilesを使用した経験がある人は、このまま読み進めてください。 前提条件
実は、AJAXは新しいテクノロジではありません。XML-RPCコールは何年も前からありました。また、しばらくはSOAPを使ったWebサービスが大いに注目を集めていましたが、さまざまな理由からAJAXほど大々的に取り上げられることはありませんでした。開発コミュニティにこれほどの活気をもたらしたAJAXの利点を1つ1つ取り上げて正確に説明することは困難です。ただし、サービス指向アーキテクチャの利点が認められつつあることや、AJAXによってユーザーインターフェイスが著しく改善されていることがAJAXの利点と関係しているのは確かです。 従来のWebの「要求-応答」モデルでは、ユーザーが閲覧しているデータを変更するにはWebフォームの送信とページ全体のリロードが必要でした。一方、AJAXには、ページの一部分をその場で修正する仕組みが用意されています。この処理は高速に行われるため、ユーザーにコンテキストの中断を意識させません。最近のブラウザはページ上のすべての要素を解析木(Document Object Model:DOM)という形で整理しており、AJAXの応答処理はこのDOMの一部分を選択的に更新できるため、サーバーからページ全体をリロードするというコストのかかる処理を行わずに済むのです。Dojoツールキットは、リモートリソース(本稿の場合はURL)を呼び出すための強力なJavaScript APIであり、堅牢性を備えた補助的なJavaScriptユーティリティクラス群も提供しています。 ページ全体をリロードせずにリモートリソースを呼び出せるということは、ユーザーがページ上のデータとのインタラクションを段階的に行えることを意味しています。一般的にWebページは、 Dojoの設定はとても簡単です。メインのJavaScriptリソースファイル「dojo.js」をWebサーバー上に置き、ページ内からそのファイルを参照するだけです。次のようなJavaScriptのブロックを含めることで、Dojoに「自分自身の居場所」を伝えます。 var djConfig = { baseRelativePath: "js/dojo", isDebug: true, preventBackButtonFix: false }; 「tiles-def.xml」には、Dojoや他のJavaScriptのリソースファイルを再利用可能な形で指定するためのベーステンプレートを定義します。他のタイルは、拡張によってこの「ベースタイル」を再利用できます。以下に、ベースタイルの定義を示します。 <!-- Foundation Template Definitions --> <definition name="basic" path="/templates/basic.jsp"> <putList name="reusableJavascripts"> </definition> この「basic.jsp」タイル(本稿のサンプルコードを参照)には、Strutsの <tiles:importAttribute scope="page"/> <tiles:useAttribute id="tileDefinedJS" name="reusableJavascripts" scope="page"/> <logic:present name="tileDefinedJS"> <logic:iterate id="tileDefinedJS" name="reusableJavascripts"> <script language="javascript" src="<bean:write name="tileDefinedJS"/>" type="text/javascript"></script> </logic:iterate> </logic:present> このベースタイルを拡張する他のタイルは、「tiles-def.xml」内の自身のタイル定義のreusableJavascriptsリストに目的のスクリプト名を追加で指定することで、適切なDojoのJavaScriptリソース参照を自動的にインクルードするようになります。 <definition name="famousPeople" extends="jstemplate" > <put name="body" value="/tiles/famousPeopleList.jsp"/> <putList name="reusableJavascripts"> <add value="js/dojo/dojo.js"/> </putList> </definition> Tilesを使ってJavaScriptリソースファイルの参照を追加すると、タイプミスや、不注意で間違った場所を指定する可能性が低くなります。またTilesによって、アプリケーションの保守性も高まります。Tilesはこれらの定義を集中管理してくれるため、開発者は個々のJavaScriptページを見て.jsリソースが利用されているかどうかを判断せずに済みます。 余談ですが、Dojoを(Webサーバーを使わない)ローカル開発環境向けに設定する場合は、少し違ったアプローチが必要になります。ローカルのDojoリソース(dojo.js)のURI表記がブラウザ依存であることに注意してください(私は苦心の末にこのことに気付きました)。詳しくは、以下のマークアップを見てください。 <!-- this form recognized by both IE and Firefox --> <script src="file:///c:/dojo-0.2.2-ajax/dojo.js" type="text/javascript" language="JavaScript1.5" ></script> <!-- this form recognized only by IE - not Firefox --> <script src="/dojo-0.2.2-ajax/dojo.js" type="text/javascript" language="JavaScript1.5" ></script> ソートの例DojoがどのようにStrutsやTilesと連携するのかを理解するため、具体的な問題に取り組んでみましょう。付録のサンプルコードでは、まず有名人のリストを図1のような形式でユーザーに表示します。 このデータを、最初のページ読み込み時と異なる順序でソートして表示するには、2つの方法があります。1つ目は、従来どおりの、フォーム全体をサーバーに送り返してデータを並べ替え、ページ全体をリロードするという方法です。2つ目は、もうお分かりでしょうが、AJAXを利用する方法です。このAJAXによる方法を実装するには、ページのマークアップを編集して、 具体的には、次のような <div id="famousPeople"> <table> <tr> <td> <ul> <li>Dave Thomas</li> <li>Ronald McDonald</li> <li>George Washington</li> </ul> </td> </tr> </table> </div> ただし、上記の <div id="famousPeople"> <tiles:insert definition="famousPeopleList" flush="true"> <tiles:put name="listbean" beanName="famousFolkForm" beanProperty="famousFolkList" /> </tiles:insert> </div> このサンプルコードでは、有名人の名前のキャッシュリストを「famousFolks」というセッション属性で定義しています。このリストは、Dojoが次のようなURLに対する標準的なStrutsマッピングを使って呼び出すサーブレットによって再ソートされます。 <action path="/famousPeopleSort" name="famousFolkForm" scope="request" validate="false" type="com.tillman.dojo.action.FamousPersonageSort"> <forward name="success.sort" path="/sortUpdateXml.jsp"/> </action> 新しくソートされたリストは「sortUpdateXml.jsp」に渡されます。このJSPには、 それでは、もう少し機能を追加してみましょう。このAJAXコールを含んでいるページには、sortByという名前のドロップダウンリストも含まれています。このドロップダウンリストの <select name="sortBy" styleId="sortBy" onchange="javascript(doSort(this.value));"> <option name="person" value="George Washington">George Washington ... </select> 新しいソートパラメータが選択されると、 Dojoには堅牢なAPIが多数用意されており、パッケージ化の構造もおおむね直感的です。そうは言っても、AJAXコールのセットアップにはdojo.ioパッケージの function doSort(sortTerm) { dojo.io.bind( { url: "famousPeopleSort.shtml", content: {sortedBy: sortTerm}, method: "POST", mimetype: "text/html", load: function(type, value, evt) { processReturnValue(value); }, backButton: function(){ //for maintaining Dojo back button stack dojo.io.bind({ url: "famousPeopleSort.shtml", content: {sortedBy: previousSortTerm}, method: "POST", mimetype: "text/html", load: function(type, value, evt) { resetDropDown(previousSortTerm); processAjaxResponse(value); }, error: function(type, error) { alert("Error: " + error); } }); }, error: function(type, error) { alert("Error: " + error.reason); } }); このメソッド呼び出しでは、HTTP PostのURL引数に、「struts-config.xml」(サンプルコードを参照)内で「famousPeopleSort.shtml」にマッピングされているサーブレットを割り当てます。 上記のJavaScriptメソッドから、Dojoがブラウザの[戻る]ボタンの動作を管理できることがお分かりになったかもしれません。Dojoは独自のコールスタックを作成することでこれを行っています。これが便利なのは、もちろん、多くのユーザーは[戻る]ボタンを使って前のページに戻ろうとするからです。こうした状況では、ページ全体がリロードされず、Dojoによる処理が失われるため、この動作は混乱を招くかもしれません。個々のDojoコールは直前のコールの上にスタックされており、ユーザーが[戻る]ボタンをクリックすると、一番上のバインドされた(つまり、 このスタックは、「dojo.js」と同じディレクトリにあるHTMLファイル「iframe_history.html」の助けを得て管理されています。私の場合はDojoの[戻る]ボタン管理機能をうまく実装できましたが、他の開発者からはうまくいかなかったという報告も聞いています。問題に遭遇したときには、私の事例が少しでも参考になれば幸いです。 [戻る]ボタンをプログラムで管理するのは素晴らしいことですが、仕事の関係者がDojoの[戻る]ボタンの動作に不慣れな場合は、開発に入る前に、彼らのためにプロトタイプやデモを作成すべきです。AJAXコールは、「想定されるもの」とはかけ離れたナビゲーションやページフローのパラダイムを作り上げます。さらに、サイトの反応を鈍らせる何か未知の問題(または目に見えない問題)が起こっていると、大抵はユーザーに嫌な思いをさせることになり、アプリケーションは失敗に終わる可能性が高くなります。そのため、Dojoコールの実行開始時に起動し、完了時には消えるプログレスバーなどのビジュアルキュー(視覚的な合図)の追加を検討したくなるかもしれません。 返されたデータがどのように処理されるかを説明する前に、そのデータを保持する応答ドキュメントを見てみましょう。今回の有名人ソートのための応答ドキュメントはXML形式になっており、これが、前述の <%@page contentType="text/xml" %> <%@taglib prefix="tiles" uri="/WEB-INF/tiles.tld" %> <%@taglib prefix="html" uri="/WEB-INF/struts-html.tld" %> <%@taglib prefix="bean" uri="/WEB-INF/struts-bean.tld" %> <ajax-response> <field id="famousFolk1" attribute="innerHTML"> <![CDATA[ <tiles:insert definition="famousPeople" flush="true"> <tiles:put name="famousFolkList" beanName="famousFolkForm" beanProperty="famousFolkList"/> </tiles:insert> ]]> </field> </ajax-response> function processResponse(response) { if (djConfig["isDebug"]) { dojo.debug("ajax response: " + response); } //if the XML element ?ajax-response? //isn’t found then write out what was returned if (response.toLowerCase().indexOf("<ajax-response>") < 0) { document.write(response); document.close(); return; } //parse the AJAX XML response into a document. var xmlDoc = dojo.dom.createDocumentFromText(response); //refer to the XML response above ? the ?field? element is inside //the response document. var fields = xmlDoc.getElementsByTagName("field"); for (var i = 0; i < fields.length; i++) { var id = fields[i].getAttribute("id"); var attribute = fields[i].getAttribute("attribute"); var value = null; if (fields[i].hasChildNodes()) { for (var j=0; j<fields[i].childNodes.length; j++) { var currentNode = fields[i].childNodes[j]; if(currentNode.nodeName.toLowerCase()== "#cdata-section") { value = currentNode.nodeValue; } } } //Dynamically builds a function //called replaceValue and then invokes it eval("window.replaceValue = function(value) { dojo.byId(’" この応答の処理は、返されるXMLドキュメントの構文解析の途中で行われます。上記のコードは、この応答内にあるフィールド要素を探し、その内容を取り出します。続いて、ページ上の ここ数年、StrutsとTilesは企業でのJava開発における重要なフレームワークになっています。また、AJAXとDojoツールキットは、問題を分離し、コードおよび表示要素の双方の再利用を促進する大まかなサービス指向の方法でStrutsとTilesを利用するための優れた組み合わせです。こうした強力なフレームワークどうしを統合するためには習熟が必要ですが、その努力に見合うだけの結果が得られます。 最前線で利用されるDojoの問題
私はDojoをとても気に入っていますが、[戻る]ボタンや一部の不親切なデバッグステートメントに難点があると感じました。最初にインストールしたDojo 0.2.2には、
dojo.byIdの呼び出しにいくつか問題がありました。Strutsはidプロパティをhtml:hiddenタグのプロパティとしてサポートしていません。IEの場合は、dojo.byId("field")の呼び出し途中でこのプロパティが見当たらなくても、うまくやり過ごしてそのフィールドの名前を参照します。一方、Firefoxの場合は、エラーをスローして、このフィールドがそのようなプロパティ(この場合はidプロパティ)をサポートしていないことを知らせます。私にとって幸運だったのは、同僚の1人が、html:hiddenタグのstyleldフィールドがJSPのレンダリング時にidプロパティに変換されるんだよとヒントを与えてくれたことでした。さらに運が良かったのは、そのときの会話でもう1人の同僚が、[戻る]ボタンに類似した機能を実行するためにはchangeURLパラメータをdojo.bind()メソッドに追加しなければならない、と教えてくれたことです。これで問題の全容が見えてきたのではないでしょうか。私もそうでした。私の同僚たちは、彼らが[戻る]ボタンを動作させるために行ったさまざまなハッキングに関する知識を惜しみなく与えてくれました。しかし彼らのヒントがあったにもかかわらず、これらの問題や次から次へと現れる新たな問題への対処方法を思い出すには、思ったよりも時間がかかりました。本稿の発表前にこれらすべての問題の原因を探り当て、あらゆる問題を解決したサンプルコードを紹介できればよかったのですが、残念ながらそれはできていません。また、インストールされているFirefox拡張機能の数など、ある種の特異な環境上の問題も、不安定な動作につながる可能性があります。 これらの問題のいくつかに遭遇した後、自分のローカル環境にあったDojoツールキットのアップグレードを行ったため、現在、私のDojoに関する不完全ながらも増えつつある知識は、バージョン0.2.2と0.3.1の両方に対応しています。ちょっとした調整:Webを検索してみたところ、「bootstrap1.js」ファイルでは、 djConfigのpreventBackButtonFixのデフォルト値がtrueに設定されていることが分かりました。それまで私のコードではこれをfalseに設定していたのですが、この「bootstrap1.js」の値を同じように変えると、驚いたことに、Firefoxだけに見られたページ読み込みのエラーが消えたのです。普通、[戻る]ボタンはFirefoxと連動するのですが、バージョン0.2.2でうまくいったのと同じテクニックを0.3.1で使うと、フィールドの一部の更新がスムーズに行われませんでした。また、私のサンプルをOperaで読み込んだところ、[戻る/進む]ボタンの機能はOperaではサポートされていません、というメッセージが表示されましたが、それ以外のコードは正常に動作していました。奇妙なことに、ある同僚がバージョン0.2.2を使ってテストした際にはOperaでも正常に動作したそうです。要するに、Dojoにはまだ何らかの問題が残っているので、正しい方向に向かってDojoの開発が活発に進められている限り、当面は困難に遭遇する覚悟が必要と言えそうです。 参考文献著者紹介Doug Tillman(Doug Tillman)
Grainger.comで活躍するベテランのJavaおよびPython開発者。開発者の役に立つツールを作成することに強い関心を持ち、積極的に取り組んでいる。
|