基于HTML5 Widget Dojo开发教程(1.9)
2011-09-08 09:38 佚名 IBM 我要评论(0) 字号:T | T
本文通过例子介绍了基于 HTML5 的 Dojo Widget 的设计思想和实现方式,利用 HTML5 中的 Canvas 特性和 Dojo 的 Widget 机制创建了一个对话框 Widget。
(本文是在前辈的基础上改进的,从老版本升级到新版本1.9,大部分理论性的东西没有改过,只是把我自己在实践过程中的问题作了一下补充,实际运行过了,可以运行。)
HTML5 Widget Dojo开发教程是本文要介绍的内容,相较于传统的基于 HTML4 和 CSS2 的Widget 设计和实现,基于 HTML5 技术的 Widget 具有很多天然的优势和良好的特性。虽然现阶段 HTML5 尚未得到广泛的支持,但相信市场对 HTML5 中各种新特性的需求会驱动 HTML5的迅速普及,届时会有各种基于 HTML5 功能的 Widget 出来,将我们的页面装饰的更加丰富多彩。
当前的 Web 开发存在着两种趋势,一是基于 HTML、CSS 和 JavaScript 技术,比如以 Dojo 工具包、jQuery 等为基础的 HTML 页面;另一种是基于浏览器端运行时,如基于 Flash、Silverlight 等技术的 RIA 应用。前者占用的资源少,不需在浏览器上安装插件支持,后者界面更为美观,且便于实现一些较为复杂的展现功能。而且,因为 JavaScript 和 Flash 等平台之间的交互功能很受局限,基于这两类技术开发的页面内容很难做深度的集成。
HTML5 作为新一代的 HTML 标准,不但对 JavaScript 的支持更加完善,同时还包含了丰富的绘图功能,这等同于兼具上面提到的两类技术的各自的优势。本文主要介绍基于 HTML5 来扩展 Dojo Widget,它不仅具有强大的 JavaScript 逻辑控制,而且具有丰富的页面展现和良好的运行性能。
若干年前,很少有人会想到一家生产电脑的公司会凭借一款功能设计上存在着不少缺陷的手机在市场上取得巨大的成功。也很少有人会想到一家曾经占据着智能手机大部分市场份额的厂商会落入今天这样举步维艰的局面。人们不禁惊讶的发现,精美的界面、方便的操作对于消费者的吸引力要大于完善的功能及稳定的系统。用户体验的优劣在一定的程度上决定了一个面向终端用户的产品能否在市场上生存。移动设备的设计如此,互联网应用的设计亦如此。现在,互联网上充斥着各种精美的 CSS 式样、JavaScript 特效、Flash 动画等等,来吸引大众的眼球以获得更多的用户关注和经济收益。这其中存在着两种流行的设计趋势。
一种是以高级的 JavaScript 技术和 CSS 技术为基础的 DHTML,以此来实现美观和交互性强的用户界面。这种技术的优势是浏览器能够提供天然的支持而不依赖于任何插件而且能够很灵活的访问页面上的内容,但是这种技术的不足是浏览器自身的局限使得一些功能实现起来很困难。比如若要实现网页上的矢量绘图,虽然有 VML、SVG 等技术,但是它们不是广泛支持的标准,需要在不同的浏览器上做不同的处理。
另一种是在浏览器上面安装某种包含运行环境的插件来运行某些 RIA 的应用,如 Flash、Silverlight、JavaFX 等,这些技术都是基于矢量绘图,能够呈现绚丽的用户界面和灵活多变的用户交互。但它们的缺点就是需要在浏览器上再安装插件,而且运行效率往往也会存在着一定的问题。
新一代的 Web 页面标准 HTML5 则可以帮助我们很好的解决这一问题,它不但提供了很多诸如 Web 套接字、Web 存储等技术,而且提供了 Canvas 以便在 Web 页面上直接进行矢量绘图。作为 HTML5 标准的一部分,Canvas 将天然地被各种浏览器支持,而且便于与 JavaScript 进行交互。从某种意义上说可以作为当前流行的 Flash 技术的替代品。所以,HTML5 与 Flash 技术孰优孰劣的争论这两年就一直不断。
现在 Web 前端开发领域流行着不少 JavaScript 类库,如 YUI Library、Ext JS、Dojo Toolkit 等,其中一些封装了各种前端控件。这些控件的实现是基于 HTML4 的标准和复杂的 JavaScript 及 CSS 技术。但随着 HTML5 技术的发展,它的各种强大特性为这些控件的结构和功能提供新的设计和实现方式。因此,如何将 HTML5 的特性灵活的运用到前端控件开发就是本文的关注点。由于 HTML5 的特性很多,而流行性的 JavaScript 库中的前端控件也五花八门,本文只能举例说明。读者可以根据自身的需求结合 HTML5 中的特性开发出各种强大的前端页面控件。
新一代 Web 标准 HTML5
HTML5 是新一代的 HTML 标准,它里面包含了很多 HTML4 中没有的新标签和应用程序接口,如 audio 标签、video 标签、矢量绘图、Web 套接字、离线数据存储等。这些新特性可以使 Web 页面具有更丰富的功能和更好的用户体验,其中的很多都可以用在网页控件设计上,从而使得网页上的内容更加丰富。
在 HTML5 众多的功能中,有一个功能非常重要,它不仅是一项被众多网页设计人员期待已久的功能,而且为网页的功能和外观设计留下了巨大的空间,它就是 HTML5 中的矢量绘图。现在,不仅不少的业内人士将 HTML5 的矢量绘图视作 Flash 的挑战者,甚至连 Flash 的支持厂商 Adobe 都推出了基于 HTML5 矢量绘图的动画制作工具。本文后面将会介绍借助 HTML5 的矢量绘图技术实现 Dojo Widget。
在此之前,为了帮助读者能够更好的理解本文的内容,这里先对 HTML 中的矢量绘图做一些简要的说明。HTML5 的矢量绘图的功能由 Canvas 标签和各种绘图 API 构成。在 JavaScript 的脚本中,通过 Canvas 节点可以获得绘图上下文,通过它调用 API 就可以绘制各种矢量图,如下所示。
清单 1. 利用 HTML5 Canvas 绘制的矩形和三角形
1 <html>
2 <head>
3 <script>
4 window.onload = function() {
5 var canvas = document.getElementById('canvas1');
6 var ctx = canvas.getContext('2d');
7 ctx.fillRect(25,25,100,100);
8 ctx.clearRect(45,45,60,60);
9 ctx.strokeRect(50,50,50,50);
10 ctx.beginPath();
11 ctx.moveTo(125,125);
12 ctx.lineTo(205,125);
13 ctx.lineTo(125,205);
14 ctx.fill();
15 }
16 </script>
17 </head>
18 <body>
19 <canvas id="canvas1" height="600" width="600">
20 </canvas>
21 </body>
22 </html>
图 1. HTML5 的 Canvas 绘制的矩形和三角形
在上面的例子中,我们在一个 HTML 的文档中加入了一个 Canvas 标签,利用基于 JavaScript 的 API 来获得绘图上下文(Context),并在上面绘制了我们所要的图形。除了绘制 2D 图形,HTML5 还支持 3D 矢量绘图,它与 2D 的使用方式类似,此处不再详述。
值得注意的是,HTML5 还是一个发展的标准,至今并没有被所有主流浏览器全面支持。但是,即使是曾经是对 HTML5 支持较少的 IE 浏览器也会在新版本 IE9 中支持 Canvas 绘图等 HTML5 关键标签技术。所以相信在不久的将来,HTML5 的普及就会实现。
Dojo Widget
近些年,页面设计的易用性、功能性和交互性已经成为了业界的主流趋势。网页的功能越来越丰富,用户体验也越来越舒适。这一切都离不开前端以 JavaScript 和 CSS 为基础的 DHTML 技术的迅猛发展。但是,前端大规模的 JavaScript 和 CSS 开发的复杂度比较高,而且还要支持不同的浏览器平台,于是诞生了很多 JavaScript 库用来帮助前端开发者完成较为复杂的页面逻辑同时屏蔽浏览器的差异,如 jQuery、YUI Library、Ext JS 等。
另外,越来越多的互联网公司也将自己的 JavaScript 库发布出来,如淘宝的 KISSY、豆瓣的 Do 等等。每种库都支持封装前端复杂的控件,如 jQurey UI、Dojo Widget 等,但方式却不相同。
本文选取 Dojo Toolkit 作为控件实现的基础来介绍基于 HTML5 的控件的设计思想,当然,这种设计并不是只能在 Dojo Toolkit 上得到实现,其它的类库也可以作为实现基础。Dojo Toolkit 是当前页面前端开发领域流行的 DHTML 库,它不但包括丰富的页面基础功能,如 CSS 选择器、DOM 节点操作、动画效果等,还包括良好的面向对象的封装结构和以此为基础的 Dojo 控件技术 Dojo Widget(简称 Dijit)。Dojo Widget 中包含了对网页控件的生命周期管理,包括初始化渲染、属性映射、事件绑定、控件销毁等。
基于 HTML5 的 Dojo Widget 的设计
如前文所述,HTML5 中包含了很多强大的特性,它们的普及和发展会给前端页面的控件技术带来巨大的变化。本文不去描绘这种改变将会是什么样子,而是举一个具体的例子来为读者掀开未来的一角并由读者亲身品位。HTML5 中的很多特性都可以用于页面控件功能的实现,如前文提到的 Web 套接字、离线存储、拖拽、矢量绘图等。本文将利用 HTML5 中的 Canvas 矢量绘图来渲染 Dojo Widget 的视图,并在此基础上设计了属性映射和事件绑定。
当前,很多网站的页面都会在适当地方弹出一些对话框,图 2 所示是 Google maps 网站上的对话框,图 3 所示的是腾讯的 Web QQ 网站上的对话框。一般来讲,网页上的对话框都是通过 DIV 或是 Table 来进行布局。有的设计力求简洁,如图 2 中的对话框,只用一层 DIV 表示外框;有的设计则力求美观,如图 3 中的对话框,用了 9 个 DIV 来描述外框。页面上的对话框的外观设计的关键是边框的设计。
以往的技术,如图 2 和图 3 都是利用 DIV 加一些式样和背景图片来实现对话框。但 HTML5 中的 canvas 给了我们另外一种实现页面上控件外观的手段,就是用矢量图将对话框的边框“画”出来,而不是通过 DIV“拼”出来。这样可以利用矢量图技术来为对话框增加各种新特性,比如对话框的阴影、圆角、渐变等各种效果,再比如特殊形状的对话框,如椭圆形,菱形等等。
此外,利用矢量绘图技术去“画”对话框的另一个好处就是可以很方便的调整大小和形状。例如要求设计一个椭圆形的对话框并且可以设置尺寸,如果没有矢量绘图,或许还可以用椭圆背景图片来实现,但设置大小的需求就很难实现。所以 HTML5 中的矢量绘图确实能为页面前端控件的外观设计带来灵活性。我们这里会用 HTML5 的 Canvas 实现对话框控件。
图 2. Google Maps 网站上弹出的对话框
图 3. 腾讯的 Web QQ 网站上的弹出对话框
利用 HTML5 我们可以画出图 4 所示的对话框的外观,包括标题栏和主体两部分,在标题栏的右侧还有一个关闭按钮。与上面例子中的对话框类似,我们也会使用两个 DIV 分别作为标题栏内容和主体内容的容器。得到的对话框 Widget 结构上会由三部分组成,分别是:绘制对话框外观的 Canvas、包含标题内容的 DIV 和包含主体内容的 DIV。
图 4. HTML5 Canvas 上画出的对话框外观
设计好对话框 Widget 的外观和结构后,接下来需要考虑如何为它绑定事件。图 2 和图 3 中的对话框中的每一个组成部分都是一个或几个 HTML 元素,换句话说就是可以对应到页面上的一个或几个 DOM 节点。比如 Google Maps 和腾讯 Web QQ 网站上的对话框中的关闭按钮都是 Anchor 元素,其所对应的 DOM 节点上可以直接绑定事件处理函数。但是,对于图 4 中的那个关闭按钮,则不能通过简单的 DOM 节点事件绑定来完成。
为 Canvas 矢量图上的某个区域进行事件绑定,如为图 4 中的关闭按钮添加事件响应,需要首先监听 Canvas 节点的相应事件,再在事件处理函数中进行事件分发。同样以图 4 中的关闭按钮为例,要监听它的鼠标点击事件,需要监听 Canvas 的鼠标点击事件,在其回调函数中计算鼠标的坐标是否落入了关闭按钮的区域内,若是则调用关闭按钮的事件点击处理函数。
对于 Widget 外观矢量图上表示出的嵌套关系,如图 4 中的外层对话框包含里面的关闭按钮,更好的实践是将矢量图上的内容分成不同的实体进行封装,如可将外层对话框和里面的关闭按钮封装成不同的组件,这样整个对话框就变成了一个组合控件。这种组合关就可以用树的结构来进行描述,并以此设计类似浏览器 DOM 树上的事件捕获和冒泡机制,如图 5 所示。因为 HTML5 的 Canvas 的矢量绘图不允许将事件响应绑定到矢量图中的某个具体图形上,所以图 5 中 Widget3 的鼠标单击事件处理需要从 Canvas 的鼠标单件事件处理中逐级分发,在事件分发的过程中加入事件捕获和事件冒泡的响应。
图 5. 组合模式的 Widget 的事件捕获和冒泡
在 Canvas 上设计好 Widget 的外观后,就可以将其包装到 Dojo Widget 中,然后按照清单 3 和清单 4 中给出的方式来使用它。
基于 HTML5 的 Dojo Widget 的实现
做为一个提供良好面向对象封装的 JavaScript 类库,Dojo Toolkit 提供了完善的 Widget 封装机制用于创建各种控件,如 Dijit 中的 Form 表单控件、布局控件,Dojox 中的表格控件、颜色选项板控件等。这些控件实现的功能千差万别,却遵循同样的结构,可见 Dojo 所提供的 Widget 机制具有十分良好的适用性。
一般来讲,每一个 Dojo Widget 都要继承 Dojo 中两个抽象类 dijit._Widget 和 dijit_Templated 并实现其中的一些方法。dijit_Widget 主要用于实现 Dojo Widget 的生命周期管理,dijit._Templated 用于实现 Widget 的视图渲染和属性映射,对于我们所要实现的 Widget 也会继承这两个接口。我们的 Widget 的视图主要有三部分组成,一个 Canvas 节点用于绘制外观,一个 DIV 节点用于容纳标题内容,一个 DIV 节点用于容纳主体内容,Canvas 节点中的矢量图作为两个 DIV 节点的背景。整个 Widget 的结构如图 6 所示。
图 6. 所要实现的 Widget 的结构
在实现 Widget 结构的同时,利用 Dojo 提供的模板的机制,可以轻松的将属性设置反映到视图上。
define([
"dojo/_base/declare",
"dijit/_WidgetBase",
"dijit/_TemplatedMixin",
"dojo/dom-style",
"dojo/text!./MyWidget.html"
],function(declare,_WidgetBase, _TemplatedMixin,domStyle,templement) {
return declare([_WidgetBase, _TemplatedMixin], {
templateString:templement,
width : 200,
height: 150,
dialogTitle : "",
onClickListeners : [],
postCreate : function() {
this._drawDialog(this.width, this.height, this.canvasNode);
// new domDnd.Moveable(this.domNode, {handle:this.titleNode});
console.log(this.canvasNode.toString());
if(this.canvasNode!=null)
{
console.log("I am not NULL");
}
this.inherited(arguments);
domStyle.set(this.titleNode, "height", 20 + "px");
domStyle.set(this.titleNode, "width", (this.width - 54) + "px");
domStyle.set(this.containerNode, "width", (this.width - 30) + "px");
domStyle.set(this.containerNode, "height", (this.height - 60) + "px");
this.titleNode.innerHTML = '<font color=white>' + this.dialogTitle + '</font>';
console.log("nbi");
},
_onCanvasClick : function(e) {
for(var i = 0; i < this.onClickListeners.length; i ++) {
if(this.onClickListeners[i].isInScope(e.layerX, e.layerY)) {
this[this.onClickListeners[i].handler]();
}
}
},
_onClose : function() {
this.hide();
},
show : function() {
dojo.style(this.domNode, "display", "block");
},
hide : function() {
dojo.style(this.domNode, "display", "none");
},
_drawDialog : function(width, height, canvasNode) {
var canvas = canvasNode;
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.shadowOffsetX = 4;
ctx.shadowOffsetY = 4;
ctx.shadowBlur = 8;
ctx.lineWidth = 7;
ctx.strokeStyle = '#fff';
ctx.shadowColor = 'rgba(0, 0, 0, 0.25)';
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
drawBody(ctx, 7, 7, width - 20, height - 20, 15);
drawCloseButton(ctx, width - 20 - 10, 20);
this.onClickListeners.push({isInScope: function (x, y) {
return (x - width + 20 + 10) * (x - width + 20 + 10) + (y - 20) * (y - 20) < 49
},
handler: "_onClose"});
function drawBody(ctx, x, y, width, height, radius) {
drawRoundedRect(ctx, x, y, width, height, radius);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.strokeStyle = '#fff';
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 4;
ctx.shadowBlur = 8;
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(x, 35);
ctx.lineTo(x + width, 35);
ctx.closePath();
ctx.stroke();
}
function drawRoundedRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x, y + radius);
ctx.lineTo(x, y + height - radius);
ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
ctx.lineTo(x + width - radius, y + height);
ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
ctx.lineTo(x + width, y + radius);
ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
ctx.lineTo(x + radius, y);
ctx.quadraticCurveTo(x, y, x, y + radius);
ctx.stroke();
console.log("drawRoundedRect");
}
function drawCloseButton(ctx, x, y) {
ctx.fillStyle = "#ff0000";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(x, y, 7, 0, Math.PI * 2, true);
ctx.closePath();
ctx.stroke();
ctx.strokeStyle = '#ff0000';
ctx.beginPath();
ctx.moveTo(x - 5 * 0.707, y - 5 * 0.707);
ctx.lineTo(x + 5 * 0.707, y + 5 * 0.707);
ctx.moveTo(x - 5 * 0.707, y + 5 * 0.707);
ctx.lineTo(x + 5 * 0.707, y - 5 * 0.707);
ctx.closePath();
ctx.stroke();
}
}
});
}
);
其中的templateString较为重要可以是向上述代码一样单独建立一个heml页面,也可以直接将heml语句直接写到当前页面中。单独html页面目录结构为
其中的MyWidget.html实现如下:
<div style="position: relative;">
<canvas data-dojo-attach-point="canvasNode" height="${height}" width="${width}"
style="position: absolute;">
</canvas>
<div data-dojo-attach-point="titleNode"style="position:absolute;top:10px;left:12px;">
</div>
<div data-dojo-attach-point="containerNode" style="position:absolute;
top:40px;left:12px;overflow:auto">
</div>
</div>
实现 Widget 的 postCreate 方法
postCreate方法就好像是初始化页面函数一样,在函数中为属性赋值,也可以绘制图形,具体和构造函数有什么区别,我不太清楚,但是在自定义Widget是必须实现此方法。
上面实现了 Widget 的视图,接下来我们要为它绑定两个事件响应。首先是对话框 Widget 的鼠标拖动,即为对话框 Widget 标题栏添加拖放功能。为了实现这个功能可在 canvas 的鼠标事件响应中进行处理,判断事件触发点是否落在标题栏的位置上并处理。另外,也可以直接在标题栏 DIV 节点上绑定事件处理。考虑到实现上的简单,我们采用第二种方式,将 Dojo 提供的拖放功能直接绑定到标题栏 DIV 节点上。
然后是对话框的关闭按钮响应,即通过对话框上的关闭按钮来关闭对话框。实现这个功能只能通过在 canvas 的鼠标事件响应中判断触发点位置的方式。
程序的主要页面代码如下。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>new widget</title>
</head>
<body>
<script>
var dojoConfig = (function(){
var base = location.href.split("/");
base.pop();
base = base.join("/");
console.log(base.toString());
return {
async: true,
isDebug: true,
packages: [
{
name: "js",
location: base + "/js"
}
]
};
})();
</script>
<!-- load Dojo -->
<script src="//ajax.googleapis.com/ajax/libs/dojo/1.9.2/dojo/dojo.js"></script>
<script>
require(["dojo/dom","js/MyWidget","dojo/domReady!"],function(dom,myWidget){
var authorContainer = dom.byId("authorContainer");
//主要是placeAt函数
var widget=new myWidget().placeAt(authorContainer);
})
</script>
<div id="authorContainer">
</div>
</body>
</html>
最终会在页面上生成如图 7 所示的对话框。
图 . 由 Widget 生成的对话框
注意:在实际应用中要想出现以上效果,在运行时需要借助服务器运行,在IIS中通过建立虚拟目录的方法来浏览。
在实际应用中,本文介绍的对话框 Widget 的设计和实现可以作为原型来进一步增强和扩展,以满足实际项目的需求。而这种结合了 HTML5 新特性的 Widget 设计方法和实现思路则可以运用到很多应用场景中。
小结:基于HTML5 Widget Dojo开发教程的内容介绍完了,希望通过HTML5 Widget内容的学习能对你有所帮助!