Monthly Archives: 11月 2010

[javascript] javascriptテンプレートエンジン(?)表示の繰り返し更新対応

どうも、懲りない僕です。JavaScriptでビュー書いてるんだから、動的にビュー動かしたいよね?だから、TemplateEngineRunnerは繰り返し実行できなきゃ意味ないよね?なんてわけで、対応しました。
それにしてもアレなサンプル……
http://s-ishigami.appspot.com/files/test.html

ソース

TemplateEngine = new function() {
var currentNode;
this.setCurrentNode = setCurrentNode;
this.propertyModelValue = function(object, propName) { return new PropertyModel(object, propName).getValue; };
this.writeText = function (value) { return new WriteText(value); };
this.writeHtml = function (value) { return new WriteHtml(value); };
this.setAttribute = function (name, value) { return new SetAttribute(name, value); };
this.loopWhile = function(condition, subController) { return new LoopFor(emptyFunction, condition, emptyFunction, subController); }
this.loopFor = function(init, condition, after, subController) { return new LoopFor(init, condition, after, subController); }
function emptyFunction() {}
function setCurrentNode(node) {
currentNode = node;
}
function functionOrValue(f) {
if (typeof f == 'function') {
return f();
}
return f;
}
function PropertyModel(object, propName) {
this.getValue = getValue;
function getValue() {
return object[propName];
}
}
function WriteText(value) {
this.execute = execute;
function execute() {
currentNode.text(functionOrValue(value));
}
}
function WriteHtml(value) {
this.value = value;
this.execute = execute;
function execute() {
currentNode.html(functionOrValue(value));
}
}
function SetAttribute(name, value) {
this.execute = execute;
function execute() {
currentNode.attr(name, functionOrValue(value));
}
}
function LoopFor(init, condition, after, subController) {
this.execute = execute;
function execute() {
init();
while (condition()) {
var clone = currentNode.clone(true);
clone.attr('id', null);
clone.addClass('GENERATED_NODE');
clone.addClass(currentNode.attr('id') + "_CLONE");
if (subController != null) {
var thisNode = currentNode;
TemplateEngineRunner.run(subController, clone);
setCurrentNode(thisNode);
}
clone.find('*').attr('id', null);
clone.insertBefore(currentNode);
after();
}
currentNode.addClass('ORIGINAL_NODE');
currentNode.hide();
}
}
}
TemplateEngineRunner = new function() {
this.run = run;
function run(controller, parentNode) {
clearGeneratedNodes(parentNode);
if (typeof parentNode == 'String') {
parentNode = $('#' + parentNode);
}
for (var key in controller) {
if (parentNode == null) {
TemplateEngine.setCurrentNode($('#' + key));
} else {
TemplateEngine.setCurrentNode(parentNode.find('#' + key));
}
controller[key].execute();
}
}
function clearGeneratedNodes(parentNode) {
if (parentNode == null) {
$('.GENERATED_NODE').remove();
$('.ORIGINAL_NODE').show();
} else {
if (typeof parentNode == 'String') {
parentNode = $('#' + parentNode);
}
parentNode.find('.GENERATED_NODE').remove();
parentNode.find('.ORIGINAL_NODE').show();
}
}
}
var myModel = {
name: 'テスト',
sex: 'male',
position: 0,
goNext: function() {
this.position += 1;
},
nameChange: function() {
this.name = this.name == "テスト" ? "太郎" : "テスト";
}
};
with (TemplateEngine) {
var yamanote = ['大崎', '品川', '田町', '浜松町', '新橋', '有楽町', '東京', '神田', '秋葉原', '御徒町', '上野', '鶯谷', '日暮里', '西日暮里', '田端', '駒込', '巣鴨', '大塚', '池袋', '目白', '高田馬場', '新大久保', '新宿', '代々木', '原宿', '渋谷', '恵比寿', '目黒', '五反田'];
var i = 0;
var controller = {
// オブジェクトのプロパティ出力(動的)
name: writeText(propertyModelValue(myModel, 'name')),
// 固定値出力
test: writeText('生麦生米生卵'),
// 動的値出力
now: writeText(function() {return new Date().toString();}),
// 固定HTML出力
testHtml: writeHtml('<span style="color: blue;">青</span>巻紙<span style="color: red;">赤</span>巻紙<span style="color: yellow;">黄</span>巻紙'),
// 属性変更
attr: setAttribute('style', 'font-size: x-large; color: blue;'),
// ループ(indexを外側の変数で定義しなければいけないことが課題)
'for': loopFor(
function() {i = 0},
function() {return i < 10},
function() {i++;},
{
counterFor: writeText(function() {return yamanote[(myModel.position + i) % yamanote.length];})
}
)
}
}
TemplateEngineRunner.run(controller);
<!DOCTYPE html>
<html lang="ja">
<head>
   <meta charset="utf-8">
   <title>Template Engine Test</title>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<a href="javascript: myModel.goNext(); TemplateEngineRunner.run(controller)">次の駅へ</a>
<p>現在日時: <span id="now"></span></p>
<table border="1">
<col width="200">
<col width="200">
<tr>
<th bgcolor="silver">key</th>
<th bgcolor="silver">val</th>
</td>
<tr>
<th>氏名</th>
<td align="center"><a href="javascript: myModel.nameChange(); TemplateEngineRunner.run(controller)"><span id="name"></span></a></td>
</td>
<tr>
<th>得意技</th>
<td><span id="attr"><span id="test"></span></span></td>
</td>
<tr>
<th>得意技2</th>
<td><span id="testHtml"></span></td>
</td>
<tr id="for">
<th>次は</th>
<td id="counterFor"></td>
</tr>
</table>
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">google.load("jquery", "1.4.2");</script>
<script type="text/javascript" src="templateEngine.js"></script>
<script type="text/javascript" src="test.js"></script>
</body>
</html>


propertyModelValueとか、とてもWicket風。Mayaaを目指したらWicketに似てきた。最初からWicket風につくるべきだったか?

Javascriptクライアントサイドテンプレートエンジンのプロトタイプ

前回の続きです。

とりあえず、それっぽく動くようになってきたので、公開してみます。
http://s-ishigami.appspot.com/files/test.html

作ってて思ったんですが、テンプレートエンジンというより、jQueryベースのHTML変換ライブラリだなと。JSONベースのXSLTだなと。jQueryわかってるJavaScriptプログラマーならいらないんじゃないかと。

でも、僕の周りでMayaaが大ヒットしているのを見ると、こういうアプローチもありなのではと思いもします。

つくり込むなら、JSONPを受け取るfunctionとか色々やりたいですね。
JavaScriptなので横からfunction足し放題なので、アプリごとにカスタマイズもありじゃないかと、

ちなみに、上記URLは、見ての通りappengine(py)の物で、indexにアクセスすると、python練習のために作ったFizzBuzzが出力されますが、見ないでね!

JavaScript テンプレートエンジン #3 (Loop実装)

さらに続きです。

今日はループを実装してみました。

TemplateEngine = new function() {
var currentNode;
this.setCurrentNode = setCurrentNode;
this.writeText = function (value) { return new WriteText(value); };
this.writeHtml = function (value) { return new WriteHtml(value); };
this.setAttribute = function (name, value) { return new SetAttribute(name, value); };
this.loopWhile = function(condition) { return new LoopWhile(condition); }
function setCurrentNode(node) {
currentNode = node;
}
function WriteText(value) {
this.execute = execute;
function execute() {
currentNode.text(value);
}
}
function WriteHtml(value) {
this.value = value;
this.execute = execute;
function execute() {
currentNode.html(value);
}
}
function SetAttribute(name, value) {
this.execute = execute;
function execute() {
currentNode.attr(name, value);
}
}
function LoopWhile(condition) {
this.execute = execute;
function execute() {
while (condition()) {
var clone = currentNode.clone(true);
clone.insertAfter(currentNode);
}
}
}
}
TemplateEngineRunner = new function() {
this.run = run;
function run(controller) {
for (var key in controller) {
TemplateEngine.setCurrentNode($('#' + key));
controller[key].execute();
}
}
}
<!DOCTYPE html>
<html lang="ja">
<head>
   <meta charset="utf-8">
   <title>Template Engine Test</title>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<span id="test"></span>
<span id="testHtml"></span>
<span id="attr">test</span>
<span id="loop">loop</span>
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">google.load("jquery", "1.4.2");</script>
<script type="text/javascript" src="templateEngine.js"></script>
<script>
   with (TemplateEngine) {
       var i = 0;
       var controller = {
           test: writeText('hoge<b>aaa</b>'),
           testHtml: writeHtml('<i>aaa</i>'),
           attr: setAttribute('style', 'font-weight: bold;'),
           loop: loopWhile(function() {return i++ < 10;})
       }
   }
   
   TemplateEngineRunner.run(controller);
</script>
</body>
</html>

出力結果

hogeaaa aaa test looplooplooplooplooplooplooplooplooplooploop


ループしながらそれぞれの要素をどうにかしないといけないのと、このままでは同じidが複数できてしまってDOM的にNGなのが気になります。が、そろそろ色々できるようになってきたので、GitHubあたりで公開したほうがいいでしょうか?とすると、プロジェクト名とか決めなきゃいけないですね。募集します(笑)

ループのネスト実装したけど

これじゃいけてないよね?

   with (TemplateEngine) {
var i = 0;
var data = {};
var controller = {
test: writeText(function() {return 'hoge<b>aaa</b>'}),
testHtml: writeHtml(function() {return '<i>aaa</i>';}),
attr: setAttribute('style', function() {return 'font-weight: bold;'}),
loop: loopWhile(
function() {return i++ < 10;},
{
counter: writeText(function() {return i;})
}
),
'for': loopFor(
function() {i = 0},
function() {return i < 3},
function() {i++;},
{
counterFor: writeText(function() {return i;})
}
)
}
}

まあ、でも、Mayaaで言うところの、${}が、function() {}に相当するから、仕方ないのかなこれは。

追記(疑問)

ところで、クラスのprivateにvarを、publicにthisを使うような使い分けを上記のようにやってるんだけど、この書き方って、一般的?

続き→http://d.hatena.ne.jp/s-ishigami/20101119/1290151078