非同期Javascriptの追加

テンプレートにJavaScriptを追加したかどうか思い出してください。この章ではMochiKit Javascriptライブラリを利用してアプリケーションにAJAXを追加します。(実際には、JSONを使うだけでAJAXではないです。TurboGearsではそれは簡単に作れます。)

よく使う機能を一つ、ページリロードをしないでインラインでアイテムリストを編集できる機能をアプリケーションに追加します。
コーディングを始める前に、ここに必要な概要を上げます。

  • 各アイテムへの編集ボタンを加える。
  • ボタンを押したときに、textアイテムをinputフィールドに置き換える。
  • ボタンを押したときに、editボタンを置き換えてsaveボタンにもする。
  • 変更してsaveボタンを押したとき、サーバに変更の値を送る。
  • サーバ上で、コントロールメソッドはデータベースへ内部アイテムの修正と書き込みを処理する。
  • inputフィールドが値の変更と同時にテキストに戻ったとき変更する。


最初は簡単です。それぞれのリストアイテムの横にEditボタンを置きます。 tutorial/templates/user.kidに、それぞれのアイテムリストに新しいフォームを追加します。フォームとJavascripを一緒にサブミットするようにしてからは、actionやmethodは必要はなく、代わりに、フォームにonsubmit属性、ボタンにonclick属性を加えます。

<ol>
    <li py:for="item in userList.items">
        <form style="float: right" action="/removeItem" method="POST">
            <input type="hidden" name="userID" value="${user.id}"/>
            <input type="hidden" name="itemID" value="${item.id}"/>
            <input type="submit" value="Remove"/>
        </form>
        <form onsubmit="return saveItem(this)">
            <input type="button" style="float: right" value="Edit"
                   onclick="editItem(this)"/>
            <span>${item.value}</span>
            <input type="hidden" name="itemID" value="${item.id}"/>
        </form>
    </li>
</ol>

見てわかるように、2つのまだ実在しないJavaScript関数を呼び出しました。これから、一つのページだけで、先ほどの機能を実現します。tutorial/staitc/javascript/user.jsに新しくファイルを作ります。そしてそれをuser.kidので読み込みます。

<script type="text/javascript" src="/static/javascript/user.js"></script>

はじめに、前述のinputフィールドと一緒にitemテキストを置き換えるEditボタン(editItemへ呼ぶ)を作成します。
user.jsに加えます。

var editItem = function(button) {
    var item = getElementsByTagAndClassName('span', null, button.parentNode)[0];
    var editBox = INPUT({'type': 'text', 'value': item.innerHTML});
    swapDOM(item, editBox);
    swapDOM(button, INPUT({'type': 'submit', 'style': 'float: right',
                           'value': 'Save'}));
}

このJavaScripthaは、MochiKitを少し利用します。より明確にはINPUTです。"input"エレメントは特別な属性とともに作られます。INPUTはeditフィールドとSaveボタンを作るために、ここで利用されます。

今、Saveボタンがあり、押すとフォームを送信します。フォームのonsubmit属性はこのようにsaveItemを呼びます。

var saveItem = function(form) {
    var fields = getElementsByTagAndClassName('input', null, form);
    var editBox = fields[1];
    var itemID = fields[2].value;
    var button = fields[0];
    var d = postJSON('/editItem', {'itemID': itemID, 'value': editBox.value});
    d.addCallback(showChanges, editBox, button);
    return false;
}

関数戻り値はfalseです。
なぜならonsubmitの戻り値がfalseに遭遇した時は、フォームactionのトリガーではないからです。ふつう、この原因はページのリロードです。
(フォームにactionを与えておらず、代わりにJavaScriptを一緒にしてるということを思い出してください)

この関数の最も重要なのは、postJSONへ呼ぶことです。JSONとはJavascript Object Notationで、データをとり回す軽量フォーマットです。
XML構造のパースと組み立てを要求します。ここではXMLの代わりとして使います。MochiKitTurboGearsは、必要な箇所でのJavaScriptの中へJSONの動的な変換ルーチンを提供します。そして、コントローラーの中のPythonオブジェクトへ代入します。
残念ながら、postJSONはMochiKitの一部ではありませんが、このコードで実行できます。

var postJSON = function(url, postVars) {
    var req = getXMLHttpRequest();
    req.open("POST", url, true);
    req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    var data = queryString(postVars);
    var d = sendXMLHttpRequest(req, data);
    return d.addCallback(evalJSONRequest);
}

When postJSON is called, it immediately returns a Deferred object. If you're familiar with the Twisted library for Python, the concept is the same.
postJSONを呼び出したとき、直ぐに違うオブジェクトが帰ってきます。PythonのTwistedライブラリに精通していたなら、コンセプトは同じです。

Adding callbacks to a Deferred causes them to be invoked when the result of the call is ready.
遅延箇所に追加したコールバックは、呼び出し準備ができた時に呼び出されます。

In saveItem, we add the showChanges function as a callback.
saveItem内で、コールバックとしての機能をshowChangesに追加します。

showChanges will change the client's display of the modified item once it is sure that the server received the changes.
サーバの変化の確認を受け取ると、showChangesはクライアントの変更された項目の表示を変更します。

This requires replacing the edit field with the new item text, and replacing the Save button with the Edit button:
この要求は新しいitemと一緒に編集フィールドを置き換えます。そして、Editボタンと一緒にSaveボタンへ変更します。

var showChanges = function(editBox, button, data) {
    swapDOM(editBox, SPAN(null, data.value));
    swapDOM(button, INPUT({'type': 'button', 'style': 'float: right', 'value': 'Edit',
                           'onclick': function() { editItem(this); }}));
}

That's all the JavaScript needed. One more addition to the Root controller and you're done. Notice that our postJSON call sent its input data to /editItem. This is the new resource to add:

すべてのJavaScriptには必要です。Rootコントローラにもう一度加えると終了です。
注意:postJSONはinputデータを/editItemに送ります。

class Root(controllers.RootController):
    ...

    @turbogears.expose(format="json")
    def editItem(self, itemID, value):
        try:
            itemID = int(itemID)
            item = model.Item.get(itemID)
        except (ValueError, model.SQLObjectNotFound):
            raise CherryPy.NotFound
        else:
            # Remove extra spaces
            value = value.strip()
            # Remove null bytes (they can seriously screw up the database)
            value = value.replace('\x00', '')
            if value:
                item.value = value
            else:
                value = item.value
        return dict(value=value)

新しいことは、turbogears.exposeへ通過するformat="jsonだけです。
このメソッドから返ってくる引数は、控えているJSONJavaScriptへと送られます。
TurboGearsJSONへの変換します。私たちには中のJSONobjectが返ってきます。

必要ならばサーバを起動します。そしていくつかitemを編集します。localhostからアクセスしているならば、itemの保存はとても敏感に動きます

MochiKitを巧みに使うの非同期JavaScriptとDOMのヘルプはここにあります。
http://mochikit.com/doc/html/MochiKit/index.html