Meteor minimongo动态光标



在我的客户端UI中,我有一个带有不同搜索标准的表单,我想被动更新结果列表。搜索查询被转换为一个经典的minimongo选择器,保存在Session变量中,然后我让观察者对结果进行处理:

// Think of a AirBnb-like application
// The session variable `search-query` is updated via a form
// example: Session.set('search-query', {price: {$lt: 100}});
Offers = new Meteor.Collection('offers');
Session.setDefault('search-query', {});
resultsCursor = Offers.find(Session.get('search-query'));
// I want to add and remove pins on a map
resultCursor.observe({
  added: Map.addPin,
  removed: Map.removePin
});
Deps.autorun(function() {
  // I want to modify the cursor selector and keep the observers
  // so that I would only have the diff between the old search and
  // the new one
  // This `modifySelector` method doesn't exist
  resultsCursor.modifySelector(Session.get('search-query'));
});

如何在游标对象上实现这个modifySelector方法?

基本上,我认为这种方法需要更新游标的编译版本,即selector_f属性,然后重新运行观察程序(而不会丢失以前结果的缓存)。或者有更好的解决方案吗?


编辑:你们中的一些人误解了我要做的事情。让我提供一个完整的例子:

Offers = new Meteor.Collection('offers');
if (Meteor.isServer && Offers.find().count() === 0) {
  for (var i = 1; i < 4; i++) {
    // Inserting documents {price: 1}, {price: 2} and {price: 3}
    Offers.insert({price:i})  
  }
}
if (Meteor.isClient) {
  Session.setDefault('search-query', {price:1});
  resultsCursor = Offers.find(Session.get('search-query'));
  resultsCursor.observe({
    added: function (doc) { 
      // First, this added observer is fired once with the document 
      // matching the default query {price: 1}
      console.log('added:', doc);
    }
  });
  setTimeout(function() {
    console.log('new search query');
    // Then one second later, I'd like to have my "added observer" fired
    // twice with docs {price: 2} and {price: 3}. 
    Session.set('search-query', {});
  }, 1000);
}

这并没有以您想要的方式解决问题,但我认为结果仍然是一样的。如果这是你明确不想要的解决方案,请告诉我,我可以删除答案。我只是不想把代码放在评论中。

Offers = new Meteor.Collection('offers');
Session.setDefault('search-query', {});
Template.map.pins = function() {
   return Offers.find(Session.get('search-query'));
}
Template.map.placepins = function(pins) {
   // use d3 or whatever to clear the map and then place all pins on the map
}

假设你的模板是这样的:

<template name="map">
  {{placepins pins}}
</template>

一种解决方案是手动区分新旧游标:

# Every time the query change, do a diff to add, move and remove pins on the screen
# Assuming that the pins order are always the same, this use a single loop of complexity 
# o(n) rather than the naive loop in loop of complexity o(n^2)
Deps.autorun =>
  old_pins = @pins
  new_pins = []
  position = 0
  old_pin  = undefined # This variable needs to be in the Deps.autorun scope
  # This is a simple algo to implement a kind of "reactive cursor"
  # Sorting is done on the server, it's important to keep the order
  collection.find(Session.get('search-query'), sort: [['mark', 'desc']]).forEach (product) =>
    if not old_pin? 
      old_pin = old_pins.shift()
    while old_pin?.mark > product.mark
      @removePin(old_pin)
      old_pin = old_pins.shift()
    if old_pin?._id == product._id
      @movePin(old_pin, position++)
      new_pins.push(old_pin)
      old_pin = old_pins.shift()
    else
      newPin = @render(product, position++)
      new_pins.push(newPin)
  # Finish the job
  if old_pin?
    @removePin(old_pin)
  for old_pin in old_pins
    @removePin(old_pin)
  @pins = new_pins

但它有点粗糙,效率也不高。此外,diff逻辑已经在minimongo中实现,因此最好重用它

也许一个可以接受的解决方案是在本地集合中跟踪旧引脚?类似这样的东西:

Session.setDefault('search-query', {});
var Offers = new Meteor.Collection('offers');
var OldOffers = new Meteor.Collection(null);
var addNewPin = function(offer) {
  // Add a pin only if it's a new offer, and then mark it as an old offer
  if (!OldOffers.findOne({_id: offer._id})) {
    Map.addPin(offer);
    OldOffers.insert(offer);
  }
};
var removePinsExcept = function(ids) {
  // Clean out the pins that no longer exist in the updated query,
  // and remove them from the OldOffers collection
  OldOffers.find({_id: {$nin: ids}}).forEach(function(offer) {
    Map.removePin(offer);
    OldOffers.remove({_id: offer._id});
  });
};
Deps.autorun(function() {
  var offers = Offers.find(Session.get('search-query'));
  removePinsExcept(offers.map(function(offer) {
    return offer._id;
  }));
  offers.observe({
    added: addNewPin,
    removed: Map.removePin
  });
});

我不确定这比你的数组答案快多少,尽管我认为它可读性更强。您需要考虑的是,随着查询的变化,对结果进行区分是否真的比每次删除所有引脚并重新绘制要快得多。我怀疑这可能是一个过早优化的情况。您希望用户多久更改一次搜索查询,以便新旧查询的结果之间有大量重叠?

我在自己的业余爱好Meteor项目中也遇到了同样的问题。

选择器存储的是filter会话变量。触发任何复选框或按钮都会更改filter和所有UI重新呈现。

该解决方案有一些缺点,主要是你不能与其他用户共享应用程序状态。

所以我意识到更好的方法是将应用程序状态存储在URL中。

也许对你来说也更好?

点击按钮现在可以改变URL和基于它的UI呈现。我用FlowRouter实现了这一点。

有用的阅读:在URL 上保持应用程序状态

最新更新