jQuery MobileでCRUDアプリケーションのフロントを作って学んだ5つのこと(Node.js+jQuery Mobile+MongoDBでCRUDアプリケーションを作る(その2))
はじめに
この記事は、JavaScript Advent Calendar 2011 (フレームワークコース)の16日目の記事です。
Node.js+jQuery Mobile+MongoDBでCRUDアプリケーションを作った際に、フロントを1.0がリリースされたjQuery Mobileを使ったみました。そこで実際にCRUD全部を作ってみて学んだことを5つのポイントにしてまとめました。
前提(基本方針など)
jQuery Mobileのリファレンス
本家のドキュメントが一番のリファレンスですが、色々なページに飛ばないと必要な属性が分からないので、2010-10-19 - へっぽこプログラマーの日記が非常に参考になりますので、一読されることをオススメします。
テンプレートエンジン jade について
jadeはexpressがデフォルトで利用するテンプレートエンジンです。通常のHTMLでWebアプリケーションを作る際には、あまりにもプログラマ向けのライブラリなので、ちょっと使えないと思っていたのですが、マークアップ主体のjQuery Mobileとは相性が良いと感じています。インデントベースでタグ記号「<, >」不要、閉じタグ不要なので、記述量が減るのが嬉しいです。
今回のページは、共通部をlayout.jade, body部をindex.jadeに記述しています。実際には次のようになります。
layout.jade
!!! 5 html head title='Memo' meta(name='viewport', content='width=device-width, initial-scale=1') link(rel='stylesheet', href='http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.css') script(src='http://code.jquery.com/jquery-1.6.4.min.js') script(src='http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.js') body!= body
layout.jadeのHTMLへの変換後
<!DOCTYPE html> <html> <head> <title>Memo</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.css"> <script src="http://code.jquery.com/jquery-1.6.4.min.js"></script> <script src="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.js"></script> </head> <body> </body> </html>
index.jade(1ページ分)
div#view(data-role='page') div(data-role='header') a(data-rel='back', data-icon='back', data-derection='reverse') Back h1 Memo View a(href='#edit', data-icon='gear', data-transition='flip') Edit div(data-role='content') div(data-role='filedcontain') label(for='memo-view') Memo p#memo-view div.ui-header(data-role='footer') a(href='#add', data-icon='plus') Add h4 Kenichiro Murata a#del-btn(href='#', data-icon='delete') Delete
- 閉じタグが不要
- idはtag#idで表記可能(divの場合はタグを省略できて#idで表記可能だが、分かりやすさ優先で省略しない方針とします)
- classはtag.classで表記可能
index.jade(1ページ分)のHTMLへの変換後
<div id="view" data-role="page"> <div data-role="header"> <a data-rel="back" data-icon="back" data-derection="reverse">Back</a> <h1>Memo View</h1> <a href="#edit" data-icon="gear" data-transition="flip">Edit</a> </div> <div data-role="content"> <div data-role="filedcontain"> <label for="memo-view">Memo</label> <p id="memo-view"> </p> </div> </div> <div data-role="footer" class="ui-header"> <a href="#add" data-icon="plus">Add</a> <h4>Kenichiro Murata</h4> <a id="del-btn" href="#" data-icon="delete">Delete</a> </div> </div>
とまぁ、このような感じです。それでは5つのポイントを紹介します。
0. 画面イメージ
node-ninjaで動かしています。
1. footerのボタン配置について
headerは、タイトル以外にa-linkを1つ配置すると左側に、2つ目を配置すると右側に自動でボタンが配置されます。しかし、footerはこのボタン配置のコントロールが少し異なります。header内ではui-btn-rightをclass指定すると位置を制御できますが、footerだと上手く行きません。
複数のボタンを配置するとなると、navbarを使うことになるのですが、ボタン2つまでしか置かないのであれば、headerと同じように配置させたい。そんな時は、footerにui-headerをclass指定すると、headerと同じようにボタンを配置できます。
このTipsは404 Error - Not Foundで紹介されています。
2. ul listview の動的構築
listviewにはServerSideから取得したリストデータを表示します。このアプリでは2種類の方法で実現してみました。
2-1. 初期表示時にjadeのテンプレート機能で初期構築
div#index(data-role='page') div(data-role='header') h1 Memo div(data-role='content') ul#memolist(data-role='listview', data-inset='true') - for(var index=0; index<memos.length; index++) { li a(href='#view', id=memos[index]._id) p=memos[index].content - } div.ui-header(data-role='footer') a(href='#add', data-icon='plus') Add h4 Kenichiro Murata
- アプリの初期表示画面がlistviewの場合、ServerSideでデータを取得した後、jadeのテンプレート機能を使ってレンダリングし(memosがバインドするリストオブジェクト)、その結果をHTMLとして返します。
- jadeの組み込み構文?(-for)を使って、ulタグの下にli, aタグを追加しています。
- aタグのid属性に、バインドしたデータを識別できるようにするためのキーとなるidを埋め込んでいるのが後でポイントになります。
2-2. jsonデータをajaxで取得して動的に構築する
$(function() { $("#index").bind('pageshow', function(e, ui) { $.get( 'memo/list' , function(data) { $("#memolist").empty(); for(var index=0; index < data.length; index++) { $("#memolist").append('<li><a href="#view" id="' + data[index]._id + '"><p>' + data[index].content + '</p></a></li>'); } $("#memolist").listview('refresh'); } ); }); });
- pageshowイベントは最初に表示するページに対してはイベントが発生しません。(mobileinitで順序に注意して定義すると可能なようですが、今回は試していません)
- ul listviewの子ノードを削除する際に、remove()を使うとulノード自体も削除されてしまうので、empty()を使います。
- ulノードが残っていれば、DOMを構築した後に、ulに対してlistview('refresh')を実行することで、リストが再構成されます。
3. listviewで選択された行を特定し、キーとなるidを取得して、ServerSideからデータを取得する
script var mstore = {}; $(function() { $("#memolist").delegate('a', 'click', function(e) { mstore.selectedid = this.id; }); $("#index").bind('pageshow', function(e, ui) { $.get( 'memo/list' , function(data) { $("#memolist").empty(); for(var index=0; index < data.length; index++) { $("#memolist").append('<li><a href="#view" id="' + data[index]._id + '"><p>' + data[index].content + '</p></a></li>'); } $("#memolist").listview('refresh'); } ); }); $("#view").bind('pageshow', function(e, ui) { $("#memo-view").html(''); $.get( 'memo/' + mstore.selectedid , function(data) { $("#memo-view").html(data.content); } ); });
- listviewのaタグのクリックした際に、選択した行からキーとなるidを取得するには、次の手順を踏んでいます。
- aタグのidにキーとなるidを埋め込んでおく
- aタグのclickイベントを補足して、選択行のキーとなるidを保存
- ページ遷移時のpageshowイベントで、保存していたidを使ってajaxによるデータ取得を行う
- aタグのclickイベントを補足する時に、delegateかliveを使うのがポイント(ここでbindを使うとイベントが補足できてもaタグのidが取得できません)
- bindはイベント設定時に要素が存在している必要がある
- live, delegateはイベント設定時に要素が存在している必要がない
- つまり、今回のように動的にDOMを削除、追加するような場合ではlive, delegateを使わないと、選択したaタグのidが取得できません。その他の違いは0-9, jQueryのliveやdelegateは実際何をやってるのかに詳しく書かれています。
4. ダイアログをscriptから表示させる
div#msg-dialog(data-role='page') div(data-role='header') h1 Message div(data-role='content') p#message a(href='#index', data-role='button') OK script $(function() { function onSuccess(data) { $("#message").html(data.message); $.mobile.changePage('#msg-dialog', {transition : 'slidedown', role : 'dialog'}); };
- $.mobile.changePageを使って、roleにdialogを指定します。
5. ajaxをfalseにせずに、jQuery Mobileのイベントプロセスに合わせる
div#edit(data-role='page') div(data-role='header') a(data-rel='back', data-icon='back', data-derection='reverse') Back h1 Memo Edit a#save-ebtn(href='#', data-icon='check') Save div(data-role='content') div(data-role='filedcontain') label(for='memo-edit') Memo textarea#memo-edit div.ui-header(data-role='footer') a(href='#add', data-icon='plus') Add h4 Kenichiro Murata
$("#save-abtn").bind('click', function(e) { $.post( 'memo' , { content : $("#memo-add").val() } , onSuccess , 'json' ); }); function onSuccess(data) { $("#message").html(data.message); $.mobile.changePage('#msg-dialog', {transition : 'slidedown', role : 'dialog'}); };
今回はajaxによるページ遷移をfalseにせずに、jQuery Moileに身をゆだねる?ようにしています。上記3.にてclick時には選択行のidだけ保存して、pageshowイベントで処理するなどがそうです。
また、今回の例では保存する、削除するなどのServerSideに処理を要求する場合、次の手順で処理しています。
- aタグのhrefには#だけを指定しページ遷移しない(a#save-ebtn)
- aタグのclickイベントを補足し、ServerSideに要求する
- コールバック内でダイアログを表示し、OKボタンでページ遷移
(追加)