2010/12/31

HTMLをサーバー側で生成すべきか?

DjangoTemplateとAjax でも書きましが、Django Template はサーバー側でのHTML生成をサポートする仕組みです。GAE/Python では Django Template が標準装備されており、スタートガイドのチュートリアルでも使用方法が紹介されています。その流れでDjango Templateを使用して自分でもアプリを作ってみました。しかしAjaxを使用する段になって、サーバー側でHTMLを生成してクライアント側でinnerHtmlを書き換えるという手法に、違和感を覚えました。

HTMLはMVCパターンでいうところのViewに当たる部分です。そのViewをサーバー側で作ってしまったら、疎結合なサービスとして提供できなくなってしまいます。たとえデフォルトのViewは提供するとしても、Viewと切り離したサービス(つまりデータとビジネスロジックであるModel)のインターフェースを公開しておけば、誰でも好きなViewを勝手に作ることができるようになります。(お前のアプリのViewなんか誰が作るんだ、という話は別にして(^_^;))。

従って、AjaxではJSON(XMLだとデータサイズが大きくなるのでJSONを第一選択としたい)でデータを取得して、JavaScriptのControllerでHTMLを生成というのが、ストレートな解法だと思います。

しかし、課題もあります。
1. Django Template のような仕組みを、JavaScript + JSON で実現する必要がある
2. クライアント側に処理負担がかかる(未検証)

1. についてはライブラリを探すとして、2. についてはちょっと注意が必要です。
処理を 「サーバー側処理 + 通信 + クライアント側処理」に分けると、クライアント側でHTMLを作成する場合は、サーバー側処理と通信(HTML > JSONとして)の負荷が減ります。しかし、サーバー側の処理が減った分、クライアント側で処理が増えます。
モバイル端末の性能向上には目覚しいものがあるとは言え、比較的重たい処理を行う場合は処理速度の検証が必要でしょう。

とここまで考えて探してみると、中島聡氏がブログでもっと正確で判り易くて格好良い説明を図入りでされていました(「RESTful MVC」なアーキテクチャの話)。しかも 1. のクライアント側Templateの仕組みをjQueryのplug-inとして作成されていました(jQBinder, ブラウザー側でのHTML templateを可能にするjQuery plug-in)。ちょっと凄過ぎです。
さらに、既に2005年の段階でAjaxの本質として「データ・バインディングはサーバー側ではなく、クライアント側で行う」ということを挙げられています(Ajaxの本質、「非同期メッセージ型ウェブ・アプリケーション」のススメ)。

以前ブログを読んだ時にはちゃんと分かっていなかったということが、わかりました。

それでは皆さん、良いお年を (^O^)/

ChromeExtensionsのpopupで選択中のタブを取得する方法

ポップアップタイプの Chrome Extensions で選択中のタブを取得する方法のメモです。
結論としては、以下のようになります。

chrome.windows.getCurrent(function(currentWindow){
    chrome.tabs.getSelected(currentWindow.id, function(selectedTab){
            :
            :
    })
});

chrome.tabs モジュールにはもっとシンプルな getCurrent() 関数があります。
しかし、ドキュメントに
May be undefined if called from a non-tab context (for example: a background page or popup view).
との記載がある通り、ポップアップタイプのChrome Extensionsで使用しても現在のタブが取得できません。そこで
  1. chrome.windows.getCurrent() 関数で選択されているWindowのIDを取得する
  2. WindowのIDを指定してchrome.tabs.getSelected() 関数で選択中のタブを取得する
という段取りになります。
JavaScript初心者に分かり難いのは、getCurrent()/getSelected() 関数がともに、WindowやTabを受け取るコールバック関数を引数にとるという点です。たとえば、getSelected() であれば以下のような形です。
    chrome.tabs.getSelected(integer windowId, function callback)
    callback --->  function(Tab tab) {...};
そのコールバック関数を無名関数でカスケードさせたのがこの記事の最初のスニペットです。

ところでドキュメントを読めば、chrome.tabs.getSelected()の第一引数WindowIDはOptionalでDefaultはCurrentWindowとなっています。そのため以下のように、第一引数にnullを指定することで、コールバック関数のカスケードを回避するが可能です。

chrome.tabs.getSelected(null, function(selectedTab){
            :
            :
});
とはいえ、JavaSccriptで省略可能な引数を引数リストの末尾に持ってこないのは、ちょっとマナー違反です。明示的にnullを渡さないといけないのですが、これではもう省略可能ではありません。また後でコードを読んだ時に、null が何を表すかわからなくなりそうです。

DjangoTemplateとAjax

Django Template は、サーバー側(GAE)でHTMLを生成する仕組みです。その対比でいくとAjaxは、クライアント側(ブラウザ)でHTMLを生成/変更/削除する仕組みと言えます。ではDjango Templateを使ってAjaxを実現するにはどうしたら良いのか、ちょっと悩んでしまいました...

で、結論としては、Django Templateで部分HTMLを作成してAjaxでHTMLを差し替える、というのが解の1つかと思いました。

つまり以下のようなHTMLを用意しておいて、
<html>
    <head>
            :
            :
    </head>
    <body>
            :
            :
        <div id="TargetForAjax"></div>
            :
            :
    </body>
</html>
getElementById("TargetForAjax").innerHTML = (Templateで作成したHTML)
としてやれば良いわけです。

prototype.jsを使用するのであれば、クライアント側のJavaScriptは以下のような感じです。
new Ajax.Request(
    URL,
    {
        method: "get",
        parameters: {arg: myArgument},
        onSuccess: function(transport){
            Element.update($("TargetForAjax"), transport.responseText);
        },
        onFailure: function(){alert("Something went wrong...") }
    }
);

Ajaxというと受信するデータはついつい、XML/JavaScript/JSONなどを想定してしまいますが、部分HTMLでも良いわけですね。

2010/12/30

DjangoTemplateでGAEデータストアのIDを使用する

2ヶ月ぶりのブログ更新です。遊んでいたわけではなく、GAEとChrome Extensionsで遊んでいた(^_^;)からです。ということで、Code Snippets いきます。

GAEのデータストア内のレコードには、アプリケーション全体で一意となる数値IDが自動でふられます。しかしGAEのドキュメントには、そのIDをDjango Templateで使用する方法が書かれていませんでしたので、メモを残しておきます。

Python側
例えば以下のように、MyModelのレコードを10件取得するとします。
template_values = {
  "items" : MyModel.all().fetch(10)
}

Template側
「key.id」でアクセス可能です。
{% for item in items %}
    {{ item.key.id }}</br>
{% endfor %}

たったこれだけですが、意外に悩んでしまいました。