使用 Knockout.js 去控制級聯選擇
使用 Knockout.js 去控制級聯選擇
介紹
該篇文章提供了源代碼,可以使你很容易的配置一個包含多個SELECT控件用來過濾一些記錄的頁面(通常顯示在網格或表格中)。在這篇文章中,我們使用一些虛構的地址來表示將要被出租的房子。該頁面用戶允許通過狀態(state), 城市(city), 郵編(zip code), 臥室個數(# of bedrooms)以及住宅類型(residence type)等字段來過濾這個列表。
這個頁面依賴于Knockout.js庫提供的數據綁定。這個綁定使得用戶改變某一選擇時,會立即反映到其他控件中可以使用的選項。舉例來說,如果用戶選擇了City控件中的Philadelphia,緊接著,Zip Code控件中將只會顯示和Philadelphia相關的郵編,以此類推。
背景
在過去,我使用javascript一次又一次的實現了這個功能。雖然可以正常工作,但是代碼非常的冗長,并且工作量很大。我最近開始使用 Knockout(http://knockoutjs.com/)這個javascript庫,所以,我決定試一下看看Knockout是否能將這個問題變得簡單。
我想使這個庫更加的靈活,以便:
1. 任何數量的選擇控件可以以等級的方式相連。你可以通過指定一個你想過濾的記錄中的屬性名稱,以及一個可選的和父級控件對應的屬性名稱來配置每一個控件。
2. 控件可以是下拉控件或多選控件的混合體。
3. 控件之間可以有任意級別的父級和子級關系,或者其中的某些控件可以獨立于其他的控件。
4. 控件是被動態創建的,因此,不必對每一個控件進行硬編碼。這意味著通過使用控件的配置變量來配置頁面。
內部結構
所有的內部javascript代碼都放入了 SelectFilters.js文件中。 這就很容易實現類之間的兼容,并且應用中的多個web頁面都可以引用這個文件。
SelectFilters.js文件定義了2個類. 第一個是 'ViewModel' 對象. 正如所有的 Knockout 頁面一樣,頁面上控件的數據綁定引用viewmodel或其包含的對象的屬性。
第2個類稱作 'selectFilter'. selectFilter 對象是為每個SELECT控件而創建的,SELECT控件使用Knockout數據綁定直接綁定到該對象,
當用戶使用SELECT控件進行選擇的時候,就有消息通過虛擬的數據綁定屬性發送到對應的 selectFilter 對象,該屬性由SELECT控件定義如下:
<select style="vertical-align:top" multiple data-bind="attr: { multiple:multiSelect}, options: availableValues, value: value, selectedOptions: values" >
這個對象轉而調用 viewModel.resolveSelections() . ViewModel 對象則遍歷所有的selectFitler對象,并在每個對象上調用 selectFilter.setAvailableOptions().這個時候,選項列表會重新計算.因為這個選項列表(availableValues)是一個 ko.observableArray,并且是數據綁定到SELECT控件選項的,所以這個選項列表會自動更新.
在調用 viewModel.resolveSelections() 的過程中,所有記錄的列表都是基于用戶選擇重新篩選的.在我們的頁面中,它是數據綁定到頁面底部的表格(table)的,有如下的數據綁定屬性:
<tbody data-bind="foreach: selectedItems">
因為viewModel.selectedItems 列表也是一個 ko.observableArray, 所以顯示的表格(table)也會自動更新.同樣,在函數中,activeFilters 數組(array)也會更新.它也是數據綁定到列表的,這個列表是在表格(table)之上過濾數據的.
文件
在SelectFiltersExample.zip之中最重要的文件如下:
-
SelectFiltersExample/Views/Home/Index.cshtml -- 主頁的HTML文件.
-
SelectFiltersExample/Scripts/SelectFilters.js -- 兩個javascript類和一個Knockout自定義函數loadByProperties.
-
SelectFiltersExample/Scripts/knockout-3.1.0.js -- Knockout庫, 下載至knockoutjs.com
任何想要在另一個環境中,如PHP,使用這些理念的人,僅僅只需要從zip文件中獲取頭兩個文件.
使用代碼
你可以簡單的將包含的代碼的加入到你的工程之中.我選擇在后端用ASP.NETMVC實現,但是選擇的服務平臺沒有差別,都是javascript和html.你可以在Linux服務器或其他服務器上面,用PHP來使用代碼.
為了運行工程里的代碼,你可以打開MVC工程的zip文件,然后加到到Visual Studio.或者只需要添加一些包含的代碼到已有的工程.
為了給你自己的web頁面增加這種能力,你需要做一些事情:
1. 包含一個<script>引用到knockout.js庫和SelectFilters.js文件, 就像它們是這個工程里面HTML文件的最頂層(當然,要拷貝js文件到你的Scripts文件夾).
2. 在你的頁面編寫一些類似的HTML...
這是SELECT控件的HTML.表達式 data-bind="foreach: selectFilters" 將會使得<p>里面的內容重復一次,它針對在viewmodel列表的每一個selectFilter對象.
<!-- SELECT controls from the viewModel.selectFilters collection --> <p data-bind="foreach: selectFilters"> <span style="font-weight: bold; vertical-align: top" data-bind="text: nameLabel"> </span> <select style="vertical-align: top" data-bind="attr: { multiple: multiSelect }, options: availableValues, value: value, selectedOptions: values"> </select> </p>
下面是當前使用的過濾器的可選列表的HTML,它包含一個"clear"連接:
<!-- list the currently active filter values --> <ul data-bind="foreach: activeFilters"> <li> <span style="font-weight: bold" data-bind="text: nameLabel"></span> <span data-bind="text: valueText"></span> <a href="#" data-bind="event: { click: reset }">clear</a> </li> </ul>
3. 在服務器方面,提供了一種方式去下載記錄.在我的MVC工程里面,我使用了一個jQuery AJAX調用,它在ready()函數里面返回JSON:
$.getJSON("/Home/GetHomes", model.loadData(model));
4. 在頁面里面修改代碼去加載過濾器,指出你希望用戶過濾下載記錄的什么屬性,以及控件間的所屬關系.示例代碼如下:
// Define the filtering select controls this way. // Parameters to selectFilter() are: // name: name of property to filter on // parentName: name of master select control's property // model: the model object for this view // multiselect: whether to allow selection of multiple values function loadSelects(model) { new selectFilter('State', '', model, 'state', true); new selectFilter('City', 'State', model, 'city', false); new selectFilter('Zip', 'City', model, 'Zip code', true); new selectFilter('BRs', 'Zip', model, '# of bedrooms', true); new selectFilter('HomeType', 'BRs', model, 'home type', true); }
舉例來說,第二個selectFilter()調用創建了一個javascript的selectFilter對象,它與'City' SELECT控件對應: new selectFilter('City', 'State', model, 'city', true);
第二個參數匹配的指定的名稱,并傳遞給第一個selectFilter,因此其父SELECT控件就是'State'列表框。
第三個參數是參考的視圖模式。
第四個參數提供一個標簽值顯示給用戶。你可以根據語言等(條件)來改變它。
第五個參數表示多選(multiselect=false),因此,它可以作為下拉框替代列表框。
有可能的是,下載的這些列表參數是來自于服務器JSON的,是它們動態定義了SELECT控件。僅需要確認第一個和第二個參數在下載的數據記錄中匹配的屬性名稱。
概要
隨意的修改這個Html頁面里面的代碼, 例如: 修改元素的名字(父節點有selectFilter()構造器的),然后觀察頁面的顯示。你也可以修改最后一個參數把true 變成false,等等。
我歡迎任何反饋。 我是Konockout的新手,并且也不是javascript方面最大的專家,所以我確信肯定有人能提出很多改進這個代碼好的建議。