使用 Knockout.js 去控制级联选择

jopen 10年前

使用 Knockout.js 去控制级联选择

介绍

该篇文章提供了源代码,可以使你很容易的配置一个包含多个SELECT控件用来过滤一些记录的页面(通常显示在网格或表格中)。在这篇文章中,我们使用一些虚构的地址来表示将要被出租的房子。该页面用户允许通过状态(state), 城市(city), 邮编(zip code), 卧室个数(# of bedrooms)以及住宅类型(residence type)等字段来过滤这个列表。

这个页面依赖于Knockout.js库提供的数据绑定。这个绑定使得用户改变某一选择时,会立即反映到其他控件中可以使用的选项。举例来说,如果用户选择了City控件中的Philadelphia,紧接着,Zip Code控件中将只会显示和Philadelphia相关的邮编,以此类推。

使用 Knockout.js 去控制级联选择

背景

在过去,我使用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&apos;s property      //   model:        the model object for this view      //   multiselect:  whether to allow selection of multiple values      function loadSelects(model) {          new selectFilter(&apos;State&apos;, &apos;&apos;, model, &apos;state&apos;, true);          new selectFilter(&apos;City&apos;, &apos;State&apos;, model, &apos;city&apos;, false);          new selectFilter(&apos;Zip&apos;, &apos;City&apos;, model, &apos;Zip code&apos;, true);          new selectFilter(&apos;BRs&apos;, &apos;Zip&apos;, model, &apos;# of bedrooms&apos;, true);          new selectFilter(&apos;HomeType&apos;, &apos;BRs&apos;, model, &apos;home type&apos;, 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方面最大的专家,所以我确信肯定有人能提出很多改进这个代码好的建议。