knockout.js - 嵌套数组数据和级联预填充下拉列表绑定

时间:2013-01-23 23:54:28

标签: javascript data-binding knockout.js cascadingdropdown

我对knockout.js很新,不过,我一直很乐意在我的ASP.NET MVC 4项目中使用它,直到我遇到这个困扰我一段时间的障碍,似乎无法忍受把手指放在上面。

我正在处理的场景需要几种位置数据组合(区域,国家,城市),即级联下拉列表,这在输入新数据时不是问题,但我遇到了问题(s )当试图编辑保存的数据时。

数据采用JSON格式,嵌套数组如下所示(为简化说明而缩短):

var newData = 
[
  {
    "ID":1,
    "Name":"Australia and New Zealand",
    "Countries":[
      {
        "ID":13,
        "Name":"Australia",
        "Cities":[
          {
            "ID":19,
            "Name":"Brisbane"
          },
          {
            "ID":28,
            "Name":"Cairns"
          },
...

我怀疑我无法正确加载数据(或更清楚地说,绑定),因为我无法访问Region子阵列(包含Region的国家/地区)和国家/地区子阵列(包含国家的城市)。

然后是预先填充选项的问题,这部分工作,viewmodel加载行数,但不选择任何东西。

这是VM:

   var existingRows = [
    {
        "Region": 1,
        "Country": 13,
        "City": 19
    },
    {
        "Region": 1,
        "Country": 158,
        "City": 3
    }];

   var Location = function (region, country, city) {
       var self = this;
       self.region = ko.observable(region);
       self.country = ko.observable(country);
       self.city = ko.observable(city);

       // Whenever the region changes, reset the country selection
       self.region.subscribe(function () {
           self.country(undefined);
       });

       // Whenever the country changes, reset the city selection
       self.country.subscribe(function () {
           self.city(undefined);
       });
   };

   var LocationViewModel = function (data) {
       var self = this;

       self.lines = ko.observableArray(ko.utils.arrayMap(data, function (row)
       {
           var rowRegion = ko.utils.arrayFirst(newData, function (region)
           {
               return region.ID == row.Region;
           });
           var rowCountry = ko.utils.arrayFirst(rowRegion.Countries, function (country) {
               return country.ID == row.Country;
           });
           var rowCity = ko.utils.arrayFirst(rowCountry.Cities, function (city) {
           return city.ID == row.City;
           });
           return new Location(rowRegion, rowCountry, rowCity);
       }));

       // Operations
       self.addLine = function () {
           self.lines.push(new Location())
       };
       self.removeLine = function (line) {
           self.lines.remove(line)
       };
   };

   var lvm = new LocationViewModel(existingRows);

   $(function () {
       ko.applyBindings(lvm);
   });

HTML code:

<tbody data-bind="foreach: lines">
    <tr>
        <td><select data-bind="options: newData, optionsText: 'Name', optionsValue: 'ID', optionsCaption: 'Select a region...', attr: { name: 'SubRegionIndex' + '['+$index()+']' }, value: region"></select></td>
        <td><select data-bind="options: Countries, optionsText: 'Name', optionsValue: 'ID', optionsCaption: 'Select a country...', attr: { name: 'CountryIndex' + '['+$index()+']' }, value: country"></select></td>
        <td><select data-bind="options: Cities, optionsText: 'Name', optionsValue: 'ID', optionsCaption: 'Select a city...', attr: { name: 'CityIndex' + '['+$index()+']' }, value: city"></select></td>
        <td><a href='#' data-bind='click: $parent.removeLine'>Remove</a></td>
    </tr>    
</tbody>

我尝试使用预先填充的数据修改knockout.js网站上的购物车编辑器示例,但实际上没有取得多大进展,我似乎错过了一些东西。没有真正找到嵌套数组的东西所以我被困在这里......

我在这里提出了JSFiddle的完整代码: http://jsfiddle.net/fgXA2/1/

任何帮助都将不胜感激。

1 个答案:

答案 0 :(得分:5)

问题在于您绑定选择列表中所选项目的方式:

<select data-bind="
    options: newData, 
    optionsText: 'Name', 
    optionsValue: 'ID', 
    value: region">
</select>

在这里,您将JSON数据中的ID属性绑定到视图模型上的region属性。

这意味着当您绑定第二个选择列表时:

<td data-bind="with: region">
    <select data-bind="
        options: Countries, 
        optionsText: 'Name', 
        optionsValue: 'ID', 
        value: $parent.country">
    </select>
</td>

您尝试绑定到region.Countries。但是,region只包含所选区域ID。在这种情况下,控制台是你的朋友:

  

未捕获错误:无法解析绑定。消息:ReferenceError:   国家没有定义;

同样的问题适用于您的第三个城市选择列表,因为您现在正尝试绑定到country.Cities country也只是ID

这里有两种选择。第一种是删除optionsValue参数,从而将实际 JSON对象绑定到视图模型属性。那个和你的城市选择框上的绑定错误(你绑定到CityName而不是Name)是唯一的问题:

http://jsfiddle.net/benfosterdev/wHtRZ/

从示例中我可以看到,我使用了ko.toJSON实用程序来输出视图模型的对象图。这在解决问题时非常有用(在您的情况下,您会看到region属性只是一个数字)。

上述方法的缺点是,您最终会在视图模型中存储所选国家/地区的所有国家/地区的副本。

如果处理大型数据集将是一个更好的解决方案,那就是只存储选定的标识符(我相信您最初尝试这样做),然后定义过滤单个数据集以获取所需值的计算属性。

可以使用以下计算属性在http://jsfiddle.net/benfosterdev/Bbbt3上看到此示例:

    var getById = function (items, id) {
        return ko.utils.arrayFirst(items, function (item) {
            return item.ID === id;
        });
    };

    this.countries = ko.computed(function () {
        var region = getById(this.selectedRegion.regions, this.selectedRegion());
        return region ? ko.utils.arrayMap(region.Countries, function (item) {
            return {
                ID: item.ID,
                Name: item.Name
            };
        }) : [];
    }, this);

    this.cities = ko.computed(function () {
        var region = getById(this.selectedRegion.regions, this.selectedRegion());
        if (region) {
            var country = getById(region.Countries, this.selectedCountry());
            if (country) {
                return country.Cities;
            }
        }

    }, this);

您可以从渲染对象图中看到,只有当前选定的国家/地区和城市被复制到视图模型。