jQuery Mobile上でのスクリプティング

jQuery Mobileは、Ajaxによるナビゲーションシステムを用いています。その中でスクリプトによりコンテンツを制御しようとする際に、知っておいたほうが良いいくつかのポイントがあります。モバイルAPIの詳細については、グローバル設定イベントメソッドとプロパティを参照してください。また、ナビゲーションの機構についてはAjaxとハッシュ、履歴を御覧ください。

ヘッドでのスクリプトとスタイル

jQuery Mobileを使って構築されたサイト上でリンクがクリックされると、デフォルトでフレームワークはリンクの href 属性を用いてAjaxリクエストを行います(ブラウザによるデフォルトの挙動である、href属性によるリンク先を画面全体にロードする代わりに)。Ajaxによる読み込みで、フレームワークはページ全体をいったん取得します。しかし、そこから body 要素(あるいは data-role=“page” の指定された要素があれば、そちら)の内容だけをDOMに挿入して用います。つまり head 要素の中身は何も使われないことになります(ただしページタイトルだけは別機能として抽出していますが)。

これはすなわち、Ajaxにより読み込まれたページの head で参照されているスクリプトやスタイルシートは、無意味になるということです。一方で、このページが通常のHTTPリクエストで読み込まれれば、それらはきちんと実行されます。jQuery Mobileでサイトを構築する場合、この両方のケースを考慮しておかなければなりません。Ajaxリクエスト時に head の内容を無視する仕様になっている理由は、非常に多くの場合において同じJavaScriptが実行されることになってしまうからです(サイト内の各ページに同じ外部スクリプトファイルが取り込まれているのは、一般的なことです)。この問題をシステムで解決することは、非常に困難です。そのため、特定ページのみで実行したいスクリプトについての問題解決は開発者側に委ねられ、head要素でのスクリプトは1度のブラウジングセッションで1度しか実行しないこととなっています。

最もシンプルなjQuery Mobileによるサイト構築時のアプローチは、サイト内の全てのページで同じスクリプトとスタイルシートのセットを参照することです。もし特定のページでのみ実行したいスクリプトがある場合、該当するページが構築される際(ページの id など見分ける方法はたくさんあります)の pagecreate イベント(詳細は後述)にロジックをバインドすることをお勧めします。そうしておけば、ページが直接開かれた場合でも、Ajaxにより他のページに取り込まれた場合でも、コードが実行されるようになります。

他の方法としては、body要素の最後にスクリプトの取り込みを記述しておくことです。この方法でも、通常のHTTPリクエストで開かれた場合であれ、Ajaxにより読み込まれた場合であれ、スクリプトは実行されます。しかしこの場合、各ページで同じスクリプトが取り込まれていた場合も、ページが読み込まれる度に実行されてしまいます。これは問題を引き起こす可能性が高いので、注意してください。この方法を採る場合、ページのコンテンツを data-role=“page” を指定した要素で囲んでおくことをお勧めします。そして、全てのページで取り込みたいスクリプトはこの要素の外に、特定のページでのみ取り込みたいスクリプトは要素の内に置いておきます。そうすれば、Ajaxによる取り込み時には内側のスクリプトだけが実行されるようになります。

pagecreate = DOM ready

jQueryを習った開発者が最初に覚えるのは $(document).ready() を使うことでしょう。ページが読み込まれてDOMが扱える状態になった直後(これは多くの場合 onload イベントよりも前に)実行されるイベントです。しかしながらjQuery Mobileで作られたサイトやアプリケーションでは、ページはAjaxにより読み込まれて同じDOM中に挿入されます。そのため、DOM readyは最初のページが開かれた際に1度実行されるのみで、使い勝手が良いとは言えません。新しいページがjQuery Mobileにより読み込まれた際に実行したいコードは、pagecreateイベントに記述できます。

この pagecreate イベントは、ページが読み込まれて初期化された後で呼び出されます。jQuery Mobile公式ウィジェットのほとんどは、このイベントで自身を自動初期化しています。ページを初期化したい場合も、同様の方法で可能です。

$( document ).delegate("#aboutPage", "pagecreate", function() {
  alert('IDが "aboutPage" のページが、jQuery Mobileにより構築されました!');
});

もしウィジェットが自動初期化される pagecreate イベントより先にページを操作したいような場合は、代わりに pagebeforecreate イベントを使うことができます。

$( document ).delegate("#aboutPage", "pagebeforecreate", function() {
  alert('IDが "aboutPage" のページが、jQuery Mobileにより構築されました!');
});

ページ遷移

JavaScriptを使ってページを遷移させたいような場合、$.mobile.changePageメソッドを使うことができます。ページ遷移時に設定できるオプションやイベントは数多くありますが、ここでは2つの簡単なサンプルを紹介します。

// "About us"ページに slideup 効果を使って遷移
$.mobile.changePage( "about/us.html", { transition: "slideup"} );   

// IDが "search" の入力データを使って、検索結果ページへ遷移
$.mobile.changePage( "searchresults.php", {
        type: "post", 
        data: $("form#search").serialize()
});

ページ読み込み

ページを読み込んでコンテンツを初期化し、DOMにそれを挿入するには、$.mobile.loadPageを用います。読み込み時に設定できるオプションやイベントは数多くありますが、ここでは簡単なサンプルを紹介します。

// "About us"ページを読み込んでDOMに挿入
$.mobile.loadPage( "about/us.html" );

新たなマークアップの拡張

ページプラグインは、ほとんどのウィジェットが自身を自動的に初期化させるために pagecreate イベントを発行します。ウィジェットのスクリプトが参照され、ページ上でそのウィジェットに該当する要素がみつかれば、それらは自動的にインスタンス化されます。

しかし、新しいマークアップをクライアント側で作成したり、あるいはAjaxにより読み込んだような場合は、この新たなマークアップに対して自動初期化を行わせるために自分で create イベントを発火することができます。このイベントは、どんな要素にも(たとえページコンテナの div そのものに対してでも)発光することができます。これは手動でリストビューやボタン、セレクトメニューなどのプラグインを初期化するよりも遙かに手間を軽減できます。

たとえば、ログインフォームのようなHTMLマークアップのブロックがまるごとAjaxにより読み込まれたような場合、そこに対して create イベントを実行すれば、そこに含まれるテキスト入力ボタンなど全てのウィジェットへ自動的に初期化処理が行われます。これをコードで実際に書くと、次のようになります。

$( ...new markup that contains widgets... ).appendTo( ".ui-page" ).trigger( "create" );

Create と Refresh の重要な違い

いくつかのウィジェットが持つ create イベントと refresh メソッドには、大きな違いがあることに注意してください。まず create イベントは、幾つかのウィジェットを包含するプレーンなマークアップを拡張するのに適しています。一方 refresh メソッドは、既に拡張されたウィジェットに対してプログラムからUIを更新された際に発行するものです。

たとえば、ページが構築された後で新たに data-role=“listview” 属性を持つリスト(ul)を挿入されたぺーじがある場合、このリストの親要素に対して create を発行することによりリストビューウィジェットに変換されます。その後、更にリストアイテムがプログラムから追加された場合、そのリストビューウィジェットに対して refresh を実行することによって新たなアイテムを反映させることができます。

ページ内のスクロール

フレームワークがURLハッシュ(#)を「戻る」ボタンの挙動などに使っている関係上、通常のアンカーリンク(#fooのような)によりページ内の特定の位置へジャンプするような機能はサポートされなくなっています。そのかわり、$.mobile.silentScrollメソッドを使って特定のY座標位置に、スクロールイベントを発行させることなくスクロールさせることができます。Y座標の指定は、引数 yPos に指定してください。たとえば、次のようにします。

// Y座標 300px にスクロール
$.mobile.silentScroll(300);

マウス/タッチイベント

モバイルサイトにおいて最も重要なポイントのひとつは、マウス/タッチイベントの制御です。これらのイベントの実装は、プラットフォームによって大きく異なっています。しかし多くに共通しているのは、クリックイベントが発生するまでに500~700ミリ秒の遅れが出るという点です。この遅れは、ブラウザにとって必要なものです。ユーザの挙動がダブルタップなのか、あるいはスクロールやホールドタップでないのかを確認するために、しばらく待つ必要があるからです。こうした遅れを回避するためには、クリックではなくタッチイベント(たとえば touchstart など)を用いることができます。しかし問題は、幾つかの携帯プラットフォーム(WP7やBlackberryなど)ではタッチイベントをサポートしていないことです。そして問題を更に複雑化させているのは、いくつかのプラットフォームではマウスとタッチ両方のイベントを同時に発行することです。そのため、両方のイベントにバインドすれば、一度のユーザアクションで同時に2度スクリプトが実行されてしまう場合もあるのです。

これらの問題を解決するため、マウス/タッチイベントを標準化した仮想マウスイベントが用意されています。これによって開発者は、mousedown/mousemove/mouseup/clickのような基本的なマウス関連のイベントとして処理をバインドすることが出来ます。また、これらのイベントは前述の遅れを軽減するため、該当するイベントの内で最も速く発火されるイベントにバインドされるように作られています。仮想マウスイベントは、システム上次のような名前になっています: vmouseover, vmousedown, vmousemove, vmouseup, vclick, vmousecancel

ページへのパラメータ付与

jQuery MobileはURLへのクエリパラメータをサポートしていません。たとえば、フレームワークが “#somePage?someId=1” というハッシュURLを処理する場合、内部的に “#somePage” 部分だけを見て、対応する somePage というIDを持つページコンテナを表示します。そしてページコンテナの data-url には #somePage?someId=1 と設定します。次に、このページから “#somePage?someId=2” というリンクがクリックされた場合、jQuery Mobileはやはり同じページコンテナを見つけてしまうので、ページは遷移されません。

ページ間の遷移にクエリパラメータが必要であるならば、解決するために2つのプラグインがあります。ひとつは Page Param プラグイン という軽量のもので、もうひとつは更に高機能な jQuery Mobile router プラグイン という backbone.js や spine.js を使うためのものです。