【问题标题】:Text Duplicating in Meteor?流星中的文本复制?
【发布时间】:2015-03-30 15:19:11
【问题描述】:

我正在尝试制作一个允许多人同时编辑正文的 Meteor 应用程序(想想 Google Docs/Drive)。

我想为了做到这一点,我需要一个模板来显示当前数据库中的文本,然后每当修改文本时,它都需要更新数据库中的文本。

我能够在下面的这个最小复制中从我的完整应用程序中重现相同的问题(设计为由单个用户而不是多个用户使用,因此从使用 Mongo 集合交换为会话。)

<body>
  {{> hello}}
</body>

<template name="hello">
  <pre contentEditable="true">{{text}}</pre>
</template>

(如果我使用div 而不是pre,则会出现完全相同的问题。)

if (Meteor.isClient) {
  Session.setDefault('text', "Edit me!");

  Template.hello.helpers({
    text: function () {
      return Session.get('text');
    }
  });

  Template.hello.events({
    "input pre": function (event) {
      Session.set('text', $(event.target).text());
    }
  });
}

尝试在应用程序中输入一些内容,您很快就会发现该错误:每次击键时,它都会获取所有现有文本并将其附加到您输入的内容(因此每次击键时,它都会复制所有文本)。这是真正奇怪的部分:这种行为并不总是立即开始......事实上,我还没有找到任何特别可靠的重现它的方法。一旦它复制了一次文本,它就会在每次击键时可靠地一次又一次地重复,直到您刷新页面为止。刷新页面后,有时该错误会在您下一次击键时再次出现,有时可能需要大约 20 次击键才会出现。

我已经在 Safari 8(OS X 和 iOS)、Chrome(OS X 和 Windows)和 Firefox(仅 OS X)上对此进行了测试,并且问题出现在每个浏览器中。

如果您还不能重现它,请尝试突出显示所有文本、删除它并输入。也尝试开始一个新行。我发现这些操作似乎更有可能引发文本重复,但即使是那些也不会始终如一地引发问题。

我的问题是:

  1. 为什么会出现这个错误?
  2. 我怎样才能阻止它发生?

如果您想直接查看问题而不必运行流星服务器(尽管我已经为您提供了所有代码......)我把它扔了here

【问题讨论】:

  • 抱歉,您将无法使用 Session 进行多用户编辑,因为它仅在浏览器客户端中可用(并且也不会像 cookie 那样持续存在,因此当您刷新时它就消失了) .您需要使用 Collection 以便所有用户都可以检索内容。
  • @IanJones:请参阅我的问题的第 3 段,我解释说我能够使用 Sessions 而不是 Collections 重现我的特定问题。我这样做是为了尽量减少需要共享的代码量(当你试图找出问题的根本原因时,尽量减少问题的重现也是一个好主意。)
  • 对不起,我误解了......我现在明白了你的意思是使用 Session 只是为了举例。 'input pre' 究竟是一个事件?对我来说,这似乎不是一种事件类型。

标签: javascript meteor spacebars


【解决方案1】:

这是一个已知问题,详细讨论here。我正在使用this solution(来自 Swavek),效果很好。

简单地说,当两个人同时操作相同的 DOM 元素时,问题就出现了:

  1. 流星,虽然是反应性的
  2. 负责管理 contenteditable div 内部的浏览器代码。

解决方案是告诉 Meteor 不要操作 contenteditable div 的内部,而是刷新整个 div。你这样做:

<body>
  {{> hello}}
</body>

<template name="hello">
  {{{getContenteditableDiv}}}  <!-- Beware: triple brackets! -->
</template>

Template.hello.helpers({
  getContenteditableDiv: function() {
    return '<pre contentEditable="true">' + Session.get('text') + '</pre>';
  }
});

【讨论】:

  • 嗨,谢谢分享。不过,它似乎有问题。当我运行它时,this.text 返回undefined。此外,在页面加载和getContenteditableDiv 第一次被调用后,它就再也不会被调用了。对于我的最小复制,这并不重要,因为会话从未从其他任何地方设置,但在我的完整应用程序中,编辑可能来自其他用户更新数据库。基本上,我担心这会破坏反应性。
  • 我对此表示赞同,因为它开头的链接是相关的,并且看起来应该很有用。但是,正如我几个小时前评论的那样,该代码现在实际上并不适合我,所以在修复其中的代码之前我不会接受它。
  • 我已经更新了我的答案,以更加坚持您的示例代码,其中“文本”取自 Session 变量而不是模板上下文。请让我知道这是否适合您。不应破坏反应性。
  • 好的,有了这个编辑,它现在可以工作了,所以我把它标记为接受。在我看来,现在不再需要名为 textTemplate.hello.helpers。我是否缺少有关此解决方案如何工作的一些微妙细节,或者我认为我现在可以删除它是否正确?
  • 是的,您可以删除“文本”助手。
【解决方案2】:

这感觉和看起来都像黑客......但它比原始代码稍微好一点,所以我会分享它作为答案:

在每个input之后,将新内容插入后备存储后,使用$(event.target).contents().remove();清除字段。

例子:

Template.hello.events({
  "input pre": function (event) {
    Session.set('text', $(event.target).text());
    $(event.target).contents().remove();
  }
});

注意事项:

  1. 每次击键后,文本会立即消失(有时它很简短,您甚至都没有注意到。)
  2. 每次键入时光标都会跳回到文本的开头。 (但光标也有在原始代码中跳跃的趋势。)

这两个警告都非常糟糕......基本上所有发生的事情都是我交换了出现的重复文本,而文本暂时消失了。

一个真正的解决方案是,如果有某种我喜欢的方法,可以连接到空格键或其他东西……比如,我需要一些代码在空格键响应更新后立即运行。或者,如果我可以修改空格键的响应式更新方式。也许我会查看 Spacebars 上的文档和/或源代码,并根据我的发现提交错误报告或功能请求......

【讨论】:

    【解决方案3】:

    我认为这是因为您正在捕获所有事件,而不是将其缩小到特定事件类型。

    我只是在MeteorPad 上尝试了几分钟,但它从未复制文本:

    /main.html

    <body>
      {{> hello}}
    </body>
    
    <template name="hello">
      <pre contentEditable="true">{{text}}</pre>
    </template>
    

    /client/app.js

    if (Meteor.isClient) {
      Session.setDefault('text', "Edit me!");
    
      Template.hello.helpers({
        text: function () {
          return Session.get('text');
        }
      });
    
      Template.hello.events({
        "keyup pre": function (event) {
          $(event.target).text("I was called");
          Session.set('text', $(event.target).text());
        }
      });
    }
    

    【讨论】:

    • 我还没有尝试过,但我怀疑你认为这段代码没有我的问题的原因是因为它是无效的,所以函数的主体永远不会运行。这意味着数据库没有使用最新的代码副本(或者,在我的最小复制情况下的会话)进行更新。不更新后备存储意味着流星永远不会看到更新的文本,也永远不会尝试将其插入到同一个文本字段中。我不确定,但我很确定“keyup input pre”在 Meteor 中不是一个有效的东西......如果是,它是“input pre”的超集,而不是子集
    • @ArtOfWarfare - 不,我没有想到它是无效代码。我刚刚去了meteorpad.com 并复制了您的示例进行测试。然后我添加了 keyup 事件前缀,它停止复制文本,所以你可能想自己尝试一下。
    • 是的,如果您添加keyup,它是无效的并且永远不会被调用。作为在 MeteorPad 上的最小复制中验证的一种简单方法,将函数的主体替换为 $(event.target).text("I was called.");。如果曾经调用过该函数,它将用它替换页面上的文本,但由于不是,您可以很好地编辑文本(它只是一个普通的 HTML contentEditable 没有 Meteor 做任何事情,在那个案例。但我希望 Meteor 使文本与我的后备存储保持同步。)
    • 嗯...很奇怪。我用了你的例子,现在明白你的意思了。但是我删除了“输入”部分,因为您在示例中只有一个“pre”字段,并且它开始触发 Session.set 代码。使用您的示例,它现在会覆盖输入字段,但至少它正在触发。
    • 我希望它启动,并且在我的原版中它启动得很好。问题是,由于某种原因,当 Spacebars 被告知后备存储已更改时,它决定将文本插入该字段两次。后备存储实际上具有它应该具有的确切文本(我可以在我的完整程序中查询 Mongo 并验证这一点......您可以记录会话变量以在我的最小复制中验证它。)只是出于某种原因 Spacebars 选择呈现好像我的模板是{{text}}{{text}} 而不仅仅是{{text}}
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-07
    • 1970-01-01
    相关资源
    最近更新 更多