DTL 语言着力于为静态的文本提供足够的编程功能,如提供分支和循环等决定呈现相关的逻辑。它主要用于分割文档的表示和数据的字符串文本,模板定义了占位符和各种定义文档应该如何显示的基本逻辑(模板标签“template tag”),模板可用来生成 HTML 或者各种基于文本的格式。Dojo 的 DTL 工具包实现了 DTL 语法的解析,并提供了一系列简单易用的接口用于接收参数,生成和解析基于 DTL 的各种文本或 HTML 页面。这样一来,我们可以基于简单的 DTL 语法构造 HTML 模板,并基于 Dojo 的 DTL 接口实现代码的充分复用。这篇文章将重点介绍 Dojo 的 DTL 工具包以及他们的各种使用方式和技巧。
var context = new dojox.dtl.Context(
{foo: "foo", bar: "bar", get: function(key){ return key + "TEST"; }});
var tpl = new dojox.dtl.Template("{{ foo }}-{{ bar }}");
t.is("fooTEST-barTEST", tpl.render(context));
var context = new dojox.dtl.Context({ foo: "one", bar: "two", baz: "three" });
var filtered = context.filter("foo", "bar");
t.is(filtered.foo, "one");
t.is(filtered.bar, "two");
t.f(filtered.baz);
filtered = context.filter({ bar: true, baz: true });
t.f(filtered.foo);
t.is(filtered.bar, "two");
t.is(filtered.baz, "three");
filtered = context.filter(new dojox.dtl.Context({ foo: true, baz: true }));
t.is(filtered.foo, "one");
t.f(filtered.bar);
t.is(filtered.baz, "three");
var context = new dojox.dtl.Context({ foo: "one" });
var extended = context.extend({ bar: "two", baz: "three" });
t.is(extended.foo, "one");
t.is(extended.bar, "two");
t.is(extended.baz, "three");
extended = context.extend({ barr: "two", bazz: "three" });
t.is(extended.foo, "one");
t.f(extended.bar);
t.f(extended.baz);
t.is(extended.barr, "two");
t.is(extended.bazz, "three");
t.f(context.bar)
t.f(context.baz);
t.f(context.barr);
t.f(context.bazz);
通过“extend”方法,我们可以在已有 Context 对象的基础上扩展新的属性。“context.extend({ bar: "two", baz: "three" })”扩展了“bar”和“baz”属性,其值分别为“two”和“three”。这里需要注意:原来的 Context 对象(这里是“context”变量)是没有任何变化的,改变的只是扩展后的那个变量(这里是“extended”变量)。
var dd = dojox.dtl;
var template = new dd.Template(
'{% extends "../../dojox/dtl/tests/templates/pocket.html" %}
{% block pocket %}Simple{% endblock %}');
t.is("Simple Pocket", template.render());
这里我们建立了一个模板,里面包含了“extends”和“block”,所以很明显这里是 DTL 里面的一个简单的模板继承语法,用子模板“pocket ”:“{% block pocket %}Simple{% endblock %}”来替代父模板“pocket.html”(“{% block pocket %}Hot{% endblock %} Pocket”)。通过“template.render()”来执行语法的解析,最后的结果为"Simple Pocket",完全符合 DTL 语法的解析结果。
var dd = dojox.dtl;
// 参数传递 1
var context = new dd.Context({
parent: "../../dojox/dtl/tests/templates/pocket.html"
})
template = new dd.Template('
{% extends parent %}{% block pocket %}Variabled{% endblock %}');
t.is("Variabled Pocket", template.render(context));
// 参数传递 2
context.parent = dojo.moduleUrl("dojox.dtl.tests.templates", "pocket.html");
template = new dd.Template('
{% extends parent %}{% block pocket %}Slightly More Advanced{% endblock %}');
t.is("Slightly More Advanced Pocket", template.render(context));
// 参数传递 3
context.parent = {
url: dojo.moduleUrl("dojox.dtl.tests.templates", "pocket.html")
}
template = new dd.Template('
{% extends parent %}{% block pocket %}Super{% endblock %}');
t.is("Super Pocket", template.render(context));
这里列出了三种参数传递方式。我们不用像之前那样将 HTML 文件的路径写在 Template 里面了,可以通过 Context 的参数传递到 Template 里,通过 moduleUrl 或者拥有 url 属性的对象传递均可。
var dd = dojox.dtl;
var context = new dd.Context({
parent: dojo.moduleUrl("dojox.dtl.tests.templates", "pocket2.html"),
items: ["apple", "banana", "lemon" ]
});
var template = new dd.Template("
{% extends parent %}{% block pocket %}My {{ item }}{% endblock %}");
t.is("(My apple) (My banana) (My lemon) Pocket", template.render(context));
这里有两个参数:“parent”和“items”,我们来看看父模板的内容:
{% for item in items %}(
{% block pocket %}
Hot
{% endblock %}
) {% endfor %}
Pocket
var template = new dd.Template('
Hot{% comment %}<strong>Make me disappear</strong>{% endcomment %} Pocket');
t.is("Hot Pocket", template.render());
通过“{% comment %}”和“{% endcomment %}”来添加注释,注释中的代码不会被执行。
var context = new dd.Context({
items: ["apple", "banana", "lemon"],
unplugged: "Torrey"
});
var template = new dd.Template("
{% for item in items %}{% cycle 'Hot' 'Diarrhea' unplugged 'Extra' %}
Pocket. {% endfor %}");
t.is("Hot Pocket. Diarrhea Pocket. Torrey Pocket. ", template.render(context));
Cycle 标签表示循环在列表里面取值,由于“items”里面只有三个元素,所以这里的取值应该是"Hot Pocket. Diarrhea Pocket. Torrey Pocket.",最后的“Extra”不会被取到。
context = new dojox.dtl.Context({ unplugged: "Torrey" });
template = new dd.Template("
{% cycle 'Hot' 'Diarrhea' unplugged 'Extra' as steakum %}
Pocket. {% cycle steakum %} Pocket. {% cycle steakum %} Pocket.");
t.is("Hot Pocket. Diarrhea Pocket. Torrey Pocket.", template.render(context));
var template = new dd.Template('
{% filter lower|center:"15" %}Hot Pocket{% endfilter %}');
t.is(" hot pocket ", template.render());
var dd = dojox.dtl;
var context = new dd.Context({
found: "unicorn"
});
var template = new dd.Template("{% firstof one two three four found %}");
t.is("unicorn", template.render(context));
context.four = null;
t.is("null", template.render(context));
context.three = false;
t.is("false", template.render(context));
这里的“firstof”会取得第一个有值的变量,由于只有“found”被赋值,所以这里的“{% firstof one two three four found %}”应为“unicorn”,当然,如果您手动给 context 变量赋值,该语句的运行结果也会有相应改变。
var dd = dojox.dtl;
var context = new dd.Context({
hello: dojo.moduleUrl("dojox.dtl.tests.templates", "hello.html"),
person: "Bob",
people: ["Charles", "Ralph", "Julia"]
});
var template = new dd.Template("{% include hello %}");
t.is("Hello, <span>Bob</span>", template.render(context));
和 HTML 里面的“include”标签一样,它用来引入外部的文本内容。
var dd = dojox.dtl;
var template = new dd.Template("
{% spaceless %}<ul> \n <li>Hot</li> \n\n<li>
Pocket </li>\n </ul>{% endspaceless %}");
t.is("
<ul><li>Hot</li><li>Pocket </li></ul>",
template.render());
这里的“spaceless”标签用于去除所有空格,只留下非空格字符。
var dd = dojox.dtl;
var context = new dd.Context({ four: 4 });
tpl = new dd.Template('{{ four|add:"6" }}');
t.is("10", tpl.render(context));
context.four = "4";
t.is("10", tpl.render(context));
tpl = new dd.Template('{{ four|add:"six" }}');
t.is("4", tpl.render(context));
tpl = new dd.Template('{{ four|add:"6.6" }}');
t.is("10", tpl.render(context));
注意这里的“add”操作:“{{ four|add:"6" }} ”,由于“context”里面指定了“four”的值为“4”,所以这里的输出为“10”。
var dd = dojox.dtl;
var context = new dd.Context({ uncut: "Apples and oranges" });
var tpl = new dd.Template('{{ uncut|cut }}');
t.is("Apples and oranges", tpl.render(context));
tpl = new dd.Template('{{ uncut|cut:"A" }}');
t.is("pples and oranges", tpl.render(context));
tpl = new dd.Template('{{ uncut|cut:" " }}');
t.is("Applesandoranges", tpl.render(context));
tpl = new dd.Template('{{ uncut|cut:"e" }}');
t.is("Appls and orangs", tpl.render(context));
"cut"主要用于删除相关字符,这里的“{{ uncut|cut:"A" }}”用于删除“uncut”变量值里面的所有“A”字符,注意:这里是区分大小写的。
var dd = dojox.dtl;
var context = new dd.Context();
tpl = new dd.Template('{{ empty|default }}');
t.is("", tpl.render(context));
tpl = new dd.Template('{{ empty|default:"full" }}');
t.is("full", tpl.render(context));
context.empty = "not empty";
t.is("not empty", tpl.render(context));
“default”这里的基本原理是:如果变量有值,则取变量值,否则取“default”的值。
var dd = dojox.dtl;
var context = new dd.Context({
fruit: [
{ name: "lemons", toString: function(){ return this.name; } },
{ name: "apples", toString: function(){ return this.name; } },
{ name: "grapes", toString: function(){ return this.name; } }
]
});
tpl = new dd.Template('{{ fruit|dictsort|join:"|" }}');
t.is("lemons|apples|grapes", tpl.render(context));
tpl = new dd.Template('{{ fruit|dictsort:"name"|join:"|" }}');
t.is("apples|grapes|lemons", tpl.render(context));
“dictsort”主要用于排序,这里对“fruit”排序并通过“|”连接起来(join:“|”),所以结果为:“lemons|apples|grapes”。如果有多个属性,可以指定属性排序:|dictsort:“name”,即基于“name”属性排序。
var dd = dojox.dtl;
var context = new dd.Context({ word: "potted meat writes a lot of tests" });
var tpl = new dd.Template("{{ word|truncatewords }}");
t.is(context.word, tpl.render(context));
tpl = new dd.Template('{{ word|truncatewords:"1" }}');
t.is("potted", tpl.render(context));
tpl = new dd.Template('{{ word|truncatewords:"2" }}');
t.is("potted meat", tpl.render(context));
tpl = new dd.Template('{{ word|truncatewords:20" }}');
t.is(context.word, tpl.render(context));
context.word = "potted \nmeat \nwrites a lot of tests";
tpl = new dd.Template('{{ word|truncatewords:"3" }}');
t.is("potted \nmeat \nwrites", tpl.render(context));
顾名思义,“truncatewords”操作主要用于截取文字:“{{ word|truncatewords:"2" }}”相当于截取前两个文字,所以其结果为“potted meat”,这种操作在我们日常开发中,尤其是页面排版中经常需要用到。
var dd = dojox.dtl;
var template;
var found = false;
try {
template = new dd.DomTemplate('No div');
dd.tests.dom.util.render(template);
}catch(e){
t.is("Text should not exist outside of the root node in template", e.message);
found = true;
}
t.t(found);
t.is("potted meat", tpl.render(context));
template = new dd.DomTemplate('<div></div>extra content');
found = false;
try {
dd.tests.dom.util.render(template);
}catch(e){
t.is("Content should not exist outside of the root node in template", e.message);
found = true;
}
t.t(found);
这里我们的内容为“No div”,显然不符合 HTML 的规范,所以 Dojo 的接口会抛出异常:“文本不能在根节点以外存在”。同样,对于“<div></div>extra content”的情况也是如此。
template = new dd.DomTemplate('<div></div><div></div>');
found = false;
try {
dd.tests.dom.util.render(template);
}catch(e){
t.is("Content should not exist outside of the root node in template", e.message);
found = true;
}
t.t(found);
template = new dd.DomTemplate('{% if missing %}<div></div>{% endif %}');
found = false;
try {
dd.tests.dom.util.render(template);
}catch(e){
t.is("Rendered template does not have a root node", e.message);
found = true;
}
t.t(found);
可见,“<div></div><div></div>”这种没有独立根节点,或者说又多余一个的根节点的情况也是不被允许的。同样,“{% if missing %}<div></div>{% endif %}”这种在根节点外存在逻辑控制的情况也是禁止的。
var dd = dojox.dtl;
var template = new dd.DomTemplate('
<div>{% for item in items %}<a index="{{forloop.counter0}}"
id="id_{{item.param}}">{{item.param}}</a>{% endfor %}</div>');
var context = new dd.Context({
items: [
{
name: "apple",
param: "appleparam"
},
{
name: "banana",
param: "bananaparam"
},
{
name: "orange",
param: "orangeparam"
}
]
});
doh.is('<div><a index="0" id="id_appleparam">appleparam</a><a index="1"
id="id_bananaparam">bananaparam</a><a index="2"
id="id_orangeparam">orangeparam</a></div>',
dd.tests.dom.util.render(template, context));
可以看到,这里的 HTML 元素的各种属性都是可以通过变量和逻辑构建出来的:“<a index="{{forloop.counter0}}" id="id_{{item.param}}">”就是一个构建“index”属性和“id”属性的示例。
var dd = dojox.dtl;
var context = new dd.Context({frag: {start: 10, stop: 20}});
var template = new dd.DomTemplate('
<div startLine="{{ frag.start }}" stopLine="{{ frag.stop }}">abc</div>');
doh.is('
<div startline="10"
stopline="20">abc</div>',
dd.tests.dom.util.render(template, context));
dojo.declare("Fruit", [dijit._WidgetBase, dojox.dtl._DomTemplated], {
widgetsInTemplate: true,
items: ["apple", "banana", "orange"],
keyUp: function(e){
if((e.type == "click" || e.keyCode == dojo.keys.ENTER) &&
this.input.value){
console.debug(this.button);
var i = dojo.indexOf(this.items, this.input.value);
if(i != -1){
this.items.splice(i, 1);
}else{
this.items.push(this.input.value);
}
this.input.value = "";
this.render();
}
},
templateString: dojo.cache("dojox.dtl.demos.templates", "Fruit.html"),
});
<div>
<input dojoAttachEvent="onkeyup: keyUp" dojoAttachPoint="input">
<button dojoType="dijit.form.Button" dojoAttachPoint="button"
dojoAttachEvent="onClick: keyUp">
Add/Remove Item
</button>
<div id="pane" dojoType="dijit.layout.ContentPane parsed">
<ul>
{% for item in items %}
<li>
<button dojoType="dijit.form.Button parsed" title="Fruit: {{ item }}"
otherAttr2="x_{{item}}">
{{ item }}
<script type="dojo/connect" event="onClick" args="e">
console.debug("
You clicked", this.containerNode.innerHTML);</' + 'script>
</button>
</li>
{% endfor %}
</ul>
</div>
</div>
可以看到,这里的“{% for item in items %}”会去遍历我们“Fruit”的 Widget 中的“items”成员变量,从而构建“Fruit”这个 Widget 的最终模板。
这篇文章介绍了 Dojo 中 DTL 工具包的一些特性,首先从 DTL 语言本身入手,介绍了 DTL 语言的出处,基本规则和特点,然后引出 Dojo 的 DTL 工具包,并由浅入深的逐步介绍了 Dojo 的 DTL 工具包的各种对象和接口,如 Context 对象,Template 对象以及 Render 方法等等。从文本标签,文本过滤器和 DOM 模板三个方面分别阐述了这些接口的特点和使用方式。最后,介绍了如何基于 Dojo 的 DTL 工具包构建基于 DTL 模板的 Widget。这些接口对我们的日常开发都很有帮助,建议大家平时可以多关注一下。
本文首发于IBM Developer Works:http://www.ibm.com/developerworks/cn/web/1206_zhouxiang_dojodtl/