在Knockout JS中将新数组赋给observableArray时,绑定无法检测到observableArray的更改

时间:2011-12-29 08:28:37

标签: knockout.js ko.observablearray

我有一个模板绑定到observableArray

<ul data-bind="template: { name: 'defaultTemplate', foreach: itemsArray}"></ul>
...

我需要通过点击链接每次刷新数据

<a data-bind="click: LoadData"><span>Refresh</span></a>

我的viewModel的第一个版本:

function viewModel (){  
    this.itmesArray = ko.observableArray([]);
    self = this;
    this.LoadData(){
        if('undefined' != typeof MusicArray )
            self.itmesArray.removeAll();
        LoadDataFromServerAsync(self.itmesArray);
    }   
    //ini
    LoadData();
    ...
}

但问题是数据是从异步中的服务器加载的,所以当我用很少的时间间隔快速点击链接时,数据可能会在我的obeservable数组中重复多次。 所以我认为我需要在每次刷新时将数据加载到新数组。

然后是viewModel的第二个版本:

function viewModel (){  
    this.itmesArray = ko.observableArray([]);
    self = this;
    this.LoadData(){
            if('undefined' != typeof MusicArray )
                self.itmesArray.removeAll();
            var tempArray = new Array();
            LoadDataFromServerAsync(tempArray);
            self.itmesArray(tempArray);
    }   
    //ini
    LoadData();
    ...
}

而新的问题是UI无法感知数组的变化,似乎 self.itmesArray(tempArray)将构造一个新的observableArray对象,但模板绑定仍然是跟踪旧的observableArray对象,我不确定这个。 所以我想知道如何通知我的阵列已更改的模板/ ui绑定,或者是否还有其他解决方法来解决我的问题?

已添加:jsFiddle上的代码http://jsfiddle.net/zzcJC/10/

4 个答案:

答案 0 :(得分:1)

你可以像Gene建议的那样做。为了在客户端执行所有操作,您可以创建一个辅助对象来封装逻辑:它触发请求,获取响应,有一个“中止”方法来停止请求(如果用户在等待数据时按“刷新”) ),并仅在所有数据到达时更新viewmodel。

我创建了一个jsFiddle:http://jsfiddle.net/saurus/dE6S9/

这使用“setTimeout()”来模拟ajax调用,将超时ID存储在一个数组上(你将存储由ajax调用返回的xhr),并调用“cancelTimeout()”来中止调用(你将使用“xhr” .abort或类似的。)

答案 1 :(得分:0)

如果要以异步模式加载数据,问题可能就是行

self.itmesArray(tempArray);

之前执行
LoadDataFromServerAsync(tempArray);

已完成从服务器加载新数据,因此它会保持绑定空数组。

要修复它,您应该在服务器返回新数据后执行绑定。例如,使用jquery进行ajax调用,您应该在“success”回调中更新viewmodel。我是这样做的:

    $.ajax({
      type: "POST",
      url: "ws.asmx/GetData",
      data: "{}",
      contentType: "application/json; charset=utf-8",
      dataType: "json",
      processData: false,
      success: function(msg) {
           viewModel.itmesArray(msg);
      }
    });

如果这对您没有帮助,我认为我们需要查看'LoadDataFromServerAsync'实现。

答案 2 :(得分:0)

所以,如果我理解正确,你想点击“刷新”,这会触发一些ajax调用,从这些调用返回的数据完全替换旧数据。

一种方法是不确定每个呼叫是否独立地为其他呼叫工作。例如,我在评论中快速建议做一些事情:将每个调用分开,当单个调用返回新数据时,从数组中删除旧数据并添加新数据。这假设您可以区分不同调用返回的元素。

另一种方法是完全避免多次调用,你可以阻止UI交互直到完成(例如使用jquery blockui插件或类似的东西),或者如果你想让用户在你更新时继续使用界面它,你可以简单地避免在有些人还在飞行中时发出新请求。

这两种方式都需要知道请求序列何时开始,但这很容易:这种情况发生在“LoadData”方法的开头。知道完成所有请求的时间有点复杂,也许有更好的方法,如果没有,你可以创建一个计数器,为你发出的每个ajax请求递增1,然后,对于每个完成的请求,“推送”新数据在observableArray上,并递减计数器(在jquery中我将使用“完整”回调,因为即使出现错误也可以被调用)。在“LoadData”的最开始(甚至在清除数组之前)添加一个测试:

if (counter > 0) return;

如果你使用“blockui”方式,你应该在减去它之后检查每个“完整”回调中的计数器,如果它为零则是时候取消阻止UI(“blockui”选项需要一些工作来避免做UI事情在viewModel)。

除非我还缺少某些东西......

答案 3 :(得分:0)

也许处理重叠请求的一种方法是在客户端中保留用户操作计数器,并将该值传递给服务器,假设您可以控制服务器发回的内容。然后,您可以忽略具有过期用户操作值的响应。如果您无权访问服务器数据,则仍可以通过将操作计数器值粘贴到成功处理程序中来使用此方法。这是这段代码的草图。 (警告:未经测试)

function viewModel() {
    var self = this;
    var actionCounter = 0;
    var items = ko.observableArray([]);


    // This is triggered by a user request
    this.handleUserAction = function() {
        self.clearData();
        self.loadData(0, ++actionCounter); // assume 0=root
    }

    this.clearData = function() {
        this.items.removeAll();
    }

    // return some handle of the next request, or null
    this.moreRequestsRequired = function(data) {
         ...
         return ...;
    }

    this.processData = function(data, reqNo) {
        // this is where you would append it to the array
        ...

        // this is where the recursive call could be made
        var handle = self.moreRequestsRequired(data);
        if (handle)
            loadData(handle, reqNo);
    }

    this.loadData = function(handle, n) {
        var reqNumber = n;
        $.ajax('server/request', {
            data: {dir: handle},
            success: function(data) {
                if (reqNumber == actionCounter)
                   processData(data);
            }
        });
    }
};