Как я могу сделать реактивный массив из коллекции метеоров?



Я хочу взять список имен элементов из коллекции в виде простого массива, чтобы использовать его для таких вещей, как автоматическое заполнение пользовательского ввода и проверка на дубликаты. Я бы хотел, чтобы этот список был реактивным, чтобы изменения в данных отражались в массиве. Я попробовал следующее, основываясь на документации 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}


Этот код, кажется, работает, но он убивает время загрузки моего приложения (занимает 5-10 секунд для загрузки на dev/localhost против ~1 секунды без этого кода). Я что-то делаю? неправильно? Есть ли лучший способ сделать это?

553   4  

4 ответов:

Вы должны иметь возможность использовать Items.find({},{name: 1}).fetch(), который возвращает массив элементов и является реактивным, поэтому он будет повторно запускать свою функцию включения всякий раз, когда результаты запроса изменяются, пока он вызывается в реактивном контексте.

Для помощника Template.myForm.set_typeahead можно вызвать этот запрос внутри самого помощника, сохранить результат в локальной переменной, а затем вызвать Meteor.defer с помощью функции, которая ссылается на эту переменную. В противном случае я не уверен, что запрос будет находиться в реактивном контексте, когда он называемый.

Edit : я обновил приведенный ниже код как потому, что он был хрупким, так и для того, чтобы поместить его в контекст, чтобы его было легче тестировать. Я также добавил предостережение - в большинстве случаев вы захотите использовать методы @zorlak или @englandpost (см. ниже).


Прежде всего, спасибо @zorlak за то, что откопал мой старый вопрос, на который никто не ответил. С тех пор я решил эту проблему с помощью нескольких идей, полученных от @David Wihl, и опубликую свое собственное решение. Я буду держаться подальше. выбор правильного ответа до тех пор, пока другие не будут иметь шанс взвесить.

Ответ@zorlak решает проблему автозаполнения для одного поля, но, как указано в вопросе, я искал массив, который будет обновляться реактивно, и автозаполнение было только одним примером того, для чего оно будет использоваться. Преимущество использования этого массива заключается в том, что он может быть использован в любом месте (не только в помощниках шаблона) и что его можно использовать несколько раз в коде без необходимости повторного выполнения запрос (и _.pluck(), который сводит запрос к массиву). В моем случае этот массив заканчивается несколькими полями автозаполнения, а также проверкой и другими местами. Возможно, что преимущества, которые я выдвигаю, не являются существенными в большинстве приложений Meteor (пожалуйста, оставьте комментарии), но это кажется мне преимуществом.

Чтобы сделать массив реактивным, просто постройте его внутри обратного вызова Meteor.autorun() - он будет повторно выполняться каждый раз, когда целевая коллекция изменяется (но только тогда, избегая повторения запросы). Это было ключевое озарение, которое я искал. Кроме того, использование обратного вызова Template.rendered() чище и менее взломано, чем помощник шаблона set_typeahead, который я использовал в вопросе. В приведенном ниже коде используется символ подчеркивания .js'S _.pluck() для извлечения массива из коллекции и использует Twitter bootstrap $.typeahead() для создания автозаполнения.

обновленный код : я отредактировал код, так что вы можете попробовать это с помощью тестовой среды stock meteor createD. Ваш html будет нуждаться в строке <input id="typeahead" /> в шаблон "привет". @Items имеет знак @, чтобы сделать Items доступным в качестве глобального на консоли (Meteor 0.6.0 добавлена переменная уровня файла scoping). Таким образом, вы можете ввести новые элементы в консоль, такие как 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 изменение -- a шаблон, использующий #each для итерации над items, будет пустым и никогда не будет перезаписан. Вы можете заставить его действовать как реактивный Источник (Самый простой способ-хранить результат с помощью Session.set(), или Вы можете сделать это сами), но если вы не делаете очень дорогое вычисление, которое должно выполняться как можно реже, вам лучше использовать методы @zorlak или @englandpost. Это может показаться дорогим, чтобы ваше приложение запрашивало базу данных повторно, но Minimongo кэширует данные локально, избегая сети, так что это будет довольно быстро. Таким образом, в большинстве ситуаций лучше просто использовать

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

Если только вы не обнаружите, что ваше приложение действительно увязает.

Вот мое быстрое решение для 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

На самом деле я подошел к проблеме автозавершения совершенно по-другому, используя клиентский код вместо запросов к серверам. Я думаю, что это лучше, потому что модель данных Meteor позволяет быстро выполнять поиск по нескольким правилам с пользовательскими списками.

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

Автозавершение пользователей с помощью @, где пользователи в сети отображаются зеленым цветом:

Введите описание изображения здесь

В той же строке, автозавершение чего-то else с метаданными и значками начальной загрузки:

Введите описание изображения здесь

Пожалуйста, вилка, тянуть, и улучшить!

Comments

    Ничего не найдено.