
JSONPなどでデータを取得して、HTML中の任意なAttributeにマクロを埋むタイプのテンプレートキットを作りました。
別にJSONPじゃなくても、staticもデータ構造を定義する事も出来るし、Ajaxサポート書けばAjax経由でデータを取得できます。
マクロ展開はDOM探索で色々処理をしています。 Model = JSONP 、 View = DOM って感じかも。
他にもJKL.Hinaや、JSmartyなどがあります、大きな違いは専用構文を利用しないでテンプレート展開が出来たりと、かなりシンプルです。
他の特徴は
・DOM操作のみでテンプレート展開
・JSONP対応
・テンプレート用のデータ領域を用意する事無く、HTMLに直接テンプレートを記入出来る
・データ展開先を設定不要。class要素などからテンプレートエンジンが、自動的に展開先を選択する
・DOM操作を基本としてる為、if/foreach等のスコープが実際のHTML構造と連動していて直感的
・DOM要素の属性を柔軟にカスタマイズできる
・フィルタチェイン
・フィルタの拡張が可能
などなどです。
デモ用のサンプルはhttp://tech.yappo.jp/demo/simplemacro/
tracはhttp://trac.yappo.jp/trac/browser/javascript/cjtk/trunk
svnは
svn co http://svn.yappo.jp/repos/public/javascript/cjtk/trunk
マクロと言っても、簡単に使う分には専用の言語らしき物は見えません。
たとえばdel.icio.usのJSONPだと(http://del.icio.us/feeds/json/yappo?callback=delisious.callback)
new Array(の様な構造になっているので、これを簡単に展開したい場合には
{
u: url,
d: タイトル,
n: コメント,
t: new Array(tag1, tag2)
},
{
u: url,
d: タイトル,
n: コメント,
t: new Array(tag1, tag2)
}
);
<javascript>とかけば、「URL:」や「タイトル:」の後に、それぞれURLとタイトルが挿入されます。
var config = {
classname: 'delicious', // class属性がdeliciousのDOM要素をテンプレートとして使用する
foreach: true, // 上記で取得したDOM要素を元にして、JSONPで取得じた全レコード分を展開
url: 'http://del.icio.us/feeds/json/yappo?callback=delisious.callback', // JSONPのURL
};
var delisious = new Cjtk(config);
delisious.process();
</javascript>
<div class="delicious">
<div class="u">URL:</div>
<div class="d">タイトル:</div></div>
<div class="delicious">これで、URLとタイトルそれぞれにURLへのリンクが張られます。
<div>URL:<a class="[% $u | attr 'set' 'href' %][% $u | inner %]</a></div>
<div>タイトル:<a class="[% $u | attr 'set' 'href' %][% $d | inner %]</a></div></div>
<div class="[% 'D' | $d %][% 'A' | cat 'B' 'C' | cat $d | inner %]"></div>なんて事が書けますが、これを分りやすくコメントを付けると
<div class="[%と、こんな感じになります(実際は#のコメントは書けません)。
'D' | $d # $dという変数に文字列'D'をセット
%][%
'A' | # 文字列'A'を次の処理へ転送
cat 'B' 'C' | # 文字列'A', 'B', 'C'を順番に結合して、文字列'ABC'を作成して次の処理へ転送
cat $d | # 文字列'ABC'と変数$dの中の文字列'D'を結合して、文字列'ABCD'を作成して次の処理へ転送
inner # 転送されて来た文字列'ABCD'を、現在のDOM要素の子TextNodeとして追加する
%]"></div>
<div class="delicious">ifはパイプの前の処理結果があれば(もしくは0でなければ)子要素を表示します。
<div class="u">URL:</div><div class="d">タイトル:</div>
<div class="[% $n | if %]"><div class="n">コメント:</div></div>
</div>
<div class="delicious">と表示され、一致しなければ
<div class="u">URL:</div><div class="d">タイトル:</div>
<div class="n">コメント:</div>
</div>
<div class="delicious">となります、defaultではif/unless文のあるDOM要素は削除されます。
<div class="u">URL:</div><div class="d">タイトル:</div>
</div>
<div class="delicious">と書くことも可能です。
<div class="u">URL:</div>
<div class="d">タイトル:</div><div class="[% $n | if '1' %][% $n | inner %]">コメント:</div>
</div>
<div class="delicious">ulというDOM要素を削除しないままtagの数だけliタグを展開します。
<div class="u">URL:</div><div class="d">タイトル:</div>
<ul class="[% $t | foreach 'tag' '1' %]">
<li><a href="http://del.icio.us/tag/" class="[% $tag.v | attr 'append' 'href' %][% $tag.v | inner %]"></a></li>
</ul></div>
次のようなレコードのIDが固定的なデータの場合にはforeachオプションでテンプレート展開をせずに、レコードIDを指定したテンプレート構築が出来ます。
レコードIDはdefaultではtitle属性に入れます。
例えば
var json = {
hatena: {
url: 'http://www.hatena.ne.jp/',
cto: 'naoya'
},
mixi: {
url: 'http://www.mixi.co.jp/',
cto: 'Boofy'
}
};なデータの時には<javascript>という記述が可能です。
var config = {
classname: 'mixna', // class属性がmixnaのDOM要素をテンプレートとして使用する
params: json // 入力データをJSONをやめてローカルなデータを使う
};
var c = new Cjtk(config);
c.process();
</javascript>
<div class="mixna" title="hatena">はてな
<div class="url"></div>
<div class="cto"></div>
</div>
<div class="mixna" title="mixi">
みくしい
<div class="url"></div><div class="cto"></div>
</div>
ちなみにこの例ではJSONPを使っていませんが、JSONPを使用する場合には該当するDOMのtitleからレコードIDを全てかき集めてJSONPでリクエストする時にクエリパラメータとして送信します。
今回の例だと
json.js?id=hatena;mixiといった形でクエリを自動的に構成する事が出来ます。
プラグイン機構を使って、マクロを追加出来ます。
Cjtk.register_function(拡張名, function(context, obj, func, stdin, args){});と拡張を登録する事によって[% 拡張名.マクロ名 %]という風に追加したマクロが使えます。
全てのマクロは|(pipe)で処理をつなげる事が出来ます。
マクロの戻り値が次のマクロの入力に使われます。
例外としてマクロの最後に$valueのような変数が指定されていた場合は、変数に文字列が代入されます。
[% 'test' | lc | replace 'T' 'P' | $value %]だったら、$valueに'PESP'が入ります。
分岐条件です。
if
[% '0' | if %] # 条件成立せずif文に引数を与えると、if文があるDOM要素の削除を行いません。
[% '' | if %] # 条件成立せず
[% 'a' | if %] # 条件成立
[% '' | if | cat 'a' | inner %] # cat以降は処理しない
[% '' | if %][% 'test' | attr 'set' 'title' %] # 'test'以降は処理しない
<div class="[% '1' | if %]">test</div>これが
test
となるところを
<div class="[% '1' | if '1' %]">test</div>
で
<div>test</div>
となる
ifの真偽の条件が逆になっただけです。
入力で与えられた配列データをforeach展開します。
foreach 展開後の変数名flagを与えるとifの時のように、自DOM要素の削除を行いません。
子DOM要素にTextノードを作成して、入力のあった文字列を挿入します。
inner <'set|append'>setを指定すると、子要素全てを削除してから挿入を行います。
自DOM要素の指定した属性を操作します。
attr 'get|set|append' '属性名'getを指定すると指定した属生の値を取得します。
文字列を結合します。
joinのような感じです。
入力値があれば入力値を第一引数としてあつかいます。
lcです。
ucです。
javascriptのreplaceです。
ret.stdout = typeof stdin == 'string' ? stdin.replace(args<0], args[1>) : '';こんな実装。
htmlエスケープさせるフィルタ。
何もしません。
if文の後にnopを置くと、次のマクロにif文の出力を引き継がないのでcatつか使えるようになります。
new Cjtk(config);した時に指定出来る設定は次の通り
var config= {
type: 'text/javascript', # JSONP利用時のscriptタグで仕様
charset: 'utf-8', # JSONP利用時のscriptタグで仕様 JSONPのcharsetを入れる
separator: ';', # foreach展開をしない時にレコードIDをクエリパラメータに含める際のセパレータ
foreach: false, # foreach展開をするか
ajax: false, # ajax利用するか(ajaxのコードは未実装)
start_tag: '[%', # マクロの開始タグ
end_tag: '%]', # マクロの終了タグ
macro_attribute: 'class', # マクロを入れる属性名
rowid_attribute: 'title', # レコードIDを入れる属性名
params: '', # JSONPなどを利用しない場合は、ここにデータを指定する
process_callback: function(context){} # テンプレート展開が終わった時に呼び出される関数
};
いかがでしょう?
こちらも勉強させていただきます。
本当にありがとうございます。
Yatena