When it’s ready.

出来るまで出来ない

SNBinderに目からウロコ 小さなMVCが今現実に

タイトルはちょっと大げさすぎですね。もうブカブカです。

WebアプリのMVCネタで、毎度同意しまくりのsatoshiさんが、JSのライブラリSNBinderを公開されています。
Life is beautiful: JavaScript HTMLテンプレートエンジン SNBinder 公開

BODYタグオンリー大好きっ子なので、これは使わずには居られないと思い早速やってみました。
ついでに、恐れ多くもgithubでForkしてreadmeの和訳などしています。

コードサンプルを元に動かしてみたんですが色々と動かない・・・
私の理解ミスな気がするのですが、ツッコミもらおうとコード晒します。オリジナルのまま動いてる人いたらどこが違うか教えてくださいませ。

SNBinderの使い方として、READMEに書かれているやり方は、4種類あります。

  1. 変数一つをテンプレートに埋め込む
    1. SNBinder.bind(template, hash)
  2. 配列から一つずつ抜き出しテンプレートを複数展開し変数を埋め込む
    1. SNBinder.bind_rowset(template, hash)
  3. 外部からテンプレートを読み込み変数を埋め込む
    1. SNBinder.get_named_sections(url, callback)
  4. 外部からテンプレートを読み込み、外部エンドポイントから取得したJSONデータを埋め込む
    1. SNBinder.get(url, params, isJson, callback, options) と上記組み合わせ

特に3,4の外部からテンプレートを読み込むところがこれだー!って感じです。
NSBinderは、DOM要素ごとに、テンプレートファイルから部品を取り出し完全にJSのコントロール下でHTMLパーツを配置していきます。例えていうと、パレットから必要な色を取り出しキャンバスを彩る油絵の如く、テンプレートファイル内から必要な部品を取り出し必要な場所に出したり消したりして画面を構築します。

JS内にテンプレート(HTML)要素が混ざることもないし、テンプレート(HTML)内にJS(ロジック)が混ざることもない。銀座かわむらのコンソメスープのごとく一切混ざり物なしでソースをクリーンな状態に保つことが出来そう。

NSBinderとflaskでテストしてみました。

Flaskサーバーサイド

まずは,Flaskのmain.py

#!/usr/bin/env python
# coding:utf-8

from flask import Flask, render_template, jsonify
from datetime import datetime

app = Flask(__name__)
app.debug = True

@app.route('/')
def index():
  p = {}
  return render_template('index.html', p = p)

### API ###
@app.route('/api/get/time')
def api_index():
  hash = {'now':str(datetime.now())}
  return jsonify(hash)

app.run(host='0.0.0.0', port=9091)

'/' と '/api/get/time' の2つのエンドポイントだけからなるシンプルなサーバー
'/'にアクセスがあるとindex.htmlを返します。
ダミーで作った'/api/get/time'は、シンプルなjsonを返します。

{
  "now": "2011-02-01 23:25:20.120097"
}

HTML等ブラウザサイド

htmlファイルは、3つのJSを読み込みます。

  • jquery-1.5.min.js (出たばっかり!)
  • snbinder-0.5.3.js (今回の主役)
  • index.js (実装ファイル)

そして、index.jsファイルから静的ファイルのテンプレートを読み込みます

  • template.html

Flaskの作法に従い各ファイルの配置は以下のようになります(brewで入れたtreeだけどなんか見難い気がする)

├── main.py
├── static
│├── css
│├── js
││├── index.js
││└── lib
││├── jquery-1.5.min.js
││└── snbinder-0.5.3.js
│└── template.html
└── templates
    └── index.html

では、各ソース

まず発端となるHTMLファイル

index.html

<!DOCTYPE HTML>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title></title>
  <script src="/static/js/lib/jquery-1.5.min.js" type="text/javascript"></script>
  <script src="/static/js/lib/snbinder-0.5.3.js" type="text/javascript"></script>
  <script src="/static/js/index.js" type="text/javascript"></script>
  <script type="text/javascript">
    $(document).ready(function() {
      SNBinder.init({});
      rend_item();
      rend_items();
      static_data();
      get_data();
    });
  </script>
</head>
<body>
  <h1>SNBinder test</h1> 
  <div id="item"></div>
  <div id="items"></div>
  <div id="from_file"></div>
  <div id="time"></div>
</body>
</html>

ヘッダーのJS内で、SNBinderを初期化して4つメソッド呼び出してるだけ
ボディーの中に、上記4つのメソッドがそれぞれ埋め込む先のDIVタグが四つ
以上!

参考なので、divタグ入れたけど、body指定すればコレすらも要らない。but シンプルisベスト

そして、今回の主役のJSファイル

index.js

// 変数一つをテンプレートに埋め込む
function rend_item(){
  var template = "<p>Hello $(.name)!</p>";
  var user = { "name":"Leonardo da Vinci" };
  $('#item').html(SNBinder.bind(template, user));
}

// 配列をテンプレートに従い複数埋め込む
function rend_items(){ 
  var template = "<li>Hello $(.name)!</li>";
  var users = [
        { "name":"Leonardo da Vinci" },
        { "name":"Michelangelo di Lodovico Buonarroti Simoni" },
        { "name":"LDonato di Niccol di Betto Bard" }
      ];
  $('#items').html(SNBinder.bind_rowset(template, users));
  }

// 外部テンプレートファイルを読み込み変数を埋め込む
function static_data(){
  SNBinder.get_named_sections("/static/template.html", '', function(templates) {
    var user = { "name":"Leonardo da Vinci" };
    $('#from_file').html(SNBinder.bind(templates.tmpl_name, user));
  });
}

// 外部テンプレートファイルを読み込み、別のURLからJsonを取得しデータを埋め込む
function get_data(){
  SNBinder.get_named_sections("/static/template.html", '', function(templates) {
    var req_url = '/api/get/time';
    console.info(templates);
    SNBinder.get(req_url, '', true, 
      function(json) {
        $('#time').html(SNBinder.bind(templates.tmpl_time, json));
     });
  });
};

上から順番に、シンプルなやり方から複雑なやり方に並んでいる。実際利用す際には、一番下の外からテンプレートファイルを読み込みつつ非同期に取得したJSONを食わせてレンダリングするパターンを多用することになる。上の二つは下の段階的説明のために有るのであって、特に1番上の奴だけを利用するケースはないと思う(jQueryだけでやった方がいいと思う)

で、影の立役者且つ最も重要な部分 template.html

SNBinderでもっとも関心したところのtemplate.html。このファイルには、ページ内の要素をプリミティブな要素まで分解した一つずつがバラバラに記載されている。そして、JS側で表示させたいDOMに対してbindメソッドでプリミティブな要素を呼び出し突っ込んでレンダリングする。
 このテンプレートを含めJSも全てクライアントサイドで動くことになるので、サーバー上のフレームワークで毎週のように言われている「どのテンプレートエンジンが速いのか?」といった話題に全く影響を受ける事がない。さらに、HTMLコードと何をレンダリングすべきか考えるコントロールのJSが、綺麗に分割されている。コレがまた気持ち良い。

template.html

{%}tmpl_name{%}
<p>name is $(.name).</p>

{%}tmpl_time{%}
<p>time is $(.now).</p>

このテンプレート内で {%}で囲まれた部分がテンプレートの部品の名前で、次の名前が始まるまでが一区切り
要するにこのテンプレートには2種類の部品があり、tmpl_nameとtmpl_timeという二つの部品がある。
上記コードでは、プリミティブのギャラリー感が少ないので、実際satoshiさんが、デモで作成された
サイト http://www.fruence.com/ のテンプレート部分がとても参考になる。http://www.fruence.com/static/templates.htm にアクセスすることで部品が羅列されているのが
よく分かる。と思う、これらパーツをJSが適宜イベントに応じて必要なところに必要なタイミングで挿し込む

実行とか

そして、これらのファイルを作成しおもむろに

python main.py
open http://localhost:9091/  #osxの場合

とブラウザでアクセスすると

SNBinder test

Hello Leonardo da Vinci!

Hello Leonardo da Vinci!
Hello Michelangelo di Lodovico Buonarroti Simoni!
Hello LDonato di Niccol di Betto Bard!
name is Leonardo da Vinci.

time is 2011-02-01 23:48:24.264479.

と表示される。見た目を変えたければ、template.htmlをじれば良い。

他のサンプル、リンク

以前、大きなMVCと小さなMVCというエントリーを書いたことがあった。アイデアはあるし、そうなるよう心がけながら毎回JSを実装していたつもりだったけど、思い込みだった感が強い。今回、satoshiさんがSNBinderを公開されて、htmlを骨組みとよりプリミティブなパーツの2種類に分けることでよりVCが明確に分離される事を肌身で感じることが出来た。言うだけとやって見せる違いは大きいぞと!改めて痛感した。

久しぶりに長いブログを書いたら疲れたナリ

PS:帰宅途中にNEX5落としてレンズ割れたのが痛いナリ・・・・