JavaScript

JavaScriptのテンプレートJsRenderとJsViewsの紹介

More than 3 years have passed since last update.

かって、JavaScriptのテンプレートとしてjquery.tmpl.jsが存在した。

jQuery公式サイトにものっていたが、現在はメンテナンスされておらず、公式から削除された。

https://github.com/BorisMoore/jquery-tmpl

しかしながら、同じ作者のJsRender、JsViewsといわれるライブラリが後継として存在している。

このドキュメントでは JsRender, JsViewsについての解説を行う。

http://www.jsviews.com/


JsRender

JsRenderはDOMやjQueryの依存関係なしで、軽量でありながら強力で拡張可能なテンプレートエンジンである。

JsRenderは以下からダウンロードすることが可能だ。

http://www.jsviews.com/#download


実装のサンプルとJsRenderの機能

JsRenderを利用した実装のサンプルを用いてJsRenderの機能を紹介する。


単純なオブジェクトの出力例

ここでは単純なオブジェクトをテンプレートを使用して出力するサンプルをしめす。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="./jsrender.min.js" type="text/javascript"></script>

<script id="theTmpl" type="text/x-jsrender">
<p> Name: {{:name}} </p>
</script>

<script type="text/javascript">
$(function () {
var data = {
name: 'Jack'
};
var html = $('#theTmpl').render(data);
$('#result').append(html);
});
</script>
</head>
<body>
<div id="result"></div>
</body>
</html>

出力:

Name: Jack 

まず、text/x-jsrenderというtypeのscript中にテンプレートの記述を行う。

<script id="theTmpl" type="text/x-jsrender">

<p> Name: {{:name}} </p>
</script>

これはrender functionによりデータとひもづけられてHTMLが作成される。

  var html = $('#theTmpl').render(data);

この時、作成されるHTMLは次のようになる。

  <p> Name: Jack </p>


配列の出力例

先の例ではテンプレートに渡すデータがオブジェクトだった。もし、配列を渡した場合はどうなるであろうか?

先のサンプルを配列に置き換えてみる。

  var data = [

{name: 'Jack'},
{name: 'Sara'},
{name: 'Dick'}
];

ここで作成されるHTMLは次のようになる。

  <p> Name: Jack </p>

<p> Name: Sara </p>
<p> Name: Dick </p>

データとして配列を渡した場合は、複数のデータが作成されることになる。

もし、渡すデータとしてはオブジェクトであり、そのプロパティが配列だった場合は、テンプレートの中でループをすることで表示することが可能だ。

以下のようなデータがあったとしよう。

  var data = {

title: 'タイトル',
names:[
{name:'Jack'},
{name:'Sara'},
{name:'Dick'}
]
};

この時はテンプレートにループを加えるとよい。

<script id="theTmpl" type="text/x-jsrender">

<h1>Title {{:title}}</h1>
{{for names}}
<p>Name: {{:name}}</p>
{{/for}}
</script>

この時、生成されるHTMLは次の通りになる。

  <h1>Title タイトル</h1>

<p>Name: Jack</p>
<p>Name: Sara</p>
<p>Name: Dick</p>

もし、データの配列中にさらに配列があったらどうであろうか?

  var data = {

title: 'タイトル',
names:[
{name: 'test1', sub:['a','b','c']},
{name: 'test2', sub:['e','f','g']}
]
};

以下のように、テンプレート中のループの中にさらにループを記述するだけでよい。

<script id="theTmpl" type="text/x-jsrender">

<h1>Title {{:title}}</h1>
{{for names}}
<p>Name: {{:name}}</p>
{{for sub}}
<p>subitem: {{:}}</p>
{{/for}}
{{/for}}
</script>

この場合の出力結果は以下のようになる。

  <h1>Title タイトル</h1>

<p>Name: test1</p>
<p>subitem: a</p>
<p>subitem: b</p>
<p>subitem: c</p>
<p>Name: test2</p>
<p>subitem: e</p>
<p>subitem: f</p>
<p>subitem: g</p>


オブジェクトのプロパティの列挙

オブジェクトのプロパティ名とその値を列挙するサンプルを示す。

以下のようなオブジェクトが存在しており、addressのプロパティ名と値を列挙したいものとする。

  var data = [

{
"name": "Pete",
"address": {
"street": "12 Pike Place",
"city": "Seattle",
"ZIP": "98101"
}
},
{
"name": "Heidi",
"address": {
"street": "5000 Broadway",
"city": "Sidney",
"country": "Australia"
}
}
];

この場合はテンプレートにforではなくpropsを用いる。

すると、>keyでプロパティ名、>propでプロパティの値が取得できる。

<script id="theTmpl" type="text/x-jsrender">

<h1>Name {{:name}}</h1>
{{props address}}
<b>{{>key}}:</b> {{>prop}}<br/>
{{/props}}
</script>

この時のHTMLの出力は次のようになる。

  <h1>Name Pete</h1>

<b>street:</b> 12 Pike Place<br/>
<b>city:</b> Seattle<br/>
<b>ZIP:</b> 98101<br/>
<h1>Name Heidi</h1>
<b>street:</b> 5000 Broadway<br/>
<b>city:</b> Sidney<br/>
<b>country:</b> Australia<br/>


文字列をテンプレートとして使用する場合

今までは、テンプレートの記述はscriptタグで行ったが、これを直接文字列で記述することも可能である。

  var data = {

name: 'Jack'
};
$.templates('testTmpl', '<label>Name:</label>{{:name}}');

var html = $.render.testTmpl(data);
$('#result').append(html);

templates関数にテンプレート名とテンプレートを指定することでテンプレートを登録できる。登録したテンプレートは、テンプレート名を指定することで利用することができる。

<p>Name:Jack</p>


テンプレートの遅延読み込み

文字をテンプレートとして、利用できることを利用してテンプレートの遅延読み込みができることを意味する。

たとえば次のような、テキストファイルが存在する。


test.txt

<label> Name:</label> {{:name}}


これを以下のような、コードでtext.txtをテンプレートとして遅延読み込みができる。

<script type="text/javascript">

$(function () {
var data = {
name: 'Jack'
};
$.get('./test.txt', function(value) {
console.log(value);
var testTmp = $.templates(value);
var html = testTmp.render(data);
$('#result').append(html);
});
});
</script>


テンプレート内で別のテンプレートを使用する場合

テンプレート内でincludeタグを利用する事で別のテンプレートを使用する事が可能である。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="./jsrender.min.js" type="text/javascript"></script>

<script id="peopleTemplate" type="text/x-jsrender">
<div>
{{:name}} lives in {{include tmpl="#addressTemplate"/}}
</div>
</script>

<script id="addressTemplate" type="text/x-jsrender">
<b>{{>address.city}}</b>
</script>

<script type="text/javascript">
$(function () {
var data = [
{
"name": "Pete",
"address": {
"city": "Seattle"
}
},
{
"name": "Heidi",
"address": {
"city": "Sidney"
}
}
];
var html = $('#peopleTemplate').render(data);
console.log(html);
$('#result').append(html);
});
</script>
</head>
<body>
<div id="result"></div>
</body>
</html>

この時のHTMLの出力は次のようになる

 <div>

Pete lives in
<b>Seattle</b>

</div>

<div>
Heidi lives in
<b>Sidney</b>

</div>

includeタグはtmpl属性で指定したテンプレートの内容に置き換わっていることが確認できる。

このincludeを利用する事で、テンプレートの共通化を行える。


分岐の例

テンプレート中で{{if}}}タグを利用することでテンプレートを分岐することができる。

これは必要に応じて{{else}}タグも利用できる。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="./jsrender.min.js" type="text/javascript"></script>

<script id="theTmpl" type="text/x-jsrender">
{{if displayType == 0}}
<p>Name: {{:name}} </p>
{{else displayType == 1}}
<p>Name: {{:azana}} </p>
{{else}}
<p>Name: {{:nickName}} </p>
{{/if}}
</script>

<script type="text/javascript">
$(function () {
var data = [
{
"name": "Kannu",
"azana": "Unchou",
"nickName": "Hige",
"displayType": 0
},{
"name": "Chouhi",
"azana": "Ekitoku",
"nickName": "Haruhi",
"displayType": 1
},
{
"name": "Ryubiu",
"azana": "Gentoku",
"nickName": "Kotei",
"displayType": 2
},
{
"name": "ShokatuRyou",
"azana": "Koumei",
"nickName": "awawa",
"displayType": 3
}
];
var html = $('#theTmpl').render(data);
console.log(html);
$('#result').append(html);
});
</script>
</head>
<body>
<div id="result"></div>
</body>
</html>

ここで作成されるHTMLの結果は以下のようになる。data.displayTypeにより表示される名前が切り替わっていることが確認できる。

    <p>Name: Kannu </p>

<p>Name: Ekitoku </p>
<p>Name: Kotei </p>
<p>Name: awawa </p>


テンプレートに渡すデータのエスケープ処理

テンプレートに渡すデータ中の文字にHTMLのタグが存在する場合がある。このデータ中のタグをエスケープ処理を行うには、{{>データ名}}を用いる。

以下のサンプルで、エスケープ処理を行わない場合と、行った場合でどのような違いが生じるか確認する。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="./jsrender.min.js" type="text/javascript"></script>

<script id="theTmpl" type="text/x-jsrender">
<p>{{:description}}</p>
<p>{{>description}}</p>
</script>

<script type="text/javascript">
$(function () {
var data = {description: "A <b>very nice</b> appartment"};
var html = $('#theTmpl').render(data);
console.log(html);
$('#result').append(html);
});
</script>
</head>
<body>
<div id="result"></div>
</body>
</html>

出力結果は以下の通りとなり、{{:}}の場合は、渡された文字データがそのまま出力されているが、{{>}}でエスケープ処理が適切になされることが確認できる。

  <p>A <b>very nice</b> appartment</p>

<p>A &lt;b&gt;very nice&lt;/b&gt; appartment</p>


まとめ

JsRenderを用いることで、JavaScriptのテンプレートを記述することができ、ビューとロジックの分離を容易に行うことができる。


JsViews

JsViewsはJsRenderとJsObservableと呼ばれるライブラリを用いてテンプレートとデータのリンクを行うことができる。

これにより、データに変更があった場合に描画の自動更新をすることが可能になる。


実装のサンプルとJsViewsの機能

JsViewsを利用した実装のサンプルを用いてJsViewsの機能を紹介する

JsViewsは、JsRenderと同じページからダウンロードできる。


data-linked tagの例

ここではdata-linked タグの使用例を見てみよう。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="./jsviews.min.js" type="text/javascript"></script>
<script id="teamTemplate" type="text/x-jsrender">

<div class="buttons">
<button id="add">Add</button>
<button id="update">Update</button>
</div>
<ol>
{^{for members}}
<li>
{^{:name}}
{^{:age}}
<img class="remove" src="http://www.jsviews.com/resources/images/close.png" />
</li>
{{/for}}
</ol>

</script>

<script type="text/javascript">
$(function () {
var team = {
members: [
{name: "Robert", age:10},
{name: "Sarah", age:12}
]
};
var cnt = 1;
$.templates("#teamTemplate").link("#team", team)
.on("click", ".remove", function() {
var view = $.view(this);
$.observable(team.members).remove(view.index);
})
.on("click", "#add", function() {
$.observable(team.members).insert(0, {name: "new" + cnt++, age:1});
})
.on("click", "#update", function() {
for(var i = 0; i < team.members.length;++i) {
$.observable(team.members[i]).setProperty("age", team.members[i].age + 1);
}
});
});
</script>
</head>
<body>
<div id="team"></div>
</body>
</html>

出力例:

無題.png

このプログラムを実際動作させると、Addボタンでアイテムの追加、xボタンでアイテムの削除、Updateボタンで年齢の更新が行われることがわかるだろう。つまり、ある種の操作により、表示の内容が更新されていることが確認できる。

どのように作っているかを説明する。

まずは、テンプレートの部分を見てみる。

<script id="teamTemplate" type="text/x-jsrender">

<div class="buttons">
<button id="add">Add</button>
<button id="update">Update</button>
</div>
<ol>
{^{for members}}
<li>
{^{:name}}
{^{:age}}
<img class="remove" src="http://www.jsviews.com/resources/images/close.png" />
</li>
{{/for}}
</ol>

</script>

JsRenderの場合、{{for members}}や{{:name}}となっていたものが、{^{for members}}や{^{:name}}になっているのがわかるだろう。「^」を使用することでデータがリンクされていることを示している。

次にデータとテンプレートを関連付けているところを見てみよう。

  $.templates("#teamTemplate").link("#team", team)

.on("click", ".remove", function() {
var view = $.view(this);
$.observable(team.members).remove(view.index);
})
.on("click", "#add", function() {
$.observable(team.members).insert(0, {name: "new" + cnt++, age:1});
})
.on("click", "#update", function() {
for(var i = 0; i < team.members.length;++i) {
$.observable(team.members[i]).setProperty("age", team.members[i].age + 1);
}
});

「#tempTemplate」で指定したテンプレートとオブジェクト「team」を関連付けて「#team」に出力している。

.onで続くものは、どのようなイベントで、このテンプレートの内容が更新されるかを記述している。

更新する場合、追加には「\$.observable.indert」,削除には「\$.observable.remove」,特定のプロパティの更新には「\$.observable.setProperty」を使用する。

その他observableの機能はJsObservableのAPIを参照するとよい。

http://www.jsviews.com/#jsoapi


data-link属性の例

ここでは data-link属性を用いた例を紹介する。

INPUTなどにdata-link属性をつけることで、INPUTで入力した内容で、data-linkで指定したデータのプロパティの変更を行うことができるようになる。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="./jsviews.min.js" type="text/javascript"></script>
<script id="theTmpl" type="text/x-jsrender">
<input data-link="name trigger=true"/>-
<input data-link="lastName trigger=true"/>
<br>
{^{:name}}-{^{:lastName}}
</script>

<script type="text/javascript">
$(function () {
var data = {
"name": "Jack",
"lastName": "Hanma"
};

var template = $.templates("#theTmpl");
template.link("#result", data);

$("#check").click(function(){
alert(JSON.stringify(data));
});
});
</script>
</head>
<body>
<div id="result"></div>
<button id="check">check</button>
</body>
</html>

無題.png

このサンプルを実行すると、INPUTの内容を変更すると、表示しているデータはもちろん、データとして渡したJSONの内容も変更されていることが確認できる。


まとめ

JsViewsを用いてテンプレートとデータを関連付けを行うと、画面の入力によりデータを容易に更新したり、Observerを使用してデータを更新することで画面の更新も容易に行える。


カスタムタグ

JsRenderとJsViewsの項目で{{for}}や{{else}}など様々なタグを紹介した。

JsViewsを使用することにより、開発者が任意のタグを作成することが可能だ。

この方法は下記に記述してある。

http://www.jsviews.com/#customtagsapi