使用 Knockout.js 控制级联选择器
一种轻松控制多个相关 SELECT 控件选项列表的方法。
引言
本文提供源代码,以便您轻松配置一个包含多个 SELECT 控件的网页,用于筛选记录集合(通常显示在网格或表格中)。在示例中,我们使用了一个虚构的住宅地址集合,代表待出租的房屋。该页面允许用户通过州、市、邮政编码、卧室数量和住宅类型来筛选此列表。
该页面依赖于 Knockout.js 库提供的数据绑定。这种绑定会导致用户选择的更改立即反映在其他控件中可用的选项上。例如,如果用户在“市”控件中选择了“费城”,那么“邮政编码”控件将只显示费城的邮政编码,以此类推。
在 FiddleJS 上查看 |
注意:该功能已抽象出来,可以应用于任何类型的记录集合。您只需要指定要筛选的属性名称!
背景
过去,我曾反复用纯 Javascript 实现过这个功能。虽然可行,但编码非常繁琐且劳动量大。我最近开始玩 Javascript 库 Knockout (http://knockoutjs.com/),所以我决定看看 Knockout 是否能让这个问题变得更简单。
我使这个库具有灵活性,以便
1. 任意数量的 Select 控件可以以层级方式链接。您可以通过指定要筛选的记录的属性名称,以及可选的对应父控件的属性名称来配置每个控件。
2. 控件可以是下拉菜单或多选列表框的混合。
3. 控件可以作为父子关系以任意深度层级关联,或者其中一些可以独立于其他控件。
4. 控件是动态创建的,因此不需要硬编码每个控件。这意味着页面可以配置为使用可变的控件集。
内部结构
所有内部 Javascript 代码都放在文件 SelectFilters.js 中。这使得类的移植变得容易。应用程序中的多个网页都可以引用此文件。
在 SelectFilters.js 中,定义了两个类。第一个是 sfViewModel
对象。这是一个子模型,包含在页面本身定义的 modelView
对象中。与所有 Knockout 页面一样,页面控件中的数据绑定引用视图模型或其包含对象之一的属性。
第二个类称为 selectFilter
。为涉及的每个 SELECT 控件创建一个 selectFilter
对象,并通过 Knockout 数据绑定直接将 SELECT 控件绑定到该对象。sfViewModel
包含一个 selectFilter
对象集合。
当用户通过 SELECT 控件进行选择时,通过 SELECT 控件上定义的数据绑定属性,会向相应的 selectFilter
对象发送一条消息。
<select style="vertical-align:top" multiple data-bind="attr: { multiple:multiSelect}, options: availableValues, value: value, selectedOptions: values" >
然后,该对象会调用 sfViewModel.resolveSelections()
。sfViewModel
接着遍历其 selectFilter
对象列表,并对每个对象调用 selectFilter.setAvailableOptions() ,重新计算选项列表。由于选项列表(availableValues
)是 ko.observableArray
并与 SELECT 控件的选项进行数据绑定,因此选项列表会自动更新。
在调用 viewModel.resolveSelections()
时,会根据用户选择重新过滤所有记录的列表。在我们的页面中,它与页面底部的表格通过数据绑定属性进行数据绑定。
<tbody data-bind="foreach: selectedItems">
由于 viewModel.selectedItems 列表也是一个 ko.observableArray
,所以显示的表格也会自动更新。此外,在此函数中,还会更新 activeFilters
数组,该数组也与表格上方的过滤值列表进行数据绑定。
文件
SelectFiltersExample.zip 中最重要的文件是
-
SelectFiltersExample/Views/Home/Index.cshtml
-- 主页的 HTML。 -
SelectFiltersExample/Scripts/SelectFilters.js
-- 2 个 Javascript 类和一个名为loadByProperties
的 Knockout 自定义函数。 -
SelectFiltersExample/Scripts/knockout-3.1.0.js
-- Knockout 库,从 knockoutjs.com 下载。
任何希望在其他环境(如 PHP)中使用此概念的人,只需从 zip 文件中获取前两个文件即可。
使用代码
您可以轻松地将包含的代码添加到您自己的项目中。我选择在后端使用 ASP.NET MVC 来实现它,但服务器平台无关紧要;关键在于 Javascript 和 HTML。您可以使用此代码在 Linux 服务器上配合 PHP,或者任何您需要的环境。
要运行此项目中的代码,请打开 MVC 项目 zip 文件并在 Visual Studio 中加载它,或者将包含的代码添加到您现有的项目中。
要为您的网页添加此功能,您需要执行以下操作:
1. 包含对 knockout.js 库和 SelectFilters.js 文件的 <script> 引用,就像在此项目 HTML 文件顶部一样(当然,请将 js 文件复制到您的 Scripts 文件夹)。
2. 在您的页面中插入一些 HTML...
以下是 SELECT 控件的 HTML。表达式 data-bind="foreach: selectFilters"
会导致 <p> 标签内的内容为视图模型列表中的每个 selectFilter
对象重复一次。
<!-- SELECT controls from the viewModel.selectFilters collection -->
<p data-bind="foreach: selectFilterVM.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,以及它们的“清除”链接。
<!-- list the currently active filter values -->
<ul data-bind="foreach: selectFilterVM.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 项目,我在 ready() 函数中使用了返回 JSON 的 jQuery AJAX 调用。
$.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', false);
第二个参数匹配传递给第一个 selectFilter
的名称,“State”,因此其父 SELECT 控件是“State”列表框。
第三个参数是视图模型引用。
第四个参数提供一个要显示给用户的标签值。您可以根据语言等进行更改。
第五个参数指示 multiselect=false,因此它将显示为下拉框而不是列表框。
也可以从服务器以 JSON 格式下载这些参数列表,以动态定义 SELECT 控件。只需确保第一个和第二个参数与下载的数据记录中的属性名称匹配即可。
摘要
随时在 HTML 页面中尝试修改代码 — 例如,更改一些 selectFilter()
构造函数调用中的父级名称,并观察页面的行为。您还可以将最后一个参数从 true 更改为 false 等。
我将不胜感激任何反馈!我对 Knockout 相当陌生,对 Javascript 也不是特别精通,所以肯定有人有一些关于如何改进这段代码的好建议。
历史
2014 年 7 月 17 日: 初始发布
2014 年 7 月 22 日: 我希望此代码能与页面上的其他 Knockout 模型对象和绑定共存,因此我将 SelectFilters.js 中的 modelView 类重命名,使其成为子模型而不是页面的主视图模型。我将该类重命名为 fsViewModel
。现在,我在页面本身添加了一个新的 viewModel
对象,该对象有一个名为 selectFilterVM
的属性,它是包含筛选选择器的 fsViewModel
子模型。现在,数据绑定中对视图模型属性的所有引用都带有前缀 selectFilterFM.