4

我对 knockout.js 还很陌生,但是,我一直很高兴在我的 ASP.NET MVC 4 项目中使用它,直到我遇到了这个困扰我一段时间的障碍,似乎无法将我的手指在它上面。

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

数据采用 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 的国家)和 Country 子数组(包含国家的城市) .

然后是预填充选项的问题,它部分工作,视图模型加载行数,但不选择任何内容。

这是虚拟机:

   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 代码:

<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/

任何帮助,将不胜感激。

4

1 回答 1

5

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

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

在这里ID,您将 JSON 数据中的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:未定义国家;

您的第三个 Cities 选择列表也存在同样的问题,因为您现在尝试绑定到country.Citieswherecountry也只是ID.

这里有两个选项。第一个是删除参数,从而将实际optionsValue的 JSON 对象绑定到您的视图模型属性。唯一的问题是您的 Cities 选择框上的绑定错误(您绑定到而不是):CityNameName

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);

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

于 2013-01-27T20:56:11.230 回答