列表元件抽象(3):分頁和排序管理說明

發表於2016-09-20

本文介紹列表元件中我對分頁和排序的抽象思路。

先來說分頁,因為之前寫過一篇簡單封裝分頁功能pageView.js,這次封裝分頁時的思路基本與那篇部落格的想法完全一樣,只不過考慮到我要寫的列表元件,還有其它的分頁形式,比如點選載入更多進行翻頁,基於瀏覽器標準的scroll事件進行翻頁,基於iscroll外掛派發的scroll事件進行分頁。於是我在該文的基礎上進一步抽象,將分頁的一些公共邏輯提煉到一個基類中,僅僅將UI與分頁控制的邏輯留給子類實現,這樣能夠最大程度地簡化程式碼。這個基類的最終實現是:https://github.com/liuyunzhuge/blog/blob/master/form/src/js/mod/listView/base/pageViewBase.js,它其實就是簡單封裝分頁功能pageView.js這篇部落格中pageView.js分離出來的,所以如果想要去了解這個檔案的說明,可以訪問之前的那篇部落格。

當我把分頁的一些公共邏輯抽象到pageViewBase之後,在簡單封裝分頁功能pageView.js這篇文章中的pageView.js就會變得特別簡潔,我最終的實現是:https://github.com/liuyunzhuge/blog/blob/master/form/src/js/mod/listView/simplePageView.js。當你檢視pageViewBase.js的原始碼和simplePageView.js的原始碼,會發現它們合起來就是簡單封裝分頁功能pageView.js裡面的pageView.js。

其它的分頁元件實現有:

基於瀏覽器標準的scroll事件進行翻頁,https://github.com/liuyunzhuge/blog/blob/master/form/src/js/mod/listView/scrollPageView.js

基於iscroll外掛派發的scroll事件進行分頁,https://github.com/liuyunzhuge/blog/blob/master/form/src/js/mod/listView/iscrollPageView.js

這兩個分頁元件其實跟simplePageView沒什麼大的不同,只是分頁使用到的事件不同,另外就是由於涉及到滾動翻頁,所以還有一個何時進行自動翻頁的判斷問題,這個判斷問題我會在後面的文章介紹滾動分頁列表元件的時候再來說明。

再來看排序

相比之下,排序會比分頁麻煩一些。做排序管理的目的在於,列表為使用者提供資料的時候,為了更有針對性的檢視資料,使用者一般會希望能夠主動地控制列表的排序規則,所以得考慮排序管理的功能,以便列表能夠實現自定義的排序。在使用者做了排序操作之後,我們需要告訴後臺當前排序操作結果對應的排序欄位以及每個欄位對應的排序值。由於有可能有多列排序的情況,所以傳遞排序引數時,還得按排序操作時的順序,組織好排序欄位的順序,以便後臺能夠按照使用者的操作結果,來進行排序處理。類似下面這樣的資料結構就可以正確地反映一個排序操作的結果:

欄位在陣列中的先後關係即可代表排序時的先後關係。只要能夠得到這樣一個結構,就能把轉成json格式的字串傳遞給後臺進行處理。最終這個資料結構對應到資料庫中的排序規則時,就是這樣的:

 從排序操作上來說,常見的table外掛是這麼做的:

1. 如果僅僅是滑鼠單擊排序列,那麼執行的就是單列排序操作。只要按照 不排序->升序、升序->降序、降序->不排序的切換規則,在滑鼠單擊排序列之後,改變該列對應的排序欄位的排序方式,然後觸發查詢即可。傳遞到後臺時,排序引數最多包含一個欄位。

2. 如果在滑鼠單擊排序列之前,使用者先摁住了shift鍵,再做點選操作,此時使用者執行的就是多列排序操作,在shift鍵摁住期間,先點選的排序列對應的欄位在排序結果中的順序靠前,後點選的靠後。單個排序列還是按照 不排序->升序、升序->降序、降序->不排序的切換規則來更改自身的排序方式,但是在單擊完之後並不會立即觸發列表查詢,而是要等到shift鍵釋放之後,再來查詢。傳遞到後臺時,排序引數可能包含多個欄位。

按照前面的這個需求,我的實現思路時:先把排序引數的管理和排序操作的控制分開,寫成兩個元件;排序引數的管理元件僅負責排序欄位的資料這一層級的控制,不與任何UI打交道;排序操作的管理元件負責與DOM互動,響應使用者的鍵鼠操作,內部例項化一個排序引數管理的元件,利用這個元件例項來完成對排序欄位的修改。這麼做的好處在於將資料與UI分離,其實也就是表現與行為分離,簡化UI層的邏輯,讓程式碼看起來更加清晰。

最後考慮到不同的列表元件,可能有不同的排序UI控制邏輯,所以也決定把排序元件抽象出一個基類,像pageViewBase一樣,把一些排序元件公共的邏輯出現出來,比如事件監聽,啟用禁用以及排序引數管理元件的例項化等。最終我得到了以下2個核心的排序元件相關的檔案:

排序引數管理元件:https://github.com/liuyunzhuge/blog/blob/master/form/src/js/mod/listView/base/sortFields.js

排序控制管理元件的基類:https://github.com/liuyunzhuge/blog/blob/master/form/src/js/mod/listView/base/sortViewBase.js

下面我把這兩個檔案的一些要點一一說明。

先說sortFields。

這個檔案比sorViewBase還長,可想而知,如果我把sortFields的邏輯不分離,直接寫在sortViewBase裡面,sortViewBase的複雜性肯定會增加不少。為了瞭解這個元件的作用,我先用幾段簡單的程式碼來演示它的用法,雖然在實際使用中,這個元件並不需要直接例項化,但是它還是可以直接例項化的,不然就無法為sortView元件所用了。

通過下面的方式來例項化一個sortFields的元件。

先說config這個option,其它的介紹後面的用法再補充。config用來配置排序管理元件要管理的排序欄位。用陣列的形式來配置多個排序欄位,單個排序欄位的排序定義用一個js的字面量物件來配置。用field屬性來定義排序欄位的名稱;用value屬性來配置該欄位初始化時的排序方式;用type屬性來配置該欄位的資料型別,如string,int等,這個有可能在後臺會需要;用order屬性來配置該欄位在排序規則中的初始化位置。如以上config,在初始化後,實際上對應的排序規則就是:

為啥是email在前,contact在後面,這個就是order屬性的作用了。

sortFields元件提供了一個getConfig的例項方法,這個方法返回所有排序欄位的當前狀態:

459873-20160919104853137-202205033

在通過後面要介紹的changeState方法,改變了單個排序欄位的排序方式後,我們可以在其它位置通過呼叫getConfig方法,獲取排序欄位最新的狀態,從而更新UI:

459873-20160919104855090-689485306

比如simpleSortView裡面的render方法就是這麼做的:

sortFields提供了getValue方法,可以得到當前的排序結果,這個方法基於getConfig實現,過濾掉不排序的欄位,同時按order屬性對getConfig返回的陣列進行排序:

sortFields最重要的方法是changeState(fieldName, multiple),這個方法用來改變某個排序欄位的排序方式,它接收兩個引數,第一個引數代表欄位的名稱,第二個參數列示是否進行多列排序。

這個時候再來補充說明下前面幾個事件option的詳細內容:

當我們在UI層進行排序操作時,只有第一個欄位在呼叫changeState時,會觸發sortStart事件,也就是onSortStart那個回撥,表示排序開始;

每次呼叫changeState方法,都會觸發sortStateChange事件,也就是onStateChange那個回撥,表示某個欄位的排序方式改變;

如果是單個欄位排序,在changeState方法最後會主動呼叫endSort方法來結束排序,在endSort方法內部,會判斷當前所有的排序狀態以及順序與排序操作前的狀態順序是否有變化,如果有變化則觸發sortChange事件,也就是onSortChange那個回撥;

如果是多個欄位排序,在changeState方法最後就不會主動呼叫endSort方法。因為對sortFields元件來說,多列排序的時候,它根本不知道什麼時候結束排序,所以必須由UI層主動呼叫endSort方法,比如說在shift鍵釋放的時候;

在endSort方法最後,會觸發sortEnd事件,也就是onSortEnd那個回撥,表示排序結束。

下面先看通過這個方法進行單列排序的演示:

(VM601都是console裡面複製的時候帶出來的,不用關注~)從這個演示能看到,所有回撥都被觸發,並且getValue方法返回了changeState最新的排序結果。

再來看看多列排序的情況:

在這個演示中,我前後改變了兩個欄位的排序方式,先改變的是contact,後改變的是name,最後通過endSort結束了這次多列排序,然後在onSortChange回撥中,我們看到了跟我們排序操作一致的排序結果。同時也可以看到sortStart等幾個事件回撥,在多列排序操作時的執行情況,跟我前面的說明是完全一致的。

到這裡為止,我說明了sortFields的實現思路和使用方法,根據以上內容再去閱讀原始碼,應該就比較好理解了。

接下來介紹sortViewBase.

其實這個類就很簡單了。程式碼結構跟之前的listViewBase和pageViewBase都一致。

defaults定義如下:

這個config是在內部例項化sortFields元件的時候用到的,sortParamName是在為列表元件提供排序引數時用到的,這個引數名將會用來傳遞到後臺,onChange也是提供給列表元件使用的,外部在此回撥內觸發列表查詢。

這個基類的實現也有用到模板方法。init方法實現跟前面的部落格介紹的元件差不多,它在中間的程式碼例項化了前面寫的sortFields元件:

 render是sortView元件的一個例項方法,在sortFields重置,排序狀態改變的時候,都會呼叫這個render方法來實現UI層的更新。

然後這個基類還提供了enable和disable方法,做啟用和禁用的控制。

最後來介紹sortViewBase的一個實現:simpleSortView。

在demo中,listView_1.html裡面,下面這個UI內容,就是simpleSortView元件的例項:

459873-20160919104856512-553259611

tableView.html裡面,整個表頭就是一個simpleSortView的例項:

459873-20160919104857606-1285694419

simpleSortView的defaults如下:

sortItemSelector用來篩選那些與每個排序欄位對應的元素,後面兩個cssClass是作為排序狀態類來使用的。把這些定義成option也是為了增加元件的靈活性。

simpleSortView其實就是做了些事件繫結來控制排序操作,以及UI渲染的邏輯。

rende方法在前面已經說明過了,需要補充一下的就是,simpleSortView為了能夠將DOM元素與排序欄位對應起來,必須在DOM元素加些特定的屬性來標識,這裡我用的是data-field屬性。只要把某個DOM元素的data-field屬性的值,配置成排序欄位的名稱,它們就關聯起來了。

排序操作的控制邏輯其實也非常簡單:

 也就是按前面的單列和多列排序操作的需求實現而已。由於用到了$document這種公共的DOM物件來註冊事件,所以為了避免事件衝突,在已有的名稱空間的基礎上,又加了一個隨機數作為新的名稱空間。如果沒有這個,當頁面內包含多個simpleSortView元件例項時,keydown和keyup事件就會衝突。

到此為止,分頁元件和排序元件的一些要點也都介紹完了,希望這些東西能幫助感興趣的朋友理解我的思路。

相關文章