对dijit框架的基础知识,可以参看前两篇教程
难度:中等
适用Dojo版本: 1.6
作者:Brian Arnold
Brian Arnold is a software engineer at SitePen, Inc. He has a lovely wife, two cute dogs, is an active member of (and presenter at) Webuquerque, and ranks among the top 3% of fake guitarists in Rock Band.
原文连接:http://dojotoolkit.org/documentation/tutorials/1.6/recipes/custom_widget/
译者 : feijia tiimfei@gmail.com
Dojo的Dijit 库包含了丰富的界面小部件(Widgets),通过使用这些小部件,可以打造出强大的Web应用界面,从高级的表单元素,到复杂页面布局。
但是对于一些较复杂的应用,开发者仍会碰到做进一步定制的需求,例如非常复杂的信息展示需求。你当然可以手动的自己构造DOM来展示数据,但是如果利用Dijit已经提供的框架和工具,我们可以快速的开发出灵活又强大的自定义小部件。
场景
假设我们需要开发一个能展示所有Dojo教程作者的简介信息的页面。我们手头的数据源是如下的JSON数据:
[ { "name": "Brian Arnold", "avatar": "/includes/authors/brian_arnold/avatar.jpg", "bio": "Brian Arnold is a software engineer at SitePen, Inc., ..." }, /* More authors here... */ ]
我们的需求是把这些信息以下面的DOM结构展示在页面上:
<body> <!-- Headers and whatnot --> <h2>Authors</h2> <div id="authorContainer"> <!-- Authors go here! --> </div> </body>
当然,我们希望这个展示页面可以再加点效果,例如当鼠标移到某个作者上时,背景色可以淡入显示。
最终展示的页面效果如下:
解决方案
我们可以创建一个自定义的Dojo小部件来实现这一需求,分如下几个步骤:
1. 创建必要的小部件目录结构
2. 创建展示单个作者的HTML标记
3. 在#2的基础上把这些标记变成Dijit模板
4. 使用Dojo.declare 来创建我们的小部件类
5. 进行必要的CSS美化
第一步是为自定义小部件创建必要的目录结构
虽然这一步看起来有些多余,但是为你开发的自定义小部件安排一个合理的目录结构是一种良好的编程习惯的。在这里我会创建一个名为custom的目录,作为存放我所有代码和模板的命名空间。当然目录的名称完全取决于你自己,可以是你就职的机构的名称。 然后我为即将创建的自定义小部件取名叫AuthorWidget,所以我会创建一个AuthorWidget目录,这个目录会包含与这个小部件相关的css和html模板以及源代码。 最终的文件结构会如下图所示:
第二步 创建展示单个作者的HTML片段
作为第一个自定义小部件,我们会试着从最简单的做起。它仅仅在页面上展示一些数据。
这里我们使用如下的简单片段来展示一个作者的信息。最外层是一个容器div,记住创建Dijit模板时一定要有一个唯一的根节点。这里这个容器div就是我们模板的根节点。然后我们使用一个H3标签来展示作者名字,一个img标签来展示头像,一个p标签来展示作者的简介。
<div> <h3>Brian Arnold</h3> <img src="/includes/authors/brian_arnold/avatar.jpg"> <p>Brian Arnold is a software engineer at SitePen, Inc., ...</p> </div>
第三步 把HTML片段变成Dijit模板
当使用Dijit._Templated (我们小部件的基类)时,你对模板会有许多操作的方式:
1. 你可以在模板中自动插入变量值
2. 你自己在模板中定义附着点元素(attach point),这样你在widget中引用并编程操纵这些DOM元素
3. 可以在模板中的元素上设定DOM事件的处理函数
在我们这个例子里,我们主要会用到自动的变量值插入。现在我们来创建文件: custom/AuthorWidget/templates/AuthorWidget.html . 文件的内容基本和第二步中的HTML片段相似,只是会添加一些Dijit模板特有的属性。
<div> <h3 data-dojo-attach-point="nameNode">${name}</h3> <img class="${baseClass}Avatar" src="" data-dojo-attach-point="avatarNode"> <p data-dojo-attach-point="bioNode">${!bio}</p> </div>
在这一模板中:
1. 我们使用 ${attribute}的语法来直接插入一些值,例如这里我们插入了作者姓名${name}
2. 类似的,还有一种语法是${!attribute}. 这两者的区别是,${!attribute} 会对值原样插入而不做转义。在这里我们在${!bio}位置要替换的值会包含一些HTML标记,我们不希望Dijit._templated 对这些标记做转义。
3. 所有基于dijit._Widget的小部件都有一个baseClass属性。
4. 在上述模板中,我为每个节点都定义了附着点属性,这样在我的代码中就可以直接使用诸如myAuthor.nameNode 这样的代码直接访问到H3节点。
你也许已经注意到了我在模板中的img标签里没有指定src属性值。 那么如果我们的数据里某个作者没有包含头像图片怎么办?我们需要能够在创建我们的小部件时自动设定处理这类情形的默认值。 后面的步骤会进一步解释如何解决这个问题。
第四步 使用dojo.declare创建小部件的类
这一步我们要在custom目录里创建AuthorWidget.js 。 并且我们添加一个默认的头像的图片文件。
创建之后的目录和文件结构如下:
在AuthorWidget.js 里我们将使用dojo.declare 创建我们的小部件类。 如下:
// custom.AuthorWidget dojo.provide("custom.AuthorWidget"); // 声明依赖的模块和基类 dojo.require("dijit._Widget"); dojo.require("dijit._Templated"); // Create our widget! dojo.declare("custom.AuthorWidget", [dijit._Widget, dijit._Templated], { /* 我们的自定义小部件属性将会被添加在这里 */ }) // and that's it!
这段代码中:
1. 我们使用dojo.provide 来声明我们这个js文件提供了一个名为custom.AuthorWidget 的资源。 从1.6开始, Dojo加入了对异步模块加载机制的支持(AMD),但是为了简单起见,这里我们沿用了传统的Dojo模块声明和加载系统(dojo.provide 和dojo.require)。
2. 我们使用了dojo.require 来引用我们的小部件所依赖的模块 (dijit._Widget 和dijit._Templated 这两个基类)
3.我们使用dojo.declare 声明了一个类, custom.AuthorWidget,该类使用dijit._Widget 和dijit._Templated作为基类。
注意在这个js文件中,我们不需要使用dojo.ready ---- 这是因为我们在开发一个dojo的模块。 而Dojo的模块加载系统会保证你所依赖的模块都加载成功后再运行加载后续的模块。
当然,上面的代码还只是一个空架子,要让它真的可以工作,我们还需要给我们的小部件设置一系列属性。 下面是加上了属性设置的dojo.declare的代码片段。
dojo.declare("custom.AuthorWidget", [dijit._Widget, dijit._Templated], { // 设置一些默认值 // These typically map to whatever you're handing into the constructor name: "No Name", // Using dojo.moduleUrl, we can get a path to our AuthorWidget's space // and we want to have a default avatar, just in case avatar: dojo.moduleUrl("custom.AuthorWidget", "images/defaultAvatar.png"), bio: "", // 加载我们的模板 - important! templateString: dojo.cache("custom.AuthorWidget", "templates/AuthorWidget.html"), // 将会被应用到模板根节点的css类名 baseClass: "authorWidget", // 指向我们背景动画对象的引用 mouseAnim: null, // 用于背景的颜色属性 baseBackgroundColor: "#fff", mouseBackgroundColor: "#def" });
首先我们定义了一些属性用于保存作者的基本信息:姓名,简介和头像。我们也提供了这些属性的默认值。 在设置头像的默认值时,我们使用了dojo.moduleURL来引用当前小部件所在的路径,并访问该路径下的图片文件夹。
通过使用templateString属性,和dojo.cache 我们加载了之前定义的模板文件
设置baseClass属性,该属性会被设置为小部件的DOM根节点的CSS类。 在这里就是我们模板中的最外层div节点。
我们还留出了用于设置动画的属性和背景色的属性。
到目前为止,我们的小部件已经初具雏形,实际上它已经可以运行并显示一些简单的信息了。 当然我们还需要进一步完善它。
接下来我们会添加postCreate方法,一个定制的头像设定方法,已经一个辅助方法用来变换背景颜色。
postCreate方法是用来添加我们的工作逻辑的主要入口。它的调用时机是在小部件的DOM结构成功创建后,但是还没有被添加到页面的DOM树之前(也即用户看不到这个小部件的DOM节点)。因此通常我们把初始化的工作放在这个方法中。
postCreate: function(){ // Get a DOM node reference for the root of our widget var domNode = this.domNode; // Run any parent postCreate processes - can be done at any point this.inherited(arguments); // Set our DOM node's background color to white - // smoothes out the mouseenter/leave event animations dojo.style(domNode, "backgroundColor", this.baseBackgroundColor); // Set up our mouseenter/leave events - using dijit._Widget's connect // means that our callback will execute with `this` set to our widget this.connect(domNode, "onmouseenter", function(e) { this._changeBackground(this.mouseBackgroundColor); }); this.connect(domNode, "onmouseleave", function(e) { this._changeBackground(this.baseBackgroundColor); }); }
在postCreate里我们利用baseBackgroundColor属性设置了domNode的背景色,并且设置了onmouseenter和onmouseleave的事件处理函数,因此当鼠标悬停在我们的DOM节点上时,_changeBackground 函数就会被调用。 下面我们来看看这个函数:
_changeBackground: function(toCol) { // If we have an animation, stop it if (this.mouseAnim) { this.mouseAnim.stop(); } // Set up the new animation this.mouseAnim = dojo.animateProperty({ node: this.domNode, properties: { backgroundColor: toCol }, onEnd: dojo.hitch(this, function() { // Clean up our mouseAnim property this.mouseAnim = null; }) }).play(); }
注: 为什么我们要在这个方法名前加下划线呢?这是一种dojo的命名规范,在方法和对象属性前加下划线表示该方法或对象是类内部成员,不应被用户直接使用。虽然JavaScript语言没有强制禁止这类调用,但是这是一个良好的编程习惯,也是一种提示用户正确使用API的方法。
在这个方法中,我们先检查是否当前有动画正在执行,如果有的话我们先停止它。 然后在设置新的动画并保存在mouseAnim中,并开始播放。 这个例子十分类似于我们在Dojo动画教程http://dojotoolkit.org/documentation/tutorials/1.6/animation/ 中的效果,只不过颜色有所差别。
最后,我们需要解决一个问题,就是如果某个作者没有提供头像图片,我们需要给他设置一个默认的头像。 这里我们通过创建一个定制的属性设置方法(custom setter) 来实现。 这类方法有固定的命名规则, _setXXXAttr 其中XXX是你要设置的属性的名称(首字母需大写)。 例如我们这里要设置的是“avatar属性,因此我们需要的方法名是_setAvatarAttr
在小部件被创建或者用户使用属性设置方法:myWidget.set("avatar",somePath) 时,定制属性方法就会被调用.
_setAvatarAttr: function(av) { // We only want to set it if it's a non-empty string if (av != "") { // Save it on our widget instance - note that // we're using _set, to support anyone using // our widget's Watch functionality, to watch values change this._set("avatar", av); // Using our avatarNode attach point, set its src value this.avatarNode.src = av; } }
这个方法主要是做一个安全检查,在用户传入空字符串时,就使用默认的头像。
从Dojo1.6开始,所有基于dijit._Widget的小部件都会自动继承dojo.Stateful ,这个类加入了对类属性的变化的监控。我们在_setAvatarAttr 中使用this._set() 方法就是为了遵循dojo.Stateful的规则,保证所有属性的变化可以被检测到。
所有这些都完成后,我们现在有了一个可以工作的小部件。 虽然看起来还不是那么美观。
查看示例
第五步: 美化
使用Dijit._Widget的好处之一,就是他提供了一个baseClass属性作为根节点的CSS样式类。之前创建的目录结构中有专门的css目录,接下来我们在css目录中创建一个AuthorWidget.css 文件。
/* AuthorWidget.css */ .authorWidget { border: 1px solid black; width: 400px; padding: 10px; overflow: hidden; /* I hear this helps clear floats inside */ } .authorWidget h3 { font-size: 1.5em; font-style: italic; text-align: center; margin: 0px; } .authorWidgetAvatar { float: left; margin: 4px 12px 6px 0px; max-width: 75px; max-height: 75px; }
可以看到我们定义了authorWidget 类,这个类是baseClass所制定的,它会被应用到模板的div根节点上。 同时,模板中还有 ${baseClass}Avatar类,因此我们定义了authorWidgetAvatar类。(这只是一些最基本的美化,别对我要求太高了,我不是一个设计师)
最后一步在页面的header部分加入我们的css文件,我们就有了一个美观的作者列表小部件啦!
查看示例
小结
这个教程里,我们看到使用dijit._Widget 和 dijit._Templated 作为基类,创建一个自定义的小部件很简单。 我们可以快速的创建模板,添加自定义逻辑,并且定义自己的css来美化小部件的外观。
虽然这个例子很简单,但是你要知道绝大多数Dijit里的小部件都是基于这两个基类开发的,使用这些工具可以开发出强大的界面组件。下面的参考阅读中列出了一些很好的文档可供你参考。
* dijit._Widget on the Dojo Reference Guide
* dijit._Templated on the Dojo Reference Guide
* Tutorial on dojo.declare
* Dojo Reference Guide: Writing Your Own Widget