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/