3

我正在编写一个 Knockout.js 应用程序,它在实体图上执行基本的 CRUD 操作。我从我的服务器加载了两个 JSON 编码的对象:第一个是包含一堆产品详细信息的数组,第二个是索引图像列表,如下所示:

function UploadedImage(data) {
    this.UploadedImageId = data.UploadedImageId;
    this.Url = data.Url;
    this.Name = data.Name;
    this.AltText = data.AltText;
}

...通过 AJAX 请求存储在 ViewModel 中,如下所示:

$.post("@Url.Action("JsonRepairData")", {}, function (response) {
    self.repairData(_.map(response.repairData, function(d) { return new AppleProduct(d); }));
    self.images(_.map(response.images, function (i) { return new UploadedImage(i); }));
    self.dataLoaded(true);
}, 'json');

我已经验证数据在对象图中和图像数组中都正确加载。对象图中的图像 ID 对应于数组中的图像 ID。为了显示为对象图中的相关数据条目选择的图像,我选择了一个选择列表,如下所示:

<tbody data-bind="foreach: repairData">
    <tr>
        <td>@Html.TextBoxFor(m => m.Name, new { data_bind = "value: Name" })</td>
        <td><select data-bind="options: $root.images,  optionsCaption: 'Pick an image...', optionsValue: 'UploadedImageId', optionsText: 'Name', value: UploadedImageId"></select></td>
        <td data-bind="html: ImgTag"></td>
        <td><a href="#" data-bind="click: $root.deleteProduct">delete</a></td>
    </tr>
</tbody>

请注意,包含在 ViewModel 中的 repairData 是上述对象图。所有其他数据都正确绑定。选择框已成功填充。在操作选择框时,ViewModel 中绑定的 UploadedImageId 的值会更新。但是,ViewModel 的 UploadedImageId 初始值(来自对象图)被忽略,当页面呈现时,UploadImageId 立即更新为未定义。这是在对象图中设置 UploadedImageId 的初始值的方式:

function AppleProduct(data) {
    var productSelf = this;
    this.Id = data.Id;
    this.Name = ko.observable(data.Name);
    this.UploadedImageId = ko.observable(data.UploadedImageId);
    this.Variations = ko.observableArray(_.map(data.Variations, function (v) { return new ProductSeries(v); }));
    this.ImgTag = ko.computed(function () {
        var img = _.find(self.images(), function (i) { return i.UploadedImageId == productSelf.UploadedImageId(); });
        return img ? '<img src="'+img.Url+'" />' : '';
    });
    // Remove computed ImgTag from data addressed to the server
    this.toJSON = function () {
        var copy = ko.toJS(this);
        delete copy.ImgTag;
        return copy;
    };
}

我一直试图弄清楚如何让选择框正确设置几个小时,但我仍然完全被难住了。我所能确定的是,在 2.1.0 淘汰赛 js 源文件中,负责在第 1402 行(见下文)设置选择框初始值的代码被传递了两次。第一次模型值正确对应于图像 ID,但 element.options.length == 0 并且不使用模型值。第二次传递此代码时,模型值都未定义,但选择框已填充(因此 element.options.length 是正数),因此再次未设置正确的初始值。

for (var i = element.options.length - 1; i >= 0; i--) {
    if (ko.selectExtensions.readValue(element.options[i]) == value) {
        element.selectedIndex = i;
        break;
    }
}

任何有助于理解我所缺少的东西将不胜感激!

4

1 回答 1

3

正如 nemesv 巧妙地发现的那样,问题在于 ViewModel 数据的总体排序:

$.post("@Url.Action("JsonRepairData")", {}, function (response) {
    self.repairData(_.map(response.repairData, function(d) { return new AppleProduct(d); }));
    self.images(_.map(response.images, function (i) { return new UploadedImage(i); }));
    self.dataLoaded(true);
}, 'json');

由于 Knockout.js 依赖跟踪的激进性,系统甚至在访问图像数据之前就尝试填充对象图视图。这就是为什么当它尝试创建选择框时,选择框没有数据来填充它们并且无法设置它们的默认值。通过切换repairData 和images ViewModel 数据加载的顺序,图像数据在需要并且应用程序运行时变得可用。

这似乎表明,通过 ko.observables 进行的 Knockout.js 依赖项跟踪虽然是一种强大的表达工具,但如果不对 ViewModel 状态突变导致的意外逻辑采取适当的措施,可能会很危险。

于 2013-11-07T14:07:55.770 回答