ExtJs源码分析与学习—ExtJs元素Element(二)

来源:互联网 发布:java注册zookeeper服务 编辑:程序博客网 时间:2024/06/05 11:52

元素操作核心类Ext.Element

 

      类Ext.Element对DOM元素的进行了封装,并对DOM加入了简单的操作,实现了各浏览器的兼容;该类的实例可以更容易的对DOM elements进行操作;类中的事件,即封装了浏览器的事件,也可以访问底层浏览器事件(Ext.EventObject{@}#browserEvent);该类操作DOM元素时,很多情况下会用到一些动画效果(可选的),要设置动画效果,一般是把方法中的最后一个参数设为true或配置动画参数,如下代码

 

Js代码  收藏代码
  1. var el = Ext.get("my-div");  
  2.     el.setWidth(100, {  
  3.         duration: 1,//动画持续的时间(单位:秒)  
  4.         callback: function(){alert('动画结束');},  
  5.         scope: this  
  6. });  
  7. <div id='my-div'>  
  8.      动画效果  
  9. </div>  

 

     该类的实现同样也采用了匿名闭包自执行函数来实现,执行的结果把函数赋给了Ext.Element,同时扩展了Ext.Element的prototype。首先看Ext.Element的定义

 

Js代码  收藏代码
  1. Ext.Element = function(element, forceNew){  
  2.     var dom = typeof element == "string" ?  
  3.               DOC.getElementById(element) : element,  
  4.         id;  
  5.   
  6.     if(!dom) return null;  
  7.   
  8.     id = dom.id;  
  9.   
  10.     if(!forceNew && id && Ext.elCache[id]){ // element object already exists  
  11.         return Ext.elCache[id].el;  
  12.     }  
  13.   
  14.     /** 
  15.      * The DOM element 
  16.      * @type HTMLElement 
  17.      */  
  18.     this.dom = dom;  
  19.   
  20.     /** 
  21.      * The DOM element ID 
  22.      * @type String 
  23.      */  
  24.     this.id = id || Ext.id(dom);  
  25. };  

 

      该类的实现采用了构造器方法,两个参数分别为
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调用函数则会出现非预期的效果。对于这点说明详见:具名函数的四种调用方式 系列 。

     利用构造器定义完该类后,代码定义了一些变量

 

Js代码  收藏代码
  1. var DH = Ext.DomHelper,  
  2.       El = Ext.Element,  
  3.       EC = Ext.elCache;  

 

       Ext采用这种简单的设计模式:组合。Bruce Eckel在《Think in Java》中所言,避免滥用继承,多数情况下组合已足以。下面看原型prototype的实现

 

Js代码  收藏代码
  1. set : function(o, useSet){  
  2.         var el = this.dom,  
  3.             attr,  
  4.             val,  
  5.             useSet = (useSet !== false) && !!el.setAttribute;  
  6.   
  7.         for (attr in o) {  
  8.             if (o.hasOwnProperty(attr)) {  
  9.                 val = o[attr];  
  10.                 if (attr == 'style') {  
  11.                     DH.applyStyles(el, val);  
  12.                 } else if (attr == 'cls') {  
  13.                     el.className = val;  
  14.                 } else if (useSet) {  
  15.                     el.setAttribute(attr, val);  
  16.                 } else {  
  17.                     el[attr] = val;  
  18.                 }  
  19.             }  
  20.         }  
  21.         return this;  
  22.     },  

     该方法设置元素属性(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);该方法后续会讲到。

      接下来看代码

 

Js代码  收藏代码
  1. defaultUnit : "px",  

     CSS值的单位。如不指定则默认为px。

 

Js代码  收藏代码
  1. is : function(simpleSelector){  
  2.     return Ext.DomQuery.is(this.dom, simpleSelector);  
  3. },  

      该方法功能为:如果这个元素符合传入的简易选择符的条件就返回true,简易选择符形如div.some-class或span:first-child。实现详见Ext.DomQuery

 

Js代码  收藏代码
  1. focus : function(defer, /* private */ dom) {  
  2.    var me = this,  
  3.        dom = dom || me.dom;  
  4.    try{  
  5.        if(Number(defer)){  
  6.            me.focus.defer(defer, null, [null, dom]);  
  7.        }else{  
  8.            dom.focus();  
  9.        }  
  10.    }catch(e){}  
  11.    return me;  
  12. ,  

 

      方法focus使这个元素得到焦点。defer (optional)为延时执行focus的毫秒数。该方法忽略了任何已捕获的异常。与focus对应的方法为blur

 

Js代码  收藏代码
  1. blur : function() {  
  2.         try{  
  3.             this.dom.blur();  
  4.         }catch(e){}  
  5.         return this;  
  6.     },  

下面看方法getValue

 

Js代码  收藏代码
  1. getValue : function(asNumber){  
  2.     var val = this.dom.value;  
  3.     return asNumber ? parseInt(val, 10) : val;  
  4. },  

      返回元素的value属性值,如果asNumber为true,则转换为十进制数字
      下面的几个方法是与事件相关的,主要调用Ext.EventManager来实现的,这里说明一下,通过这几个方法的定义,该类实现了元素对浏览器所有事件的支持。
addListener /  removeListener / removeAllListeners / purgeAllListeners 

     下面看方法load

 

Js代码  收藏代码
  1. load : function(url, params, cb){  
  2.      Ext.Ajax.request(Ext.apply({  
  3.          params: params,  
  4.          url: url.url || url,  
  5.          callback: cb,  
  6.          el: this.dom,  
  7.          indicatorText: url.indicatorText || ''  
  8.      }, Ext.isObject(url) ? url : {}));  
  9.      return this;  
  10.  },  

    运行该方法时,会调用请求,用返回的值赋给元素的innerHTML,该方法的实现参见Ext.Ajax.request

 

Js代码  收藏代码
  1. isBorderBox : function(){  
  2.      return Ext.isBorderBox || Ext.isForcedBorderBox || noBoxAdjust[(this.dom.tagName || "").toLowerCase()];  
  3.  },  

      该方法测试不同的CSS规则/浏览器以确定该元素是否使用Border Box。IE并且是标准模式则返回Ext.isBorderBox,如果是非IE并且强制设置border-box model则返回1,否则标准模式中对于具有border的元素如select会返回1,怪异模式中对于input,select,textarea元素返回1,同时利用代码noBoxAdjust['button'] = 1,button也会返回1。

 

Js代码  收藏代码
  1. remove : function(){  
  2.       var me = this,  
  3.           dom = me.dom;  
  4.   
  5.       if (dom) {  
  6.           delete me.dom;  
  7.           Ext.removeNode(dom);  
  8.       }  
  9.   },  

 

      从DOM里面移除当前元素,并删除缓存以及该element注册的事件(参见Ext.removeNode)。
看以下代码

 

Js代码  收藏代码
  1. hover : function(overFn, outFn, scope, options){  
  2.      var me = this;  
  3.      me.on('mouseenter', overFn, scope || me.dom, options);  
  4.      me.on('mouseleave', outFn, scope || me.dom, options);  
  5.      return me;  
  6.  },  

 

     该方法设置元素事件句柄,当鼠标进入和离开该元素时调用传入的事件函数,方法中调用me.on,前面已讲过该方法的使用。方法中用mouseenter和mouseleave,而没有用mouseover和mouseout,好处前面已提到,ExtJs已经扩展实现了这两个事件不同浏览器之间的兼容。

 

Js代码  收藏代码
  1. contains : function(el){  
  2.      return !el ? false : Ext.lib.Dom.isAncestor(this.dom, el.dom ? el.dom : el);  
  3.  },  

    该方法用来判断当前元素是否是传入元素的父辈级元素(ancestor),是返回true。

    下面看代码

 

Js代码  收藏代码
  1. getAttributeNS : function(ns, name){  
  2.     return this.getAttribute(name, ns);  
  3. },  
  4. etAttribute : Ext.isIE ? function(name, ns){  
  5.     var d = this.dom,  
  6.         type = typeof d[ns + ":" + name];  
  7.   
  8.     if(['undefined''unknown'].indexOf(type) == -1){  
  9.         return d[ns + ":" + name];  
  10.     }  
  11.     return d[name];  
  12. } : function(name, ns){  
  13.     var d = this.dom;  
  14.     return d.getAttributeNS(ns, name) || d.getAttribute(ns + ":" + name) || d.getAttribute(name) || d[name];  
  15. },  

      这两个方法功能为返回DOM节点中的某个元素属性的值。当有命名空间时,应传入ns参数,注意IE和非IE的实现不一样。IE中对undefined,unknow类型做了处理,其它浏览器则是标准的实现。关于该方法带命名空间的使用下面给出一个简单的例子

 

Js代码  收藏代码
  1. <div id='attDemo'  ext:url='自定义属性测试结果'  style="width :200px;">带命名空间的自定义属性</div>  
  2.   
  3. var attDemo = Ext.get('attDemo');  
  4. var att = attDemo.getAttribute('url','ext');  
  5. alert(att);  

    最后看方法

 

Js代码  收藏代码
  1. update : function(html) {  
  2.     if (this.dom) {  
  3.         this.dom.innerHTML = html;  
  4.     }  
  5.     return this;  
  6. }  

    更新该元素的innerHTML

    下面看Ext.Element的静态方法

 

Js代码  收藏代码
  1. var ep = El.prototype;  
  2. El.addMethods = function(o){  
  3.       Ext.apply(ep, o);  
  4. };  

    该方法内部调用Ext.apply,把o中的所有属性克隆到ep上

    下面看重要方法get

 

Js代码  收藏代码
  1. El.get = function(el){  
  2.     var ex,  
  3.         elm,  
  4.         id;  
  5.     if(!el){ return null; }  
  6.     if (typeof el == "string") { // element id  
  7.         if (!(elm = DOC.getElementById(el))) {  
  8.             return null;  
  9.         }  
  10.         if (EC[el] && EC[el].el) {  
  11.             ex = EC[el].el;  
  12.             ex.dom = elm;  
  13.         } else {  
  14.             ex = El.addToCache(new El(elm));  
  15.         }  
  16.         return ex;  
  17.     } else if (el.tagName) { // dom element  
  18.         if(!(id = el.id)){  
  19.             id = Ext.id(el);  
  20.         }  
  21.         if (EC[id] && EC[id].el) {  
  22.             ex = EC[id].el;  
  23.             ex.dom = el;  
  24.         } else {  
  25.             ex = El.addToCache(new El(el));  
  26.         }  
  27.         return ex;  
  28.     } else if (el instanceof El) {  
  29.         if(el != docEl){  
  30.             // refresh dom element in case no longer valid,  
  31.             // catch case where it hasn't been appended  
  32.   
  33.             // If an el instance is passed, don't pass to getElementById without some kind of id  
  34.             if (Ext.isIE && (el.id == undefined || el.id == '')) {  
  35.                 el.dom = el.dom;  
  36.             } else {  
  37.                 el.dom = DOC.getElementById(el.id) || el.dom;  
  38.             }  
  39.         }  
  40.         return el;  
  41.     } else if(el.isComposite) {  
  42.         return el;  
  43.     } else if(Ext.isArray(el)) {  
  44.         return El.select(el);  
  45.     } else if(el == DOC) {  
  46.         // create a bogus element object representing the document object  
  47.         if(!docEl){  
  48.             var f = function(){};  
  49.             f.prototype = El.prototype;  
  50.             docEl = new f();  
  51.             docEl.dom = DOC;  
  52.         }  
  53.         return docEl;  
  54.     }  
  55.     return null;  
  56. };  

 

      该方法是一静态方法,他的功能是获取元素对象。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。该方法调用了以下方法

 

Js代码  收藏代码
  1. El.addToCache = function(el, id){  
  2.     id = id || el.id;  
  3.     EC[id] = {  
  4.         el:  el,  
  5.         data: {},  
  6.         events: {}  
  7.     };  
  8.     return el;  
  9. };  

 

      该方法,会在Ext缓存中以id为key存放到Ext.elCache中,存放的对象有三个属性,分别为Ext.Element,存储数据(El.data用到)、Ext封装的事件(见Ext.EventManager方法addListener),events保存了以事件名为属性的事件信息,包括调用函数,作用域等。
      接下来是一个垃圾回收函数

 

Js代码  收藏代码
  1. function garbageCollect(){  
  2.     if(!Ext.enableGarbageCollector){  
  3.         clearInterval(El.collectorThreadId);  
  4.     } else {  
  5.         var eid,  
  6.             el,  
  7.             d,  
  8.             o;  
  9.         for(eid in EC){  
  10.             o = EC[eid];  
  11.             if(o.skipGC){  
  12.                 continue;  
  13.             }  
  14.             el = o.el;  
  15.             d = el.dom;  
  16.             if(!d || !d.parentNode || (!d.offsetParent && !DOC.getElementById(eid))){  
  17.                 if(Ext.enableListenerCollection){  
  18.                     Ext.EventManager.removeAll(d);  
  19.                 }  
  20.                 delete EC[eid];  
  21.             }  
  22.         }  
  23.         // Cleanup IE Object leaks  
  24.         if (Ext.isIE) {  
  25.             var t = {};  
  26.             for (eid in EC) {  
  27.                 t[eid] = EC[eid];  
  28.             }  
  29.             EC = Ext.elCache = t;  
  30.         }  
  31.     }  
  32. }  
  33. El.collectorThreadId = setInterval(garbageCollect, 30000);  

      可以看到,该函数每隔30秒会进行一次垃圾回收。即遍历Ext.elCache,取Ext元素的dom属性,如果存在以下条件则清空(包括其上的所有事件handler)

  •     dom不存在
  •     dom的父元素不存在
  •     页面上获取不到该dom元素


     用户也可以取消垃圾回收,只要把参数Ext.enableGarbageCollector设为false即可。该方法的思想也可以用到其他函数或类中
     接下来是Ext.Element.fly方法,他的别名是El.fly

 

Js代码  收藏代码
  1. var flyFn = function(){};  
  2. flyFn.prototype = El.prototype;  
  3. // dom is optional  
  4. El.Flyweight = function(dom){  
  5.     this.dom = dom;  
  6. };  
  7. El.Flyweight.prototype = new flyFn();  
  8. El.Flyweight.prototype.isFlyweight = true;  
  9. El._flyweights = {};  
  10.   
  11.   
  12. El.fly = function(el, named){  
  13.     var ret = null;  
  14.     named = named || '_global';  
  15.   
  16.     if (el = Ext.getDom(el)) {  
  17.         (El._flyweights[named] = El._flyweights[named] || new El.Flyweight()).dom = el;  
  18.         ret = El._flyweights[named];  
  19.     }  
  20.     return ret;  
  21. };  

 

      首先利用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),它通过颗粒化将属性分为内部及外部以达到共享从而节省内存。享元模式的实现通常需用到工厂模式。

原创粉丝点击