【问题标题】:The binding could not detect changes of observableArray when assign a new array to observableArray in Knockout JS在 Knockout JS 中将新数组分配给 observableArray 时,绑定无法检测到 observableArray 的更改
【发布时间】:2011-12-30 18:02:33
【问题描述】:

我有一个绑定到 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();
    ...
}

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

然后是第二版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/

【问题讨论】:

    标签: knockout.js ko.observablearray


    【解决方案1】:

    你可以按照吉恩的建议去做。为了在客户端做所有事情,您可以创建一个封装逻辑的辅助对象:它触发请求,获取响应,有一个“中止”方法来停止请求(如果用户在等待数据时按下“刷新” ),并且只有在所有数据到达时才更新视图模型。

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

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

    【讨论】:

      【解决方案2】:

      如果您是在异步模式下加载数据,问题可能在于该行

      self.itmesArray(tempArray);
      

      之前执行
      LoadDataFromServerAsync(tempArray);
      

      已经完成从服务器加载新数据,所以它一直绑定一个空数组。

      要修复它,您应该在服务器返回新数据后进行绑定。例如,使用 jquery 进行 ajax 调用,您应该在“成功”回调中更新视图模型。这是我的做法:

          $.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”实现。

      【讨论】:

      • 我知道这个问题,但我的数据将来自多个 ajax 请求(实际上数据来自一个文件夹递归,每个文件夹都是一个请求)。所以我会在ajax请求的每个响应中将数据推送到数组中
      • 那么,您的问题是在插入新元素之前从数组中删除正确的元素?如果服务器返回的每个元素都有一个属性指示来源(我们称之为
      • [上一条评论发送得太早了,抱歉] 那么,您的问题是在插入新元素之前从数组中删除正确的元素?如果服务器返回的每个元素都有一个指示来源的属性(我们称之为“文件夹”),你可以编写类似 items.remove(function(item){ return item.folder == newdata[0].folder; })在添加新数据之前。我在我的平板电脑上写这个,如果需要我可以稍后再详细说明。
      • 我想在点击“刷新”时清空整个数组,然后在这个数组中插入新数据,否则我将构造新对象并进行对象比较
      【解决方案3】:

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

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

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

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

      if (counter > 0) return;
      

      如果你使用“blockui”方式,你应该在每次“完成”回调后检查计数器,如果它是零,那么是时候解锁 UI(“blockui”选项需要一些工作来避免做 UI 事情在 viewModel 中)。

      除非我仍然错过了什么......

      【讨论】:

      • 我计划了你的借调方式,但是这种方式会强制阻止用户刷新数据,他们必须等待旧请求完成。所以我仍然希望我可以将新数据加载到新数组中,以便忽略旧的未完成请求。
      【解决方案4】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-03-19
        • 2013-06-30
        • 1970-01-01
        • 1970-01-01
        • 2012-01-31
        • 1970-01-01
        • 2013-11-29
        • 1970-01-01
        相关资源
        最近更新 更多