【问题标题】:Knockout forEach not displaying new items when using sort使用排序时,淘汰赛 forEach 不显示新项目
【发布时间】:2014-07-29 22:47:36
【问题描述】:

您好,感谢您的关注。

我有一个层次很深的动态调查:

<assessment>
    <criterionTypes>
      <criterionType>
        <criteria>
          <criterion>
            <responses>
              <response>
                <assessmentResponses>
                  <assessmentResponse>

有时,我会让数组中的一些可观察项没有以正确的顺序绑定到表单。

尽管我迭代了该集合,在将其移交给视图之前调整了排序,甚至在 Chrome 调试器中以正确的顺序查看该列表。我能够通过在 foreach 中为那些表现不佳的集合应用一个排序例程来解决这个问题。像这样:

  <!-- ko foreach: criterionTypes -->
  <div data-bind="foreach: criteria.sort($root.context.sort.criteria),

<!-- ko foreach: assessmentResponses.sort(function (l, r) { return (l.id() == r.id()) ? (l.id() > r.id() ? -1 : 1) : (l.id() > r.id() ? -1 : 1) }) ... -->

到目前为止一切顺利。但是,我有一个功能,用户可以添加新行,并且当模型在淘汰赛中更新时,UI 不会反映这些更改。因此,如果我放弃排序,模型绑定会正常工作,并且 UI 会按预期更新。

在我的更新按钮中,我尝试在点击事件中重新绑定:

var addResponse = function (response) {
        core.addResponse(assessment(), response); 
        ko.applyBindings(assessment);
    };

但无论我尝试绑定什么对象(评估、响应等),我都会遇到同样的错误

未捕获的错误:无法解析绑定。 消息:ReferenceError:路由器未定义; 绑定值:compose: {model: router.activeItem, afterCompose: router.afterCompose, 过渡:'入口'}

我不确定我该如何处理。也许一个自定义绑定会在 foreach 上执行排序,但我无法解决这个问题(双关语)。

    <div id="boolean.secondaryResponse" data-bind="if: isSecondaryResponse(), visible: $parent.showSecondaryResponse()>
<!-- ko foreach: assessmentResponses.sort(function (l, r) { return (l.id() == r.id()) ? (l.id() > r.id() ? -1 : 1) : (l.id() > r.id() ? -1 : 1) }) -->                      
                          <!-- ko if: customResponse().template() == 'ingredientSource'-->
                          <div class="col-md-2 col-lg-2" style="z-index: 10"><select data-bind="value: explanation, options: $root.controls.ingredientOrigins, event: { change: $root.context.selectionChanged }, attr: { class: 'form-control ' + criterionCode() + ' ordinal-' + customResponse().ordinal() } " class="remove"></select> &nbsp;</div>&nbsp;
                          <!-- /ko -->
                          <!-- ko if: customResponse().template().startsWith('span')-->
                          <div data-bind="attr: { class: customResponse().template() + ' col-sm-2  col-md-4' }" style="margin-left: -10px; z-index: 10; height: 69px"><input type="text" data-bind="value: textualResponse, attr: { class: 'form-control auto' + customResponse().name() + ' ordinal-' + customResponse().ordinal(), placeholder: customResponse().placeholder }" class="remove" /></div>
                          <!--/ko -->
                          <!-- /ko -->
                          <!-- /ko -->
</div>

【问题讨论】:

  • 您是否考虑过使用 ko.computed 对 viewmodel 中的 observable 数组进行排序并绑定计算的 foreach。过去这对我来说效果很好。

标签: knockout.js


【解决方案1】:

如何处理数组排序取决于您的应用程序设计。最重要的问题通常是“排序对模型重要还是仅对视图(UI)重要?”

按视图排序

如果它只对视图很重要,那么您无需担心保持视图模型中的数据排序。您只需要在视图中显示它时对其进行排序。为此,您可以绑定到已排序数组的副本:

foreach: criteria.slice(0).sort(criteriaSortingFunction)

你也可以在你的视图模型中使用一个计算出来的 observable 来帮助保持你的视图干净:

this.sortedCriteria = ko.computed(function () {
    return criteria.slice(0).sort(criteriaSortingFunction);
}, this);

模型排序

如果在模型中对数据进行排序很重要,那么一种方法是确保在更新 observable 之前对数据进行排序:

this.addCriteria(toAdd) {
    var rawArray = this.criteria();
    rawArray.push(toAdd);
    rawArray.sort(criteriaSortingFunction);
    this.criteria(rawArray);
}

如果您在很多地方更新数组,这可能会重复。或者,您可以添加一个扩展器来保持数组排序:

ko.extenders.sorted = function (obs, sortFunction) {
    obs.sort(sortFunction);
    obs.subscribe(function (array) {
        array.sort(sortFunction);
    });
}

这可以在您的视图模型构造函数中应用如下:

this.criteria = ko.observableArray(initialCriteria).extend({sorted: criteriaSortingFunction});

【讨论】:

  • 迈克尔,感谢您的周到和深思熟虑的回答。我想标记两个答案,因为它们同样具有指导性和正确性;我给 huocp 打分只是因为你的地位要高得多,但我的感谢同样衷心。
【解决方案2】:

除了Michael的回答,我想澄清一下对ko的observableArray.sort()的一些理解。

在控制台中尝试

> var arr = ko.observableArray([3,1,4,2]);
> var b = arr.sort();
> arr();  // arr.sort() mutate arr itself
< [1, 2, 3, 4]
> ko.isObservable(b); // result of arr.sort() is not a observable
< false
> b
< [1, 2, 3, 4]

在 ko 中,绑定中的任何重要表达式都会自动包装为 ko.computed( ko.dependentObservable )。对于data-bind="foreach: arr.sort()",ko 将构建一个ko.computed(function() { return arr.sort();})

这里的问题是arr.sort() 不会触发 ko 中的自动依赖跟踪机制。

在控制台试试这个

// k1 is not dependent on arr!
// (I'm not sure whether this is intended in knockout)
> var k1 = ko.computed(function() { return arr.sort();});
> k1.getDependenciesCount();
< 0
> arr.getSubscriptionsCount();
< 0

// k2 is dependent on arr!
> var k2 = ko.computed(function() { return arr().sort();});
> k2.getDependenciesCount();
< 1
> arr.getSubscriptionsCount();
< 1

这是一个演示,显示 k1 不响应 arr 更改,但 k2 可以。 http://jsfiddle.net/gfHz3/8/

所以一个简单的解决方法是使用assessmentResponses().sort(...)

assessmentResponses() 上的原生 sort(...) 仍然会变异 assessmentResponses 持有的内容。在迈克尔的回答中,他使用slice(0) 来解决依赖问题,它还具有在排序前复制数组的效果。就个人而言,我总是使用underscore sortBy 功能,这是非破坏性的。

顺便说一句,还有一件事,这一行

return (l.id() == r.id()) ? (l.id() > r.id() ? -1 : 1) : (l.id() > r.id() ? -1 : 1);

完全一样

return l.id() > r.id() ? -1 : 1;

我认为您尝试编写以下内容?

return (l.id() == r.id()) ? 0 : (l.id() > r.id() ? -1 : 1);

【讨论】:

  • 一篇非常出色和深思熟虑的帖子,不仅因为您解决了我的问题,还因为您以清晰的方式解释了我忽略的内容。我读过的另一篇关于排序的文章说要执行 collection.sort() 以避免性能下降,但没有提到为了获得这种效率而放弃什么。也感谢顺便说一句的提示,我在发帖后也发现了这个错误,但我的笔记本电脑很快就融化了(没有真正的最终温度读数是 212!)
猜你喜欢
  • 2013-05-29
  • 2013-08-03
  • 2013-09-16
  • 2019-06-17
  • 1970-01-01
  • 1970-01-01
  • 2016-04-27
  • 2014-12-06
  • 1970-01-01
相关资源
最近更新 更多