Lab

by engineering@dwango.jp

jQuery Mobileの紹介

今回は、jQuery Mobileを用いたアプリケーションについて解説します。jQuery Mobileを用いることで、非常に簡単にリッチなインターフェースを持つwebアプリケーションを作ることが出来ます。

対象読者

  • JavaScriptでの開発経験がある方
  • jQueryを使用したことがある方

jQuery Mobile

jQuery Mobileは、jQueryを開発しているThe jQuery Projectによって開発されたスマートフォン向けのJavaScript UIフレームワークです。iOSやAndroid、Windows Phone、BlackBerry等の様々なモバイルプラットフォームとデバイスに対応していて、OSに依存しないUIを提供しています。

jQuery Mobileでは、独自に定義したHTML5のカスタムdata-*属性を指定することで、リストビューやナビゲーションバー等様々なUIコンポーネントを利用することが可能です。また、1つのHTMLドキュメント内で複数のページを記述することができ、同一ファイル内でのページ切り替えには、CSS3 Transitionsを使用したアニメーションが有効になります。

この記事は、jQuery Mobile RC1を元にしたものであり、今後正式版リリース時に変更される可能性があります。

data-role属性

jQuery MobileのUIを使用するには、主にdiv要素に対してdata-role属性を指定します。

data-role属性に指定する値としてよく使われるものには、以下のようなものがあります。

説明
page 要素内を1つのページとして扱う
content ページの内容を表示する
header ページのヘッダ領域
footer ページのフッタ領域
fieldcontain 入力系コントロールをまとめる
button リンクをボタンとして表示する。a要素に対して使用する
listview リストをリストビューとして表示する。ul要素やol要素に対して使用する
none 要素内をjQuery MobileのUIを使用せずに表示する

イベント

jQuery Mobileでは、タップやスワイプといった、スマートフォン向けのUIイベントや、ページの遷移に関するイベントが追加されています。jQuery Mobileで追加されたイベントについては、jQuery Mobile Eventsに詳細が記載されています。

Hello World

まずはdata-rolepagebuttonを使用してHello Worldを表示してみましょう。1ページ目にはヘッダとボタン、2ページ目にはヘッダと戻るボタンを配置し、1ページ目のボタンを押すことで2ページ目に移動、2ページ目の戻るボタンで1ページ目に戻る、というサンプルを作成します。

準備

サンプルをスマートフォン上で実行するために、PhoneGapを使用します。Project/assets以下のディレクトリ構成を以下のようにします。

assets
css
js
phonegap-1.1.0.js
index.html

PhoneGapの導入については、PhoneGap の公式のサイトを参照してください。この記事はAndroidを前提として書いていますが、iPhoneの場合はwwwディレクトリがassetsに相当します。

jQuery Mobileの導入

まずは、jQuery Mobileの導入を行います。下記のjQuery Mobileのダウンロードページから、JavaScriptとCSSのまとまったアーカイブをダウンロードし、それぞれをassets/jsとassets/css以下に配置します。また、jQuery Mobileは、jQueryに依存しているので、jQueryをダウンロードし、assets/js以下に配置します。

実装

では、実際にHTMLを記述してみます。

<html>
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
        <title>Hello World</title>
        <link rel="stylesheet" href="css/jquery.mobile-1.0rc1.min.css" />
        <script type="text/javascript" src="js/jquery-1.6.4.min.js"></script>
        <script type="text/javascript" src="js/jquery.mobile-1.0rc1.min.js"></script>
    </head>
    <body>
        <div data-role="page">
            <div data-role="header">
                <h1>Hello World</h1>
            </div>

            <div data-role="content">
                <a href="#hello2" data-role="button">Hello World</a>
            </div>
        </div>

        <div id="hello2" data-role="page" data-add-back-btn="true" data-back-btn-text="戻る">
            <div data-role="header">
                <h1>Hello World</h1>
            </div>

            <div data-role="content">
                Hello World!
            </div>
        </div>
    </body>
</html>

このコードを実行すると以下のようになります。

図1 Hello World1
図2 Hello World2

サンプルページを用意しましたので、ぜひ試してみてください。

ボタンを押してページを切り替える際、左右にスライドしながらページが切り替わっているのがわかります。2ページ目から戻るとき、AndroidではBackボタンで戻ることも出来ます。

このように、アニメーションをしながらページ遷移する複雑なコンテンツを、JavaScript、CSSを1行も書かずに実現することが可能です。

Tumblrの投稿を取得するアプリ

次に、もう少し発展させたサンプルとして、tumblrから特定のユーザーの投稿を取得し、リストに表示、項目をタップすると各投稿の詳細を表示する簡単なアプリケーションを実装します。

JSONの読み込み

tumblrの投稿取得APIは、

http://username.tumblr.com/api/read/json

となっています。サンプルでは、usernameに指定したユーザー名を入れ、そのユーザーの公開されている投稿を20件取得します。返ってくる投稿内容のJSONはリンクや引用等、種類によって内容が変わりますが、簡単のため通常の投稿とリンク、引用、写真のみを抽出してリストに表示します。

tumblr APIの仕様については、Tumblr API v1を参照してください。

実装

では、実装してみます。コードは以下をご覧ください。

tumblr-post-reader.js

function TumblrPostReader() { };

TumblrPostReader.prototype = {
    postCount : 20,

    getUserName : function() {
        return $("#user-name").attr("value");
    },

    splitTags : function(text) {
        return text.replace(/<\/?[^>]+>/gi, "");
    },

    showResult : function(json) {
        var that = this;
        var list = $("#post-list")
        list.empty();
        $.each(json.posts, function(index, post) {
            var text = null;
            switch (post.type) {
                case "regular":
                {
                    text = post["regular-title"];
                    break;
                }
                case "link":
                {
                    text = post["link-text"];
                    break;
                }
                case "quote":
                {
                    text = post["quote-source"];
                    break;
                }
                case "photo":
                {
                    text = post["photo-caption"];
                    break;
                }
            }

            if (text != null) {
                text = that.splitTags(text);
                var template = $("<li><a><span class='text'></span></a></li>");
                template.find(".text").html(text);
                template.find("a").bind("click", function() { that.showDetail(post); });
                template.appendTo(list);
            }
        });
        list.listview("refresh");
    },

    showDetail : function(post) {
        var header = $("#post-title");
        var content = $("#post-content");

        switch (post.type) {
            case "regular":
            {
                header.html(this.splitTags(post["regular-title"]));
                content.html(post["regular-body"]);
                break;
            }
            case "link":
            {
                var text = post["link-text"];
                var url = post["link-url"];
                var link = $("<a></a>");
                link.attr("href", url);
                link.attr("data-role", "button")
                link.html(text + "<br>URL: " + url);

                header.html(text);
                content.html(link);
                break;
            }
            case "quote":
            {
                header.html(this.splitTags(post["quote-source"]));
                content.html(post["quote-text"] + "<br>" + post["quote-source"]);
                break;
            }
            case "photo":
            {
                var img = $("<a class='src'><img class='img'/><span class='caption'></span></a>");
                img.find(".img").attr("src", post["photo-url-400"]);
                img.find(".caption").html(post["photo-caption"]);
                img.find(".src").attr("href", post["url"]);

                header.html(this.splitTags(post["photo-caption"]));
                content.html(img);
                break;
            }
        }

        $.mobile.changePage($("#detail"));
    },

    getPost : function() {
        var that = this;
        var json = $.getJSON("http://" + this.getUserName() + ".tumblr.com/api/read/json?callback=?",
        { num : that.postCount },
        function(json) {
            $("#result").bind("pageshow", function() { that.showResult(json); });
            $.mobile.changePage($("#result"));
        });
    }
};

$(document).bind("mobileinit", function() {
    tumblr = new TumblrPostReader();
});

index.html

<div data-role="page" id="search">
    <div data-role="header">
        <h1>tumblr投稿閲覧</h1>
    </div>

    <div data-role="fieldcontain">
        ユーザー名: <input type="search" id="user-name">
        <input type="button" onclick="tumblr.getPost()" value="検索" width="100%">
    </div>
</div>

<div data-role="page" id="result" data-add-back-btn="true" data-back-btn-text="戻る">
    <div data-role="header">
        <h1>結果</h1>
    </div>

    <ul data-role="listview" id="post-list" data-inset="true">
    </ul>
</div>

<div data-role="page" id="detail" data-add-back-btn="true" data-back-btn-text="戻る">
    <div data-role="header">
        <h1 id="post-title"></h1>
    </div>

    <div data-role="content" id="post-content"></div>
</div>

これを実行すると以下のようになります。

図3 検索画面
図4 取得結果画面

こちらもサンプルページを用意しましたのでぜひ試してみてください。

以上のように、リストビューや検索ボックスなども、要素に属性をいくつか追加するだけで簡単に実装することが可能です。JavaScriptからのページの切り替えも、メソッド1つで簡単に行うことが出来ます。

注意点

jQuery Mobileを使用する上での注意点は、JavaScriptファイルの読み込み順序です。初期化時に独自の処理を実行する場合、mobileinitというイベントにメソッドをバインドします。ただし、このイベントはjQuery MobileのJavaScriptファイルが読み込まれるときに発生するため、jQuery Mobileを読み込む前に、初期化処理をmobileinitにバインドする必要があります。

まとめ

今回は、jQuery Mobileを用いて、簡単なアプリケーションを実装しました。このように、jQuery MobileでのUIの実装が非常に簡単に出来ることがわかります。

今後、スマートフォンでのWebアプリケーション開発におけるUIフレームワークの有力な選択肢になるでしょう。