这一期的 掌握 Dojo 系列 文章将主要讲述 Dojo 工具包的 UI 组件库 - Dijit 中类型多样的 Widget 及其使用方法。
Dijit 简介
从 Dojo 0.9 开始,Dojo 把 Widget 从 Dojo 的核心包中分离出来,组成 Dijit 。 Dojo 在 Dijit 中为 Widget 家族添加了多位成员,增强了 Widget 的实力的同时也加快了其在页面中的加载速度。
为改善 Widget 的外观,Dijit 提供了多套样式主题,比如默认绑定的样式主题 Tundra,提高页面可访问性的样式主题 A11y,以及其它两种可供选择的主题 Soria 和 Nihilo 。并且开发人员还可以根据自身项目的需求开发个性化的主题。同时 Dijit 对国际化和针对残障人士的可访问性的支持成度很高。可以说 Dijit 已成为 Dojo 工具包中三辆马车之一。
本文将会详细介绍 Dijit 中的 Widget 的使用。由于 Dojo Widget 数量众多,不能一一介绍。为此从功能的角度把 Widget 分为三类:表单 Widget,布局 Widget,高级 Widget 。在每个类别中选择代表性的 Widget 结合示例加以介绍。
表单 Widget 的使用
在有用户概念的 Web 应用中,注册是一项必不可少的功能,同时注册也是有点枯燥的任务。有个不争的事实就是:没人喜欢填表单——无论是网上还是网下。所以设计有效的页面表单不是件容易的事情。如果不能改变注册表单存在的事实,那么就要改变表单枯燥令人生厌的现状,让表单或整个注册过程变得轻松省力。为实现这个目标,Web 开发人员会把较长的注册表单设计成标签的形式;当用户填错信息时,利用 Javascript 和正则表达式的结合给用户一些提示信息;设计方便的日期选择组件等等。可是这些提高用户友好性的努力往往会给开发人员增加很大的工作量,并且确保这些组件的浏览器无关性也不是一件容易的事情。
有没有更简便的方法来开发 Web 表单呢?当然有!Dijit 为 Web 开发人员提供了一系列的表单 Widgets,利用这些表单 Widgets,Web 开发人员可以轻松的设计出功能强大、用户友好性高的表单。
表单 Widget 简介
可以说每一个 HTML 表单控件都可以在 Dijit 找到与其对应的表单 widgets 。下面列表列出了目前 Dijit 提供的 Form Widgets:
- Form – 类似于 HTML 的 [form] 控件,同时提供了一些有用的方法和扩展点;
- Button – 类似于 HTML 的 [button] 风格的控件,同时增加了一些高级的特性;
- CheckBox
- RadioButton
- ToggleButton
- ComboBox – 类似于 HTML 的 [select] 组合框和 [text] 域控件的结合体。可以像 [select] 组合框那样提供一列可选值;同时允许用户忽略这些可选值,而像在 [text] 域控件里那样输入自己想要输入的任何值;
- FilteringSelect – 类似于 HTML 的 [select] 控件,可以动态填充可选项,并且可以按需设置加载选项的数量;
- Textbox – 类似于 HTML 的 [text] 控件,同时提供一系列很酷的功能:可以裁空,改变大小写,设置必填,验证输入合法性,日期组件等;
- Validation
- Currency
- Date, Time
- Integer
- Textarea – 类似于 HTML 的 [textarea] 控件,同时可以根据文本的容量动态调整自己的大小,达到了真正的按需分配空间;
- Slider – 这个 widget 没有相对应的 HTML 控件,是一个相对独立的图形化的组件,可以通过鼠标、方向键来控制刻度尺的刻度。
- NumberSpinner – 数字转轴,应用此 widget,会让数字的输入更方便,可以通过输入框右侧的上下按钮(支持键盘上下方向键)来调节数字大小。
所有的这些表单 widgets 都可以放置在 HTML 的 [form] 标签内,也可以放在 dijit.form.Form widget 内,甚至可以放在 [form] 标签外。这些表单 widgets 拥有标准的 HTML 控件的所有属性和方法,在实际开发中可以完全取代标准的 HTML 控件。同时它们都继承于 dijit.form._FormWidget,所以这些表单 widgets 还统一拥有一些附加的属性和方法。下表是它们共有的属性和方法:
表 1. dijit.form._FormWidget 属性和方法
属性 | 属性类别 | 描述 |
disabled | Boolean | 判断此 widget 是否响应用户的输入,如果为真则此 widget 不响应用户的输入。可以用方法setAttribute("disabled", true/false)来改变此属性值。 |
intermediateChanges | Boolean | 判断在调用 setValue 方法后是否立即引发 onChange 事件。 |
tabIndex | Integer | 当用户点击 tab 键在 widget 中切换时,可以通过此属性来设置 widget 获得焦点的顺序。 |
方法 | 描述 | |
focus | 在 widget 上设置 focus | |
getValue | 获得 widget 的值 | |
setValue | 设置 widget 的值 | |
reset | 重置 widget 的值为初始值 | |
undo | 恢复 widget 的值为之前最后一次更改的值 |
表单 Widget 使用示例
接下来,我们将通过一个网站注册表单案例的实现来介绍表单 widget 的使用方法。此案例的场景是一个电子商务类型网站的用户注册页面。表单的设计概要需求为:
- 表单结构为多步骤,需要给出清晰的导航
- 使用进度标尺来告诉用户当前的位置和整个步骤
- 强调几个步骤中的逻辑联系,比如标明:第一步、第二步、第三步等
- 表单的布局
- 尽量使用对齐的字段、等长的输入框以及一致的视觉样式来减少视觉干扰
- 尽量控制在一屏内出现 3-6 个字段或输入框
- 标明选填和必填的差别
- 注册过程中的提示
- 提示信息(tips)尽量在需要帮助的地方和时间出现
- 填写表单时如有出错,即时显示提醒 / 警示信息,指引用户改正
- 尽量避免出现弹出框的警示提醒
这个注册页面设计成两步三屏组成:设置用户名和密码,填写个人资料,注册完成。这三屏由 Slider widget 作为进度标尺串联而成,下面分步介绍这些表单 widgets 。
图 1
在由图 1 可以看到所需要填写的域有用户名、密码、设置密码保护问题、答案等。在处理表单提交的数据时,经常会碰到非法数据,此时就需要对数据的合法性进行校验。目前主流应用都会采用服务器端验证和客户端验证相结合的方案以兼顾网站安全性和用户友好性。 Dijit 的 TextBox 家族提供了 Validation, Currency, Number, Date, Time 等 Widgets 。可以说它们是标准 text 标签的功能加强版,在方便用户输入的同时,加强了数据的合法性校验能力。
对于需要用户输入的文本域,ValidationTextBox widget 提供了强大的正则匹配功能,正好符合此需求;对于设置密码保护问题这一项,需要为用户提供备选项,同时又允许用户输入自己的希望输入的内容。本案例选择了用 ComboBox widget 来实现。所以在第一步中应用到了三类 widgets:Form widget,TextBox widget 和 ComboBox widget 。可以说这三个 widget 的用法都相对简单,下面来看一下它们用法。
首先看 Form widget 的使用方法,清单 1 是实例化 Form widget 的代码。
清单 1
<form dojoType="dijit.form.Form" id="registerForm" action="showPost.php" execute="showSteps(4);alert('Execute form w/values:\n'+dojo.toJson(arguments[0],true));" >
从清单 1 中可以看到实例化 Form widget 首先要声明dojoType="dijit.form.Form"
,同时这里应用了 Form widget 的扩展点:execute 。这个扩展点就相当于 HTML 的 form 控件的事件 onSubmit,会在用户提交表单时执行一些 Javascript 方法。本案例中调用了一个方法 showSteps() 来显示完成页面,同时把用户填写的表单数据用 alert 的方式打印出来。
设置好 Form widget 之后就需要向其中添加 ValidationTextBox widget 和 ComboBox widget,清单 2 是用户名、密码、确认密码、设置密码保护问题、答案等域的实现代码。
清单 2
<div id="firststep"> <div class="formSteps"> <span>设置用户名和密码</span> </div> <div id="step1" class="formAnswer"> <label class="firstLabel" for="name">用户名 *</label> <input type="text" id="name" name="name" class="medium" dojoType="dijit.form.ValidationTextBox" required="true" trim="true" invalidMessage="请输入用户名!"/> <br> <label class="firstLabel" for="password">密码 *</label> <input type="password" id="password" name="password" class="medium" dojoType="dijit.form.ValidationTextBox" required="true" regExp="[a-zA-Z]\w{5,17}" promptMessage="密码必须以字母开头,长度在6~18之间,并且只能包含字符、数字和下划线。" invalidMessage="请确认密码以字母开头,只能包含字符、数字和下划线,同时长度在6~18之间!"/> <br> <label class="firstLabel" for="validate">确认密码 *</label> <input type="password" id="validate" name="validate" class="medium" dojoType="dijit.form.ValidationTextBox" required="true" validator="return this.getValue() == dijit.byId('password').getValue()" invalidMessage="请确认两次输入密码一致!"/> <br> <label class="firstLabel" for="pwdquestion">设置密码保护问题 *</label> <select dojoType="dijit.form.ComboBox" value="" id="pwdquestion" autocomplete="true" hasDownArrow="true" > <option></option> <option>您的宠物名字是什么?</option> <option>北京奥运开幕式是哪天?</option> </select> <br /> <label class="firstLabel" for="answer">答案 *</label> <input type="text" id="answer" name="answer" class="medium" dojoType="dijit.form.ValidationTextBox" required="true" trim="true" invalidMessage="请输入答案!"/> <br> </div> <center> <button dojoType="dijit.form.Button" οnclick="showSteps(2)" iconClass="dijitEditorIcon dijitEditorIconSave" type=button> 下一步 </button> </center> </div>
在清单 2 中有四处用到了 ValidationTextBox widget,其中用户名和答案这个两个文本域的验证相对简单,只是通过属性required="true"
来设置此域是必须填写的。同时通过设置属性 invalidMessage 来设置错误提示信息。不过请读者注意,这里的只是做了一个简单的 Demo,以演示 Widgets 的用法。在实际应用中应需要从客户端和服务器端两方面对用户名等表单数据的合法性进行校验。
下图是用户没有填写用户名时,显示的错误提示信息。错误提示信息会以 Dijit 的一个高级 widget - Tooltip 为载体显示出来,开发人员可以通过 ValidationTextBox widget 的属性 tooltipPosition[] 来设置 Tooltip 显示的位置,关于 Tooltip 的用法会在本文第四部分:高级 Widget 的使用中介绍。
图 2
看到这里,有的读者可能会问,难道 ValidationTextBox widget 就只能设置文本域是否必填么?不是的,它还有更炫的利用正则匹配进行验证的机制。通过密码和确认密码这两个文本域的应用可以初步了解 ValidationTextBox widget 相对高级的验证方式。
ValidationTextBox widget 一般采用正则表达式来验证,利用正则表达式强大的匹配功能,可以说 ValidationTextBox widget 可以满足目前各种常用输入域的格式要求。比如 IP 地址的验证,URL 的验证,Email 地址的验证,密码格式的验证等等。清单 2 中密码输入域的验证就是采用了正则表达式匹配验证。
ValidationTextBox widget 提供了两种引用正则表达式的方式:直接引用和通过调用方法返回正则表达式的方式引用。在清单 2 中 ID 为 password 的 ValidationTextBox widget 采用的是第一种引用正则表达式的方式:直接设置属性 regExp 的值为正则表达式,如regExp="[a-zA-Z]\w{5,17}"
。这里的正则表达式要匹配的是以字母开头([a-zA-Z]
),后接 4 到 16 个任意单一字符(\w 表示任意单一字符 , 同 [a-zA-Z0-9])。 ValidationTextBox 另外一种方式:设置属性 regExpGen 的值为返回正则表达式的方法名,如regExpGen="dojox.regexp.emailAddress"
。方法 dojox.regexp.emailAddress 中提供了域名列表,更明确的限制了域名的合法性。建议简单的验证可以选择设置regExp
属性,而较为复杂的验证选择设置regExpGen
属性,这样可以提供较为复杂的正则表达式组合。
与用户名输入域相比,密码输入域还增加了一个方法:promptMessage 。这是 ValidationTextBoxes widget 获得焦点时弹出的辅助提示信息,而 invalidMessage 是在用户输入不合法时的即时提示。图 3 是这两种提示信息的比较图。当密码输入域获得焦点时,会弹出辅助提示信息:密码必须以字母开头,长度在 6~18 之间,并且只能包含字符、数字和下划线。此时输入域处在编辑的状态,并且颜色没有改变。当用户进行输入未符合要求时,会弹出错误提示信息:请确认密码以字母开头,只能包含字符、数字和下划线,同时长度在 6~18 之间!此时输入域为黄色,并且在输入框末尾有警示图标。
图 3
另外在第一步中还有一个 widget:ComboBox 。可以说 ComboBox widget 一个自动完成、辅助用户输入的文本输入域。它是 [select] 组合框和 [text] 输入域的结合体:既可以像 [select] 组合框那样提供一列可选值,也可以像在 [text] 输入域里那样输入用户想要输入的任何值。首先来了解一下 ComboBox widget 的属性:
表 2. dijit.form.ComboBox 的属性列表
属性 | 属性类别 | 描述 |
autoComplete | Boolean true | 判断是否自动完成用户输入的内容。当值为真时,用户输入部分字符串,ComboBox widget 会把能匹配上的可选值列出,如果光标离开此 widget,会显示第一个匹配的选项值。 |
hasDownArrow | Boolean true | 判断是否现实下拉按钮。 |
ignoreCase | Boolean true | 判断是否忽略大小写(针对英文的输入)。 |
pageSize | Integer Infinity | 此属性可以设置下拉列表显示的条数,如果出现多页的情况,会在下拉列表中显示” Previous choices ”和” More choices ”按钮。用户可以通过点击这两个按钮来查找选项。 |
query | Object {} | 设置查询表达式以过滤’ store ’里的选项。 |
searchAttr | String name | 设置查找的匹配表达式 |
searchDelay | boolean true | 当用户输入内容后到 Dojo 开始查找用户输入值的匹配项之间的间隔时间。 |
store | Object | 数据提供对象的一个引用。 .Dijit 中一般应用 JSON 格式的数据。 |
清单 2 中的 ComboBox widget 实例用到了两个属性 autoComplete 和 hasDownArrow 。这两个属性已经表 2 中介绍过了,这里不再多说。 ComboBox widget 的另一亮点就是可以从外部文件动态加载选项,并且提供了属性来过滤选项,同时可以设置下拉列表每页显示选项的数量。
第一步就讲到这里,下面来看一下第二页中用到的 widgets 。
图 4
在图 4 中可以看到需要填写的项为:真实姓名、邮编、手机号。在这里仍然选择 ValidationTextBox widget 。性别项为单选项,本例选择了 RadioButton widget 。出生日期项需要用户按照 MM/DD/YYYY 的格式输入日期,以往 Web 开发人员可能会需要写大段的 JS 代码来实现一个日期控件,并且还要写一堆日期格式的验证代码。现在 Dijit 提供了一个 DateTextBox widget,一行代码搞定所有的工作。对于“省”这一项,选择的是 FilteringSelect widget 。可以说 FilteringSelect 跟 ComboBox 非常类似,不过 FilteringSelect 不允许用户输入可选项之外的值。因为市名特别多,并且不易收集完整,所以这个 Demo 中选择使用了 ComboBox widget 来实现。在给出用户一定的可选项的同时,允许用户自己输入。最后同意条款的复选框理所当然的选择了 CheckBox widget 。
由于在第一步中已经介绍过 ValidationTextBox widget,这里不再赘述。 RadioButton widget 和 CheckBox widget 同属于 Button 类型的 widget,其使用非常方便:只需要在 HTML 标准的 checkbox / radio 控件上加上 dojoType 的属性,值是” dijit.form.CheckBox ”或者” dijit.form.RadioButton ”。此处要注意的是声明 dojoType 时,大小写敏感。清单 3 中图 4 中使用 CheckBox widget 的代码示例:
清单 3
<input type="checkBox" name="agreement" id="ag" value="ag" dojoType="dijit.form.CheckBox" /> <label for="ag">我同意并接受以下条款</label>
当 Dojo 解析到控件的属性 dojoType 为 dijit.form.CheckBox 时,会应用 dijit.form.CheckBox 中定义的 templateString(清单 4)替换掉源代码中 [checkbox] 的定义增加了一些属性和方法,然后输出到页面(清单 5)。
清单 4
<div class=\"dijitReset dijitInline\" waiRole=\"presentation\"\n\t> <input\n\t \t type=\"${type}\" name=\"${name}\"\n\t\t class=\"dijitReset dijitCheckBoxInput\"\n\t\t dojoAttachPoint=\"focusNode\"\n\t \t dojoAttachEvent=\"onmouseover:_onMouse,onmouseout:_onMouse,onclick:_onClick\"\n/> </div>\n
清单 5
<div wairole="presentation" class="dijitReset dijitInline dijitCheckBox" role="wairole:presentation" widgetid="ag"> <input type="checkbox" dojoattachevent="onmouseover:_onMouse,onmouseout:_onMouse,onclick:_onClick" dojoattachpoint="focusNode" class="dijitReset dijitCheckBoxInput" name="agreement" id="ag" value="ag" tabindex="0" style="-moz-user-select: none;" pressed="false"/> </div>
由清单 4 和清单 5 可以看出,Dojo 接管了标准 [checkbox] 控件的三个事件:onmouseover,onmouseout 和 onclick 。分别代替为 _onMouse 和 _onClick,以此更改 Checkbox 的表现形式。
提示:
如果有兴趣可以在文件 _FormWidget.js 中找到 _onMouse 方法的定义,在 Button.js 文件中找到 _onClick 方法的定义。
图 5
图 5 是完成页面的截图,非常简单,只显示了注册完成的提示信息。不过这步让包括了一个我们还没有介绍的 widget:Slider widget 。其实 Slider 贯穿了三步,在每屏都会显示注册的进度,可以说是表单 Widget 中比较独特的一个,下面详细介绍了 Slider Widget 的使用。
Slider Widget 的使用
与 CheckBox widget 相比,Slider widget 较为复杂,至少在标准的 HTML 标签库中是没有 Slider 控件的。 Slider widget 的使用却如同使用 CheckBox widget 一样的方便。用户可以用鼠标点击、拖拉、中键滚动或者用键盘的上、下、左、右按键来选择 Slider widget 的刻度。
Silder 是由六个 widgets 组成,分别是:dijit.form.HorizontalSlider, dijit.form.VerticalSlider,dijit.form.HorizontalRule, dijit.form.VerticalRule,dijit.form.HorizontalRuleLabels, dijit.form.VerticalRuleLabels 。这里可以“望文生义”一下:从 widgets 的名字可以看出 dijit.form.HorizontalSlider, dijit.form.VerticalSlider 分别是水平方向和垂直方向的 slider,提供可调节大小的标尺。其属性如下表:
表 3. HorizontalSlider 和 VerticalSlider 的属性
属性 | 属性类别 | 描述 |
clickSelect | boolean true | 判断是否可以点击进度条来更改刻度值 |
discreteValues | integer Infinity | 在最大值与最小值之间可以设置的最大值 |
intermediateChanges | Boolean false | 判断在调用 setValue 方法时是否直接触发 onChange 事件。如果为’ true ’,则每次执行 setValue 方法时都会触发 onChange 事件;如果为’ false ’,则 onChange 事件只有在自己被调用时才会被触发。 |
maximum | integer 100 | 可设置的最大刻度值 |
minimum | integer 0 | 可设置的最小刻度值 |
pageIncrement | integer 2 | 点击键盘按键 pageup/pagedown 时一次变化的刻度值 |
showButtons | boolean true | 判断是否在刻度条两端显示增加和减小的按钮 |
dijit.form.HorizontalRule, dijit.form.VerticalRule 可以为 HorizontalSlider 和 VerticalSlider 设置标识线,其属性如下:
表 4. HorizontalRule 和 VerticalRule 的属性
属性 | 属性类别 | 描述 |
container | Node containerNode | 设置要连接的父节点 |
count | Integer 3 | 标识线的数量 |
ruleStyle | String | 为个别标识线设置 CSS style |
当在页面中有多个 dijit.form.HorizontalRule widgets 时,通过设置 container 值来区分他们,比如清单 6 中第一个 dijit.form.HorizontalRule widget 设置container="topDecoration"
,而第二个设置container="bottomDecoration"
。这样用户就可以看到在标尺的上下两方各有一组标识线。同时设置属性 count 的值来确定标识线的个数,譬如清单 6 中第一个 dijit.form.HorizontalRule widget 的 count 的值为 6,则在标尺上方会有六个标识线,每两个标识线间距是总长的 1/5 。
dijit.form.HorizontalRuleLabels, dijit.form.VerticalRuleLabels 可以为标尺提供刻度标签,其属性如下:
表 5. HorizontalRuleLabels 和 VerticalRuleLabels 的属性
属性 | 属性类别 | 描述 |
labels | Array [] | 文本类型的数组,存放要展现的标签值 |
labelStyle | String | 为个别标签设置 CSS style |
dijit.form.HorizontalRuleLabels 是继承 dijit.form.HorizontalRule,所以 RuleLabels 类型的 widgets 都具有 Rule 类型 widgets 的属性,同时也有自己特有的属性。通过 labels 属性,开发人员可以随心设置刻度标签的值。
Dijit 提供了两种方式来设置标签值,第一种就是直接设置 labels 属性的值为一数组或者返回数组的 Javascript 方法;第二种方式就是在页面标记为 dijit.form.HorizontalRuleLabels 的标签内部设置 <li>,这也正是清单 6 中采用的方式。当 labels 属性值为空时,Slider 会自动搜索本节点内部的 <li> 标签,取出 <li> 标签节点的 innerHTML 作为单个标签值。
下面通过实例来看 Slider widget 的使用。
清单 6
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Dijit HorizontalSlider Example</title> <style type="text/css"> @import "dojoPath/dijit/themes/tundra/tundra.css"; @import " dojoPath/dojo/resources/dojo.css" </style> <script type="text/javascript" src="dojoPath/dojo/dojo.js" djConfig="parseOnLoad: true"></script> <script type="text/javascript"> dojo.require("dojo.parser"); dojo.require("dijit.form.Slider"); </script> </head> <body class="tundra"> <div id="horizontalSlider" dojoType="dijit.form.HorizontalSlider" onChange="dojo.byId('sliderinput').value=dojo.number.format(arguments[0]/100,{places:1, pattern:'#%'});" handleSrc= "http://o.aolcdn.com/dojo/1.0.0/dijit/themes/tundra/images/preciseSliderThumb.png" value="10" minimum="0" maximum="100" discreteValues="100" intermediateChanges="true" showButtons="true" style="width:50%;"> <ol dojoType="dijit.form.HorizontalRuleLabels" container="topDecoration" style="height:1em;font-size:75%;color:gray;"> <li> </li> <li>20%</li> <li>40%</li> <li>60%</li> <li>80%</li> <li> </li> </ol> <div dojoType="dijit.form.HorizontalRule" container="topDecoration" count=6 style="height:5px;" ></div> <div dojoType="dijit.form.HorizontalRule" container="bottomDecoration" count=3 style="height:5px;" ></div> <ol dojoType="dijit.form.HorizontalRuleLabels" container="bottomDecoration" style="height:1em;font-size:75%;color:gray;"> <li>0%</li> <li>50%</li> <li>100%</li> </ol> </div> Slider Value:<input readonly id="sliderinput" size="4" value="10.0%"> </body> </html>
在清单 6 中可以看到 Slider,Rule,RuleLabels 的应用。 dijit.form.HorizontalSlider 是把 dojoType 属性设置在’ DIV ’标签内的,由于intermediateChanges="true"
,onChange 事件会在调用 setValue 方法时自动触发。这里设置的 onChange 事件是更新 id 为’ sliderinput ’的一个只读域的值,效果如图 6 所示。在实际项目中 Slider widgets 可以应用到多个模块中,比如图片的浏览和缩放,多页面间的转换,文本长度的控制等等。
图 6
布局 Widget 的使用
Web 应用的页面布局一直是令 web 开发者头疼的一件事情。在 Web 2.0 的时代,Web 页面布局的设计越来越多样化,仅仅依靠 Tables 和 CSS 控制的页面布局已难满足用户的需求。为此 Dijit 提供了一系列的布局 Widgets 辅助 Web 开发人员实现复杂的页面布局。
布局 Widget 简介
首先来浏览一下布局 Widgets 的成员。 Dijit 中提供的布局 Widgets 可以分为三类:
- 对齐方式容器:用以盛放屏面类 widgets,并且可以设置这些 widgets 的排列方式。这类的布局 Widgets 有 BorderContainer,LayoutContainer,SplitContainer 。其中 BorderContainer 是在 Dojo1.1 中引进的轻量级组件,有取代 LayoutContainer,SplitContainer widgets 之势。目前 Dojo 不推荐使用 LayoutContainer,SplitContainer widgets ;
- 堆叠容器:此类的 widgets 可以把前两种 widgets 层叠在一起,而一次只显示一个屏面。这类的的布局 Widgets 有 AccordionContainer,TabContainer,StackContainer 等。
- 屏面:盛放和显示大块的内容,包括文本、图片、图表,甚至是其它 widgets 。这类的布局 Widgets 有 ContentPane 等;
在设计页面布局时,首先应选择页面整体的框架:上下两栏、左右两栏、上中下三栏、左中右三栏、上一栏下两栏、上下左右中五栏等。在以往的 Div + CSS 设计布局时,虽然可以轻松得做到前五种布局的实现,但如果要实现最后一种五栏的布局,却有些困难。并且设计后的布局间的比例或者每栏的大小都是固定的,当一栏的内容超出栏宽 / 高时,只能通过左右拉条或者下拉条的拖动来显示超出的内容。可以说既麻烦又不美观。
在 Dijit 的布局 widgets 中,对齐方式容器类的 BorderContainer widget 提供了一套简单的 API,可以在页面中设置上下左右中五栏的内容 ―― 也就是设置屏面的内容,甚至可以嵌套设计。同时在每两个相邻的屏面间有一分割的组件,可以调节屏面的大小。
为解决页面内容多,导致出现左右或者上下拖动条的情况,Dijit 提供了堆叠类容器。无论是 AccordionContainer,TabContainer,还是 StackContainer 它们实现的功能是一样的:把内容分成多个屏面,每次只显示一屏的内容,要想显示其它屏的内容,需要点击那屏的标题栏。
如果说前面这两类布局 widgets 为页面提供了骨架和骨骼的话,那么屏面类 widgets 就是填充这些骨架的真材实料。下面简单介绍这三类布局 widgets 的属性、方法以及用法等。
BorderContainer widget
首先介绍页面的骨架:对齐方式容器。前面已经说过 LayoutContainer 和 SplitContainer widgets 在 Dojo1.1 中被标注为不推荐使用,所以此类 widget 首推 BorderContainer 。可以说 BorderContainer 是 LayoutContainer 的升级版,同时集成了 SplitContainer 优点:为用户提供可拖动的边界。
像之前提过的那样,BorderContainer 可以向五个区域输出:上下左右中。同时这五个区域也有两种不同的摆设,可以通过属性 design 来设置。如果 design 为“ headline ”,上下两个区域的宽度就会与 BorderContainer 的宽度相同;如果 design 为“ sidebar ”,左右两个区域的高度就会和 BorderContainer 的高度相同。下面是这两种设计形式的示意图,其中第一张图片的 design 属性为“ headline ”。
图 7 design 属性分别为“headline”和“sidebar”的 BorderContainer 示意图
清单 7 是图 7 中 design 属性值为“ headline ”的实现代码,直接更改 design 属性值为” sidebar ”就可以实现第二张图的效果。同时从清单 7 中还可以注意到
清单 7
<style type="text/css"> html, body, #main{ width: 100%; /* make the body expand to fill the visible window */ height: 100%; } </style> <div dojoType="dijit.layout.BorderContainer" design="headline" id="main"> <div dojoType="dijit.layout.ContentPane" region="leading" splitter="true" style="background-color: #acb386; width: 100px;"> leading </div> <div dojoType="dijit.layout.ContentPane" region="top" style="background-color: #b39b86; height: 100px;"> top bar </div> <div dojoType="dijit.layout.ContentPane" region="center" style="background-color: #f5ffbf; padding: 10px;"> main panel </div> <div dojoType="dijit.layout.ContentPane" region="bottom" style="background-color: #b39b86; height: 100px;" splitter="true"> bottom bar </div> <div dojoType="dijit.layout.ContentPane" region="trailing" style="background-color: #acb386; width: 100px;" splitter="true"> trailing </div> </div>
通过清单 7 也可以看到样式的定义中设置了属性 width 和 height,这里也是要注意的地方:BorderContainer 节点需要设置 width 和 height 。同时左右两个子节点可以设置宽度,而上下两个子节点可以设置高度。中间区域的子节点无需设置大小,刨去四个边界区域占有的空间,剩下的就是中间区域。
当需要调节区域大小时,spliter 属性就派上用场了。当开发人员希望用户可以自己调节屏面大小时,可以设置此属性值为“ true ”。这样在两屏面间就会出现一个可以拖拽的边界。同时如果开发人员不希望用户自己调节屏面大小,就可以设置此属性为“ false ”。
TabContainer widget
当一个页面内容较多,而用户不希望像看十米长卷一样一直向下托动滚动条来浏览页面时,应用堆叠容器 widgets 是一个比较不错的选择。将功能类似的一些信息放在同一个标签页内,用户可以方便的在不同的标签页之间切换,并且可以关闭不想要的标签页。 AccordionContainer,TabContainer,StackContaine 三个 widgets 实现的功能相同,只是表现形式不同,这里通过 TabContainer 的介绍来了解一下堆叠容器 widgets 的使用。
首先看一下实现一个 TabContainer 所需要的元素。 TabContainer 实现的功能就是包含多个内容面,而一次只显示一个。为了可以选择用户需要的内容面,就需要为每一个内容面配备一个标签。就像平时自己整理文档一样,每个标签上面都有标题来标注此类文档的用途。在现实生活中,有时候不需要某份文档了,就会把此文档粉碎。为模仿此操作就需要为每个标签配备一个关闭按钮(可选的)。那好,到目前为止构建一个 TabContainer 所需的元素凑齐了:内容面,标签,关闭按钮。那剩下来就是技术活:关联这三个元素,TabContainer 已经为开发者做好了这项工作,开发人员所需要的就是创建 TabContainer 和需要的 ContentPane 。下面来看一代码片段:
清单 8
<div id="mainTabContainer" dojoType="dijit.layout.TabContainer" style="width:500px;height:100px"> <div id="FirstPane" dojoType="dijit.layout.ContentPane" selected="true" title="The first pane"> The first pane. Can put text,picture or dialog here! </div> <div id="SecondPane" dojoType="dijit.layout.ContentPane" title="The second pane" closable="true"> The second pane! You can close me, Cool ha! </div> </div>
在清单 8 中的代码片断是一个简单的 TabContainer 例子,声明了一个 TabContainer widget 和两个 ContentPane widget 。其中 TabContainer 的声明很方便,直接设置dojoType="dijit.layout.TabContainer"
就可以,省下来的工作就是声明 ContentPane 。 ContentPane widget 会有一些特殊的属性,比如closable="true"
。这是标签是否附带关闭按钮的标示,如果为“ true ”则标签上会显示关闭按钮。同时标签的内容是通过 title 属性来设置的,图 8 是清单 8 在浏览器中的输出。
图 8 TabContainer 示例效果图
如果希望不同的标签显示的是单独的页面文件时,可以设置 dijit.layout.ContentPane 的属性 href 。譬如<div id="
Third
Pane" dojoType="dijit.layout.ContentPane"
href=
"
test.html
"
title="The
third
pane">
。
ContentPane widget
ContentPane 是所有布局 widgets 的基石,其他的任何一个布局 widgets 都可以用 ContentPane 作为内容或者子 widget 的载体。同时 ContentPane 也可以单独使用,可以盛放文本、图片、图表,甚至其它 widgets 。首先看一下 ContentPane wdiget 的属性和方法。
表 6.ContentPane widget 的属性列表
属性 | 属性类别 | 描述 |
errorMessage | String Locale dep. | 错误提示信息,可以在 loading.js 文件中更改默认信息。 |
extractContent | Boolean false | 当取回的内容是页面时,判断是否抽取页面标签 <body> … <//body> 内的可见的内容。 |
href | String | 当前实现内容的超链接。如果在构造 ContentPane widget 的时候设置此项,就可以在 widget 显示的时候加载数据。 |
isLoaded | Boolean false | 设置加载状态。 |
loadingMessage | String Locale dep. | 加载时显示的信息,同 errorMessage 一样可以在 loading.js 文件中更改默认信息。 |
parseOnLoad | Boolean true | 解析取回的内容,如果有 widgets 的声明,会实例化 widgets 。 |
preload | Boolean false | 强制加载数据。 |
preventCache | Boolean false | 判断是否缓存取回的外部数据。 |
refreshOnShow | Boolean false | 在本 widget 从隐藏到展现时,判断是否刷新数据。 |
表 7.ContentPane widget 的方法列表
方法 | 描述 |
cancel() | 取消进行中的内容下载 |
refresh() | 强制刷新 |
resize(/* String */size) | 此方法可以重新设置 widget 的大小。 |
setContent(/*String|DomNode|Nodelist*/data) | 代替原有的内容,替换为新的内容。这个方法经常用到,可以动态向 ContentPane 中输入其它 widgets 。 |
setHref(/*String|Uri*/ href) | 替换原有的超链接,通过 XHR 的形式异步获取数据,然后重置此 widget 中的内容。 |
表 6 中列出的都是 ContentPane 作为一个单独的 widget 使用时的属性。如果把 ContentPane 作为其它布局 widgets 的子节点,就需要为不同的布局 widget 增加不同的属性。譬如在清单 7 中的region="top"
和清单 8 中的closable="true"
等。
布局 Widget 使用示例
这部分通过一个简单的页面布局示例把这三中布局 widgets 串连在一起。示例的需求是这样的:一个页面需要分为四个区域,上下左和中;头部为页面标题,左侧为导航栏,底部为另外一个导航栏,中间部分显示详细信息。图 9 本示例的效果图。
图 9 布局 Widget 示例效果图
为把页面分为四部分,需要采用 BorderContainer widget 。设置其属性 design 为“ headline ”,同时创建 4 个子节点,属性 rigion 分别为“ left ”“ top ”“ buttom ”“ center ”。其中左侧栏为导航栏,选择 AccordionContainer ;上栏只是显示标题,所以直接安放一个 ContentPane ;底部也是一个导航栏,这里选择 TabContainer 来实现;中间部分显示左侧导航栏的详细信息,选择 ContentPane 。那么这些 widgets 的层次关系就如下面的列表:
- BorderContainer ;
- Top Border Container
- BorderContainer ;
- ContentPane #1
- Left Border Container
- Accordion Container
- ContentPane #2
- ContentPane #3
- ContentPane #4
- Accordion Container
- Right Border Container
- ContentPane #5
- Bottom Border Container
- Tab Container
- ContentPane #6
- ContentPane #7
- Tab Container
- Left Border Container
根据这些 widgets 的层次关系就很容易创建这个页面的布局。清单 9 是本示例的实现代码:
清单 9
<style type="text/css"> html, body, #main{ width: 100%; /* make the body expand to fill the visible window */ height: 100%; overflow: hidden; /* erase window level scrollbars */ padding: 0 0 0 0; margin: 0 0 0 0; font: 10pt Arial,Myriad,Tahoma,Verdana,sans-serif; } </style> <div dojoType="dijit.layout.BorderContainer" design="headline" id="main"> <div dojoType="dijit.layout.AccordionContainer" duration="200" region="left" style="background-color: #acb386; width: 400px;" splitter="true" style="float: left; width: 400px; height: 300px; overflow: hidden"> <div dojoType="dijit.layout.AccordionPane" title="First pane of AccordionContainer"> <p><a href="#">the first link</a><br /> <a href="#">the second link</a><br /> <a href="#">the third link</a></p> </div> <div dojoType="dijit.layout.AccordionPane" title="Second pane of AccordionContainer"> <p> <a href="#">the first link</a><br /> <a href="#">the second link</a><br /> <a href="#">the third link</a></p> </div> <div dojoType="dijit.layout.AccordionPane" title="Third pane of AccordionContainer"> <p> <a href="#">the first link</a><br /> <a href="#">the second link</a><br /> <a href="#">the third link</a></p> </div> </div> <div dojoType="dijit.layout.ContentPane" region="top" style="background-color: #ACBFD0; height: 100px;"> <h1>Page Title</h1> </div> <div dojoType="dijit.layout.ContentPane" region="bottom" style="background-color: #ACBFD0; height: 200px;" splitter="true"> <div id="mainTabContainer" dojoType="dijit.layout.TabContainer" style="width: 100%; height: 20em;"> <div id="tab1" dojoType="dijit.layout.ContentPane" href="tab1.html" title="Tab 1"> </div> <div id="tab2" dojoType="dijit.layout.ContentPane" href="tab2.html" refreshOnShow="true" title="Tab 2" selected="true"></div> </div> </div> <div dojoType="dijit.layout.ContentPane" region="center" style="background-color: #f5ffbf; padding: 10px;"> main panel to display different contents according to the selected pane of left bar. </div> </div>
高级 Widget 的使用
本部分会简单介绍一下高级 Widget 的使用。
高级 Widget 简介
Dijit 在提供一些基本的 widgets 的同时也提供了一些高级功能的 widgets 。比如说 Editor,ProgressBar,Tooltip,ColorPaletee,Tree,Dialog 等。这里把这些高级 widgets 分为两类:用户辅助 Widget,高级编辑和显示 Widget。
用户辅助 Widget 包括:
- Dialog:相对应于 HTML 的对话框,是一个模式对话框。用户能通过此 widget 上的关闭按钮关闭此对话框,同时也可以在此对话框上放置表单 widgets,并且可以在此对话框上直接提交表单;
- TooltipDialog:此 widget 必须关联一个 DropDownButton 。用户点击 DropDownButton 时,此对话框显示,用户点击此对话框外任何位置都可以让此对话框消失;
- ProgressBar:Dijit 提供的进度条组件;
- Tooltip:此 widget 的表现形式类似于属性 title,功能却很远比属性 title 强大。弹出的提示窗口中可以显示文本、图片和页面。同时可以设置提示窗口出现的位置,之后会详细介绍 Tooltip 。
高级编辑和显示 Widget 包括:
- ColorPalette:一个颜色选择组件,为用户提供一组可供选择的颜色块。可以跟页面背景或者局部区域颜色关联起来。通过属性 palette 来设置可供选择的颜色块的数量,目前提供 "7x10" 和 "3x4" 两种选择。
- Editor:一个多文本的编辑器组件,外观类似于 word 编辑器。提供了一系列的编辑按钮,包括拷贝、粘贴、撤销、各种字体处理、数字编号或者项目符号,文本对齐格式等等。值得一提的是这个组件的架构是插件架构,支持开发人员自己开发新的命令、按钮等。
- InlineEditBox:类似于 Dojo 0.9 中的 dijit.form.InlineEditBox widget 。可以编辑页面显示的输出类型文本,当点击文本时,文本就会从浏览状态转换为可编辑状态。
- Tree:此组件可以把有层次关系的数据用树状结构展现出来,就如同 Windows 系统的资源浏览器。
下面通过 Tooltip 的介绍来帮助读者认识高级 Widget 的使用。
Tooltip Widget 的使用
目前大多数应用中,每个页面的控件都比较多,而仅仅通过控件显示的名称是不足以让用户了解各种控件的作用和一组相同类型控件间的区别。标准的 HTML 控件会提供 title 属性。当用户鼠标停留在设置好 title 属性值的控件上时,浏览器会弹出一个提示,内容就是属性 title 的值。
但是 title 属性有自己的不足之处,比如样式单一,只能显示文本,长文本在 Firefox 中不能完全显示等等。所以有不少开发人员自己动手创建自定义的提示工具。其中有的方案是把提示信息放在层标签中,在 onmouseOver 事件中调用显示层标签的方法,在 onmouseOut 事件中调用隐藏层标签的方法,以达到模仿 title 属性的目的;同时也有方案在页面初始化的时候收集所有设置了 title 属性的控件,取到 title 的值,经过一定的处理显示在自定义的提示框中。
Dojo 提供了一个更美观,实用的辅助提示 widget:Dijit.Tooltip 。其样式定义在单独的样式文件中,开发人员可以自己修改。同时也提供了一些强大的功能,比如可以显示图片、图表和从服务器请求得到的数据等,可以控制显示的时间和出现持续的时间。
实例化 Tooltip 的方法有多种,除了所有 widget 都具备的declaratively和programmatically方式外,还可以调用 Dijit 提供的方法dijit.showTooltip(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position)
显示 Tooltip,调用dijit.hideTooltip = function(aroundNode)
来隐藏 Tooltip 。在后面的实例中会涉及到这三种方法。
在看实例之前可以先浏览一下 Tooltip 的属性:
表 8. dijit.Tooltip 的属性
属性 | 属性类别 | 描述 |
connectId | String | 要挂载 Tooltip 的控件的 Id,可以为用逗号分隔的多个 Id 。 |
label | String | 要显示的提示信息 |
showDelay | Integer 400 | Tooltip 显示之前等待的时间,毫秒级 |
清单 10
<script type="text/javascript" src="dojo/dojo.js" djConfig="parseOnLoad: true"></script> <script type="text/javascript"> dojo.require("dojo.parser"); dojo.require("dijit.Tooltip"); var prTooltip = new dijit.Tooltip( {label:'This tooltip is instantiated in programmatically way',connectId:'programmatically'}, document.createElement('div')); function showTooltip(labelText, nodeId, position){ dijit.showTooltip(labelText, dojo.byId(nodeId),position); } function hideTooltip(nodeId){ dijit.showTooltip(dojo.byId(nodeId)); } </script> <div dojoType="dijit.Tooltip" connectId="declaratively" label="This tooltip is instantiated in declaratively way"> </div> <div id="declaratively">Declaratively</div> <br /> <div id="programmatically">Programmatically</div> <br /> <div id="callMethods" onmouseOver="javascript:showTooltip('This tooltip is instantiated in calling method way','callMethods',['after','before']);" onmouseOut="javascript:hideTooltip('callMethods');">Calling Methods</div>
在清单 10 中分别应用了三种实例化的方法。
Declaratively方式实例化 Tooltip 时除了要声明 dojoType 为 dijit.Tooltip,还要设置另外两个特殊的属性:connectId 和 label 。如表 4 所示,属性 connectId 的值是要关联 Tooltip 的控件 Id,属性 label 的值为提示信息的内容。
Programmatically方式实例化 Tooltip 如var prTooltip = new dijit.Tooltip( {label:'programmatically',connectId:'programmatically'}, document.createElement('div'));
第一个参数是 Tooltip 特殊的属性值:label,connected,positions 等;第二个参数是要实例化的控件,可以创建一个新的控件,也可以应用已存在的控件(用 dojo.byId(someId) 来定位要实例化为 Tooltip 的控件)。
有时候页面需要实例化的 Tooltip 非常多,曾经遇见过一个数据报表页面有五百多个链接要挂载 Tooltip !这时无论用
Declaratively方式还是Programmatically方式都会导致页面数据量猛增,达到 MB 级,在 web 应用中这是不可以接受的。在这种情况下选用第三种实例化的方式可以在很大程度上减少页面的大小。
结束语
在富客户端的时代,web 开发人员都在寻找或创造可以彰显个性的 UI 组件。为避免 web 开发人员重复造轮子,Dojo 提供了大量实用的 Widget 。开发人员所需要做的就是慷慨接受 Dojo 的这份大礼。本文介绍了各类 Widget 的基本使用方法,如果读者希望了解创建个性化的 Widget,Dijit 的主题等,请读下篇文章。
参考资料
学习
- 访问 Dojo 的官方站点,关于 Dojo 的最权威的站点。
- 掌握 Dojo 工具包:阅读本系列以前的文章。
- “教程:使用 Dojo 开发 HTML 小部件”(developerWorks,2006 年 12 月):您将学到使用 Dojo 开发 HTML 小部件的基础知识;包括如何引用一个图像、如何向 HTML 页面中添加事件处理程序以及如何处理复合小部件。
- “评论专栏: Scott Johnson:沉迷于 Dojo”(developerWorks,2008 年 4 月):Dojo 内部人员讨论 Dojo Toolkit 如此广受欢迎而成为必备下载工具的原因、其现状和未来。
- “基于 Dojo 的本地化开发”(developerWorks,2008 年 1 月):本文介绍了基于 Dojo 的本地化的实现,通过实例讲解了如何利用 Dojo 提供的本地化支持模块来实现软件的本地化。
- “提高基于 Dojo 的 Web 2.0 应用程序的性能”(developerWorks,2008 年 2 月):本文通过演示一些实用的技巧来提高 Dojo 的性能,帮助开发人员找出 Web 2.0 应用程序的性能瓶颈。
- “使用 Dojo 开发支持 Accessibility 的 Web 应用”(developerWorks,2008 年 5 月):帮助开发人员了解 Accessibility 的基本内容,掌握 Dojo 开发可访问性 Web 应用的基本技能。
- “使用 Dojo 国际化 Web 应用程序”(developerWorks,2008 年 8 月):通过本文获得有关如何使用 Dojo 这个重要特性的简短的指导。
- “用 Firebug 动态调试和优化应用程序”(developerWorks,2008 年 5 月):了解如何使用 Firefox 浏览器的免费开源扩展 Firebug,它提供了很多有用的开发特性和工具。
- wikipedia 上关于 XMLHttpRequest 对象的详细介绍。
- JSON 的官方网站,在这里可以找到最实用的关于 JSON 的第一手资料。
- wikipedia 上关于 REST 架构风格 的介绍。
- Ajax 资源中心:developerWorks 上所有有关 Ajax 的问题都可以在这里找到解答。
获得产品和技术
- 下载 最新版 Dojo 工具包。
讨论
- 访问 Dojo 发起人 Alex Russell 的博客:有很多有深度的文章。
原文来自:http://www.ibm.com/developerworks/cn/web/wa-lo-dojointro5/index.html