元素操作核心类Ext.Element
类Ext.Element对DOM元素的进行了封装,并对DOM加入了简单的操作,实现了各浏览器的兼容;该类的实例可以更容易的对DOM elements进行操作;类中的事件,即封装了浏览器的事件,也可以访问底层浏览器事件(Ext.EventObject{@}#browserEvent);该类操作DOM元素时,很多情况下会用到一些动画效果(可选的),要设置动画效果,一般是把方法中的最后一个参数设为true或配置动画参数,如下代码
- var el = Ext.get("my-div");
- el.setWidth(100, {
- duration: 1,//动画持续的时间(单位:秒)
- callback: function(){alert('动画结束');},
- scope: this
- });
- <div id='my-div'>
- 动画效果
- </div>
该类的实现同样也采用了匿名闭包自执行函数来实现,执行的结果把函数赋给了Ext.Element,同时扩展了Ext.Element的prototype。首先看Ext.Element的定义
- Ext.Element = function(element, forceNew){
- var dom = typeof element == "string" ?
- DOC.getElementById(element) : element,
- id;
- if(!dom) return null;
- id = dom.id;
- if(!forceNew && id && Ext.elCache[id]){ // element object already exists
- return Ext.elCache[id].el;
- }
- /**
- * The DOM element
- * @type HTMLElement
- */
- this.dom = dom;
- /**
- * The DOM element ID
- * @type String
- */
- this.id = id || Ext.id(dom);
- };
该类的实现采用了构造器方法,两个参数分别为
element:要封转的DOM元素或id,通常为HTMLElement
forceNew:(可选的) 构建函数默认会检查在Cache中是否已经有这个element实例。如果有,则返回缓存中的对象。如果设置为否,则跳过缓存的验查。
该构造器中初始了元素id(如果没有设置则调用Ext.id(dom))和dom,注意如果forceNew 没有传入或设为false先从缓存中找,找到返回该元素封装后的Element
return Ext.elCache[id].el;
我们知道这是在定义一个类,使用new操作符。如
var ele = new Ext.Element('head');
既然是new,里面怎么又return了呢?一般函数调用,有return时,构造器中都会定义一些字段。其实当构造器中return的是对象类型时是允许的,如果return的是基本类型,又使用new调用函数则会出现非预期的效果。对于这点说明详见:具名函数的四种调用方式 系列 。
利用构造器定义完该类后,代码定义了一些变量
- var DH = Ext.DomHelper,
- El = Ext.Element,
- EC = Ext.elCache;
Ext采用这种简单的设计模式:组合。Bruce Eckel在《Think in Java》中所言,避免滥用继承,多数情况下组合已足以。下面看原型prototype的实现
- set : function(o, useSet){
- var el = this.dom,
- attr,
- val,
- useSet = (useSet !== false) && !!el.setAttribute;
- for (attr in o) {
- if (o.hasOwnProperty(attr)) {
- val = o[attr];
- if (attr == 'style') {
- DH.applyStyles(el, val);
- } else if (attr == 'cls') {
- el.className = val;
- } else if (useSet) {
- el.setAttribute(attr, val);
- } else {
- el[attr] = val;
- }
- }
- }
- return this;
- },
该方法设置元素属性(attributes)(一个样式属性可以是字符串,对象或函数function)。传入的属性会优先设置style,其次是cls,然后是setAttribute,最后为直接设置元素属性,如table.width = '10px; '。其中参数useSet (optional) false表示用元素的setAttribute方法来设置属性值,关于setAttribute的使用以及不同浏览器的不兼容型,该文章 给出了详细的说明。
该方法内部使用for in来遍历对象,我们知道默认情况下 for in 不会去遍历原形链属性的 。
对象的hasOwnProperty方法对继承于原形链的属性是检测不到 的,即返回false。因此这里hasOwnProperty(attr) 这个判断完全是多余的。
在设置style调用了DH.applyStyles(el, val);该方法后续会讲到。
接下来看代码
- defaultUnit : "px",
CSS值的单位。如不指定则默认为px。
- is : function(simpleSelector){
- return Ext.DomQuery.is(this.dom, simpleSelector);
- },
该方法功能为:如果这个元素符合传入的简易选择符的条件就返回true,简易选择符形如div.some-class或span:first-child。实现详见Ext.DomQuery
- focus : function(defer, /* private */ dom) {
- var me = this,
- dom = dom || me.dom;
- try{
- if(Number(defer)){
- me.focus.defer(defer, null, [null, dom]);
- }else{
- dom.focus();
- }
- }catch(e){}
- return me;
- ,
方法focus使这个元素得到焦点。defer (optional)为延时执行focus的毫秒数。该方法忽略了任何已捕获的异常。与focus对应的方法为blur
- blur : function() {
- try{
- this.dom.blur();
- }catch(e){}
- return this;
- },
下面看方法getValue
- getValue : function(asNumber){
- var val = this.dom.value;
- return asNumber ? parseInt(val, 10) : val;
- },
返回元素的value属性值,如果asNumber为true,则转换为十进制数字
下面的几个方法是与事件相关的,主要调用Ext.EventManager来实现的,这里说明一下,通过这几个方法的定义,该类实现了元素对浏览器所有事件的支持。
addListener / removeListener / removeAllListeners / purgeAllListeners
下面看方法load
- load : function(url, params, cb){
- Ext.Ajax.request(Ext.apply({
- params: params,
- url: url.url || url,
- callback: cb,
- el: this.dom,
- indicatorText: url.indicatorText || ''
- }, Ext.isObject(url) ? url : {}));
- return this;
- },
运行该方法时,会调用请求,用返回的值赋给元素的innerHTML,该方法的实现参见Ext.Ajax.request
- isBorderBox : function(){
- return Ext.isBorderBox || Ext.isForcedBorderBox || noBoxAdjust[(this.dom.tagName || "").toLowerCase()];
- },
该方法测试不同的CSS规则/浏览器以确定该元素是否使用Border Box。IE并且是标准模式则返回Ext.isBorderBox,如果是非IE并且强制设置border-box model则返回1,否则标准模式中对于具有border的元素如select会返回1,怪异模式中对于input,select,textarea元素返回1,同时利用代码noBoxAdjust['button'] = 1,button也会返回1。
- remove : function(){
- var me = this,
- dom = me.dom;
- if (dom) {
- delete me.dom;
- Ext.removeNode(dom);
- }
- },
从DOM里面移除当前元素,并删除缓存以及该element注册的事件(参见Ext.removeNode)。
看以下代码
- hover : function(overFn, outFn, scope, options){
- var me = this;
- me.on('mouseenter', overFn, scope || me.dom, options);
- me.on('mouseleave', outFn, scope || me.dom, options);
- return me;
- },
该方法设置元素事件句柄,当鼠标进入和离开该元素时调用传入的事件函数,方法中调用me.on,前面已讲过该方法的使用。方法中用mouseenter和mouseleave,而没有用mouseover和mouseout,好处前面已提到,ExtJs已经扩展实现了这两个事件不同浏览器之间的兼容。
- contains : function(el){
- return !el ? false : Ext.lib.Dom.isAncestor(this.dom, el.dom ? el.dom : el);
- },
该方法用来判断当前元素是否是传入元素的父辈级元素(ancestor),是返回true。
下面看代码
- getAttributeNS : function(ns, name){
- return this.getAttribute(name, ns);
- },
- etAttribute : Ext.isIE ? function(name, ns){
- var d = this.dom,
- type = typeof d[ns + ":" + name];
- if(['undefined', 'unknown'].indexOf(type) == -1){
- return d[ns + ":" + name];
- }
- return d[name];
- } : function(name, ns){
- var d = this.dom;
- return d.getAttributeNS(ns, name) || d.getAttribute(ns + ":" + name) || d.getAttribute(name) || d[name];
- },
这两个方法功能为返回DOM节点中的某个元素属性的值。当有命名空间时,应传入ns参数,注意IE和非IE的实现不一样。IE中对undefined,unknow类型做了处理,其它浏览器则是标准的实现。关于该方法带命名空间的使用下面给出一个简单的例子
- <div id='attDemo' ext:url='自定义属性测试结果' style="width :200px;">带命名空间的自定义属性</div>
- var attDemo = Ext.get('attDemo');
- var att = attDemo.getAttribute('url','ext');
- alert(att);
最后看方法
- update : function(html) {
- if (this.dom) {
- this.dom.innerHTML = html;
- }
- return this;
- }
更新该元素的innerHTML
下面看Ext.Element的静态方法
- var ep = El.prototype;
- El.addMethods = function(o){
- Ext.apply(ep, o);
- };
该方法内部调用Ext.apply,把o中的所有属性克隆到ep上
下面看重要方法get
- El.get = function(el){
- var ex,
- elm,
- id;
- if(!el){ return null; }
- if (typeof el == "string") { // element id
- if (!(elm = DOC.getElementById(el))) {
- return null;
- }
- if (EC[el] && EC[el].el) {
- ex = EC[el].el;
- ex.dom = elm;
- } else {
- ex = El.addToCache(new El(elm));
- }
- return ex;
- } else if (el.tagName) { // dom element
- if(!(id = el.id)){
- id = Ext.id(el);
- }
- if (EC[id] && EC[id].el) {
- ex = EC[id].el;
- ex.dom = el;
- } else {
- ex = El.addToCache(new El(el));
- }
- return ex;
- } else if (el instanceof El) {
- if(el != docEl){
- // refresh dom element in case no longer valid,
- // catch case where it hasn't been appended
- // If an el instance is passed, don't pass to getElementById without some kind of id
- if (Ext.isIE && (el.id == undefined || el.id == '')) {
- el.dom = el.dom;
- } else {
- el.dom = DOC.getElementById(el.id) || el.dom;
- }
- }
- return el;
- } else if(el.isComposite) {
- return el;
- } else if(Ext.isArray(el)) {
- return El.select(el);
- } else if(el == DOC) {
- // create a bogus element object representing the document object
- if(!docEl){
- var f = function(){};
- f.prototype = El.prototype;
- docEl = new f();
- docEl.dom = DOC;
- }
- return docEl;
- }
- return null;
- };
该方法是一静态方法,他的功能是获取元素对象。get方法不会获取组件对象Ext.Component Component,而只是对DOM元素进行Ext.Element封装,要想获取组件,请调用方法Ext.ComponentMgr.get。Ext.get是该方法的别名。对于传入的参数el可分为以下几种情况
1、传入参数是dom的id:首先利用DOC.getElementById(el)判断该dom是否存在,存在的话先从缓存中取,不存在的话实例化该对象并压入到缓存中
2、传入参数是HTMLElement对象:首先判断该dom是否有id值(没有则创建),对于该种情况也是先从缓存中取,与1一样
3、传入参数是Ext.Element,即已经封装好的对象:如果不是docEl,重新给dom属性赋值后直接返回
4、传入参数是Ext.CompositeElementLite:直接返回
Ext.CompositeElementLite 在后续会介绍,这个类用来操作批量的Ext.Element
5、传入参数是数组的话:调用El.select(el)返回,后续会介绍
6、传入参数是document:
如果docEl不存在则创建。因为document是唯一的,因此该对象只创建一次。后续直接返回即可。可以看到docEl与普通Ext.Element不同之处在于其dom属性的不同。
这几种情况如果传入的元素不存在则返回null。该方法调用了以下方法
- El.addToCache = function(el, id){
- id = id || el.id;
- EC[id] = {
- el: el,
- data: {},
- events: {}
- };
- return el;
- };
该方法,会在Ext缓存中以id为key存放到Ext.elCache中,存放的对象有三个属性,分别为Ext.Element,存储数据(El.data用到)、Ext封装的事件(见Ext.EventManager方法addListener),events保存了以事件名为属性的事件信息,包括调用函数,作用域等。
接下来是一个垃圾回收函数
- function garbageCollect(){
- if(!Ext.enableGarbageCollector){
- clearInterval(El.collectorThreadId);
- } else {
- var eid,
- el,
- d,
- o;
- for(eid in EC){
- o = EC[eid];
- if(o.skipGC){
- continue;
- }
- el = o.el;
- d = el.dom;
- if(!d || !d.parentNode || (!d.offsetParent && !DOC.getElementById(eid))){
- if(Ext.enableListenerCollection){
- Ext.EventManager.removeAll(d);
- }
- delete EC[eid];
- }
- }
- // Cleanup IE Object leaks
- if (Ext.isIE) {
- var t = {};
- for (eid in EC) {
- t[eid] = EC[eid];
- }
- EC = Ext.elCache = t;
- }
- }
- }
- El.collectorThreadId = setInterval(garbageCollect, 30000);
可以看到,该函数每隔30秒会进行一次垃圾回收。即遍历Ext.elCache,取Ext元素的dom属性,如果存在以下条件则清空(包括其上的所有事件handler)
- dom不存在
- dom的父元素不存在
- 页面上获取不到该dom元素
用户也可以取消垃圾回收,只要把参数Ext.enableGarbageCollector设为false即可。该方法的思想也可以用到其他函数或类中
接下来是Ext.Element.fly方法,他的别名是El.fly
- var flyFn = function(){};
- flyFn.prototype = El.prototype;
- // dom is optional
- El.Flyweight = function(dom){
- this.dom = dom;
- };
- El.Flyweight.prototype = new flyFn();
- El.Flyweight.prototype.isFlyweight = true;
- El._flyweights = {};
- El.fly = function(el, named){
- var ret = null;
- named = named || '_global';
- if (el = Ext.getDom(el)) {
- (El._flyweights[named] = El._flyweights[named] || new El.Flyweight()).dom = el;
- ret = El._flyweights[named];
- }
- return ret;
- };
首先利用javascript的简单继承定义flyFn,该类继承Ext.Element,然后添加El.Flyweight类,同时该类的原型是flyFn的实例对象,并增加了属性isFlyweight,来标示对象是否为Ext.Element.Flyweight类型对象。而El._flyweights为暂时对象。每次调用Ext.fly(El.fly)方法时,都会把El.Flyweight对象缓存到El._flyweights的nameed中,并且将覆盖之前的(除非每次所传的第二个参数named不同),而El.Flyweight中dom保存了当前元素的dom。
Ext.fly不同于Ext.get。Ext.fly采用享元模式(Flyweight),它通过颗粒化将属性分为内部及外部以达到共享从而节省内存。享元模式的实现通常需用到工厂模式。