Widget 类包含什么?
- widget类的结构和职能
- widget类基本的属性
- 渲染方法
- 渐进增强
- 标签结构
- class名和CSS
- 默认UI事件
类的结构和职责
Widget类的结构和职责
Widget类是一个用于创建widgets的基础类。 Widget类可以实例化,但是一般都是用它作为基础类,扩展创建widgets,这些通过扩展创建的widgets上有特定的用户交互模式。
Widget类扩展Base类。因此,它像Base类一样提供Attribute、Event.provider、Plugin.host功能。除了这些功能以外,widget类还增加了以下核心功能。
基本属性
Widget类引入一组属性,每一个Widget类的实例都能使用这些属性:boundingBox、contentBox、width、height、visible、focused、disabled。
渲染阶段
Widget类在Base类的init方法和destroy方法的基础上增加了渲染方法(和事件)。
抽象渲染方法
Widget类定义抽象的方法:renderUI、bindUI、syncUI,为widgets实例的渲染提供统一的入口。
一致的渐进增强
Widget类在初始化的过程中,为渐进增强提供统一的入口。它还提供一个机制,来避免渐进增强HTML代码无样式闪动。
字符串本地化
Widget的strings属性提供了字符串本地化支持。当和internationalization功能一起使用时,可以把需要本地化的字符串与核心代码分离。
基本属性
Widget类创建了一系列属性,这些属性在所有的widget中都能使用,以下是详细描述:
属性 | 描述 |
boundingBox | widget的外层节点。用以定位和调整大小。还可以用作皮肤HTML代码的容器 |
contentBox | boundingBox的子节点,包含widget的所有内容。一般这是体现widget外观感觉的节点 |
srcNode | 页面上的一个节点。在创建widget时,如果需要渐进增强地使用页面中的标签代码,开发者需要提供这个节点 |
tabIndex | 应用于boundingBox的tabIndex。 |
focused | 一个标识。标明widget当前是否是获得焦点。widget类为boundingBox标记一个"focused"class名。除此之外,获得焦点的其他操作都是在相应的widget中实现。 |
disabled | 标明widget当前是否可用。widget类为boundingBox增加一个“disabled”class名。除此之外,disabled的其他操作都是在相应的widget中实现。 |
visible | 一个标识。标明widget当前是否可见。Widget类为boundingBox增加一个“hidden”class名。除此以外,隐藏的实现在相应的widget的CSS中实现(可以通过visibility、display、或者超出屏幕范围的定位来实现)。 |
height | 包含单位的字符串。或者一个数字。代表widget的高度。如果是数字,则使用默认单位。默认单位由DEF_UNIT静态属性定义。高度应用在boundBox上。 |
width | 包含单位的字符串。或者一个数字。代表widget的宽度。如果是数字,则使用默认单位。默认单位由DEF_UNIT静态属性定义。宽度应用在boundBox上。 |
stings | 用来标识widget UI的字符串。 |
渲染方法
Widget类在Base类的init阶段和destroy阶段中加入渲染阶段。渲染方法确定widget铺设UI(通过往DOM中增加元素或者修改元素)的时间点。然后设置监听器来激活该UI。有了这个渲染阶段,widgets类可以把它的状态及相应的逻辑和UI展示分离开来。这样的分离是为了让widgets在安全地修改它的状态以后,再把这些修改体现到DOM元素中。
这样的一个分离的概念,把代码区分成两种方法,一种方法用以处理widget的状态和逻辑,一种用以处理DOM。
生命周期方法:init、destroy、render
和init、destroy一样,Widget类的render方法是final(不能重载)的,该render方法被代理给widget实现的renderer方法。
init(继承自Base)
init方法在类层级中循环执行(从基类到子类):
- 基于类的静态属性ATTRS,为每个类配置属性。
- 然后执行该类的initializer方法。
init方法会触发init事件,阻止这个事件可以停止初始化过程。
destroy(继承自Base)
调用类层级中的destructor方法,(从子类到基类)。
destroy方法触发destroy事件,阻止destroy事件可以停止销毁(destroy)过程。
render
调用Widget实例的renderer方法。与initializer和destructor方法不同,这个方法不会在Widget类层级中自动循环。
render方法接收一个parentNode参数,这个参数用来指定页面中的某个元素,在渲染完成后,该widget会被-插到这个元素中。如果contentBox或boundingBox指向的元素不在页面上,而且parentNode未指定任何元素,widget会被-插入页面成为body的第一个子元素。
render方法会触发render事件,阻止这个事件可以停止渲染(render)过程。
Widget类的渲染方法
Widget类提供renderer方法的实现,对于大部分简单的widget来说,不需要覆盖这个实现。renderer方法的实现如下:
renderer: function() { this.renderUI(); this.bindUI(); this.syncUI(); }
renderUI,bindUI,syncUI都是抽象方法。用以为widget确立统一的开发模式。这些抽象方法扮演以下角色:
renderUI方法
该方法的职责是往页面中创建增加widget需要的HTML节点(或者是改变页面中现有的HTML节点)。通常这是widget对DOM的第一次修改。
bindUI方法
该方法的职责是添加事件监听器,将UI的状态和widget的状态关联起来。这些事件监听器一般监听属性的change 事件,响应属性值的变化,改变UI的状态。该方法还负责添加DOM事件监听器,把用户交互和widget的API关联起来。
syncUI方法
该方法的职责是在渲染的过程中,基于widget当前的状态设置UI的初始状态。
渐进增强
Widget类为需要渐进增强支持的widget提供了统一入口。这个入口以HTML_PARSER静态属性的形式存于每一个widget中。
HTML_PARSER是一个静态属性,该静态属性中定义了一系列选择器和函数,这些选择器和函数的职责是:
a) 从现有的DOM元素中为widget解析内容;
b) 在初始化过程中从attribute配置获取值,用于初始化。
MyWidget.HTML_PARSER = { // Set attributes which are Node references // using selector syntax (uses node.one()) titleNode: "span.yui3-title", // Set attributes which are multiple Node references // using selector syntax (uses node.all()) itemNodes: ["li.yui3-listitem"], // Set attributes using a parse function. Execution context // is set to the widget instance label: function(srcNode) { return srcNode.one("input.yui3-title").get("innerHTML"); }, xValue: function(srcNode) { return srcNode.one("input.yui3-slider-xvalue").get("value"); }, yValue: function(srcNode) { return srcNode.one("input.yui3-slider-yvalue").get("value"); } };
隐藏渐进增强HTML代码
Widget类不仅通过HTML_PARSER提供统一的渐进增强解决方案入口,还提供了在初始化过程中隐藏渐进增强内容的CSS接口。
- 如果JavaScript没有启用,widget的内容应该是可见的,可用的。
- 如果JavaScript启用了,widget的HTML代码应该尽早被隐藏起来,避免在JavaScript和CSS组件被添加到页面后,渲染widget时出现无样式内容闪动的情况。
- widget只有在完全渲染完成后才显示。
为了达到这个目的:
- YUI在<HTML>元素上添加一个class名 “yui3-js-enabled”;
- 然后,开发者可以在widget的内容上添加class名 “yui3-widget-loading”,代表加载中状态;
这个class名可以和“yui3-js-enabled”一起使用,在JavaScript正在加载时隐藏widget的内容.
.yui3-js-enabled .yui3-widget-loading { display: none; } .yui3-js-enabled .yui3-overlay-loading { /* Hide overlay markup offscreen */ position:absolute; top:-1000em; left:-1000em; }
-
从上面的例子中可以看到,默认提供对所有widget和某个特定widget的支持(“yui3-widget-loading” and “yui3-tabview-loading”)。
- 一旦widget渲染完成后,widget的renderer方法会把bounding box和content box上的“loading”class名去掉,将功能完备的 widget展现出来。
已渲染的HTML标签
Widget类通过boundingBox、contentBox属性建立统一的HTML标签格式。
大部分的widgets会有由bounding box包着一个content box。两个盒子默认都是DIV。可以通过覆盖BOUNDING_TEMPLATE and CONTENT_TEMPLATE原型属性来修改。如果widget不需要两个嵌套盒子这样的结构,开发者也可以把CONTENT_TEMPLATE设置成null。这样,boundingBox和contentBox属性都指向相同的HTML节点。
如果在构造函数中未提供这两个节点,widget会创建这两个节点,然后再render方法被调用的时候把这两个节点添加到页面中。如果render方法调用时,render方法的参数指向了某个节点,widget的boundingBox会被-插到这个节点中。
boundingBox
bounding box是widget最外层的元素,bounding box的目的是实现功能,而不是实现视觉效果。
- 一个用作标识的class名被添加到bounding box上。class 名的默认格式是“yui3-[widgetname]”。比如:Slider的bounding box的class名为“yui3-slider”。
- 另外, widget类层级中所有的class名也被用以标记bounding box。比如:对于继承于Slider的MultiThumbSlider类,在bounding box上会标记“yui3-widget”、“yui3-slider”和“yui3-multithumbslider”class名。(这是唯一用widget类层级上的所有类的class名来标记bounding box的地方。)
- 用以管理wideget实例状态的class名也是应用在bounding box上。常用格式如下:“yui3-[widgetname]-[state]”。比如:“yui3-slider-hidden”、“yui3-slider-disabled”。
- 如果设定的话,widget的宽和高将应用于bounding box,用于定位的top/left(xy)定位值也会应用于bounding box。
- bounding box不应该有视觉属性(比如:border、padding)。
bounding box可以有会影响widget文档流的CSS定义。比如盒子类型(“display:inline”, “display:inline-block”, “display:block”)和定位模式(”position:absolute”, “position:relative”)。
contentBox
content box是bounding box的子节点。widget将构成核心的UI元素添加到content box中。
- content box也有用作标识的class名。默认的格式是:“yui3-[widgetname]-content”。如“yui3-slider-content”。
- widget的视觉样式应用于content box(如:borders, padding, background-color,等)。
- 为了实现渐进增强,开发者在页面上提供content box中的内容,并将这内容当做contentBox传入构造器中。bounding obx在widget初始化时被动态添加。这避免页面上存在多余的代码,保持页面的语义化。
widget代码标签图示
下图展示widget的HTML标签和class名:
为什么使用嵌套的两个盒子
两个嵌套盒子为CSS应用、装饰元素支持、bounding box宽高控制提供方便。下面是详细说明:
- 嵌套的结构让bounding box作为装饰元素的容器。比如:用以实现圆角、阴影、衬垫等元素。这些元素作为content box的兄弟元素,当他们和content box有同一个父元素时,对他们的定位、大小的设定会更方便。
- 另外,鉴于所有widget有统一的结构,我们可以编写通用于所有widegt的插件,用以提供装饰支持。
- 一个没有border和padding的盒子(bounding box),能让盒子在不同浏览器的盒模型下,有一致的宽高应用。
Class 名 和 CSS
为了在所有的widget中提供统一的class名,Widget类提供两个方法,这两个方法打包了ClassNameManager功能,基于widget类的NAME属性生成class名。
实例方法:getClassName(arg1, arg2, arg3 …)
此方法可以用于生成class名, class名是由应用的“前缀配置”和widget的名字(NAME属性)组成。比如:
// A method on a Spinner widget instance, // with Spinner.NAME = "spinner"; renderSpinnerButton: function() { // generates the class name "yui3-spinner-increment-major" var btnClassName = this.getClassName("increment", "major"); }
注意:当需要在一个静态的执行环境下创建class名时,ClassNameManager.getClassName(arg1, arg2, arg3 …)方法可以代替上述方法,应用的NAME属性通过第一个参数传入。比如:
// Generates the class name "yui3-spinner-button" Spinner.BUTTON_TEMPLATE = Y.ClassNameManager.getClassName(Spinner.NAME, "button");
静态方法:Widget.getClassName(arg1, arg2, arg3 ….)
这个静态方法(是Widget类的静态方法)可以用来生成前缀为“yui3-widget”的class名。这对于插件来说相当有用,因为不管该插件被-插在哪个实例上,它都需要一个固定的class名。如:
// 生成class名"yui3-widget-shim" Widget.getClassName("shim");
CSS的应用
最佳实践是,避免用像”visible”, “disabled”, “focused”这样的style属性控制widget的状态。而是使用class名来反映widget的状态:
Attribute/State | CSS Class Applied |
---|---|
visible | yui3-[widgetname]-hidden |
disabled | yui3-[widgetname]-disabled |
focused | yui3-[widgetname]-focused |
在上述的定义中,“[widgetname]”代表widget的名字(比如:“menu”、“overlay”、“slider”)。状态标识符与widget名组合,让每个widget都能自定义操作显示状态的方式。而且,在IE6下也能正常工作。
/* Hide by positioning off-screen */
.yui3-menu-hidden {
top:-10000em;
left:-10000em;
}
/* Hide by flipping visibility */
.yui3-overlay-hidden {
visibility:hidden;
}
/* Hide by flipping display */
.yui3-slider-hidden {
display:none;
}
然而,如果想使用IE6也兼容的格式,就需要为每个widget的每个状态定义相应的css名。在以上基于widget状态的CSS规则中,每个widget都需要定义“yui3-[widgetname]-hidden”规则来实现对可见性的控制。是否要提供另两种对状态的支持,取决于这个widget是否需要为“disabled”和“focus”两种状态提供特殊的UI控制。
默认UI事件
Widget类为每一个DOM事件(click,mouseover)发布和触发自定义事件。这些事件在boundingBox内被触发。和Widget类的其他自定义事件一样,这些事件以widget名字作为前缀(“menuItem:click”),事件监听器的默认上下文对象是触发事件的widget,而不是触发该DOM事件的节点。这些事件使得监听UI事件成为widget API的一部分,而不需要考虑构成widgetUI的DOM元素。
因为很多Widget实例都会发布和触发这些事件,Widget类默认做以下事情,以保证这些事件的触发机制在不同的widget实现中都是一致的。
- 开发者不需要为监听器明确地发布某个特定的UI事件。
基于性能考虑,这些事件在被监听时才会被创建,这些事件的触发是由一个代理的DOM事件监听器完成的。 - 为了精确地控制事件的某些方面,开发者仍然可以选择发布任意一个UI事件。
最有可能的用例是为事件提供一个默认的处理器。比如:开发者可能要为Memu widget发布一个“click”事件,目的是为了提供默认的点击行为/功能支持。 - widget发布的DOM事件是由UI_EVENTS原型属性定义的。
该属性的默认值是Node.DOM_EVENTS。开发者可以通过这个属性减少/增加自动发布和触发的事件。
开发自己的Widget
要开发自己的widget,需要创建一个扩展Widget类的类,然后实现下图显示的属性和方法。
先定义构成widget的属性(ATTRS静态属性)。这些属性定义widget公开的状态和API。然后根据不同职责实现initializer、destructor、renderUI、bindUI、syncUI方法,以及属性状态修改的处理函数和API。
Extending The Widget Class这个例子教你使用widget结构模板一步一步实现一个Spinner widget。该例子和模板为开发者提供了一个很好的开始。
另外,widget结构模板可以从此结构模板文件获得,可以在该文件的基础上开发widget。
插件和扩展
除了可以基于任意Widget类创建子类,实现自定义widget外,YUI3还提供两个代码重用机制。这两个机制可以用来打包可重用的widget功能的代码,不需要将这些功能绑定到一个静态的类层级中。这两个代码重用机制就是插件(plugins)和扩展(extensions),可以从Base类获得这两种机制的支持。
一般来说,开发人员可以通过创建Widget类的子类或者创建用Y.extend方法获取Widget类功能的类的子类来实践widget的功能和特性。
这些特性和功能应被打包成扩展或者插件,以便在多个类(extension情况下)或多个实例(plugin情况下)中都能通用。
使用插件还是扩展
我们总是会遇到关于功能和特性是以插件的形式存在还是以扩展的形式存在的问题。widget开发者需要根据widget的用例来考虑widget的设计。
正如上述所说,插件和扩展提供一个创建小模块功能的机制,这些功能可以添加到widget的核心实现中。他们的差异如下:
扩展——一个类级别的概念
- 扩展提供的功能在类级别中使用;
- 扩展被用于创建共享扩展功能的多个新widget类;
- 如果功能对于类来说是必须的,它就应该存在于扩展中;
有些功能被添加到一个类中,但是这些功能是按照“便于在其他的类中重用”的方式来实现的。 - WidgetParent, WidgetChild 和 WidgetPosition, WidgetStack都是好的扩展的例子。
Tree widget类总会需要父/子支持,而Menu widget类也需要。我们想在两个类中重用这个父/子支持,而且这两个类不必继承自同一个基类。另外,父/子功能对于这两个类来说都是必须的。 - 扩展通过静态方法Base.build(或者基于Base.build方法的Base.create或Base.mix方法)应用于类。
插件——一个实例级别的概念
- 插件提供能在实例级别中应用的特性。
- 开发者用插件将功能应用于widget的某个实例。
- 如果它的功能不是类所有的实例都必须的话,该功能就应以插件的形式存在。
在页面上10个widget实例中,该功能只需要应用于其中一个实例,这个功能就应以插件的形式存在。 - Animation 和IO都是好的插件例子。
你不会把Animation和IO支持写到类中(可能需要MyAnimatedWidget, MyIOEnabledWidget, MyAnimatedAndIOEnabledWidget and MyWidget 等类)。这个功能不是必须的功能,可以以插件的形式插-入到MyWidget类的某些需要这些功能的实例中。 - 插件通过实例的plug方法插-入到实例中。
Widget扩展
当你开始用YUI3开发widget时,有些打包好的扩展可以用于向你的自定义widget类添加功能。使用Base.build (或者 Base.create, Base.mix)方法。
扩展 | 功能 |
---|---|
widget-position | 向类添加XY定位支持 |
widget-position-align | 向类添加XY对齐支持 |
widget-position-constrain | 向类添加有约束的XY定位支持 |
widget-stack | 向类添加层级(zIndex)支持 |
widget-stdmod | 向类添加标准模块(header, body, footer)支持 |
widget-parent | 添加让widget包含、管理、选择子widget的支持 |
widget-child | 添加让widget可以被包含在一个父widget中的支持 |
widget-parent和widget-child插件提供的功能,让开发者能创建嵌套的widget,该功能值得详细介绍一下:
widget-parent
添加让widget包含、管理、选择子widget的支持:
- 它提供一个统一的API,用以创建父/子关系:
-
parent.add(child, n); parent.remove(n);
- 这可以是单一层级关系(如:Tabs in a TabList, 或 Buttons in a Toolbar),或者嵌套的多层级关系(如:Menus and MenuItems, 或Trees and TreeNodes)
- 父widget自动为他们的子widget设置事件目标,让你利用冒泡的自定义事件监听更高层级上的事件。
- 父widget在渲染的时候,会自动渲染子widget。
- 父widget扩展ArrayList API,提供针对子widget的完备的迭代、遍历方法。parent.each(function(child) {...});
parent.item(n); parent.indexOf(child); parent.size();
- 提供选择支持,包括non-binary选择(all, none, some)支持。
- 最后,它提供一个sugar层,通过用一个字面量格式的对象初始化子widget,来简化在创建过程中向父widget添加子widget的操作。var tabview = new Y.TabView({
children: [{ label: 'foo', content: '<p>foo content</p>' }, { label: 'bar', content: '<p>bar content</p>' }] });
widget-child
添加让widget能被父widget包含的功能。
widget-child和widget-parent一起使用,让你支持父/子层级结构。和父widget一样,子widget提供一个统一的API来和兄弟widget及父widget进行交互。如:child.next(), child.previous() and child.ancestor()。
以下是如何使用这些扩展的例子:
- 使用扩展: Building Custom Widget Classes
- Widget-Position, Widget-Stack:A Simple Tooltip Widget
- Widget-Parent, Widget-Child:A Heirarchical ListBox Widget
你还可以看看这些使用多个扩展创建的复杂widgets:
- Overlay
使用了 widget-position, widget-position-align, widget-position-constrain, widget-stack, widget-stdmod。 - TabView
使用了 widget-parent, widget-child。
widget 插件
YUI3库在发布时,带了几个widget插件,还有一些例子教你如何创建自己的插件:
- Widget Animation Plugin (api documentation)
- Widget (and Node) Drag/Drop Plugin (api documentation)
- Creating Widget Plugins (example)
- Creating An Overlay IO Plugin (example)
- Creating An Overlay Animation Plugin (example)
另外,YUI Gallery中也有不少插件的例子。如Modal Overlay Plugin和Widget IO Plugin。
“MyPlugin” 模板文件提供了创建自己插件的模板。