1

我在我的 MVC 项目中使用淘汰赛 (KO)。我在服务器上创建了一个 MVC 模型(用于网格)并将其传递给视图。在视图上,它被序列化并转换为 KO 模型(使用 ko.mapping),该模型又用于绑定。然后在 HTML 中使用该绑定来创建网格。

这就是我的 MVC 网格模型的样子,然后通过 ko.mapping 将其转换为相应的 KO 模型:

public class GridModel
    {
        /// <summary>
        /// Grid body for the grid.
        /// </summary>
        public GridBodyModel GridBodyModel { get; set; }

        /// <summary>
        /// Grid context.
        /// </summary>
        public GridContext GridContext { get; set; }

        /// <summary>
        /// Grid header for the grid.
        /// </summary>
        public GridHeaderModel GridHeader { get; set; }
    }

public class GridBodyModel
    {
        /// <summary>
        /// List of grid body rows.
        /// </summary>
        public IList<GridRowModel> Rows { get; set; }
    }


public class GridContext
    {
        /// <summary>
        /// Total number of pages. Read-only.
        /// </summary>
        public int TotalPages{ get; set; }
    }

public class GridHeaderModel
    {
        /// <summary>
        /// List of grid header cells.
        /// </summary>
        public IList<GridHeaderCellModel> Cells { get; set; }
    }

很明显,主模型类 GridModel 由以下作为属性存在的类组成:

GridBodyModel:具有要在网格主体中呈现的行列表。

GridContext:具有总页数作为属性。它还具有其他属性,但这超出了本次讨论的范围。

GridHeaderModel:具有必须显示在网格标题中的单元格列表。

然后我有这个脚本,它将在新页面加载时执行。

$(document).ready(function () {
        // Apply Knockout view model bindings when document is in ready state.
        ko.applyBindings(Global_GridKOModel, document.getElementById("gridMainContainer"));
    });

// Serialize the server model object. It will be used to create observable model.
Global_GridKOModel = ko.mapping.fromJS (<%= DataFormatter.SerializeToJson(Model) %>);

Global_GridKOModel是全局 javascript 变量。 模型是来自服务器的 MVC 网格模型。

用户可以再次在页面上执行进一步的搜索。我通过 Ajax 发布新的搜索条件来处理这个问题。在这篇文章中,创建了一个新的 MVC 模型并作为 Ajax 响应发回。然后,这个新的 MVC 模型被简单地用于使用 ko.mapping 更新 Global_GridKOModel ,这反过来又刷新了之前在新页面加载时构建的网格(使用新数据)。我就是这样做的。

$.ajax({
        url: destUrl,
        data: dataToSend
        success: function (result) {
            ko.mapping.fromJS(result, Global_GridKOModel);
        },
        error: function (request, textStatus, errorThrown) {
            alert(request.statusText);
        }
    });

除以下情况外,一切正常。

发出一个没有返回结果的 Ajax 请求,即模型 GridModel 中的 GridBodyModel 和 GridHeaderModel 为空。该时间网格正确地显示没有找到任何记录。这是对的。这通过以下 HTML 绑定发生。

<!-- When no record is found. -->
<div data-bind="ifnot: GridContext.GridPager.TotalPages() > 0">
    No record(s) were found.
</div>

<!-- This is actual grid table container. This is binded when records are found -->
<div data-bind="if: GridContext.TotalPages() > 0">

Grid construction happens here

</div>

现在之后,如果发出另一个 Ajax 请求,但这次返回记录(我已经用 firebug 检查了响应,并确认确实返回了记录)。这次发生了网格构造,其中访问了各种可观察的数组。例如,为以下网格构造寻呼机是我编写的一段 HTML 绑定。

<td data-bind="attr:{colspan: GridHeader.Cells().length }">

这次 KO 抛出以下错误,可以在 firebug 中看到。

无法解析绑定。消息:TypeError:GridHeader.Cells 不是函数;绑定值:attr:{colspan: GridHeader.Cells().length }

只要有记录被返回,它就可以正常工作,但是如上所述在没有返回记录后它会中断。请注意,当没有返回记录时,GridHeader 在早期响应中为空。我在 ko.mapping 中闻到了一些腥味。我认为映射可观察数组时存在一些问题。

那么我做错了什么?请问有人吗?

如果我没有清楚地提到事情,请随时要求澄清。

提前致谢。

4

2 回答 2

2

行为的实际原因是最初 GridHeader 是 javascript 对象,但是当您的返回调用映射为 GridHeader 属性为 null 时,它将变为具有 null 值的可观察的,并且在所有未来的更新中它仍然是可观察的。

解决方法是(选择一个):

  1. 不要为子模型返回空值(而是返回空对象) - 我认为这是首选方式
  2. 或者 - 在调用 map 之前,运行 Global_GridKOModel.GridHeader = null; (这将确保更新后 GridHeader 不会是可观察的)
  3. 或者 - 映射后,调用 Global_GridKOModel.GridHeader = ko.unwrapObservable(Global_GridKOModel.GridHeader);

您的行为的完整解释在下面的代码中(在 jsfiddle 上复制 - http://jsfiddle.net/nickolsky/zrBuL/9/

console.log('step1');
var vm = ko.mapping.fromJS({ body: { children: [ 1, 2, 3]} });
console.log(vm.body.children()[1]); //prints 2

console.log('step2');
ko.mapping.fromJS({ body: { children: [ 4, 5, 6]} }, vm);
console.log(vm.body.children()[1]); //prints 5

console.log('step3');
//after next call, vm.body is no longer variable - it is now observable
ko.mapping.fromJS({ body: null }, vm);
console.log(vm.body); //prints observable
console.log(vm.body()); //prints null

console.log('step4');
//and it will remain observable for next call
ko.mapping.fromJS({ body: { children: [ 7, 8, 9]} }, vm);
console.log(vm.body().children()[1]); //prints 8
console.log(vm.body().children().length); //prints 3
console.log(vm.body); //prints observable function body
console.log(vm.body()); //prints object

//workaround for null issue - we can reset it to be null instead of null observable
//and it will get original behavior
vm.body = null;
console.log('step5');    
ko.mapping.fromJS({ body: { children: [ 7, 8, 9]} }, vm);
console.log(vm.body.children()[1]); //prints 8 - body is no longer observable again
​

对我来说,这看起来像是映射插件的设计问题,我无法找到一种方法来自定义子视图模型属性的映射,以确保它们在为空时不会成为可观察对象(“复制”功能将使所有内部内容成为不可观察,不幸的是)。

于 2012-05-18T14:28:44.340 回答
1

我昨晚想通了。实际上,这是我犯的非常基本的错误。将数组设置为空后,我试图访问它。我将解释它如何。

当返回结果时,定义了 GridHeaderModel 和其中的 IList 单元,即不为空。那时 ko.mapping 能够转换模型并在其中创建模型和数组。但是,当发出任何 ajax 请求时,返回 noo 结果并且 GridHeaderModel 为空,显然 IList Cells 也为空。那个时候 ko.mapping 也做了同样的事情,它更新的 KO 模型也将 GridHeaderModel 设置为 null 并且里面的可观察数组 Cells 不存在,这和 null 一样好。现在,当我发出另一个返回一些记录的 ajax 请求时,ko.mapping 尝试更新客户端上 KO 模型中不存在(或设置为 null)的可观察数组 Cells,但失败了。如果不是 ajax 而是新的页面加载,那么一切都会奏效。因此,解决方案是不要将任何未初始化的枚举(那些将被转换为可观察数组的枚举)返回给客户端以进行 KO 模型更新。因此,当不返回任何记录时,我确保 GridHeaderModel 不为空,并确保 IList Cells 已初始化,尽管它不包含任何元素。这解决了问题。

这个问题可以用下面的例子来解释。

public class Demo
{
    public IList<string> DemoArray;
}

public class DemoUser
{
    public void UseDemo()
        {
            var demoInstance = new Demo();
            demoInstance.DemoArray.Add("First Element");
        }
}

在 UseDemo() 方法中,我们已经初始化了类实例 Demo,但 IList DemoArray 仍未初始化。因此,当我们尝试访问它时,会抛出运行时异常。这就是我的情况。在第一个 ajax 响应中,我将 observable 数组设置为 null 即取消初始化它,然后在下一个 ajax 响应中我试图访问它。

于 2012-05-19T06:15:10.853 回答