Chapter 11: jQueryとAjax

jQueryとAjax

Ajax

web2pyは主にサーバーサイド開発向けですが、welcome 雛形アプリは基本的な jQueryライブラリ[jquery]、jQueryカレンダー(date picker、datetime picker、clock)、"superfish.js"メニュー、そしていくつかのjQueryに基づく追加のJavaScriptを利用することができます。

PrototypeやExtJS、YUIといったそれ以外のAjaxライブラリもweb2pyで使うことができますが、jQueryをパッケージすることに決めた理由は、他の同等なライブラリに比べて利用が簡単でかつ強力だからです。また機能的かつ簡潔であるという、web2pyの精神を捉えていることを見つけることもできます。

web2py_ajax.html

雛形アプリのweb2pyアプリケーション "welcom" は次のファイルを含みます。

1
views/web2py_ajax.html

ファイルの中身は次の通りです:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{{
response.files.insert(0,URL('static','js/jquery.js'))
response.files.insert(1,URL('static','css/calenadar.css'))
response.files.insert(2,URL('static','js/calendar.js'))
response.include_meta()
response.include_files()
}}
<script type="text/javascript"><!--
    // These variables are used by the web2py_ajax_init
        // function in web2py.js (which is loaded below).
    var w2p_ajax_confirm_message =
        "{{=T('Are you sure you want to delete this object?')}}";
    var w2p_ajax_date_format = "{{=T('%Y-%m-%d')}}";
    var w2p_ajax_datetime_format = "{{=T('%Y-%m-%d %H:%M:%S')}}";
//--></script>
<script src="{{=URL('static','js/web2py.js')}}"
        type="text/javascript"></script>

このファイルはデフォルトの "layout.html" のHEADで付け加えられ、次のようなサービスを提供します:

  • "static/jquery.js" をインクルードします。
  • "static/calendar.js" と "static/calendar.css" をインクルードします。これらはポップアップ・カレンダーで使われます。
  • 全ての response.meta ヘッダをインクルードします。
  • 全ての response.files (要求する CSS と JS、コードで宣言されたもの)をインクルードします。
  • フォーム変数の設定と、"static/js/web2py.js" をインクルードします。

"web2py.js"は次のことを行います:

  • (jQueryの$.ajaxに基づく) ajax 関数の定義をします。
  • クラス "error" のDIVの作成、あるいはクラス "flash" のタグ・オブジェクトのスライドダウンをします。
  • クラス "integer" のINPUTフィールドで、無効な整数入力をエラーにします。
  • クラス "doubles" のINPUTフィールドで、無効な浮動少数入力をエラーにします。
  • "date" タイプのINPUTフィールドと、popup date pickerを関連付けます。
  • "datetime" タイプのINPUTフィールドと、popup datetime pickerを関連付けます。
  • "time" のINPUTフィールドと、popup time pickerを関連付けます。
  • web2py_ajax_component を定義します。これはとても重要なツールで、12章で説明します。
  • web2py_websocket の定義をします。この関数はHTML5のwebsocket(ウェブソケット)のために用いられます(この本では説明しませんが、"gluon/contrib/websocket__messaging.py" のソースコードの例をみてください)。
    websockets
  • パスワードフィールドの評価と、無秩序さを計算する関数の定義をします。

また後方互換のために、popupcollapsefade 関数もインクルードします。

ここでは、他のエフェクトがどのように上手く連携しているかの例を示します。

次のモデルの test アプリケーションを考えてみましょう:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
db = DAL("sqlite://db.db")
db.define_table('child',
     Field('name'),
     Field('weight', 'double'),
     Field('birth_date', 'date'),
     Field('time_of_birth', 'time'))

db.child.name.requires=IS_NOT_EMPTY()
db.child.weight.requires=IS_FLOAT_IN_RANGE(0,100)
db.child.birth_date.requires=IS_DATE()
db.child.time_of_birth.requires=IS_TIME()

"default.py" のコントローラーは次の通りです:

1
2
3
4
5
def index():
    form = SQLFORM(db.child)
    if form.process().accepted:
        response.flash = 'record inserted'
    return dict(form=form)

そして、"default/index.html" ビュー次のようになります:

1
2
{{extend 'layout.html}}
{{=form}}

"index" アクションは次のフォームを生成します:

image

もし無効なフォームが送信されると、サーバーはエラー・メッセージを含む修正されたフォームのページを返します。エラー・メッセージはクラス "error" のDIVです。上記のweb2py.jpコードによって、エラーがスライドダウン・エフェクトで表示されます:

image

エラーのカラーは、"layout.html" のCSSコードで指定されています。

web2py.jsコードは入力フィールドへの無効な値の入力を防ぎます。これは、サーバーサイドのバリデーションの置き換えでなく、前に追加され実行されます。

web2py.jsコードは、クラス "date" のINPUTフィールドに入力した時点で、日付ピッカー(date picker)を表示し、クラス "datetime" のINPUTフィールドに入力した時は、日付時間ピッカー(datetime picker)を表示します。例えば:

image

またweb2py.jsコードは、クラス "time" のINPUTフィールドの編集時に、次ような時間ピッカー(time picker)を表示します:

image

送信時に、コントローラー・アクションはresponse flashにメッセージ "record inserted" をセットします。デフォルト・レイアウトでは id="flash" のDIVに、このメッセージをレンダリングします。web2py.jsコードはこのDIVの表示と、そのDIVをクリックした際に非表示にする役割を担っています:

image

これらと他のエフェクトは、ビューの中とコントローラーのヘルパ経由で、プログラム的にアクセスできます。

jQueryエフェクト

effects

ここで述べている基本的なエフェクトは、追加のファイルを特に必要としません。全ての必要なものは既に、web2py_ajax.htmlに含まれています。

HTML/XHTMLオブジェクトはそれらの要素(例.DIV)、クラス、idによって識別することができます。例を挙げると:

1
2
<div class="one" id="a">Hello</div>
<div class="two" id="b">World</div>

これらはそれぞれ "one" と "two" のクラスに属しています。また、idはそれぞれ "a" と "b" という値です。

jQueryでは前者を、次のようなCSSに似た表記で参照できます。

1
2
3
4
jQuery('.one')    // クラスが"one"の要素を示す
jQuery('#a')      // id "a"の要素を示す
jQuery('DIV.one') // クラスが"one""DIV"要素を示す
jQuery('DIV #a')  // id"a""DIV"要素を示す

後者は次のように参照できます。

1
2
3
4
jQuery('.two')
jQuery('#b')
jQuery('DIV.two')
jQuery('DIV #b')

両者を参照するには次のようにします。

1
jQuery('DIV')

タグオブジェクトは "onclick" などのイベントに連携します。jQueryはこれらのイベントとエフェクトをリンクできます。例えば、"slideToggle":

1
2
<div class="one" id="a" onclick="jQuery('.two').slideToggle()">Hello</div>
<div class="two" id="b">World</div>

この時、"Hello" をクリックすると "World” が消えます。もう一度クリックすると、"World" が再表示されます。hiddenクラスを付与するとタグをデフォルトで非表示にできます:

1
2
<div class="one" id="a" onclick="jQuery('.two').slideToggle()">Hello</div>
<div class="two hidden" id="b">World</div>

タグの外部でアクションとイベントをリンクすることもできます。前のコードは次のように書き換えることができます:

1
2
3
4
5
<div class="one" id="a">Hello</div>
<div class="two" id="b">World</div>
<script>
jQuery('.one').click(function(){jQuery('.two').slideToggle()});
</script>

エフェクトは呼び出しオブジェクトを返すので、それらをチェーン(訳注:method chain)させることができます。

click にはクリック時に呼び出されるコールバック関数をセットします。changekeyupkeydownmouseover などでも同様です。

ドキュメント全体がロードされた後にだけ、JavaScriptを実行したいことがよくあります。これは通常、BODYの onload 属性で実現されますが、jQueryはレイアウトを編集する必要のない別の方法を提供します:

1
2
3
4
5
6
7
<div class="one" id="a">Hello</div>
<div class="two" id="b">World</div>
<script>
jQuery(document).ready(function(){
   jQuery('.one').click(function(){jQuery('.two').slideToggle()});
});
</script>

この無名関数の本体は、ドキュメントが完全にロードされ準備ができた時にだけ実行されます。

よく使うイベント名の一覧です:

フォームイベント
  • onchange: 要素の変更時にスクリプトが実行される
  • onsubmit: フォーム送信時にスクリプトが実行される
  • onreset: フォームリセット時にスクリプトが実行される
  • onselect: 要素選択時にスクリプトが実行される
  • onblur: 要素がフォーカスを失った時にスクリプトが実行される
  • onfocus: 要素がフォーカスされた時にスクリプトが実行される
キーボードイベント
  • onkeydown: キーが押された時にスクリプトが実行される
  • onkeypress: キーが押されて離された時にスクリプトが実行される
  • onkeyup: キーが離された時にスクリプトが実行される
マウスイベント
  • onclick: マウスクリック時にスクリプトが実行される
  • ondblclick: マウスのダブルクリック時にスクリプトが実行される
  • onmousedown: マウスボタンが押された時にスクリプトが実行される
  • onmousemove: マウスポインタが動いたときにスクリプトが実行される
  • onmouseout: マウスポインタが要素の外に動いた時にスクリプトが実行される
  • onmouseover: マウスポインタが要素の上に動いた時にスクリプトが実行される
  • onmouseup: マウスボタンが離された時にスクリプトが実行される

jQueryで定義されている、よく使うエフェクトの一覧です:

エフェクト
  • jQuery(...).show(): オブジェクトを表示する
  • jQuery(...).hide(): オブジェクトを非表示にする
  • jQuery(...).slideToggle(speed, callback): オブジェクトをスライドアップ、あるいはダウンする
  • jQuery(...).slideUp(speed, callback): オブジェクトをスライドアップする
  • jQuery(...).slideDown(speed, callback): オブジェクトをスライドダウンする
  • jQuery(...).fadeIn(speed, callback): オブジェクトをフェードインする
  • jQuery(...).fadeOut(speed, callback): オブジェクトをフェードアウトする

speed引数は通常 "slow"、"fast" もしくは省略(デフォルト)です。callback引数はエフェクトが完了した際に実行されるオプションの関数です:

jQueryエフェクトは簡単にヘルパに埋め込むこともできます。例えば、ビューの例を挙げます:

1
{{=DIV('click me!', _onclick="jQuery(this).fadeOut()")}}

選ばれた要素を操作する、他の便利なメソッドと属性です。

メソッドと属性
  • jQuery(...).attr(name): 属性名の値を返す
  • jQuery(...).attr(name, value): 属性名に値をセットする
  • jQuery(...).html(): 引数無しの場合、選択された要素の内のHTMLを返す。引数として文字列を受け取る場合はタグの内容を置き換える。
  • jQuery(...).text(): 引数無しの場合、選択された要素の内のテキストを(タグ無しで)返す。 文字列が引数の場合、内側のテキストを新たに置き換える。
  • jQuery(...).css(name, value): ひとつのパラメータで、選択された要素のスタイルのCSSを返す。2つのパラメータで、指定されたCSS属性に新たに値を設定する。
  • jQuery(...).each(function): 選択された要素をループして、各要素に引数として設定や関数を呼び出す。
  • jQuery(...).index(): 引数無しの場合、最初の要素のその兄弟要素に於けるインデックス値を返す。(例.LI要素のインデックス値)引数が渡された場合, 選択された要素に於ける、その引数の要素の位置(訳注:インデックス値)を返す。
  • jQuery(...).length: 選択した要素の数を返す。

jQueryはとてもコンパクトで簡潔なAjaxライブラリです。そのためweb2pyは(後述する ajax 関数を除き)jQueryの上に追加の抽象化レイヤを必要としません。jQuery APIはそれらのネイティブな形式で必要な時にアクセスでき手軽に利用できます。

これらのエフェクトやこの他のjQuery APIについての詳細は、ドキュメンテーションを参考にしてください。

jQueryライブラリは、プラグインとユーザーインタフェースのウィジェットを使って拡張することができます。このトピックはここでは割愛します;参照[jquery-ui] に詳細があります。

フォームの条件付フィールド

典型的なjQueryエフェクトのアプリケーションは、フィールドの値によってその見た目を変えるフォームです。

これはweb2pyでは簡単です。なぜなら、SQLFROMヘルパが "CSSフレンドリー" なフォームを作成してくれるからです。フォームには列を持つテーブルが含まれます。それぞれの列は、ラベル、入力フィールド、オプション的な第3のカラムからなります。フォームの項目はテーブル名とフィールド名で厳密に指定されたidを持ちます。

約束として、全ての入力フィールドは テーブル名_フィールド名 というidを持ち、そして テーブル名_フィールド名__row というidを持つ列の一部となります。

例として、既婚者の場合のみ納税者の名前とその扶養者の名前を入力するフォームを作成します。

次のモデルを持つ、テストアプリケーションを作成します:

1
2
3
4
5
db = DAL('sqlite://db.db')
db.define_table('taxpayer',
    Field('name'),
    Field('married', 'boolean'),
    Field('spouse_name'))

以下は、コントローラー "default.py" です:

1
2
3
4
5
def index():
    form = SQLFORM(db.taxpayer)
    if form.process().accepted:
        response.flash = 'record inserted'
    return dict(form=form)

そしてビュー "default/index.html" です:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{{extend 'layout.html'}}
{{=form}}
<script>
jQuery(document).ready(function(){
   jQuery('#taxpayer_spouse_name__row').hide();
   jQuery('#taxpayer_married').change(function(){
        if(jQuery('#taxpayer_married').is(':checked'))
            jQuery('#taxpayer_spouse_name__row').show();
        else jQuery('#taxpayer_spouse_name__row').hide();});
});
</script>

ビューのスクリプトは、扶養者名を含む列を非表示にするエフェクトを持っています:

image

納税者が "married" チェックボックスをチェックすると、扶養者名フィールドが再表示されます:

image

"taxpayer_married" は、"taxpayer" テーブルの "married" という "boolean" フィールドに関連しているチェックボックスです。"taxpayer_spouse_name__row" は、"taxpayer" テーブルの "spouse_name" という入力フィールドを含む列です。

削除の確認

confirmation

もう一つの役立つアプリケーションは、編集フォーム上の削除チェックボックスのように、"delete" チェックボックスをチェックしたときに確認を要求するものです。

先ほどの例に、次のコントローラアクションを追加してみましょう:

1
2
3
4
5
6
def edit():
    row = db.taxpayer[request.args(0)]
    form = SQLFORM(db.taxpayer, row, deletable=True)
    if form.process().accepted:
        response.flash = 'record updated'
    return dict(form=form)

対応するビュー "default/edit.html" です。

1
2
{{extend 'layout.html'}}
{{=form}}
deletable

SQLFORMコンストラクタの deletable=True 引数は、web2pyが編集フォーム上に "delete" チェックボックスを表示するようにします。デフォルトは False です。

web2pyの "web2py.js" は次のコードを含みます:

1
2
3
4
5
6
jQuery(document).ready(function(){
   jQuery('input.delete').attr('onclick',
     'if(this.checked) if(!confirm(
        "{{=T('Sure you want to delete this object?')}}"))
      this.checked=false;');
});

約束に従って、このチェックボックスは "delete" と等しいいクラスを持ちます。上記のjQueryコードは、チェックボックスのonclickイベントと確認ダイアログ(JavaScript標準)を関連付け、納税者が承認しない場合はチェックボックスのチェックを外します:

image

ajax関数

web2py.jsの中で、web2pyは ajax という関数を定義しています。これはjQuery関数の $.ajax を基にしていますが、それと混同しないでください。後者(訳注:jQueryの$.ajax)は前者(訳注:web2py.jsのajax)より強力で、その使用方法については 参照[jquery] と 参照[jquery-b] を見てください。その一方で、前者は多くの複雑なタスクをこなすのに十分な機能を持ち、そして簡単に使用できます。

ajax 関数は、次のような構文を持つJavaScriptの関数です:

1
ajax(url, [name1, name2, ...], target)

この関数は、url(第1引数)を非同期的に呼び出し、リスト(第2引数)のnameと同じnameを持つフィールドの値を渡し、そしてターゲット(第3引数)と同じ値のidを持つタグのinnerHTMLに結果を保存します。

default コントローラーの例です:

1
2
3
4
5
def one():
    return dict()

def echo():
    return request.vars.name

対応する "default/one.html" ビューです: and the associated "default/one.html" view:

1
2
3
4
5
{{extend 'layout.html'}}
<form>
   <input name="name" onkeyup="ajax('echo', ['name'], 'target')" />
</form>
<div id="target"></div>

INPUTフィールドに何か入力してキーを離す(onkeyup)と同時に、ajax 関数が呼ばれ、name="name" フィールドの値が "echo" アクションに渡されます。このアクションは、そのテキストをビューに送り返します。ajax 関数はその結果を受け取り、"target" のDIVにechoの結果を表示します。

Eval ターゲット

ajax 関数の第3引数には、文字列 ":eval" を指定できます。これは、サーバーから返された文字列がドキュメントに埋め込まれずに、代わりに評価されることを意味します。

default コントローラーの例です:

1
2
3
4
5
def one():
    return dict()

def echo():
    return "jQuery('#target').html(%s);" % repr(request.vars.name)

対応する "default/one.html" ビューです:

1
2
3
4
5
{{extend 'layout.html'}}
<form>
   <input name="name" onkeyup="ajax('echo', ['name'], ':eval')" />
</form>
<div id="target"></div>

こうすることで複数のターゲットを更新でき、より複雑な結果を返せるようになります。

オートコンプリーション

web2pyにはFormsの章で説明したように、組み込みのオートコンプリート・ウィジェットがあります。ここでは簡単なものを、スクラッチで作成してみます。

上記の ajax 関数のもう一つの応用は、オートコンプリートです。月名(訳注:month name)を求める入力フィールドを作成し、訪問者が不完全な名前をタイプすると、Ajaxリクエスト経由でオートコンプリートを実施するようにします。応答すると、入力フィールドの下にオートコンプリートのドロップボックスが現れます。

次の default コントローラを介して、実現することができます:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def month_input():
    return dict()

def month_selector():
    if not request.vars.month: return ''
    months = ['January', 'February', 'March', 'April', 'May',
              'June', 'July', 'August', 'September' ,'October',
              'November', 'December']
    month_start = request.vars.month.capitalize()
    selected = [m for m in months if m.startswith(month_start)]
    return DIV(*[DIV(k,
                     _onclick="jQuery('#month').val('%s')" % k,
                     _onmouseover="this.style.backgroundColor='yellow'",
                     _onmouseout="this.style.backgroundColor='white'"
                     ) for k in selected])

対応する "default/month_input.html" ビューです:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{{extend 'layout.html'}}
<style>
#suggestions { position: relative; }
.suggestions { background: white; border: solid 1px #55A6C8; }
.suggestions DIV { padding: 2px 4px 2px 4px; }
</style>

<form>
 <input type="text" id="month" name="month" style="width: 250px" /><br />
 <div style="position: absolute;" id="suggestions"
      class="suggestions"></div>
</form>
<script>
jQuery("#month").keyup(function(){
      ajax('month_selector', ['month'], 'suggestions')});
</script>

ビジターが "month" 入力フィールドに何かタイプする度に、ビューのjQueryスクリプトがAjaxリクエストを実行します。入力フィールドの値はAjaxリクエストによって、"month_selector" アクションに送信されます。このアクションは送信された(選択された)テキストで始まる月名のリストを検索し、DIVのリスト(それぞれが候補の月名を含む)を作成し、シリアライズされたDIVの文字列を返します。ビューは返されたHTMLを "suggestions" DIVに表示します。"month_selector" アクションは、候補とDIVに埋め込まれたJavaScriptコードの両方を作成します。JavaScriptコードは、ビジターがそれぞれの候補をクリックする度に実行されます。例えば、ビジターが "M" とタイプすると、コールバックアクションは次の結果を返します:

1
2
3
4
5
6
7
8
<div>
     <div onclick="jQuery('#month').val('March')"
          onmouseout="this.style.backgroundColor='white'"
          onmouseover="this.style.backgroundColor='yellow'">March</div>
     <div onclick="jQuery('#month').val('May')"
          onmouseout="this.style.backgroundColor='white'"
          onmouseover="this.style.backgroundColor='yellow'">May</div>
</div>

最終的な結果は次のようになります:

image

もし月名が次のようにデータベースのテーブルに保存されている場合は:

1
db.define_table('month', Field('name'))

簡単に month_selector アクションを次のように置き換えるだけです:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def month_input():
    return dict()

def month_selector():
    if not request.vars.month:
        return ''
    pattern = request.vars.month.capitalize() + '%'
    selected = [row.name for row in db(db.month.name.like(pattern)).select()]
    return ''.join([DIV(k,
                 _onclick="jQuery('#month').val('%s')" % k,
                 _onmouseover="this.style.backgroundColor='yellow'",
                 _onmouseout="this.style.backgroundColor='white'"
                 ).xml() for k in selected])

jQueryは追加機能を持つオプションのオートコンプリート・プラグインを持ちますが、ここでは説明しません。

Ajaxフォーム送信

asynchronous

ページ全体をリロードしないでAjaxを利用し、メッセージを送信するページを考えてみましょう。12章で後述しますが、LOADヘルパを利用することで、web2pyはここで説明する内容よりもより良い仕組みを提供しています。ここではjQueryを使ったシンプルな方法を示します。

フォーム "myform" と "target" のDIVがあります。フォームが送信されると、サーバーはそれを許可(そしてデータベースへの登録を実行)するか、拒否(バリデーションをパスしなかったため)します。対応するメッセージがAjaxレスポンスで返され "target" のDIVに表示されます。

次のモデルを持つ、test アプリケーションを作成します:

1
2
3
db = DAL('sqlite://db.db')
db.define_table('post', Field('your_message', 'text'))
db.post.your_message.requires = IS_NOT_EMPTY()

それぞれの投稿は、空入力が許されない、一つのフィールド "your_message" を持つ点に注意してください。

default.py コントローラを編集し、次の二つのアクションを記述します:

1
2
3
4
5
6
7
8
9
def index():
    return dict()

def new_post():
    form = SQLFORM(db.post)
    if form.accepts(request, formname=None):
        return DIV("Message posted")
    elif form.errors:
        return TABLE(*[TR(k, v) for k, v in form.errors.items()])

最初のアクションは単にビューを返すだけです。

二つ目のアクションはAjaxのコールバックです。これは request.vars 内にフォーム変数を待ち、それらを処理して、成功時には DIV("Message posted") を返し、失敗時にはエラーメッセージの TABLE を返します。

さあ、"default/index.html" ビューを編集します:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{{extend 'layout.html'}}

<div id="target"></div>

<form id="myform">
  <input name="your_message" id="your_message" />
  <input type="submit" />
</form>

<script>
jQuery('#myform').submit(function() {
  ajax('{{=URL('new_post')}}',
       ['your_message'], 'target');
  return false;
});
</script>

この例はHTMLを利用して手書きでフォームが作成されていますが、フォームを表示しているのとは異なるアクションでSQLFORMによって処理されている点に注意してください。SQLFORMオブジェクトはHTMLにシリアライズされることはありません。この場合は SQLFORM.accepts はセッションを受け取らず formname=None をセットします、これは手書きのHTMLフォームにおいて、フォーム名とフォームキーをセットしないように選択したからです。

ビューの下部にあるスクリプトは、"myform" 送信ボタンとインライン関数を関連付けています。このインライン関数は、web2pyの ajax 関数を利用して id="your_message" を持つ入力フィールドを送信し、id="target" を持つDIVの中にその回答を表示します。

投票と評価

もう一つのAjaxアプリケーションは、ページでアイテムの投票(訳注:Voting)、または評価(訳注:Rating)するものです。投稿された画像に、ビジターが投票できるアプリケーションを考えてみます。そのアプリケーションは、投票結果に応じてソートされた画像を表示する単一のページからなります。ここでは、ビジターが複数回投票できるようになっています。ただしビジターが認証されていれば、この動作を変えるのは簡単です。データベースにある個別の投票を追跡し、投票者の request.env.remote_addr に関連付ければいいのです。

簡単なモデルの例です:

1
2
3
4
db = DAL('sqlite://images.db')
db.define_table('item',
    Field('image', 'upload'),
    Field('votes', 'integer', default=0))

default コントローラです:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def list_items():
    items = db().select(db.item.ALL, orderby=db.item.votes)
    return dict(items=items)

def download():
    return response.download(request, db)

def vote():
    item = db.item[request.vars.id]
    new_votes = item.votes + 1
    item.update_record(votes=new_votes)
    return str(new_votes)

downloadアクションは、list_itemsビューが "uploads" フォルダに保存されている画像を、ダウンロードできるために必要です。voteアクションはAjaxのコールバックで使用されます。

"default/list_items.html" ビューです:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{{extend 'layout.html'}}

<form><input type="hidden" id="id" name="id" value="" /></form>
{{for item in items:}}
<p>
<img src="{{=URL('download', args=item.image)}}"
     width="200px" />
<br />
Votes=<span id="item{{=item.id}}">{{=item.votes}}</span>
[<span onclick="jQuery('#id').val('{{=item.id}}');
       ajax('vote', ['id'], 'item{{=item.id}}');">vote up</span>]
</p>
{{pass}}

ビジターが "[vote up]" をクリックすると、JavaScriptコードはitem.idの値を隠している "id" INPUTフィールドに保存し、Ajaxリクエストによってサーバーに値を送信します。サーバーは対応するレコードの投票カウンターを増やし、新しい投票カウンターを文字列で返します。そしてこの値が目標の item{{=item.id}} SPANに挿入されます。

Ajaxのコールバックはバックグラウンドでコンピュータ操作の実行に利用できますが、cron もしくは(4章で説明した)バックグラウンド処理の使用を推奨します。webサーバーがスレッドのタイムアウトを強要するからです。もし計算に時間がかかり過ぎると、webサーバーは処理を終了します。タイムアウト値のセット方法は、webサーバーのパラメータを参照してください。
第3版 - 翻訳: Omi Chiba レビュー: 細田謙二
第4版 - 翻訳: Mitsuhiro Tsuda レビュー: Omi Chiba
第5版 - 翻訳: Mitsuhiro Tsuda レビュー: Hitoshi Kato
 top