前一陣子根據老闆要求,寫了一個重點商品銷售統計報表的程式,每天統計各門市到昨天為止的重點商品銷售統計,將結果輸出到網頁讓每個門市都可以瀏覽查看。這程式運作了一段時間後,老闆覺得還不錯,就要我改成暢銷商品銷售統計,納入上千項商品為暢銷商品。因為上千項商品的報表很長,老闆就要我加上排序功能。
要排序當然OK啊。只是我很懶,不想為了視覺效果去修改統計程式,更不想為了更新排序結果而要伺服端再回傳一次頁面。於是我打算以動態網頁的方式,直接用 JavaScript 對網頁上的統計表格排序。
前一陣子根據老闆要求,寫了一個重點商品銷售統計報表的程式,每天統計各門市到昨天為止的重點商品銷售統計,將結果輸出到網頁讓每個門市都可以瀏覽查看。這程式運作了一段時間後,老闆覺得還不錯,就要我改成暢銷商品銷售統計,納入上千項商品為暢銷商品。因為上千項商品的報表很長,老闆就要我加上排序功能。
要排序當然OK啊。只是我很懶,不想為了視覺效果去修改統計程式,更不想為了更新排序結果而要伺服端再回傳一次頁面。於是我打算以動態網頁的方式,直接用 JavaScript 對網頁上的統計表格排序。
首先,我們先看一份示範用的統計表格,如下所示。點擊表格標題即可依此欄位內容排序。
| 項次 | 條碼 | 商品名稱 | 銷售量 | 銷售額 |
| 1 | 4203803 | 輕鬆錠 | ||
| 2 | 4203810 | 骨錠 | 10 | 2800 |
| 3 | 4203811 | 玻尿酸膠囊 | 5 | 2500 |
| 4 | 4203812 | 膠原葡萄籽錠 | 4 | 1200 |
| 5 | 4203817 | 葉黃素膠囊 | 9 | 891 |
| 6 | 4203818 | 綜合維他命 | 12 | 6000 |
| 7 | 4203865 | 異黃酮 | 8 | 1640 |
| 8 | 4203866 | 甲殼素膠囊 | 3 | 450 |
| 9 | 4203868 | 柚兒茶素 | 3 | 210 |
| 10 | 4203876 | 魚油 | 11 | 990 |
| 合計 |
一開始,這是一張很單純的統計表格,標題只是文字而不是連結。顯示的統計結果是伺服端統計程式按項次排列之順序。現在我要加上排序功能,而且我不想要發生向伺服器要求更新頁面的動作,亦即我要以動態網頁的方式直接變動頁面上的內容排列順序。
第一步要想的是,排序資料如何得手?所幸網頁上的報表內容係基於「樣式與內容互離」之概念產生,因此報表中的內容就是純粹的資料,沒有夾雜其他樣式文字。故僅需取得報表中每一格的內容便可進行排序。排序程式僅需知道欄位名稱(或欄號),就可以直接從網頁上的報表中取得資料加以排序。
第二步則要思考如何依排序結果更新報表的每列順序。所幸網頁上的報表內容係基於良好的 HTML 格式嚴謹地產生,如下所示:
由於其文件結構良好,只需要透過 HTML DOM 取得 tr 元素陣列,再重新安插其 DOM 節點的位置即可。
連續兩個「所幸」並非偶然,因為我早知遵循這兩個原則的好處,一開始就決定如此輸出資料。不了解為何要將「樣式與內容分離」者,往往在報表內容中夾雜樣式文字,例如: <td><span style="color:red">-123</span></td> 。一但這麼寫, JavaScript 就不能簡便地自報表中取得排序資料,要多做許多節點存取動作。不了解為何要按 HTML 的良好格式輸出者,欄位標題列、合計列、分項列等等內容就會混在一起。如此就必須多出判斷更新範圍的動作,而不能簡便地更新局部內容。
在實作過程中,我先以程序導向的方式設計,定義一個變數 sorter ,將所有用得到的變數、函數都塞進裡面。我需要一組設定值告知第幾欄是什麼資料,如此一來僅需傳一個代表資料欄位的字串, JavaScript 就知道該從第幾欄位取得排序資料。我將這組設定值定義於陣列 sortConfig 。
接著定義喚起排序動作的起始函數 sortBy ,只需要傳遞欄位名稱。遞減的排序函數 funcSort_desc、遞增的排序函數 funcSort_asc、自 DOM 中取得排序資料與資料列節點的函數 getTbody, getRows 。最後定義實際進行排序及更新頁面內容的函數 doSort 。下列為程式碼:
底下是報表的原始內容 (簡化版) 。報表是由伺服端統計程式產生,所以修改伺服端統計程式的輸出動作,加上一行載入 JavaScript 的動作,並修改標題欄位為呼叫 JavaScript 排序動作的連結。
透過上述快速的實作過程,我已經驗證了程式碼可行性。最後免不了要重整一下,最好是把上面的程式碼重整成一個可再用的 class 。重整重點有二:一、將原本的變數改寫成可生成新個體的函數型態,即 JavaScript 的類別。二、設定值與排序資料區域可作為引數傳遞。下列為重整後的程式碼,重整過程非常簡單而直覺,各位可以自行比較與思考。
下列是使用案例,配置一個sorter,引數中指示排序資料區域的 ID 、預設排序型態、順序,以及各資料型態的欄位位置。 HTML 部份則不需改變。
整個修改動作很快,而且對原有程式的改動幅度意外地小。原有統計程式只改了 View 的部份,且只改了兩行。加上一行 <script type="text/javascript" src="sorter.js"></script> ,再將表格欄位標題的輸出內容改成連結。
現在有很多 JavaScript 的套件提供這些 GUI 視覺元件內容。不過像這種簡單的功能倒也不見得要用那些套件來做。更重要的是,不論用什麼套件,都應該遵循程式、資料、樣式等內容分離的原則,也不要配合套件來決定程式如何寫。本文能夠如此簡便地在最小改動幅度下實踐排序功能,便歸功於分離原則。
附帶一提,當排序資料很多時,瀏覽器會陷入不回應狀態。而底下的程式碼則是另一種版本的 DOM 節點更新程式碼。上述版本直接操作顯示中的節點,而下列版本則是先配置一個 tbody 節點,於背景排放資料列內容後,再替換整個 tbody 節點。理論上較快,但我實際使用時... 測不出效能差異 (IE, Firefox and Opera)。