【问题标题】:Jquery-UI sortable list not playing nice with reactive updates in Meteor templateJquery-UI 可排序列表与 Meteor 模板中的反应更新不兼容
【发布时间】:2015-01-12 16:42:35
【问题描述】:

我正在尝试以http://differential.com/blog/sortable-lists-in-meteor-using-jquery-ui 中描述的方式使用 JQuery-UI 实现可排序的对象列表。

但是,我不是对单独文档的列表进行排序,而是对嵌入单个文档中的对象列表进行排序。也就是说,我有一个这样的文件:

{  name: "Name of this Rolodex",
   cards: [{name: "...", rank: 0, id: "some-unique-id"},
           {name: "...", rank: 1, id: "some-other-unique-id"}, ... ]
}

我只想让卡片可排序。我的模板如下——它传递了一个 Rolodex 作为上下文:

<template name="rolodex">
  Rolodex Name: {{name}}
  <div class="cards-list">
    {{#each sortedCards}}
      {{> cardTemplate}}
    {{/each}}
  </div>
</template>

还有辅助JS文件:

Template.rolodex.helpers({
  sortedCards: function() {
    return this.cards.sort(function(cardA, cardB) {
      return cardA.rank - cardB.rank;
    });
  }
});

Template.rolodex.rendered = function() {
  this.$(".cards-list").sortable({
    stop: function(e, ui) {
      // Get dragged HTML element and one immediately before / after
      var el = ui.item.get(0)
      var before = ui.item.prev().get(0)
      var after = ui.item.next().get(0)

      // Calculate new rank based on ranks of items before / after
      var newRank;
      if(!before) {
        // First position => set rank to be less than card immediately after
        newRank = Blaze.getData(after).rank - 1;
      } else if(!after) {
        // Last position => set rank to be more than card immediately after
        newRank = Blaze.getData(before).rank + 1;
      } else {
        // Average before and after
        newRank = (Blaze.getData(after).rank + 
                   Blaze.getData(before).rank) / 2;
      }

      // Meteor method that updates an attribute for a single card in a
      // Rolodex based on IDs for the Rolodex and Card
      Meteor.call('cards/update',
                  Blaze.getData(ui.item.parent().get(0))._id, // Rolodex ID
                  Blaze.getData(el).id, // Card ID
                  {rank: newRank});
    }
  });
};

我遇到的问题是,在将卡片分类到预期位置后,DOM 会更新为卡片处于新的错误位置。但是服务器存储了正确的排名,刷新页面会导致卡片列在正确的位置(至少在尝试另一种排序之前)。

我对正在发生的事情的最佳猜测是 Meteor 的模板系统似乎不理解 JQuery-UI 已经移动了 DOM 元素并以错误的顺序响应性地更新了我的模板。

例如,假设我的牌是:A、B、C。我移动 C 使得我们现在有 C、A、B。JQuery-UI 相应地更新 DOM 并触发一个事件,导致 C 的排名更改为小于A。

然而,Meteor 并不知道 DOM 已经被 JQuery-UI 改变了。但是,它确实看到了等级到 C 的变化,并根据先前关于列表顺序的假设来反应地更新列表的顺序。在这种情况下,我们最终得到 B、C、A。

关于我在这里做错了什么有什么建议吗?

【问题讨论】:

    标签: javascript jquery-ui meteor meteor-blaze


    【解决方案1】:

    Meteor 反应性迫使您选择负责 DOM 更新的人员。

    虽然可以让 Blaze 渲染 DOM,然后使用第三方库(通常是在 .rendered() 方法中调用的 jQuery 插件)对其进行操作,但您现在处于 Blaze 不知道什么情况下发生在您的 DOM 上,因此每个后续的响应式更新都无法保持一致。

    这就是为什么对于交互式和反应式界面元素,我们需要一类新的插件/组件/包流星感知(或更好的反应感知)。例如,请参阅将 datatables.net 移植到 Meteor 与 Meteoric reactive-tablesdifficulties

    综上所述,我克服这个问题的方法是编写一些响应式代码,在数据库更新时负责销毁和重建插件。这样,你恢复了 Blaze 知道的原始 DOM,让他更新它,然后重新调用你的 jQuery 插件。这与最佳解决方案相去甚远(非常远),但救了我几次。

    对于.sortable(),也许最好的解决方案是使用Collection.find() 中的选项{reactive: false} 禁用反应性,这样当您使用Meteor.call 更新卡片的属性时,不会发生重绘,但您的界面已经一致。

    【讨论】:

    • 感谢您的快速回复。我真的不想禁用反应性(即,如果服务器说应该这样做,我确实希望使用这种方法),所以这可能只会留下破坏和重建。也就是说,我有点困惑为什么它似乎可以处理集合中的文档集,而不是单个文档中的嵌入数组。理论上,这两种情况都应该触发 DOM 和 Blaze 模型之间的相同不匹配。
    • 好的,当 jQuery 组件完全破坏 DOM 以实现他的目标时,我的解释适用。 .sortable() 不是这种情况,因此它可能与您的代码逻辑有关。例如它来自哪里Blaze.getData(el).id ?根据您的模型,它是undefined。也许如果你发布一个完整的测试用例应用程序,我可以彻底检查它。
    • 啊,你的评论帮我弄明白了。我的模型确实有一个id 属性(更新问题以反映这一点),但它应该是_id。显然,_id 属性对 Blaze 具有特殊意义(这解释了为什么 sortable 可以处理整个文档,但不一定可以处理任意对象数组)。再次感谢您的帮助。
    【解决方案2】:

    Meteor/Blaze 使用 _id 属性来识别数据对象并将它们链接到 DOM 元素。这不仅适用于 Collection 游标返回的文档数组,也适用于任何对象数组。所以在上述问题中,问题在于我使用id 值来识别每张卡,而不是_id。将id 切换为_id 可解决此问题并允许 Blaze 正确更新 DOM,即使 DOM 之前已被 JQuery-UI 的可排序插件修改。

    【讨论】:

    • 使用 _id 确实是管理可排序对象数组的最佳方式!谢谢
    • 好的,所以更改为 _id 工作,我很感激,但是没有这个我有什么办法可以让它工作吗?我不想在我的应用中更改整个架构
    • 伙计,你救了我的命!你保存了一个非常棒的项目!谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-07
    • 2019-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-26
    • 2020-06-28
    相关资源
    最近更新 更多