Ext 拖拽深入研究

整個Ext架構中組件是其重要的組成部分,除了少部分(如樹的結點)的界面表現元素不是在這樣的一個體系中,大部分的頁面表現元素都被綁定在這個體系之中,下面從這個體系的最底層即在這個繼承體系的最高層進行研究:

1.     Ext.util.Observable

所有在這個體系中的元素都需要有事件的支持,在這個系統最頂層是一個事件的發佈系統,它爲系統中的所有元素提供事件支持。Observable原意是看得見的,可觀察的,也就是一個Observable對象是一個可以被觀察的對象,它可以被誰觀察呢?如果在面向對象的語言應該是可以被對象進行觀察,在JS中這裏的觀察者通常都是由一個方法來充當,但可以爲方法指定一個運行空間或者說方法所依附的對象。一個Observable對象可以:

         添加管理的事件;

         可以添加被管理事件的監聽;

         可以刪除事件的監聽;

         通過relayEvents方法可以將另一個Observable對象的事件觸發轉接來,即當另一個Observable對象發生事件時,當前的Observable也可以感知;

         可以暫停也可以在暫停之後恢復事件的發佈;

         可以判定一個事件是否存在監聽器;

         可以爲一個監聽器添加before與after攔截,之後也可以隨時對這些攔截取消;

         可以使用靜態方法爲一個指定一個Observable對象添加一個統一的發佈事件前攔截,之後也可以使用相反的靜態取消這個統一的攔截

與Observable密切相關的一個類對象是Ext.util.Event對象,它是由Observable對象管理的事件的實體,它只是一個普通的事件實體,它有事件名稱,擁有此事件的對象(即Observable對象),以及綁定此事件上的監聽的集合,這是事件三大成員,這裏的事件與DOM結構中的事件還有一定的距離,這只是一個很簡單的屬性包裝,要將此事件與DOM事件結合,還要在這個類的子類中進行相就的轉換處理。

 

Ext3的事件分析:

 

Ext3的事件監聽模式,是在一個較高的級別上事件發佈系統,它實際與瀏覽器事件,沒有什麼關係。整個管理模式就是組件或者說一個Observable管理理着一些Ext.util.Event對象,而每一個Ext.Util.Event對象又管理着與此事件相關的監聽方法(是被裝飾之後的方法),對於一個Observable來說它可以添加事件,也可以添加監聽方法,最後很可能它在適當的時候又觸發一些事件(委託對應的事件對象來執行它管理的方法)。整個過程基本是一個封閉的結構.或者說是一個完整的事件發佈系統

 

但是有一個問題就是這裏的事件體系並沒有與瀏覽器事件進行結合,所以說這只是Ext3整個事件體系的一部分,我將它稱爲Ext標準事件子體系,或稱之爲高級事件子體系。Ext3的事件體系的另外一個部分是與瀏覽器相關的事件,我將它稱之爲低級事件子體系,或者叫底層事件子體系。事件體系的這個部分主要與Element對象相關,因爲一個低級事件總是與Dom對象相關的,而Element是Dom對象的一個包裝,在低級事件中相關的對象主要有Element,Ext.EventManager,Ext.lib.Event,Ext.EventObject在低級事件系統中圍繞事件發生的對象是dom對象,而在高級事件系統中發生的事件的對象通常情況下是組件或者說是一個Observable的子類。Ext.lib.Event是一個靜態類,它在定義的時候是沒有構造器的,它只有方法,它操作的對象全部是以dom爲中心,它使用私有的局部容器來管理監聽方法,它與高級事件系統中的事件對象有相似之處,但也有很大的不同,除了構造器之外,就是低級事件觸發是不需要手動觸發的,而是由瀏覽器自動觸發的。Ext.lib.Event它只是管理監聽方法,它自己並不是一個事件對象,真正的事件對象是Ext.EventObject,這個對象是瀏覽器事件對象經過標準化之後的一個對象,它的事件屬性要比在高級事件體系中的事件對象信息要豐富很多。可以說Ext.lib.Event與Ext.EventObject兩個全在一起基本上完成在高級事件系統中Ext.util.Event對象所完成的功能。而Ext.EventManager恰好是將這兩個對象進行了組合,這個類就完成整個低級事件的管理

 

在一個組件當中往往需要感知瀏覽器,而在組件當中,Ext採用的是高級事件體系,因爲這樣要更加靈活,比如可人爲根據定義事件,可以隨時發佈事件,也可以隨時取消事件的發佈。因爲就涉及到兩個個事件系統之間的溝通問題,這件事件一般是在Ext組件的內部根據邏輯的需要,就是轉換的,大部分的情況是當一個低級的瀏覽器事件發生是組件希望觸發一個高級,Ext組件內部一般採用的做法是,爲組件的相關Element(實際是它包裝的dom)綁定一個低級的監聽,而在這個低級的監聽方法中來發佈一個Ext的高級事件。關於這個溝通在Component組件類中有兩個支持方法:mon(綁定)與mun (御載),分別是爲組件的相關元素綁定與御載低級監聽的方法

 

2.     Ext.Component

這個類可以說是Ext所有組件的基類,Observable雖然在繼承體系的最上層但是它不具有實體,只是因爲組件類需要事件管理所以纔將它置於類的最頂層,其實任何需要事件管理的類都可以繼承Observable。而Component是一個實際意義上的組件基類,因爲它與看的見DOM實體進行聯繫,整個Component的結構爲頁面看得見的小窗體的部件元素搭建了一個抽象的框架類,規定界面元素的大體走向,與常見功能的實現,先從此基類的構造方法說起:

         組件基類首先對構造的配置參數進行處理,如果配置參數config中如果存在initialConfig則會忽略其它的配置參數,直接將initialConfig作爲config使用;

         如果config中存在tagName或dom屬性或者說config是一個字符串,則將config看成一個dom配置對象來看,它將會被看一個頁面的DOM結構中已經存在的實體,此組件的applyTo屬性將會指向此dom元素;

         在對配置參數config進行處理之後,組件的initialConfig在成員將會保存這個經過處理後的config對象,所以程序處理中如果想獲取初始的參數可以從這裏獲取;

         接着組件會將config中的所有成員拷貝到看的成員中,作爲自己的直接配置成員;

         接着組件會添加自己管理的一些最基礎的事件;

         生成組件的唯一標識id,如果已經指定了id則使用指定的;

         向組件管理器註冊此組件;

         調用超類(Observable)的構造方法以對配置的監聽進行初始化;

         調用initComponent方法來進行組件的額外的初始化操作(空方法),通常這個方法是留給子類來根據具體的情況進行初始化,所此方法是留給子類來實現的,或者說是留給子在的一個初始化的一個鉤子方法;

         如果組件配置了插件則將當前組件作爲參數傳遞給插件的init方法來進行插件的初始化,這也就是說插件必須實現init方法且接受一個組件作爲參數;

         初始化組件相關的一些用cookie記錄的狀態,例如長度,寬度等;

         如果定義了applyTo或者說renderTo屬性則提前進行初始化,applyTo與renderTo區別是applyTo指定一個存在的dom元素的父結點作爲此組件的容器,而renderTo直接指定的一個存在的dom元素作爲組件的容器;

         至此整個組件的構造初始化操作完成。

構造的完成並不意味着組件的創建完成,構造完成只是說爲創建組件準備好的基礎數據,實際組件的創建需要一個渲染的過程,當然如果存在一個applyTo或renderTo屬性,則此操作已經提前完成了,此基類中的渲染流程,將會對所有子組件的渲染起到一個規範作用,下面是基類組件的渲染流程:

         首先判定組件是否已經被渲染,如果沒有則發佈beforrender渲染前事件,如果沒有受到事件阻止,則開始渲染操作;

         渲染操作接受兩個參數,第一個爲作爲容器CT的dom結點,第二個爲渲染到此結點的哪一個位置,可以指定容器子結點的一個索引或直接指定容器的子結點,如果沒有指定容器則會使用配置屬性el的父結點作爲容器(此種情況對於el所指定的元素來說是不能移動的,類似於applyTo屬性指定的元素);

         爲容器結點添加樣式類ctCls;

         標記組件已經被渲染;

         調用onRender方法來進行渲染;

                        i.              onRender方法與render方法接收相同的參數,如果指定了autoEl屬性則會根據此屬性創建一個元素對象,並將此元素對象賦予this.el成員,因此這裏要特別小心,也就是如果你配置了autoEl屬性,則對el屬性的配置最終都會被覆蓋,這會產生很讓人迷惑的現象與頁面效果,你可以同時指定一個存在的容器與一個存在的el,而你沒有指定autoEl屬性,這會使得,這會全已經存在的el結構被移入指定的容器中,當然這不會存在問題;有兩種情況會導致Dom結構無法進行移動,實際是一種情況:1、如果指定的applyTo屬性;2、如果指定的el但是沒有指定容器對象CT,如果不能移動DOM結構,對於一個autoEl屬性是沒有意義的,因爲這個憑空創建的元素最終不能進入到頁面的Dom結構中。所以要把握兩點,如果autoEl屬性存在一定要使用allowDomMove屬性爲true,實際中就是不要設置applyTo屬性;如果沒有autoEl屬性,allowDomMove屬性可以爲true與可以爲false

                      ii.              在有了el元素之後將el的dom結構插入到容器對象中(當然allowDomMove屬性爲true的時候纔會這樣);

                    iii.              最後在el元素上添加overCls事件監聽

         如果autoShow屬性爲true則移除el元素上的隱藏類;

         如果配置了cls屬性則向el元素添加組件的類掛鉤;

         如果配置了style屬性則向el元素添加此樣式;

         發佈“render”渲染事件;

         調用afterRender方法,參數此組件的容器對象(這是留給子類的掛鉤方法);

         如果配置隱藏hidden爲true則隱藏此組件;

         如果配置禁用disabled爲true則禁用此組件;

         至此完成組件的渲染

另外組件基類還實現組件的一些基本功能,比如獲取焦點或失敗失去焦點;啓用與禁用;隱藏與顯示;

 

3.     Ext.BoxComponent

此類是所有使用一個盒容器的可視化組件的一基類,此類主要提供了一個自動盒模型的調整,當組件尺寸與位置改變的時候,此類提供的主要配置屬性有x,y,pageX,pageY, height,width;提供的主要方法有,設置組件的尺寸與位置;獲取組件的尺寸與位置,組件這兩屬性改變都委託組件的實體el元素來完成的,在改變位置與尺寸的時候都發布相應的事件,另外在改變尺寸的時候會調用onResize方法,這是一個給子類留下的鉤子方法,以便子組件在改變尺寸的時候子類能夠添加額外的邏輯;在改變位置的時候人調用onPosition方法,這也是留給子類的鉤子方法;另外還有兩個調整方法adjustSize與adjustPosition方法可以留給子類進行微調尺寸與位置用;還有兩個方法getResizeEl與getPositionEl這兩個方法用來獲取實際尺寸與位置改變的元素,因爲有小時候可能el不是需要改變的元素

 

此類還重寫了onRender方法,它在超類的渲染方法調用完之後,主要做了一件事,就是看子類有沒有額外設置resizeEl與positionEl,如果設置了則將它們作爲尺寸與位置改變的實際元素;另外此類還重寫了afterRender方法,主要是用來在組件渲染完成之後根據設置的位置與大小來更新組件

 

4.     Ext.Container

此類預期被用來作爲管理其它組件的一個容器,可以把看成是一批關聯組件的管理者,這裏的組件是普遍意義下的組件,並不一定要是此類的子類,雖然通常都是它的子類

 

Ext的一個主要目標就是在WEB頁面上實現類似窗體的程序,因此其中的小窗體組件是其中的一個重要組成部分,當然小窗體組件屬性是屬於高層封裝,所以它對一些較底層的操作是小依賴的,下面就從最常見的小窗體部件:按鈕開始研究

5.         Ext.Button

Ext.Button類的實例代表了頁面上的一個按鈕,一般構造的按鈕都是按照默認的模板對一個button元素進行包裝的,當然如果有現成的按鈕已經做好了也可以直接使用,這時可以配置一個屬性:dhconfig來替代默認的包裝形式,dhconfig可以是一個DOM對象也可以是一段HTML片段,用它來代表一個按鈕的主體。當使用自定義的按鈕時,大部分的配置屬性都不能用,例如text,cls,icon,iconCls,tabIndex,tooltip都將無效。默認的按鈕包裝使用表格對button元素進行包裝,分爲左中右三個單元格。默認按鈕使用的圖片高度固定爲21px,也就是說按鈕的高度固定爲21px高,其中涉及的幾最重要的CSS類:x-btn、x-btn-wrap、x-btn-left、x-btn-right、x-btn-center。這裏的按鈕的主要作用有:一般的事件處理;充當狀態切換開關;充當菜單觸發按鈕

 

這裏臨時記錄一個問題,就是當表單面板的尺寸發生改變時,如果讓表單元件的尺寸也隨着或者不隨着表單的大小而改變,本來這個問題是很簡單的,只需要爲表單元素指定了auchor屬性,這些元件就會隨着一起變化,但往往表單元件有時候被嵌套的層次比較深,如果其中任何一個面板不能監聽這種變化,那麼這些表單元件的大小很可能就不會改變,尤其對於那些auchorLayout佈局或者其子類佈局的面板,只有對這種容器中的組合配置auchor屬性纔會監聽這種變化;另外還發現一個需要小心的問題,當組件配置了auchor屬性,而在它的父容器的defaults屬性也配置了字段auchor那麼會出現當前第一渲染的時候採用的是defaults中的配置值,而監聽這種變化時卻是採用組件的實際配置值,這有時候會給人一種迷惑的感覺

 

Ext中的拖動:

Ext將拖動看成是元素與元素之間的作用,從整個拖動操作上看,拖動主要可以分爲兩類實體,一類實體是被拖動的對象或者說是可以被拖動的對象;一類實體是接收實體的對象或者說可以接收其它實體的對象。Ext中將這兩類實體包裝成同一類對象,可以被稱爲DD實體或者說DD對象,每一種實體與三種dom元素相關聯:鏈接元素;控制元素;象徵元素。如果不加以細分可以簡單的認爲這三個元素就是同一個元素,即被拖動的元素。在整個拖動過程中,Ext將每一個發生的細節規範成了DD對象的各種回調方法,對於這些回調用方法主要有:

 

onAvailable() : void

開發者可以在子類中覆蓋此方法,來完成子類需要初始化的邏輯

onMouseDown(Event e):void

當用戶的鼠標在對象上按下時第一個回調

startDrag(int X,int Y):void

在構建完成DD管理器此次拖動操作的環境後調用

onDrag(Event e):void

當拖動一個DD對象正在移動的時候將調用此方法(所以如果要即時修改參數可以覆蓋此方法)

onDragEnter(Event e,String|DragDrop[] id):void

當被拖動的DD對象進入在另一個DD對象上的時時候回調(注意方法參數)

onDragOver(Event e,String|DragDrop[] id):void

當被拖動的DD對象懸停在另一個DD對象上的時時候回調(注意方法參數)

onDragOut(Event e,String|DragDrop[] id):void

當被拖動的DD對象退出在另一個DD對象上的時時候回調(注意方法參數)

onDragDrop(Event e,String|DragDrop[] id):void

當被拖動的元素被放下的時候將調用此方法,注意方法的參數

endDrag(Event e):void

當被拖動的元素完成拖動的時候將調用此方法

onMouseUp(Event e):void

當鼠標鍵釋放的時將調用此方法

onInvalidDrop(Event e):void

這個是一個比較特別的回調方法,什麼時候會調用此方法呢?在系統發佈dragdrop事件時可能需要回調此方法,

它的回調條件是系統通過計算沒有發現適合的接收者,就會回調此方法,它可以看是非法放置修正回調處理器

 

 

那麼這些回調方法是如何協作的呢?下面分析整個拖動過程與回調方法的對應關係,以及在Ext中的實現原理。

 

在雖然我們說拖動包括兩類實體,但是在Ext中是以被拖動的元素爲主體,但事實上這兩種實體是相對的,拖動元素隨時也是目標元素,而目標元素隨時也可能成爲拖動元素,所以其中之一進行編程處理也是合理的。要完成一個拖動過程,

         首先需要創建一個DD對象(其中包括向DD管理器註冊,以及綁定mousedown事件)在mousedown事件處理方法之前將有一個onAvailable回調方法,以完成子類的初始化

         在一個DD對象被創建時系統只爲我們綁定了一個mousedown事件處理方法,因此這個事件處理方法是觸發整個拖動過程的入口。在這個事件處理方法中主要會調用一個onMouseDown回調方法,接着當前DD對象會將會將控制權交給DD管理器繼續進行處理

         DD管理器會以傳入的DD對象爲基礎來構建此次拖動操作的運行環境,其中最重要的一個環境就是dragCurrent表示當前正在被拖動的DD對象。完成環境構建之後,DD管理器會回調當前DD對象的startDrag回調方法。到這裏爲止被拖動元素的使命已經完成,那麼接下來的操作將如何完成呢?

         從上面的分析看程序似乎已經走到了盡頭,但現在拖動纔開始,祕密就是對body的監聽方法,分析如下:在整個拖動流程中,提供實體的一方(或者說被拖動者)一般是一個DD對象,而接收實體的一方(或者說等待接收)往往是多個DD對象,它們都是接收實體的"候選者",最終有可能被拖動的實體,被其中的一個候選者接收,也有可能沒有一個候選者會接收到這個實體,因爲有可能用戶在拖動的時候沒有拖到任何一個候選者的接收區域之內.因爲有多個DD實例是候選者,這裏採用了對body元素上的mouseup與mousemove事件進行了監聽。那麼系統究竟是如何對這兩個事件進行監聽的呢?

         從拖動的先後順序來看,肯定是先發生mousemove事件,然後才發生mouseup事件,所以這裏先看mousemove事件監聽情況。這個事件的監聽是由方法handleMouseMove方法來完成,此方法有一個重要的生命週期回就是對當前被拖動的DD對象的onDrag回調方法的調用,接着此監聽會調用一個重要的方法,但它是不是一個生命週期的回方法,這個方法的名稱是fireEvents,這個方法是用來做什麼的呢?它主要查用來在DD管理器中發佈事件的,我們說發佈事件實際是調用回調方法,只不過發佈是對多個對象進行回調,前面已經分析接收實體的DD對象也需要知道當前被拖動的DD對象的狀態,以便作出響應,這裏便用這個fireEvents方法進行了代理完成,可以看到前面的回調都是針對被拖動的DD對象來調用的,而這裏的fireEvents方法將着眼於那些準備接收實體的DD對象之上,它可以計算出,哪些對象該觸發dragover,哪些對象該觸發dragenter,哪些對象該觸發dragout事件。在這裏便有了被拖動DD對象與其它DD對象的相互作用,但觸發這三個事件的方式要特別留意,正常情況情況下應該是回調用其它對象的相應回調方法(依次爲:onDragEnter,onDragOver, onDragOut)但Ext在這裏做了一個巧妙的處理,它將這些回調轉嫁給了被拖動的對象的相應方法,所做的調整就是將本該觸發這些事件的DD對象(或id)傳遞給了這些方法,所以這些事件的布實際也變成了對被拖動的DD對象的方法回調,這樣可以讓開發人員更專注於一個對象的思考

         接下來看對mouseup事件的監聽情況。對此事件的監聽DD管理器使用方法handleMouseUp方法,那麼此方法又做了哪些事情呢?首先調用fireEvents方法來發布dragdrop事件,處理的方式與上一步調用fireEvents方法的處理方式相同,也是將方法回調轉嫁給了當前被拖動的對象(onDragDrop),接此方法調用當前被拖動的DD對象的endDrag回調方法;接着再調用當前被拖動DD對象的onMouseUp回調方法,至此就完成了整個拖動的過程

 

以下是對上述回調接口幾個系統的實現情況分析:

         Ext.dd.DD

這類主要實現了dragEl,即象徵元素一起移動的功能,因爲需要時刻修改象徵元素的位置,所實現需要回調onDrag方法以達到實時的效果,所以系統採用瞭如果實現:

b4Drag: function(e) {

        this.setDragElPos(e.getPageX(), e.getPageY());

},

         Ext.dd.DDProxy

這個實現類對Ext.dd.DD類進行擴展,主要添加了一項代理功能,因爲Ext.dd.DD實現了象徵元素與鼠標一起移動的功能,但默認情況是象徵元素與被拖動元素是同一個元素,往往這不是很好,而這個使用了一個創建虛擬的元素來替換象徵元素,這樣使得象徵元素與鏈接元素進行了分離,不過這個類在完成結束拖動的操作時候又分離出了兩個回調,一個在鏈接元素移動前,另一個是在鏈接元素移動後具有代碼如果下

endDrag: function(e) {

 

        var lel = this.getEl();

        var del = this.getDragEl();

 

        // Show the drag frame briefly so we can get its position

        del.style.visibility = "";

 

        this.beforeMove();

        // Hide the linked element before the move to get around a Safari

        // rendering bug.

        lel.style.visibility = "hidden";

        Ext.dd.DDM.moveToEl(lel, del);

        del.style.visibility = "hidden";

        lel.style.visibility = "";

 

        this.afterDrag();

    },

所以對於endDrag方法覆蓋要特別小心,因爲這個方法已經被系統佔用,要覆蓋可以選擇beaforeMove或afterDrag方法

         Ext.dd.DDTarget

此類直接繼承自Ext.dd.DragDrop類,並且去除對鏈接元素mousedown事件的監聽方法,因此它也失去了觸發拖事件功能,它也不可能被拖動,因爲它不能作爲拖動流程的入口點,但是它可以作爲一個接收其它實體的DD對象而存在,只是它自己不能被拖動而已

         Ext.dd.DragSource

這個類擴展自Ext.dd.DDProxy主要在兩方面加強了功能:代理的樣式與以及代理的復位處理,關於代理的樣式,此類採用了一個標準的代理類Ext.dd.StatusProxy。其實這兩個方面具有支持都是由這個類來支持的,並對代理拖動的流程進行了進一步規範,抽取出一批before型與after型的回調方法

         Ext.dd.DragZone

這個類做得工作並不多,它直接繼承於Ext.dd.DragSource,此類做的一個唯一的變化是對代理對象進行了註冊管理可以參考:Ext.dd.Registry

         Ext.dd.DropTarget

這個類繼承自Ext.dd.DDTarget類主要對放置對象,對於被拖動的DD對象的一個迴應操作的規範,如果進入到當前目標對象時如何響應;如果懸停到當前目標對象時如何響應;當放置到當前目標對象上時如何響應等一系列的notify系列方法。其實在分析之初就已經說了Ext將重點着眼於被拖動的DD對象,對於一個接收其它實體的DD對象基本沒有什麼關注,因爲在整個拖動過程中如果DD對象是一接收者,則在Ext.dd.DragDrop類中定義的方法全部都不會被調用,因爲前面已經分析了enter,over,out,drop等事件發生,回調用被轉移到被拖動的DD對象上去了。但是這樣會有一個問題,拖動如果最終要有效果那麼勢必要對接收的DD對象進行相應的操作,一個比較可行的辦法就是在被拖動的DD對象的onDragEnter,onDragOver, onDragOut,onDragDrop等回調用方法中對目標DD對象進行處理,那麼怎麼處理呢?方法很自由,也很多,可以發揮你的無限想像力來完成各種效果,但整個代碼都有一個基本的套路,那麼Ext.dd.DragSource類將這個基本套路進行了規範化,覆蓋了onDragEnter,onDragOver, onDragOut,onDragDrop等方法,在這些方法中對相應的目標對象進行了回調處理,或者說此類對象目標DD對象的回調操作進行規範化,而Ext.dd.DropTarget類恰好是對這個規範的一個呼應,Ext.dd.DropTarget對這些規範進行簡單的實現,這些回調接口都是以notify開頭:notifyEnter,notifyOver,notifyOut,notifyDrop其中notifyDrop是需要一個真正的業務實現的地方,一般需要子類來完成,通常是移動元素等

         Ext.dd.DropZone

這個類表示一個放置區域,它擴展自Ext.dd.DropTarget,這個似乎對超類的幾個接口方法進行了改寫具體還需要研究一下
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章