天天看点

google closure 笔记-UI Component

goo.ui.Component

Component主要提供了一个功能:定义了一个ui组件的生命周期

1,生命周期:

Component定义了一个ui组件的基本生命周期如下图所示:

创建一个组件有两种方法:render 和 decorate

区别在于:使用render方法,dom结构由rendeer方法负责创建(也就是用js来创建dom);decrote方法不会创建dom结构,而是在模板中已经输出了。

2,树形结构

三个对应的树形结构:Dom,Component,EventTarget。

Dom结构显然是一个树形结构,没什么可讲的,一般子组件的this.element_是父组件的this.element_的孩子

Component 组件应该也是一个树形的结构,和其依赖的dom结构应该是相同的。

Component内置了对子组件的操作,当在组件A中创建了一个子组件B时,要调用AddChild方法来注册这个孩子,这样,当父组件进行某些操作时,会子总调用子组件的相关方法,从下面的源码概要中可以看到,比如父组件enterDocument和exiteDocument时会同时调用所有子组件的相关方法(当然,因为创建销毁子组件时其实也会自动调用这些方法,所以父组件在调用时会先检查一下)。

component.js源码概要:

EventType: 预定义的组件事件模型。这里不提供对这些事件的支持,只能通过dispatch来触发。在其子类Control中提供了对这些事件的完整支持。

Error:

State:

indocument_/isInDocument()/:此组件是否已经添加到document中。

id/getId()/setid():获取/设置this.id_

dom_/getDomHelper(): goog.dom.DomHelper DomHelper解释见"DOM操作"

element_/getElement()/setElementInternal:此组件的根元素

model_/getModel()/setModel()://此组件的model_, model_存在的意义是存储此组件依赖的数据(MVC中的M)以及封装数据相关的逻辑。

parent/setParent()/getParent():

children{Array}://这里保存了所有的子组件

rightToLeft_/getter/setter:

getElementByClass()/getElementsByClass(); 包装后的goog.dom.getElement(s)ByClass(classname, this.element_),实际就是在this.element_中查找而已;

getHandler():不要直接用goog.events.listen来注册,而用getHandler().listener,因为getHandler.listen会自动将回调函数的this绑定为当前this。

createDom():默认创建一个空的div:this.element_ = this.dom_.createDom("div");

render(parnet): call render_(parent);

renderBefore(sibling):call render_(sibling.parentNode, sibling);

render_(parent,sibling):     //调用createdom来创建dom,然后追加到parent上(或者sibling之前)

     if this.inDocument_,throw error;

     if(!this.element_): this.createDom();创建domtree

     xxxx;插入this.element_;

     if(!thisparent_!!this.parent_.isInDocument()) this.enterDocument();

decorate(element)://

     if(this.inDocument_,throw error;

     this.decorateInternal();

     this.enterDocument();

decorateInternal(element)://默认 this.element_ = element;

enterDocument(): 

     this.inDocument = true;

     for each children:child.enterDocument(): //调用孩子的enterDocument方法

exitDocument():

      for each children:child.exitDocument(): 

     this.inDocument_ = false;

disposeInternal():

     释放所有引用

addChild()/addChildAt(index)/getChild(id)/getChildAt(index)/foreachChild(func,opt_obj):

如果自己写Component的子类:

一般情况下,render/renderBefore/decorate方法不需要重写,需要重写的是createDom,decorateInternal,enterDocument,exitDocument,

在构造函数中调用goog.ui.Component.call(this, opt_domHelper)方法,否则无法继承父类属性 会导致一些问题(如this.dom_等属性未定义的错误)。

domtree的创建分两部分:a,可以在模板中创建也可以在js中创建,b,只能在js中创建,在模板中创建非常困难(比如datepicker之类的需要计算出来的),a部分要放在createDom中或者模板中,b部分放在decorateInternal中。a部分在模板中和createDom中必须是完全一致的,因为他们最终都传给了decorateInternal来处理。因此在测试的时候,只需要测试render,如果render正常,则decorate必定正常,否则就说明模板中html代码写错了。因为我是这样想的,所以我认为任何同时支持render和decorate的组件,在createDom中都应该调用decorateInternal方法,但是在Component组件的设计中并没有这样做。

还有一个应该被强调的属性是model,对于一个简单的组件,可能不需要model,或者直接使用父组件的model。如果组件依赖一个数据结构(或数据逻辑),那么应该给他定义一个自己的Model,model中封装了所有此组件需要的数据以及对数据的操作。比如创建一个导航条组件,那么导航条中包好的链接应该放在一个Model中,还可能有额外的数据方法,比如更新数据,排序等,也应该由Model来完成,这个model提供接口给对应的组件来访问其中的数据。

Component类内置了对children的支持,当在一个组件中需要调用其他组件的时候,用addChild方法把子组件添加到children列表中,则在enterDocument等方法中会自动调用子组件的对应方法。

 goog.ui.Control

如果只看Component组件,无法感觉明显的MVC结果,但是结合Control和Renderer 以及Model,就是一个明显的MVC。

Control继承自Component类,Control是几乎所有ui组件的父类。提供了比Component更详细的有实际功能的大量方法(主要是提供了事件机制)。

简单地说,

一,Control类提供了对Component.EventType中定义的事件的支持,当条件满足时自动触发事件,其触发条件就是Component.State中定义的状态发生了改变。如active由false变为true,会触发ACTIVATE事件,反之会触发UNACTIVATE事件。

而在Component中只是定义了这些事件,并不能自动触发这些事件。

二,Control类提供了状态和class的映射关系,当组件处于某一状态时,会自动添加对应的class,退出状态后,自动去掉此class。

三,每一个Control都有对应的renderer,control不直接对dom进行操作,而是通过renderer中的对应方法来操作dom。记住renderer是单例模式的,所以render只是对dom进行操作,即不保存任何状态信息,也不保存dom结构,也不保存状态和classname的对应关系,这些都保存在control中,所以control中调用rendeer对应方法时,总是需要传这些参数过去。

control state:

每一个组件都处于处于一个状态,表示状态的变量的变化 就是 状态转换,状态的转换产生对应的事件(但是并不是所有的事件都是状态的转换产生的)。

state:

state transition:

transition events:

state type:

supported states:浏览器原生支持的state,有四种:DISABLED,HOVER,ACTIVE,FOCUSED

auto states:通过使用supported states来实现的 更高抽象的事件,比如用hover来实现hilight,unhilight状态

states with transition events:由状态的转换而产生的状态

Control默认支持的事件:ENTER,LEAVE,ACTION,SHOW,HIDE。

通过setDispatchTransitionEvents可以设置需要dispatch的transition event。

通过setAutoStates可以设置支持的auto states。

当创建一个control实例后,例如var btn=new goog.ui.Control("button");

默认只支持enter,leave,action,show,hide事件(即默认不会触发其它类型的事件)。

可以在render之前用setSupportedState()来设置supported state,不被支持的state 则无法触发依赖它的事件,例如:

setSupportedState(goog.ui.Component.State.FOCUSED,false)则依赖此state的事件 focus和blur无法被触发。

state和event的依赖关系见http://code.google.com/p/closure-library/wiki/IntroToControls下部的表格,其中有一处错误:

action依赖active而不是none。

通过setAutoStates()可以设置支持的auto states。默认是全部支持。

通过setDispatchTransitionEvents可以设置自动dispatch的transition event,包括出了默认事件的所有事件,如highlight,focus等。

Control Display Concepts:

Renderer:管理Control的display的对象。当control state发生任何变化时,renderer都会被通知,然后renderer会改变control的classes和ARIA states。 因为render只负责创建dom,而没有保存任何状态信息,所以这个是单子模式。

Control Dom Element: this.element_, 根元素

Content: 内容,是text或者domtree

Decorator Registry:包含一个map,其中记录了每一个classname对应的方法,因此通过在html中设置classes就可以指定其对应的Control class

State Styles:和状态相关的classname,比如一个active的element,应该有一个classname:goog-control-active,完整的对应关系如下表:

Classname Added When: Interpreted in Static HTML?
goog-control Element is created or decorated By getDecorator()
goog-control-rtl enterDocument() is executed, if the element has a 'direction' style attribute of 'rtl' indicating a right-to-left language No
goog-control-disabled Element becomes disabled When element is decorated, to set state.
goog-control-hover Element is highlighted When element is decorated, to set state
goog-control-active Element becomes active When element is decorated, to set state
goog-control-selected Element is selected When element is decorated, to set state
goog-control-checked Element is checked When element is decorated, to set state
goog-control-focused Element gets focus When element is decorated, to set state
goog-control-open Element is opened When element is decorated, to set state 

总结:

goog.ui.Component.state 包含:active,checked,disabled,focused,hover,opened,selected,all

相应的有is/setActive(),is/setChecked()….等对应的方法。

还有如下方法:

setRenderer(renderer),add/removeClassName(),setContent,setCaption(),setVisible(),setState,setSupportedState,setAutoStates,setDispatchTransitionEvents.

getContentElement:return this.element_;

getHandler,getRenderer,getState,isVisible….

control.js 源码解析:

注:其中的renderer是指默认的controlrenderer。

构造函数:function(content, opt_renderer, opt_domHelper):三个参数:dom, 对应的renderer(如果没有,则调用goog.ui.registry.getDefaultRenderer(this.constructor)来获取默认renderer,domhelper。

goog.ui.Control.registerDecorator = goog.ui.registry.setDecoratorByClassName;goog.ui.registry提供了一个全局的设置:可以设置classname和component 以及 renderer和component的对应关系(所谓的decorater就是control的构造函数)。

goog.ui.Control.getDecorator,同理。

goog.ui.Control.decorate ,调用goog.ui.registry,根据element的classname来取得对应的decorater,然后调用decorater.decorate(element),最终也就是调用了自己的decorateInternal;

goog.ui.Control.prototype.supportedStates_,当前支持的state,注意是二进制位来保存的,所以可以用或运算

goog.ui.Control.prototype.autoStates_,会自动处理的state

goog.ui.Control.prototype.statesWithTransitionEvents_, 当状态转换时自动触发的事件,默认没有。

goog.ui.Control.prototype.keyHandler_ / getter/ setter:

goog.ui.Control.prototype.renderer_ / getter /setter,

goog.ui.Control.prototype.extraClassNames_ / addClassName / removeClassName / enableClassName,设置到root element上的额外class

goog.ui.Control.prototype.isHandleMouseEvents / setHandleMouseEvent(enable);监听鼠标事件

goog.ui.Control.prototype.createDom:调用renderer的createDom(创建一个div,并设置对应的class(主要是extraClassName),然后将control的contentappend到这个div中)。 然后做一些初始化操作:allowTextSelection, setVisible。

goog.ui.Control.prototype.decorateInternal:调用render.decorate(), 其做的操作是:将dom结构加入到外层的root上,然后遍历rootelement 的classname,如果找到已经存在的class对应着state,那么就初始化这个state(比如,找到了active对应的class,则将初始状态置为active),然后把extraclassname加到root上。同理也做一些初始化:allowTextSelection, setVisible。

 goog.ui.Control.prototype.enterDocument:调用renderer.initializeDom(), 这个方法执行一些需要在dom加载完毕后进行的操作(默认是设置righttolef和focusable)。 然后监听键盘鼠标事件。

goog.ui.Control.prototype.exitDocument:释放事件监听。

goog.ui.Control.prototype.disposeInternal:删除所有引用元素。

一系列状态的相关的函数:setHilite/getHilite/isChecked/setChecked等,都是调用hasState和setState来实现的

hasState:用位操作来判断是否有当前状态

还有一系列事件处理函数:hanleMouseDown/handleDblClick/handleFocus等,最终都是通过调用上述的状态函数来实现的。

setState:调用renderer.setState来对dom进行操作(主要是添加/删除对应的class),然后设置this.state_将这个状态保存下来。

goog.ui.Control.prototype.isSupportedState/setSupportedState

controlrenderer.js 源码解析:

注:这里面很多方法都要传入control参数,因为renderer是单例模式,没有保存任何数据,所以总是要从control中取。

goog.ui.ControlRenderer.getCustomRenderer:只是设置一下classname,不知道什么作用。

goog.ui.ControlRenderer.CSS_CLASS:root 的默认class

goog.ui.ControlRenderer.prototype.createDom :创建一个div,应用class,写入content,(其中classname和content都是从Control中获得的)

goog.ui.ControlRenderer.prototype.enableClassName :就是add/remove classname。

goog.ui.ControlRenderer.prototype.decorate:参见control中decorateInternal的说明。

一堆的状态获取和设置函数:isFocusable/setFocusable, isVisible/setVisible等

goog.ui.ControlRenderer.prototype.setState(control, state, enable):获取对应状态的class,然后应用到root上

goog.ui.ControlRenderer.prototype.setContent (element, content) :这个content可以是字符串,数组,nodelist,做简单的处理后替换掉现有的content

goog.ui.Container

Container不是一个通用的容器,它只接收Control作为其子组件。

Container存在的意义是为了减少其子组件的EventListener数量,因为它会统一代理其内部的所有事件监听。

原理:

一旦一个子组件被添加到Container中,container会首先移除其所有的EventListener。

当一个Event发生时,Container通过getOwnerControl(event.target)来确定哪个组件应该捕获这个事件。

Container还可以通过监听特定的事件来获得子组件信息,比如通过监听goog.ui.Component.EventType.HIGHLIGHT就可以知道哪个组件处在高亮状态。

继续阅读