2

在我的客户端 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

4 回答 4

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而且效率不高。此外,差异逻辑已经在 minimongo 中实现,因此最好重用它。

于 2013-12-19T13:19:51.280 回答
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>
于 2013-12-19T01:38:41.740 回答
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
  });
});

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

于 2013-12-20T09:32:15.967 回答
0

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

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

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

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

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

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

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

于 2015-09-23T09:10:54.950 回答