From 2cc806511f1df52356e5c31c1260acc69f897934 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B8rn=20Br=C3=A5then?= <bjorbrat88@gmail.com>
Date: Thu, 28 Jan 2016 01:55:59 +0100
Subject: [PATCH] Reactive search results. Performance is not considered while
 implementing this.

---
 README.md     | 34 ++++++++++++++++++++++++++++++++++
 lib/client.js | 33 ++++++++++++++++++++++++++++++++-
 2 files changed, 66 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index ae92a95..9354048 100644
--- a/README.md
+++ b/README.md
@@ -100,6 +100,40 @@ Template.searchResult.helpers({
 });
 ```
 
+### Use reactive data in search results
+
+By default, the contents returned by `getData()` is not reactive in the sense of it reflecting the current state of the mongo database. To use reactive content, do the following when creating a SearchSource object on the client
+
+```js
+var options = {
+  keepHistory: 1000 * 60 * 5,
+  localSearch: true,
+  collection: Documents, // Collection to reflect on the client
+  subscriptionName: 'documents.byIds' // Use subscription by this name
+};
+
+// Some fields
+const fields = [ 'name', 'description' ];
+
+const DocumentSearch = new SearchSource('docs', fields, options);
+```
+
+and on the server, publish the documents by ids
+
+```js
+Meteor.publish('documents.byIds', function (ids) {
+  check(ids, [ String ]);
+
+  return Documents.find({
+    _id: {
+      $in: ids
+    }
+  });
+});
+```
+
+and the rest is as usual. One thing to notice is that `getData()` cannot return a cursor if reactive items are used. This is because we sort documents on a score field that is not attached to the documents itself, so mongo cannot take care of the ordering.
+
 ### Searching
 
 Finally we can invoke search queries by invoking following API.
diff --git a/lib/client.js b/lib/client.js
index 3963ab8..98077ce 100644
--- a/lib/client.js
+++ b/lib/client.js
@@ -19,6 +19,7 @@ SearchSource.prototype._loadData = function(query, options) {
   var self = this;
   var version = 0;
   var historyKey = query + EJSON.stringify(options);
+
   if(this._canUseHistory(historyKey)) {
     this._updateStore(this.history[historyKey].data);
     this.metaData.set(this.history[historyKey].metadata);
@@ -149,6 +150,36 @@ SearchSource.prototype.getData = function(options, getCursor) {
     transform: transform
   });
 
+  var collection = this.options.collection;
+
+  if(collection) {
+    var ids = _.pluck(cursor.fetch(), '_id');
+
+    if (!this.options.subscriptionName) {
+      throw Error('subscritionName is missing');
+    }
+
+    var sub = Meteor.subscribe(this.options.subscriptionName, ids);
+
+    if (!sub.ready())
+      return [];
+
+    var docs = collection.find({
+      _id: {
+        $in: ids
+      }
+    }, {
+      transform: transform
+    }).fetch();
+
+    var sortIds = _.invert(_.object(_.pairs(ids)));
+    var sorted = _.sortBy(docs, function(x) {
+        return sortIds[x._id];
+    });
+
+    return sorted;
+  }
+
   if(getCursor) {
     return cursor;
   }
@@ -234,4 +265,4 @@ SearchSource.prototype._getRegExpFilterRegExp = _.once(function() {
     return "\\" + c;
   }).join("|");
   return new RegExp("(" + regExpCharsReplace + ")", "g");
-});
\ No newline at end of file
+});