2009年01月19日

「flexigrid meets Django!」

最近はJavaScriptのライブラリとしてはもっぱらjQueryを使ってます。少ない行数でシンプルに書けるのがよいです。そんなにJavaScriptは得意じゃないのと、Pythonが好きということもあり、コードがシンプルに書けるのところが気に入ってます。jQueryはプラグインが何種類もあるのですが、その中で、少ない行数でソート機能、検索機能などいろんな機能を持った表を作成できるのがflexigridです。使ってみたのでメモをまとめてみました。

GTDソフト開発中

まずは関連ページ。

まずflexigridですが、以下のような機能があります。上2つに関してはmycomの日本語の紹介が分かりやすいのでそちらをどうぞ。

  • 既存の<table>タグをキレイに整形
  • テーブル、カラムの伸縮および、表示のON/OFF
  • Ajaxによる動的な表の読み込み
  • 項目のソート
  • ページング
  • 検索(というかフィルタ)
  • 追加、削除などのボタンの追加

このエントリーでは下の5つに関してのメモになります。続きをどうぞ。ちなみに動くサンプルは用意してません。このエントリーはflexigridに関するチュートリアルではなくてエッセーですので。

動的なテーブルの読み込み

まず、準備としてflexigridのソースが必要ですが、本家につながらない代わりに、上記のサンプルページからダウンロードできるファイルの中に含まれます。Ajaxの動的な表の読み込みですが当然、サーバサイドの開発が必要になります。出力のJsonなりXMLを静的にWebサーバに置いておけばエミュレートはできますが。まず、JavaScript側で以下のように記述します。

$(document).ready(function() {
    $('#task_table').flexigrid({
        url: '/read/table/',
        method: "POST",
        dataType: 'json',
        colModel : [
            {display: '', name : 'check', width : 16, sortable : false, align: 'left'},
            {display: '', name : 'icon', width : 16, sortable : false, align: 'left'},
            {display: 'Date', name : 'date', width : 72, sortable : false, align: 'left'},
            {display: 'Title', name : 'title', width : 300, sortable : true, align: 'left'},
            ]
    });
});

ここで忘れずに指定するのは以下の項目です。

  • url: テーブルを読み込みに行くURLです。省略不可。
  • method: POSTかGETを指定します。省略時はPOST。
  • dataType: jsonかxmlを指定します。省略時はxml。
  • colModel: 表示する表の設定です。省略不可。nameは適当でもいいんですが、後でソートしたりフィルタするときに重要になってきます。

これに対して、サーバ側から返すべきjsonは以下のような形式になります。XMLの方は調査してませんので分かりませんが、基本は一緒だと思います。

{"rows": [
  {"cell": ["", "", "01/15", "buy book"], "id": 2}, 
  {"cell": ["", "", "01/18", "buy milk"], "id": 23}
], "total": 2, "page": 1}

鋭い方はお気づきだと思いますが、HTMLを書くとそのまま表示されちゃいます。画像を表に入れる時には便利ですが、XSSの危険性があるので、サーバ側で動的に生成する場合はくれぐれもエスケープ処理をわすれずに。なお、今はpage=1にしていますが、ページング処理をしないときは1にしておけば大丈夫です。Djangoではこんな感じで生成してます。taskオブジェクトごとにjsonの行を作る処理は本当はモデル側にインスタンスメソッドとして定義しているんですが、メソッドが増えるとサンプルが複雑になるので一つの関数の中で処理してます。これで動的読み込みはOKです。

import cgi
from django.http import HttpResponse
from django.utils import simplejson

def read_table(request):
    rows = []
    for task in models.TaskCard.objects.all():
        if task.finish_date is None:
            check = ''
        else:
            check = ''
        icon = '' % task.image_path
        cell = [check, icon, task.target_date, cgi.escape(task.title)]
        rows.append({'id':self.id, 'cell':cell})
    result = {'page':1, 'total':len(rows), 'rows':rows}
    return HttpResponse(simplejson.dumps(result))

項目のソート

Mochikitのテーブル処理はすべてクライアントサイドのJavaScriptでソート処理ができますが、flexigridはソートの処理はサーバサイドで行います。静的な表を作った場合にソートがどうなるかは調査してません。もしためされた方がいたら教えて下さい。表の初期時にURLを指定しますが、表の項目を選択すると、そこのURLへのリクエストが発生します。ソートに関係するパラメータは以下の2つです。初期化オプション内でもデフォルトソート設定として指定できます。また、URLリクエストが発生した場合は、POST(かGETか初期化オプションで指定した方)処理の引数としてサーバサイドにわたってきます。

  • sortname、ソートするキー
  • sortorder、ascかdescか。昇順、降順

こんな感じで初期化時に設定します。

$(document).ready(function() {
    $('#task_table').flexigrid({
        url: '/read/table/',
        method: "POST",
        dataType: 'json',
        sortname: 'date', // デフォルトでは日付でソート
        sortorder: 'desc', // 
        colModel : [...] //省略
    });
});

サーバサイドでは以下のように処理します。Djangoだとall()で取ってくる部分をorder_byにして、sortnameを利用するコードを追加するだけですね。

    sortkey = request.POST['sortname']
    if request.POST['sortorder'] == 'desc':
        sortkey = "-" + sortkey
    for task in models.TaskCard.objects.order_by(sortkey):
        # 省略

ページング

ページングというのは、検索エンジンの「次の10件」みたいな処理です。行数が大量になる場合に少しずつユーザに見せるようにします。この処理はUIに関わる部分はflexigridがほとんどを面倒見てくれますが、指定ページに含まれるデータを抽出する部分はサーバサイドで行う必要があります。これをするには以下のオプションを設定します。

  • usepager: trueにするとページングをします。デフォルトはfalse。
  • rp: 1ページに表示する行数です。デフォルトは15行。
  • useRp: デフォルトはtrue。1ページの表示数を変更するプルダウンメニューを表示します。
  • rpOptions: 上記をtrueにしたときに選択できる行数のメニュー項目です。デフォルトは[10,15,20,25,40]です。

POST(かGET)で呼ばれる時には以下の2つのパラメータが設定されますので、これを見て、返す項目を選択します。

  • page、表示したいページ
  • rp、一ページあたりの表示数

ちょっと複雑になりますが、ソートと組み合わせるとこんな感じになります。DjangoのORマッパーは配列のスライスと同じ構文で抽出する項目の指定ができますので、そのための計算をします。後は最後に返すjsonのpageの項目はきちんと設定します。あと、totalも表示の行数ではなくて、すべての項目数になるのでコードの修正が必要になります。これで、検索エンジンのような表示ができ、1ページに大量の項目が表示されてユーザビリティを下げることはなくなります。

    #ここはソート
    sortkey = request.POST['sortname']
    if request.POST['sortorder'] == 'desc':
        sortkey = "-" + sortkey
    #ここはページング
    page = int(request.POST['page'])
    rp = int(request.POST['rp'])
    page_start = (page-1) * rp
    page_end = page * rp
    for task in models.TaskCard.objects.order_by(sortkey)[page_start:page_end]:
        # 省略
    result = {'page':page, 'total':models.TaskCard.objects.count(), 'rows':rows}

検索

検索に関しても、考え方はほとんどページングやソートと一緒です。サーバサイドで項目の検索を行います。まずは初期化リストに、検索したい項目をsearchitems配列に入れておきます。入れた数だけ、プルダウンメニューに追加されます。

$(document).ready(function() {
    $('#task_table').flexigrid({
        url: '/read/table/',
        method: "POST",
        dataType: 'json',
        sortname: 'date', // デフォルトでは日付でソート
        sortorder: 'desc', // 
        colModel : [...], //省略
        searchitems : [
            {display: 'Title', name : 'title'}]
    });
});

POST(もしくはGET)にはパラメータが追加されます。ちなみにデフォルトはそれぞれ空文字列なので、その場合はフィルタから外す処理は必要だと思います。

  • query、検索する場合のクエリの文字列
  • qtype、検索する場合のテーブルの列

ページングやソートの処理の後ろに下記のコードを追加(for部分は変更)します。下記の例だとDjangoのORマッパーのfilterメソッドで簡単にやってますが、datetime型だったら範囲を作って、filter(target_date__range=[start, end])のように、範囲指定をしたり、文字列も完全一致ではなくて、__containsを使って、含まれていたらOKもしくは__startswithで、スタートが一致したらOKなど、使い勝手を考えて色々アレンジできる部分です。

    query = request.POST['query']
    qtype = request.POST['qtype']
    if qtype:
        query_set = models.TaskCard.objects.filter(**{qtype:query}).order_by(sortkey)
    else:
        query_set =  models.TaskCard.objects.order_by(sortkey)
    for task in query_set[page_start:page_end]:
        # 省略
    result = {'page':page, 'total':models.TaskCard.objects.count(), 'rows':rows}

ボタンの追加

最初のスクリーンショットの上部にはAddとDeleteとボタンがついていますが、これは初期化オプションの中で設定ができます。buttonsに追加するボタン情報を入れます。

$(document).ready(function() {
    $('#task_table').flexigrid({
        url: "/read/table/',
        method: "POST",
        dataType: 'json',
        colModel : [...] //省略
        buttons : [
            {name: 'Add', bclass: 'add', onpress : add_pressed},
            {separator: true},
            {name: 'Delete', bclass: 'delete', onpress : delete_pressed}]
    });
});

nameは表示される名前です。bclassはボタンに設定されるスタイルシート名、onpressはメソッドを指定します。自分でボタンのクラスを追加する場合は、以下のCSSを書きます。これでblcass: 'view'の虫眼鏡アイコン付きボタンになります。

.flexigrid div.fbutton .view {
background: url(lib/flexigrid/images/magnifier.png) no-repeat center
left;
} 

onpressで渡す関数のパラメータは1つか2つ。1つだとnameで設定したAddなどが、2つだと2番目に表そのものの要素が入ってきます。flexigridでは、行が選択されると、cssクラスにtrSelectedが追加されるので、これを利用して処理をしてやります。削除処理はこんな感じです。Ajaxで削除処理をサーバに依頼してますが、見た目の上でもfadeOutしてます。

var delete_pressed = function(command, grid) {
    var items = $('.trSelected',grid);
    var itemlist = [];
    for(i=0 ; i< items.length ; i++) {
        itemlist.push(items[i].id);
    }
    items.fadeOut('slow')
    $.post('/delete_items/',
           { method:'delete',
             delete_items:itemlist.join(',')},
           function(return_value, status) {},
           'text');
}

flexigridのAPI/フック関数

flexigridで作った表はいくつか外部から操作することができます。完全には把握していませんが、分かっているのは以下の通りです。

  • $('#table').flexReload(); : リロードします。
  • $('#table').flexOptions(p); : オプションをアップデートらしいですが良くわかってません。
  • $('#table').flexToggleCol(cid, visible); : カラムのON/OFFを切り替えるらしいです。

初期化オプションでいくつかのフックメソッドを設定できます。onSuccessしか使ってないので他のは名前からの推測です。

  • onToggleCol : カラムの表示、非表示が切り替わったときに呼ばれる?
  • onChangeSort : ソートされたときに呼ばれる・・・のかな?
  • onSuccess : 表の読み込みが完了した際に設定します。
  • onSubmit : リクエストをカスタマイズする際に設定します。

onSuccessを使うと、読み込み終わった後にイベントハンドラを設定して、行を選択するだけで処理を行うなどの設定をするのに使えます。

一気に書いたので間違いとか色々あるかもしれません。もしお気づきの点があればご指摘願います。

posted by @shibukawa at 00:00 | Comment(271) | TrackBack(1) | プログラミング はてなブックマーク - 「flexigrid meets Django!」
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/26078475

この記事へのトラックバック

flexigridウェブサイト移転
Excerpt: 4ヶ月前のブログの記事でflexigridを取り上げましたが、最近、新しいサイトに移っているのに気づきました。 http://www.flexigrid.info/ 最新版(といっても、200..
Weblog: 渋日記
Tracked: 2009-05-22 01:57
検索ボックス

Twitter

www.flickr.com
This is a Flickr badge showing public photos and videos from shibukawa.yoshiki. Make your own badge here.
<< 2017年05月 >>
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
最近の記事
カテゴリ
過去ログ
Powered by さくらのブログ