我该如何从Meteor集合中创建一个响应式数组?

9

我希望可以从一个集合中获取一个项目名称列表的简单数组,用于自动完成用户输入和检查重复项等功能。我希望这个列表是响应式的,这样数据的更改将会反映在数组中。根据 Meteor 的文档,我尝试了以下方法:

    setReactiveArray = (objName, Collection, field) ->
        update = ->
          context = new Meteor.deps.Context()
          context.on_invalidate update
          context.run -> 
            list = Collection.find({},{field: 1}).fetch()
            myapp[objName] = _(list).pluck field
        update()

    Meteor.startup ->
        if not app.items?
            setReactiveArray('items', Items, 'name')

    #set autocomplete using the array
    Template.myForm.set_typeahead =  ->
       Meteor.defer ->
        $('[name="item"]').typeahead {source: app.items}    

这段代码似乎是可行的,但它会导致我的应用程序加载时间过长(相比没有此代码,dev/localhost上需要5-10秒才能加载,而不加此代码只需约1秒)。我做错了什么吗?有更好的方法来解决这个问题吗?

4个回答

6
你可以使用Items.find({},{name: 1}).fetch()来获取一个物品数组,这个查询是响应式的,只要在响应式上下文中被调用,当查询结果发生变化时,它会重新运行其封闭函数。对于Template.myForm.set_typeahead帮助程序,你可能需要在帮助程序中调用该查询,将结果存储在本地变量中,然后使用引用该变量的函数调用Meteor.defer。否则,我不确定这个查询是否在调用时处于响应式上下文中。

谢谢回答!这是一个我已经忘记的老问题。现在我有了自己的答案。如果您对此有任何想法,请评论。 - rdickert
看起来你的答案在大多数情况下确实是最好的。如果你感兴趣,可以查看我的修订答案,特别是注意事项。再次感谢! - rdickert

5

编辑: 我已经更新了下面的代码,因为它很脆弱,并且为了将其放入上下文中,使其更容易测试。我还添加了一个警告 - 在大多数情况下,您将希望使用@zorlak或@englandpost的方法(请参见下文)。


首先,向 @zorlak 致敬,他挖掘出了我的旧问题,而没有人回答。我已经通过从 @David Wihl 那里获得的一些 见解 解决了这个问题,并将发布自己的解决方案。在其他人有机会发表意见之前,我将暂停选择正确的答案。
@zorlak 的答案解决了单个字段的自动完成问题,但正如问题所述,我正在寻找一个可以反应更新的数组,自动完成只是其中之一的示例。拥有此数组的优点是它可以在任何地方使用(不仅限于模板助手),并且可以在代码中多次使用而无需重新执行查询(以及减少查询为数组的 _.pluck())。在我的情况下,这个数组最终会出现在多个自动完成字段以及验证和其他地方。我提出的优点可能在大多数 Meteor 应用程序中并不显著(请留言),但对我来说似乎是一个优点。
为了使数组具有响应性,只需在Meteor.autorun()回调函数内构建它 - 它将在目标集合更改时重新执行(但仅在此时,避免重复查询)。这是我正在寻找的关键洞见。此外,使用Template.rendered()回调比我在问题中使用的set_typeahead模板助手更加干净和不容易出错。下面的代码使用underscore.js的_.pluck()从集合中提取数组,并使用Twitter bootstrap's $.typeahead()创建自动完成。 更新的代码:我已经编辑了代码,这样你就可以在一个股票 meteor create 的测试环境中尝试。你的 html 需要在 'hello' 模板中添加一行 <input id="typeahead" />@Items 带有 @ 符号,以使 Items 在控制台上作为全局变量可用(Meteor 0.6.0 添加了文件级别变量作用域)。这样,您可以在控制台中输入新项目,例如 Items.insert({name: "joe"}),但是 @ 对于代码工作并不必要。独立使用的另一个必要更改是 typeahead 函数现在将源设置为函数 (->),因此当激活时它将查询 items 而不是在渲染时被设置,这允许它利用对 items 的更改。
@Items = new Meteor.Collection("items")
items = {}

if Meteor.isClient
  Meteor.startup ->
    Meteor.autorun ->
      items = _(Items.find().fetch()).pluck "name"
      console.log items  #first result will be empty - see caution below

  Template.hello.rendered = ->
    $('#typeahead').typeahead {source: -> _(Items.find().fetch()).pluck "name"}

注意!我们创建的数组本身不是一个响应式数据源。 typeahead source: 需要设置为返回 items函数 ->,原因是当 Meteor 开始运行时,代码会在 Minimongo 从服务器获取数据之前运行,而 items 设置为空数组。然后 Minimongo 接收到数据,items 被更新。如果您打开控制台运行上面的代码,您可以看到这个过程:console.log items 如果有任何存储的数据,将会记录两次。

Template.x.rendered()的调用不会设置响应式上下文,因此不会由于响应式元素的更改而重新触发(要检查这一点,请在调试器中暂停代码并检查Deps.currentComputation - 如果它为null,则您不处于响应式上下文中,对响应式元素的更改将被忽略)。但您可能会惊讶地发现,您的模板和助手也不会对items的更改做出反应 - 使用#each迭代items的模板将呈现为空,并且永远不会重新渲染。您可以使其作为响应式源(最简单的方法是使用Session.set()存储结果,或者 您可以自己完成),但除非您正在进行非常昂贵的计算,应尽可能少地运行,否则最好使用@zorlak或@englandpost的方法。虽然让应用程序重复查询数据库似乎很昂贵,但Minimongo会在本地缓存数据,避免网络,因此速度相当快。因此,在大多数情况下,最好只使用

  Template.hello.rendered = ->
    $('#typeahead').typeahead {source: -> _(Items.find().fetch()).pluck "name"}

除非你发现你的应用程序真的变得很慢。

1
@IanWhalen - 你说得对,如果单独使用代码是无法运行的。我将其放在一个更大的应用程序中,没有遇到这个问题,但它对模板呈现的时间非常敏感。我已经更新了答案,使原始代码可以工作,但我现在认为大多数人应该使用最后的新代码,基本上是按照zorlak的方法。希望这可以帮助! - rdickert
有什么想法可以将这个与使用模板事件keyup结合起来,设置一个Session变量用于订阅,并发送到发布功能中以限制发布记录吗?我正在尝试在无法将整个大集合发送到客户端但又想使用typeahead的情况下尝试这个概念。目前typeahead会随着发布更改而重新渲染。 - Steeve Cannon
这里有什么明显的理由不使用 this.find('#typeahead').typeahead 吗? - Steeve Cannon
@SteeveCannon 对于你的第一个问题:我认为对于非客户端复制的数据集,你可能希望使用Meteor方法调用,仅返回自动完成数组,而不是使用publish - rdickert
@SteeveCannon 在你的第二个问题上,我非常确定你必须使用jQuery,因为typeahead功能是作为jQuery插件实现的。this.find只会给你一个DOM元素,它没有typeahead方法。 - rdickert
显示剩余3条评论

1

这里是我快速解决Bootstrap Typeahead的方案

在客户端:

Template.items.rendered = ->
  $("input#item").typeahead
    source: (query, process) ->
      subscription = Meteor.subscribe "autocompleteItems", query, ->
        process _(Items.find().fetch()).pluck("name")
      subscription.stop() # here may be a bit different logic,
      # such as keeping all opened subsriptions until autocomplete
      # will be successfully completed and so on
      items: 5

在服务器端:
Meteor.publish "autocompleteItems", (query) ->
  Items.find
    name: new RegExp(query, "i"),
      fields: { name: 1 },
      limit: 5

谢谢,这看起来很有前途。 - rdickert

1

实际上,我最终采用了完全不同的方法来解决自动完成问题,使用客户端代码而不是查询服务器。我认为这种方法更优越,因为Meteor的数据模型允许快速进行多规则搜索,并提供自定义渲染列表。

https://github.com/mizzao/meteor-autocomplete

自动完成用户输入,使用@符号,在线用户以绿色显示:

enter image description here

在同一行中,使用元数据和Bootstrap图标自动完成其他内容:

enter image description here

请分叉、拉取并改进!(保留HTML格式)

1
这是一个不错的尝试,但是自动完成功能缺乏发布/订阅机制实在是不足以支持(另外我也不会使用它,因为它有依赖问题,但这是个人问题)。 - Jonathan Dumaine
@JonathanDumaine,正如我在README中提到的那样,将其扩展为服务器端实现将非常容易。另外,它需要哪些依赖项?它只使用核心Meteor。 - Andrew Mao

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接