Как я могу сделать реактивный массив из коллекции метеоров?
Я хочу взять список имен элементов из коллекции в виде простого массива, чтобы использовать его для таких вещей, как автоматическое заполнение пользовательского ввода и проверка на дубликаты. Я бы хотел, чтобы этот список был реактивным, чтобы изменения в данных отражались в массиве. Я попробовал следующее, основываясь на документации 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 секунды без этого кода). Я что-то делаю? неправильно? Есть ли лучший способ сделать это?
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 позволяет быстро выполнять поиск по нескольким правилам с пользовательскими списками.
Автозавершение пользователей с помощью
@, где пользователи в сети отображаются зеленым цветом:
В той же строке, автозавершение чего-то else с метаданными и значками начальной загрузки:
Пожалуйста, вилка, тянуть, и улучшить!


Comments