Dojo之Widget标签开发
Dojo widget就像是jsp中的自定义标签,通过学习开发widget标签来对其使用及其设计原理有更好的了解。本文主要是对Develop HTML widgets with Dojo一文的学习和翻译。由于本人水平有限,如有不妥或者不对之处轻各位大虾批评指正
1. Dojo widget的概念
正如上所述,dojo widget就像是jsp中自定义标签,只是dojo widget是在客户端使用的(本文中统一简称dojo widget为widget标签)。Widget标签已经脱离了传统的DOM模型结构,每个Widget标签都有唯一的widgetID,并且可以包含多个属性,而且可以包含子Widget标签。Widget共包含三个部分:
l 一个包含Widget标签逻辑的js文件,通过JavaScript DOM模型来处理可见的元素。
l HTML模板文件,它提供基本的Widget HTML视图。当然如果模板很简单,完全可以在js文件中用字符类型替代。
l Css样式文件,定义标签的样式,在js代码或者HTML模板文件中使用。
l 图片文件。
目录结构如下:
dojo/src/widget ------------------------------------ js代码文件
dojo/src/widget/templates ------------------------ HTML模板,Css样式文件
dojo/src/widget/templates/images --------------- 图片文件
2. Widget标签的使用
使用标准的Dojo Widget标签创建Button按钮,代码如下:
<html>
<head>
<script type="text/javascript" src="js/dojo/dojo.js"></script>
<script type="text/javascript">
dojo.require("dojo.widget.Button");
</script>
</head>
<body>
<div dojoType="Button">Submit</div>
<!--另一种使用Widget标签的方法-->
<!--<dojo:button caption="Submit"></dojo:button>-->
</body>
</html>
|
执行效果如图:
3. 自定义Widget标签-HelloWorld
首先创建js/dojo/src/Widget/templates/HelloWorldTemplate.html,代码如下:
创建js/dojo/src/Widget/HelloWorl.js,代码如下:
dojo.provide("dojo.widget.HelloWorld");
dojo.require("dojo.widget.HtmlWidget");
dojo.widget.defineWidget("dojo.widget.HelloWorld",
dojo.widget.HtmlWidget,
{ templatePath:dojo.uri.dojoUri("src/widget/templates/HelloWorldTemplate.html")
}
);
|
注:所有用户自定义Widget标签都必须继承HtmlWidget。
创建测试文件HelloWorld.html,代码如下:
<html>
<head>
<script type="text/javascript" src="js/dojo/dojo.js"></script>
<script type="text/javascript">
dojo.require("dojo.widget.*");
dojo.require("dojo.widget.HelloWorld");
</script>
</head>
<title>Hello World Widget test</title>
<body>
<div dojoType="HelloWorld"></div>
<dojo:HelloWorld></dojo:button>
</body>
</html>
|
如果HelloWorldTemplate模板足够简单,可以使用templateString替换templatePath,修改后HelloWorld.js代码如下:
dojo.provide("dojo.widget.HelloWorld");
dojo.require("dojo.widget.HtmlWidget");
dojo.widget.defineWidget("dojo.widget.HelloWorld", dojo.widget.HtmlWidget,
{
templateString: "<div>Hello World</div>"
}
);
|
4. 自定义Widget标签属性
以上部分讨论了如何创建简单的自定义标签,下面需要给HelloWorld添加属性以及自样式,让其更像一个标准的Html标签。让HelloWorld标签的显示内容可以有用户定义,添加text属性就可以办到。修改后的代码如下:
HelloWorld.js
dojo.provide("dojo.widget.HelloWorld");
dojo.require("dojo.widget.HtmlWidget");
dojo.widget.defineWidget("dojo.widget.HelloWorld", dojo.widget.HtmlWidget,
{
templateString: "<div>${this.text}</div>",
text: ""//default value
}
);
|
注:自定义属性必须要设置默认值,否则会被Dojo忽略。而且Dojo会忽略DOM模型下的所有属性。
HelloWorld.html
<html>
<head>
<script type="text/javascript" src="js/dojo/dojo.js"></script>
<script type="text/javascript">
dojo.require("dojo.widget.*");
dojo.require("dojo.widget.HelloWorld");
</script>
</head>
<title>Hello World Widget test</title>
<body>
<div dojoType="HelloWorld" text="you can set the text attribute"></div>
<!--<dojo:HelloWorld text="you can set the text attribute"></dojo:button>-->
</body>
</html>
|
属性的处理
处理属性的典型方法是postCreate()方法(稍候讨论),另一个方法是fillInTemplate()。许多Widget标签都是通过postCreate()方法来处理属性的,也有通过set方法。不同的是postCreate()方法是在标签创建时处理属性,而set方法是在运行时动态设置属性。
脚本片断
在HEML模板文件中加入js代码并不是一个好的方法,但有时候却必不可少。在Dojo中可以使用${…}代码片断来获取属性值,正如上例中的${this.text}。
添加样式
为了使Widget达到真正的功用,Dojo提供了定制css样式的功能,通过引用不同的自定义样式,可以方便的为应用改变显示效果。为HelloWorld标签添加样式,创建一个简单样式文件js/dojo/src/widget/templates/HtmlHelloWorld.css,代码如下:
.caption {
font-family: Verdana;
font-size: 10pt;
}
|
注:css类名必须是唯一的
修改HeolloWorld.js,代码如下:
dojo.provide("dojo.widget.HelloWorld");
dojo.require("dojo.widget.HtmlWidget");
dojo.widget.defineWidget("dojo.widget.HelloWorld", dojo.widget.HtmlWidget,
{
templateString: "<div class=\"caption\">${this.text}</div>",
text: "",//default value
templateCssPath: dojo.uri.dojoUri("src/widget/templates/HtmlHelloWorld.css")
}
);
|
5. 在js中使用Widget标签
本部分将说明如何用Dojo在js代码中使用HelloWorld标签,而传统的js方式已经不再适用。
传统方式操作Widget标签
如果想在程序中动态改变text “Hello World”,你会试着用传统的js方式来实现,但是这样做是徒劳的。传统方式的代码如下:
<html>
<head>
<script type="text/javascript" src=" js/dojo/dojo.js"></script>
<script type="text/javascript">
dojo.require("dojo.widget.HelloWorld");
</script>
</head>
<body onload="document.getElementById('HelloWorldTest').innerHTML='New
text';">
<div id=" HelloWorldTest" dojoType="HelloWorld"></div>
</body>
</html>
|
这样的代码是没有任何效果的。下面就分析一下这段为何不会有作用。
A. Body中的onload属性失效
Body中的onload属性与Dojo是不兼容的,Dojo已经在事件方法中包括了onload的事件。Onload事件在Dojo下使用,代码如下:
<html>
<head>
...
<script>
dojo.addOnLoad(
function(){
alert("onload!");
}
);
</script>
</head>
|
B. Dojo Widget标签与DOM element的区别
Dojo Widget标签已经不再是一个DOM element。Dojo创建Widget标签时,已经去掉所有DOM模式下的所有属性,除了Dojo认识的属性。例如,id已经演化成了widgetId。如果需要用传统的方式操作属性,必须通过修改模板文件来实现。如果需要获取ID,修改模板文件代码如下:
<div id="${this.widgetId}">Hello World</div>
|
Dojo方式操作Widget标签
如果要用Dojo方式操作Widget标签,必须还要修改一些代码。修改后的HelloWorld.js代码如下:
dojo.provide("dojo.widget.HelloWorld");
dojo.require("dojo.widget.HtmlWidget");
dojo.widget.defineWidget("dojo.widget.HelloWorld", dojo.widget.HtmlWidget,
{
templateString: "<div dojoAttachPoint=\"domNode\" >${this.text}</div>",
text: "",//default value
// dynamic setters
setHtml: function(htmlString) {
this.domNode.innerHTML = htmlString;
}
}
);
|
注:Dojo Widget标签使用attachment points来处理DOM element。在Dojo方式下,不需要id=”${this.widgetId}”,因为不会使用id来获取对象,而是使用attachment point。实际上attachment point是默认使用的。”domNode”是attachment points根元素
通过获取Widget标签对象实例,代码如下:
<html>
<head>
<script type="text/javascript" src="js/dojo/dojo.js"></script>
<script type="text/javascript"> dojo.require("dojo.widget.HelloWorld");
dojo.addOnLoad(function(){
dojo.widget.byId("someID").setHtml("New text");
});
</script>
</head>
<body>
<div id="someID" dojoType="HelloWorld"></div>
</body>
</html>
|
通过使用dojo.widget.createWidget()方法动态创建HelloWorld实例,代码如下:
<html>
<head>
<script type="text/javascript" src="js/dojo/dojo.js"></script>
<script type="text/javascript">
dojo.require("dojo.widget.HelloWorld");
dojo.addOnLoad(
function(){
var newWidget = dojo.widget.createWidget(
"HelloWorld", // widgetType
{}, // widget attributes, for example {title: "Some Title"}
dojo.byId("widgetPlaceholder") // reference to a DOM node that
// will be REPLACED by new widget
);
}
);
</script>
</head>
<body>
<div id="widgetPlaceholder"></div>
</body>
</html>
|
6. Widget标签嵌套HTML
本部分讨论如何实现Widget标签的嵌套
A. 嵌套静态HTML
正如前面提到的Dojo创建Widget标签时移除所有的DOM element。所以在Dojo移除之前,必须改变所要的DOM element。通过Dojo提供fillInTemplate()方法实现。此方法可以操作HTML DOM element和已经生成的Widget标签。实现改变静态HTML的HelloWorld.js代码如下:
dojo.provide("dojo.widget.HelloWorld");
dojo.require("dojo.widget.HtmlWidget");
dojo.widget.defineWidget("dojo.widget.HelloWorld", dojo.widget.HtmlWidget,
{
templateString: "<div dojoAttachPoint=\"domNode\">${this.text}</div>",
text: "",//default value
// dynamic setters
setHtml: function(htmlString) {
this.domNode.innerHTML = htmlString;
},
fillInTemplate: function(args, frag) {
// Getting original HTML element
var source = this.getFragNodeRef(frag);
// Moving all children of original element to
// the desired node of the new component
while(source.hasChildNodes()) {
var node = dojo.dom.removeNode(source.firstChild);
this.domNode.appendChild(node);
}
// Invoking original dojo fillInTemplate() method
dojo.widget.HelloWorld.superclass.fillInTemplate.call(this, args, frag);
}
}
);
|
以上代码将HTML DOM节点转移到需要的地方,另一种实现方法是设置Widet属性isContainter为true。
动态添加嵌套HTML
如何动态添加嵌套HTML实现方法要根据动态HTML的形式来决定:
l 嵌套HTML作为string提供,通过设置innerHtml属性实现。
l 嵌套HTML作为DOM节点提供,可以使用appendChild方法实现。
以上方式的实现代码如下:
dojo.provide("dojo.widget.HelloWorld");
dojo.require("dojo.widget.HtmlWidget");
dojo.widget.defineWidget("dojo.widget.HelloWorld", dojo.widget.HtmlWidget,
{
templateString: "<div>${this.text}</div>",
text: "",//default value
setHtml: function(html) {
if (dojo.lang.isString(html)) {
this.domNode.innerHTML = html;
} else if (dojo.dom.isNode(html)) {
// Cleaning whatever was there before
dojo.dom.removeChildren(this.domNode);
// Adding new element
this.domNode.appendChild(html);
} else {
dojo.lang.assert(false, "setHtml called with incorrect type: " + (typeof html));
}
}
}
);
|
7. Widget标签嵌套Widget标签
嵌套静态Widget标签
有些情况下我们需要实现Widget标签的相互嵌套,例如Dojo TabContainer和Tree标签。要实现这样的Widget标签,你需要设置isContainer为true,而且要在模板中设置attachment point为”containerNode”,代码如下:
dojo.provide("dojo.widget.HelloWorld");
dojo.require("dojo.widget.HtmlWidget");
dojo.widget.defineWidget("dojo.widget.HelloWorld", dojo.widget.HtmlWidget,
{
templateString: "<div dojoAttachPoint=\"containerNode\">${this.text}</div>",
text: "",//default value
isContainer: true
}
);
|
这种实现是依赖Dojo的,如果需要实现更加复杂的,必须要操作postCreate方法中的Children数组,数组中存了所有嵌套的静态Widget标签。
测试代码:
<html>
<head>
<script type="text/javascript" src="js/dojo/dojo.js"></script>
<script type="text/javascript">
dojo.require("dojo.widget.Button");
dojo.require("dojo.widget.HelloWorld");
</script>
</head>
<title>Hello World Widget test</title>
<body>
<div dojoType="HelloWorld" text=" ad you can set the text attribute">
<div dojoType="Button">Submit</div>
</div>
<!--<dojo:HelloWorld text="you can set the text attribute"></dojo:button>-->
</body>
</html>
|
复杂应用请参考Dojo TabContainer和Tree标签。
动态添加Widget标签嵌套
动态添加Widget标签嵌套,需要在HelloWorld.js中实现addChild()方法,重写addChild()方法中还要调用父类的addChild()方法。实现动静态添加Widget标签嵌套的代码如下:
dojo.provide("dojo.widget.HelloWorld");
dojo.require("dojo.widget.HtmlWidget");
dojo.widget.defineWidget("dojo.widget.HelloWorld", dojo.widget.HtmlWidget,
{
templateString: "<div dojoAttachPoint=\"titleNode\">${this.text}" +
"<br/><div dojoAttachPoint=\"containerNode\">"
+ "</div></div>",
text: "",//default value
isContainer: true,
postCreate: function() {
// adding statically
for(var i in this.children) {
this._addRow(this.children[i]);
}
},
addChild: function(child, overrideContainerNode,
pos, ref, insertIndex) {
this._addRow(child);
dojo.widget.HelloWorld.superclass.addChild.call(
this, child, overrideContainerNode);
},
_addRow: function(component) {
this.titleNode.appendChild(
document.createTextNode(component.widgetType));
this.titleNode.appendChild(
document.createElement("br"));
}
}
);
|
注:经测试发现,即使不实现addChild方法,同样可以动态添加Widget标签嵌套,但是在HtmlWidget类中也没有找到addChild方法的实现,这里有待进一步研究。
测试代码:
<html>
<head>
<script type="text/javascript" src="js/dojo/dojo.js"></script>
<script type="text/javascript">
dojo.require("dojo.widget.Button");
dojo.require("dojo.widget.Checkbox");
dojo.require("dojo.widget.HelloWorld");
dojo.addOnLoad(function(){
var button = dojo.widget.createWidget("Button",
{caption: "Submit"});
dojo.widget.byId('someID').addChild(button);
var checkbox = dojo.widget.createWidget("Checkbox");
dojo.widget.byId('someID').addChild(checkbox);
});
</script>
</head>
<title>Hello World Widget test</title>
<body>
<div dojoType="HelloWorld" id="someID"></div>
<!--<dojo:HelloWorld text="you can set the text attribute"></dojo:button>-->
</body>
</html>
|
8. Dojo事件处理
本部分主要介绍使用Dojo的方式处理事件,在阅读本部分之前,最好先了解Dojo事件系统。首先需要在模板中定义dojoAttachEvent=”eventName: eventHandlerMethod;”属性。eventName是标准的DOM事件名称(例如:onClick),eventHandlerMethod则为事件调用函数。修改后的代码如下:
dojo.provide("dojo.widget.HelloWorld");
dojo.require("dojo.widget.HtmlWidget");
dojo.widget.defineWidget("dojo.widget.HelloWorld", dojo.widget.HtmlWidget,
{
templateString: '<div dojoAttachPoint="domNode" dojoAttachEvent="onClick:onClickHandler;">Click here</div>',
onClickHandler: function(e) {
alert("clicked in onClickHandler");
this.onClick(e);
},
onClick:function (e) { //for dojo.event.connect
}
}
);
|
注:可以在标签中预定义某一事件的函数,如上例中的onClickHandler,同时也可以预留接口让用户绑定自定义函数。
测试代码:
<html>
<head>
<script type="text/javascript" src="js/dojo/dojo.js"></script>
<script type="text/javascript">
dojo.require("dojo.widget.HelloWorld");
function HelloWorld_onClick() {
alert("Node clicked in HelloWorld_onClick");
}
dojo.addOnLoad(function(){
var hello = dojo.widget.byId("someID");
dojo.event.connect(hello, 'onClick', 'HelloWorld_onClick')
});
</script>
</head>
<title>Hello World Widget test</title>
<body>
<div dojoType="HelloWorld" id="someID"></div>
</body>
</html>
|
总结:
通过对本文的学习,初步了解了Dojo的Widget标签的简单使用和如何实现自定义Widget标签。
做人简单一点好