Dojo学习笔记——使用声明式语法

来源:互联网 发布:淘宝优惠券怎么起名称 编辑:程序博客网 时间:2024/05/23 02:13

难度:中级 Dojo版本:1.8
原文:http://dojotoolkit.org/documentation/tutorials/1.8/declarative/

开始
使用Dojo有两种主要的编码风格,第一种称为编程式,第二种称为声明式。编程式是指只使用JavaScript实例化对象和所有行为代码均用JavaScript表达。声明式是指使用dojo/parser阅读DOM文档,解析出用特殊的属性渲染的节点,也解释某些<script>标签来扩展部件的行为。
这些风格都有优点和缺点,有时联合在一起使用。要在应用中使用声明式语法,需要考虑几点:
  • 声明式语法很易于使用,不需要深入了解JavaScript。它有着非常丰富的功能,几乎可以做一切在JavaScript中做的事情,但可以做的事总是有限的。
  • 因为其工作的原理,性能总是比编程式要差些。这是因为dojo/parser解析DOM来寻找需要处理的节点。
  • 它非常适合极为快速的UI原型,可以在生产应用中挑战管理一切。
实例化对象
声明式语法最常用的方式是用于实例化部件。这种方式通过给标记添加一个特殊的属性data-dojo-type完成,然后dojo/parser阅读文档并实例化部件。例如以下标记可以创建一个Dijit按钮:
<button type="button" data-dojo-type="dijit/form/Button"><span>Click Me!</span></button>
以上提供了代表Dojo类型type的模块ID(MID),将指导dojo/parser实例化出现在DOM中位于根节点的dijit/form/Button
未来若需要对按钮做些什么,需要获得对部件的引用。对于基于小部件的Dijit,当被实例化时将查看“取代”的节点,若是含有id属性,将以该ID在dijit/registry中注册它们自己,于是在将来可以获得对它们的引用。因此为使得更为有用,应当改变为:
<button type="button" id="myButton" data-dojo-type="dijit/form/Button"><span>Click Me!</span></button>
当为Dijit选择标签作为占位符时,应当使用最接近所使用部件的本地HTML标签。这里dijit/form/Button是一个按钮,可使用<button>标签,这样应用可以更好的降级,避免了在某些浏览器中试图取代节点时的一些问题。同时应当遵循HTML的最佳实践,在这里意味着应当总是给按钮分配type属性。
现在需要做的是调用dojo/parser。现在采用以前的Dojo配置选项parseOnLoad: true的方式,在一些极端情况下可能会遇到意想不到的结果,故而现在推荐在代码中谨慎的调用解析器:
<script type="text/javascript" src="lib/dojo/dojo.js"data-dojo-config="async: true"></script><script type="text/javascript">require(["dojo/parser", "dojo/ready", "dijit/form/Button"],function(parser, ready){ready(function(){parser.parse();});});<script>
查看示例
注意这里请求了dijit/form/Button,但从require回调中忽略了。因为在代码块中不直接提及它,但是在调用dojo/parser之前要确保模块被加载了。虽然解析器能够自动请求模块,但最好使得请求明确。

配置对象
随着HTML5的引入,规范允许使用以data-开头的自定义属性,仍然使得文档严格有效。将属性data-dojo-props专用于容纳任何需要实例化时传递给构造函数的配置。
如想要创建一个带有一个tab的TabContainer,其中包含一个按钮。编程式方式可以这么做:
require(["dojo/ready","dijit/form/Button","dijit/layout/TabContainer","dijit/layout/ContentPane",], function(ready, Button, TabContainer, ContentPane){ready(function(){var tc = new TabContainer({style: {height: "200px",width: "400px"},id: "tc"}),atab = new ContentPane({title: "A Tab",closable: false,id: "atab"}),myButton = new Button({label: "Click Me!",id: "myButton"});atab.addChild(myButton);tc.addChild(atab);tc.startup();});});
在声明式标记中是这样的:
<div id="tc" data-dojo-type="dijit/layout/TabContainer"data-dojo-props="style: { height: '200px', width: '400px' }"><div id="atab" data-dojo-type="dijit/layout/ContentPane"data-dojo-props="title: 'A Tab', closable: false"><button type="button" id="myButton"data-dojo-type="dijit/form/Button"><span>Click Me!</span></button></div></div>
查看示例
按照惯例,data-dojo-props的属性值为JavaScript对象,只是没有外面的{}。

非部件
dojo/parser常用于实例化可视元素(如Dijit部件),也可用于实例化其它非可视化元素。dojo/parser假设一个构造函数,只是将其配置作为第一个参数,总是传递节点引用作为第二个参数。这对于基于dojo/_base/declare的非可视对象相当的好。
只是普通对象不像Dijit有基于部件的注册,因此为了实例化以后可以引用它们,必须在全局范围内创建对它们的引用。dojo/parser通过查找data-dojo-id属性来实现,无论值在哪里设置都是在全局范围内。例如想要建立一个内存存储:
<div data-dojo-id="myStore" data-dojo-type="dojo/store/Memory"></div>
作为例子,建立一个供给下拉列表的内存存储,可以这样做:
<div data-dojo-id="myStore" data-dojo-type="dojo/store/Memory"data-dojo-props="data: [{ name: 'Alabama', id: 'AL' },{ name: 'Alaska', id: 'AK' },{ name: 'Arizona', id: 'AZ' },{ name: 'California', id: 'CA' },{ name: 'Colorado', id: 'CO' },{ name: 'Connecticut', id: 'CT' },{ name: 'New York', id: 'NY' }]"></div><select id="mySelect" name="state" value="CA"data-dojo-type="dijit/form/FilteringSelect"data-dojo-props="searchAttr: 'name', store: myStore"></select>
查看示例
值得注意的是,因为这些非可视对象没有注册,在全局范围使用引用,垃圾回收将不会作用,即使“创建”对象的DOM节点已不在内存中。在大的应用中,这将视为一个内存泄漏,故而当不再需要时应该确保从全局范围移除这个变量。

修改行为
为了修改部件的行为,当事件发生时,通常需要设置一些代码去执行。例如当按钮被点击时显示一个对话框,在JavaScript中会这么做:
require(["dijit/form/Button", "dijit/Dialog"], function(Button, Dialog){var myButton = new Button({label: "Click Me!",id: "myButton"}, "myButton"),someDialog = new Dialog({title: "Hello World!",content: "<p>I am a dialog. That makes me happy.</p>"}, "someDialog");myButton.on("click", function(){someDialog.show();});myButton.startup();someDialog.startup();});
为了在标记中做这些,需要使用声明式脚本,使得dojo/parser采用内联代码片段,在实例化对象时连接它们。例子如下:
<div id="someDialog" data-dojo-type="dijit/Dialog"data-dojo-props="title: 'Hello World!'"><p>I am a dialog. That makes me happy.</p></div><button type="button" id="myButton" data-dojo-type="dijit/form/Button"><span>Click Me!</span><script type="dojo/on" data-dojo-event="click">var registry = require("dijit/registry");registry.byId("someDialog").show();</script></button>
查看示例
所有声明式脚本在其自己的范围内运行,它们只能连接到全局范围,这也是为什么必须使用require("dijit/registry"),为了得到对模块的引用。
从以上可以看出使用声明式脚本修改部件的行为很简单。当加上部件可以动态加载其内容则会变得更为强大,如dijit/layout/ContentPane。因为可以通过dojo/parser传递加载的内容,不仅实例化更多的部件,还设置其行为。
例如,在文件content.html中有以下内容:
<button type="button" id="myButton" data-dojo-type="dijit/form/Button"><span>Click Me!</span><script type="dojo/on" data-dojo-event="click">console.log("I was clicked!");</script></button>
想要动态加载到一个tab中,可以这样做:
<div id="tc" data-dojo-type="dijit/layout/TabContainer"data-dojo-props="style: { height: '200px', width: '400px' }"><div id="atab" data-dojo-type="dijit/layout/ContentPane"data-dojo-props="title: 'A Tab', href: 'content.html'"></div></div>
查看示例
其中解析器支持几个<script type="dojo/*">
dojo/on
用于设置事件处理,当事件由data-dojo-event属性提供和任何命名的参数传递被传递给data-dojo-args中的逗号分隔的列表,和调用object.on()是等价的。通常这仅仅是想要命名的正规化事件的变量(例如data-dojo-args="e")。
dojo/aspect
用于修改方法处理,和使用dojo/aspect模块等价。该方法特别建议的处理是由data-dojo-advice提供。典型的使用是after,但dojo/parser也支持beforearound。该方法在data-dojo-event中提供,任何需要命名的传递给方法的参数变量是提供给在data-dojo-args中的逗号划定的列表。
dojo/watch
用于执行当性质改变时执行的处理器。性质在属性data-dojo-prop中指定,像Dijit中的watch()和基于dojo/Stateful的对象,处理器将被传递三个参数,代表了性质名称,旧的值和新的值,可以在data-dojo-args属性的逗号划分的列表命名。
dojo/method
用于在实例化执行代码或是覆写一个方法。如果没有指定data-dojo-event属性,代码块将被执行一次,当对象已被实例化。如果方法被指定了,任何存在的函数将被代码块取代。
dojo/connect
现在已经已弃用,被dojo/on或是dojo/aspect取代。

声明式请求模块
作为例子,当点击一个按钮,想要使另一个按钮有效且为点击事件设置处理,需要这样做:
<button type="button" id="button1" disabled="disabled"data-dojo-type="dijit/form/Button"><span>I'm disabled</span></button><button type="button" id="button2" data-dojo-type="dijit/form/Button"><span>Click Me!</span><script type="dojo/on" data-dojo-event="click">require(["dijit/registry"], function(registry){var button1 = registry.byId("button1");button1.on("click", function(){console.log("I was clicked!");});button1.set("label", "I'm enabled");button1.set("disabled", false);});</script></button>
如果确信先前已请求了dijit/registry,可以使用已用过的var registry = require("dijit/registry");得到对模块的引用。
正如前面提到的,所有声明式脚本只在全局范围执行,在声明式请求里的任何请求放入了全局范围,对任何声明式脚本是可用的。声明式请求的语法基本上是没有外部{}的JavaScript对象。性质名是全局变量的名称,其值应当是一个包含模块ID(MID)的字符串。为了请求dijit/registry和将其映射到名为registry的全局变量,可以这么做:
<script type="dojo/require">registry: "dijit/registry"</script>
查看示例
如果担心将自定义模块放在全局范围引起命名空间冲突,可以将模块放在它们自己的命名空间,通过为性质名称使用点标记:"myApp.registry": "dijit/registry"。解析器将深入的创建对象,需要在代码中使用myApp.registry.byId("someId")来访问注册。

自动请求
自动请求使得拼凑页面非常容易,因为根本不用担心在调用dojo/parser时是否已请求了模块。如果解析器遇到了data-dojo-type值看起来MID(例如其中有/),而该模块尚未加载,解析器在开始在页面上实例化对象之前将尝试请求该模块。
虽然该特征很好用,在展现应用时会有一些很负面的后果,若是不仔细的话。例如,可能在产品站点上使用构建版本的Dojo,期望生成器已经创建了所有需要放到一个自定义层的模块。默认的生成器不会为任何声明依赖扫描文件,因此任何由dojo/parser自动请求的模块将不大可能位于层中。这意味着每一个请求的模块,应用将必须进行一个单独的服务器请求,潜在的导致代码运行变慢。
为了告知这点,对于是否使用该特征,可以做出一个自主的决定。若是设置Dojo为调试模式(isDebug: true),则当模块自动请求时dojo/parser写日志到控制台。
以上TabContainer的例子被重写,所有需要的Dijit将自动请求,用以实际演示。其中使用了调试模式,可以检查JavaScript控制台的警告。
查看示例
这是构造器为依赖分析静态HTML文件的一种方式,并将这些建在层中。
原创粉丝点击