1

我在我的MVC项目中使用了敲除(KO)。我在服务器上创建一个MVC模型(用于网格)并将其传递给视图。在视图上,它被序列化并转换成KO模型(使用ko.mapping),然后将其用于绑定。该绑定然后用于HTML创建网格。可观察数组的敲除映射似乎不起作用

这是我的MVC网格模型看起来像这反过来又被转换成相应的KO模型由ko.mapping:

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请求,其中没有结果返回,即GridBodyModel和GridHeaderModel在模型GridModel中为null。那次电网正确地表明没有找到记录。这是对的。这发生在下面的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请求制作,但这次返回的记录(我检查与萤火虫的响应,并确认记录是否确实返回)。这次网格构建发生在访问各种可观察数组的地方。例如,为网格构建分页器,下面是我写的一段HTML绑定。

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

这次KO抛出下面的错误,可以在萤火虫中看到。

无法解析绑定。 消息:TypeError:GridHeader.Cells不是函数; 绑定值:ATTR:{列跨度:GridHeader.Cells()}长

它工作得很好,只要有返回的记录,但之后如上所述返回没有记录它打破。请注意,在没有记录返回时,GridHeader在先前的响应中为null。我在ko.mapping中闻到一些可疑的东西。我认为映射可观察数组时存在一些问题。

那么,我做得不好的是什么?请人吗?

如果我没有明确提及,请随时提出澄清。

在此先感谢。

回答

2

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

解决方法是(任选其一):

  1. 不要为童款返回空值(返回,而不是空的对象) - 我认为这是最好的方式
  2. 或者 - 调用地图前,运行 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 
​ 

对于我来说,它看起来像映射插件的设计问题,我是不是能够找到一种方法,自定义子viewmodel属性的映射,以确保它们不会变为可观察对象,如果它们为null('copy'功能将使所有内部内容不可观察,不幸)。

+0

非常感谢Artem花时间阅读。是的你是对的,null不应该返回。我也是昨晚想出来的,但没有发布。请在下面阅读我的回答,我已经提到了我的观察结果,并且它完全反映了您所说的内容。 – Aum

1

昨晚我想通了。其实这是我犯的一个非常基本的错误。我试图在将它设置为null之后访问数组。我会解释它如何。

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

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

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

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

在这里,我们已经初始化的类的实例演示,但与IList DemoArray的UseDemo()方法仍然未初始化。所以当我们尝试访问它时,运行时异常将被抛出。这就是我的情况。在第一个Ajax响应中,我将observable数组设置为null,即将其初始化,然后在接下来的ajax响应中尝试访问它。