空想犬猫記

※当日記では、犬も猫も空想も扱っておりません。(旧・エト記)

Ajax with prototype.js の実験と Web アプリの MVC に関する妄想

さて,昨日の予定通り,本日は prototype.jsAjax クラスを調べてみる。参考にしたのはこちら。だいぶ流行から乗り遅れたおかげで,資料が充実した環境で効率的に学べるという恩恵に与れるとは,マイペースも悪く無いなとおもう(我ながらおめでたい)。

Class オブジェクトくらいだと,それほど「prototype.js スゲェッ!」とは思わなかったが,Ajax クラスは確かにイイ感じ。

まずは,たんじゅんに database のテーブルをそのまま表示してみる実験をしてみた。これだったら普通に CGI を使った方がいいかもしれないけど,それなりに意味がある。CGI が HTML ではなく,XML をよこすようにすることで,CGI は完全にモデル,すなわちアプリケーションロジックを提供することに特化できるようになるからだ。

みたいな感じ? V と C に技術の入り込む余地はあまりないから,スクリプトで公開してしまっても OK だと考えると,このデザインはわりとアリかも知れないなあ。あ,でも現実には JavaScript の互換性の問題がシビアだったりするのだろうか?あー,あるいは M の隠蔽を考えると C の一部をサーバーサイドに置いて,ある程度整形された V を出力するようにしないとだめとかかなぁ・・・ぶつぶつ。

閑話休題。

で,実験の続き。具体的には,データベースのテーブルを以下のような XML で出力(Web API のつもり)。

<table rows="4" cols="3">
<schema>
<field name="id" type="long" maxLength="1"/>
<field name="name" type="var_string" maxLength="14"/>
<field name="parent" type="long" maxLength="1"/>
</schema>
<data>
<record id="1" name="Recipe" parent="0"/>
<record id="2" name="Curry" parent="1"/>
<record id="3" name="Beef" parent="2"/>
<record id="4" name="Pork" parent="2"/>
</data>
</table>

で,HTML 中に

<table id="plain_table" />

という空の table を埋め込んでおく。これに JavaScript の DOM API を使って要素(やイベント)を注入するという仕組み。ちょっと長いけど JavaScript のコードも貼付けておこう。

var PlainTable = Class.create(); PlainTable.prototype =
{
    table : null,
    url   : null,

    initialize : function(tableId, url)
    {
        this.table = $(tableId);
        this.url   = url;
    },

    update : function(onCompleteCallback)
    {
        var ajax = new Ajax.Request(this.url, { method : 'get', onComplete : onCompleteCallback });
    }
};
var PlainTable = Class.create(); PlainTable.prototype =
{
    url      : null,
    callback : null,
    indicator: null,

    initialize : function(tableId, url, indicator)
    {
        this.url      = url;
        this.callback = function(req) { plaintable_onComplete(req, $(tableId), indicator); };
        this.indicator = indicator;
    },

    update : function()
    {
        this.indicator.run();
        var ajax = new Ajax.Request(this.url, { method : 'get', onComplete : this.callback });
    }
};

function plaintable_onComplete(req, table)
function plaintable_onComplete(req, table, indicator)
{
    if (4 != req.readyState || 200 != req.status) {
        alert("ERROR:");
        return;
    }

    var i, j;
    var schema = req.responseXML.getElementsByTagName("schema")[0];
    var data   = req.responseXML.getElementsByTagName("data")[0];

    // create header
    var tf = null;
    var tr = document.createElement("tr");
    var fields = schema.getElementsByTagName("field");

    for (i = 0; i < fields.length; ++i) {
        tf = document.createElement("th");
        tf.className = "field-name";
        tf.appendChild(document.createTextNode(fields[i].getAttribute("name")));
        tr.appendChild(tf);
    }
    table.appendChild(tr);

    // data
    var records = data.getElementsByTagName("record");

    for (i = 0; i < records.length; ++i) {
        tr = document.createElement("tr");

        for (j = 0; j < fields.length; ++j) {
            tf = document.createElement("td");
            tf.appendChild(document.createTextNode(records[i].getAttribute(fields[j].getAttribute("name"))));

            if ("long" == fields[j].getAttribute("type"))
                tf.className = "num";

            tr.appendChild(tf);
        }
        table.appendChild(tr);
    }
    indicator.stop(); //added
}

で,HTML 読み込み時などに

var indicator;
var plainTable;

addEvent(window, "load", pageInit);

function pageInit()
{
    indicator = new ProgressIndicator("progress_indicator");
    plainTable = new PlainTable("plain_test", "http://localhost/~xoinu/cgi-bin/test_plain.cgi", indicator);
    plainTable = new PlainTable("plain_test", "http://localhost/~xoinu/cgi-bin/test_plain.cgi");
    indicator.run();
    plainTable.update(function(req) { plaintable_onComplete(req, plainTable.table); indicator.stop(); });
    plainTable.update();
}

こんな感じで pageInit() が呼ばれるようにする。さっき作った ProgressIndicator も埋め込んでみた。JavaScript は,先週の土曜日に考察よれば,this の振る舞いが C++ などと異なるので,plainTable.table を明示的に渡さなければならない。だからコールバックの登録が何だかぎこちなくなってしまっている。この辺りはまだ理解が怪しいところ。indicator.run(),indicator.stop(),plainTable.update() の呼び出し方法,callback の登録方法はまだ再考の余地ありということで。
→ 改良済み。だいぶすっきりした。