Meteor minimongo动态游标

时间:2013-12-15 14:35:18

标签: 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);
}

4 个答案:

答案 0 :(得分:1)

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

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>

答案 1 :(得分:1)

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

# 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

但它有点hacky而且效率不高。此外,diff逻辑已经以最小化的方式实现,因此最好重复使用它。

答案 2 :(得分:1)

或许可接受的解决方案是跟踪本地集合中的旧引脚?像这样:

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
  });
});

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

答案 3 :(得分:0)

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

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

该解决方案有一些缺点和主要内容 - 您无法与其他用户共享应用状态。

所以我意识到更好的方法是在URL中存储app状态。

在你的情况下可能会更好吗?

单击按钮现在可以根据它更改URL和UI呈现。我用FlowRouter认识到了它。

有用的阅读:Keeping App State on the URL