【问题标题】:Knockout: select element options binding + value binding + optionsCaption binding + late ajax response = weird淘汰赛:选择元素选项绑定 + 值绑定 + optionsCaption 绑定 + 后期 ajax 响应 = 怪异
【发布时间】:2012-10-16 12:14:50
【问题描述】:

考虑select 元素上的以下 ko 绑定:

<select data-bind="value: valueObservable, options: optionsObservableArray, 
    optionsCaption: '[None] - this is an optional field'">

...给定如下视图模型:

function MyViewModel()
{
    var self = this;
    self.valueObservable = ko.observable();
    self.optionsObservableArray = ko.observableArray();

    // ajax call to load options
    ko.computed(function() {
        $.ajax(...)
        .success(function(optionsResponse) {
            self.optionsObservableArray(optionsResponse)
        });
    });

    // ajax call to load data value
    ko.computed(function() {
        $.ajax(...)
        .success(function(valueResponse) {
            self.valueObservable(valueResponse)
        });
    });
}

奇怪的是,第二个(数据)ajax 调用在第一个(选项)ajax 调用之前返回。由于select 元素具有optionsCaption 绑定,因此我认为正在发生以下情况:

  1. 数据 ajax 调用完成,将 valueObservable 设置为某个值(如 6、abc 或其他一些非虚假值)。
  2. 因为select元素中只有1个option(由于optionsCaption),并且因为valueObservable绑定到它(通过value绑定),这导致@987654332 @ 改为undefined
  3. 最后,optionsObservableArray 完成并将新的option 元素添加到select,但此时为时已晚:valueObservable 现在包装了undefined 值而不是实际数据值从第一个 ajax 调用返回。

问题:解决此问题的最佳方法是什么?这是我能想到的:

  1. 使用async: false 运行第一个ajax 调用。这可能会减慢页面呈现速度。
  2. 创建一个单独的 observable 用于绑定到选择值(例如value: selectedValueObservable)。然后订阅optionsObservableArray 并使用订阅执行self.selectedValueObservable(self.valueObservable()) 之类的操作。这似乎是一种创可贴。
  3. 通过从服务器发送选项数据(使用 MVC 视图模型),在执行任何 javascript 之前将选择和选项呈现到页面。这使得处理observableArray 的选项变得更加困难。

更新

为了简化示例代码,我从这个问题中省略了另一个问题。实际上,此视图模型用于创建可编辑的数据项列表。所以实际上有超过 1 个下拉列表被渲染到页面。 data ajax 调用确实返回了一个数组,它的成功函数确实设置了一个复杂项目的observableArray。由于下拉列表选项在每个内联表单中都重复使用,因此它被放置在 $parent 视图模型上并且只加载一次。在单个 ajax 调用中传递选择选项也很困难,因为数据项是通过 WebAPI 检索的(返回 IEnumerable,没有空间发送额外的下拉选项)。

【问题讨论】:

  • 将ajax调用包装到computed的原因是什么?
  • @vittore,将 ajax 调用包装在 ko.computed 中会立即执行。基本上,它通过在构造视图模型时发送 ajax 调用来初始化视图模型。
  • 还有很多其他方法可以立即执行函数,而computed 似乎不是为此而设计的。

标签: asp.net-mvc asp.net-mvc-3 knockout.js asp.net-mvc-4 knockout-2.0


【解决方案1】:

如果可能的话,我建议您进行一次 ajax 调用。使您的控制器返回具有对象数组和选定值的复杂对象:

// ajax call to load options and data value
ko.computed(function() {
    $.ajax(...)
    .success(function(response) {
        self.optionsObservableArray(response.options);
        self.valueObservable(response.value);
    });
});

如果无法合并两个 ajax 调用,您可以将第二个 ajax 的调用放到第一个 ajax 的成功回调中:

// ajax call to load options
ko.computed(function() {
    $.ajax(...)
    .success(function(optionsResponse) {
        self.optionsObservableArray(optionsResponse)

        // ajax call to load data value
        $.ajax(...)
        .success(function(valueResponse) {
             self.valueObservable(valueResponse)
        });
    });
});

【讨论】:

  • 感谢您的回答。在我的情况下,您的第一个建议是不可行的,因为所有 ajax 调用都命中 WebAPI,而不是 MVC(我已经更新了问题以反映这一点)。第二个选项会起作用,但我不确定我是否喜欢将串行链接作为最佳实践的想法。
【解决方案2】:

有什么理由不先进行 ajax 调用并在完成时 applyBinding 吗?

$.when(getOptions(), getData()).done(bind) 


function getOptions() { 
    return $.ajax(...).success(vm.optionsObservableArray)
}

function getData() {
    return $.ajax(...).success(vm.valueObservable)
}

function bind() {
    ko.applyBindings(vm, document.getElementById('elem')) 
} 

【讨论】:

  • 这是一个有趣的解决方案。它比这稍微复杂一点,因为 vm 依赖于 getData() 函数。编辑数组中的单个项目可以更改项目应出现的排序顺序,因此在编辑后,我想重新绑定整个数组(并且我使用 ko.mapping 插件来执行此操作)以强制执行新的排序顺序.这就是为什么我让 vm 封装函数来进行 ajax 调用,而不是将它放在 vm 构造和绑定代码之外。
  • 您可以在您的视图模型中执行该操作,没有人阻止您这样做。 bind 只是一个示例,作为我仅作为示例的两个成功处理程序。
  • 我的意思是在获得所有数据后执行与 DOM 的绑定,因为无论如何您都无法在此之前正确渲染 DOM。
  • 我同意,没有什么能阻止我在视图模型中使用这些功能。但是,以这种方式执行此操作与立即执行使用 async: false 执行的视图模型封装的 $.ajax 函数之间的主要区别是什么?在这两种情况下,applyBindings 直到收到所有数据后才会发生,对吧?实际上,可编辑的表单最初对用户是隐藏的,但会显示读取的数据。用户可以单击编辑项目并显示表单,因此从需求角度来看,延迟加载选择选项是可以接受的。
  • 在你的代码中 applyBindings 被执行 before 数据被接收到。
猜你喜欢
  • 2013-05-04
  • 2013-12-06
  • 1970-01-01
  • 2018-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多