From d6f4404737310433417049fd8cb996d21ab64083 Mon Sep 17 00:00:00 2001 From: Jake Harding Date: Tue, 18 Feb 2014 00:29:22 -0800 Subject: [PATCH 1/4] Add clear and reset functionality to Bloodhound. --- src/bloodhound/bloodhound.js | 50 ++++++++++++++++++---------------- src/bloodhound/search_index.js | 8 ++++-- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/bloodhound/bloodhound.js b/src/bloodhound/bloodhound.js index d1236785..93d31407 100644 --- a/src/bloodhound/bloodhound.js +++ b/src/bloodhound/bloodhound.js @@ -83,10 +83,9 @@ return deferred; function handlePrefetchResponse(resp) { - var filtered; - - filtered = o.filter ? o.filter(resp) : resp; - that.add(filtered); + // reset to mirror the behavior of bootstrapping + that.reset(); + that.add(o.filter ? o.filter(resp) : resp); that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl); } @@ -105,13 +104,7 @@ return this.transport.get(url, this.remote.ajax, handleRemoteResponse); function handleRemoteResponse(err, resp) { - var filtered; - - // failed request is equivalent to an empty suggestion set - if (err) { return cb([]); } - - filtered = that.remote.filter ? that.remote.filter(resp) : resp; - cb(filtered); + err ? cb([]) : cb(that.remote.filter ? that.remote.filter(resp) : resp); } }, @@ -140,28 +133,29 @@ return stored.data && !isExpired ? stored.data : null; }, - // ### public - - // the contents of this function are broken out of the constructor - // to help improve the testability of bloodhounds - initialize: function initialize() { - var that = this, deferred; + _initialize: function initialize() { + var that = this, local = this.local, deferred; deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve(); - // local can be a function that returns an array of datums - this.local = _.isFunction(this.local) ? this.local() : this.local; - // make sure local is added to the index after prefetch - this.local && deferred.done(addLocalToIndex); + local && deferred.done(addLocalToIndex); this.transport = this.remote ? new Transport(this.remote) : null; - this.initialize = function initialize() { return deferred.promise(); }; - return deferred.promise(); + return (this.initPromise = deferred.promise()); - function addLocalToIndex() { that.add(that.local); } + function addLocalToIndex() { + // local can be a function that returns an array of datums + that.add(_.isFunction(local) ? local() : local); + } + }, + + // ### public + + initialize: function initialize(force) { + !this.initPromise || force ? this._initialize() : this.initPromise; }, add: function add(data) { @@ -208,6 +202,14 @@ } }, + reset: function reset() { + this.index.reset(); + }, + + clearPrefetchCache: function clearPrefetchCache() { + this.storage && this.storage.clear(); + }, + ttAdapter: function ttAdapter() { return _.bind(this.get, this); } }); diff --git a/src/bloodhound/search_index.js b/src/bloodhound/search_index.js index 86e33c15..3f342c04 100644 --- a/src/bloodhound/search_index.js +++ b/src/bloodhound/search_index.js @@ -19,8 +19,7 @@ var SearchIndex = (function() { this.datumTokenizer = o.datumTokenizer; this.queryTokenizer = o.queryTokenizer; - this.datums = []; - this.trie = newNode(); + this.reset(); } // instance methods @@ -96,6 +95,11 @@ var SearchIndex = (function() { _.map(unique(matches), function(id) { return that.datums[id]; }) : []; }, + reset: function reset() { + this.datums = []; + this.trie = newNode(); + }, + serialize: function serialize() { return { datums: this.datums, trie: this.trie }; } From f513593642562dbc679b587d8e84a04780976274 Mon Sep 17 00:00:00 2001 From: Jake Harding Date: Sat, 8 Mar 2014 21:49:38 -0800 Subject: [PATCH 2/4] Add tests for bloodhound public methods. --- src/bloodhound/bloodhound.js | 8 +-- test/bloodhound_spec.js | 100 +++++++++++++++++++++++++++++++++++ test/search_index_spec.js | 7 +++ 3 files changed, 111 insertions(+), 4 deletions(-) diff --git a/src/bloodhound/bloodhound.js b/src/bloodhound/bloodhound.js index 93d31407..2f4292e4 100644 --- a/src/bloodhound/bloodhound.js +++ b/src/bloodhound/bloodhound.js @@ -83,8 +83,8 @@ return deferred; function handlePrefetchResponse(resp) { - // reset to mirror the behavior of bootstrapping - that.reset(); + // clear to mirror the behavior of bootstrapping + that.clear(); that.add(o.filter ? o.filter(resp) : resp); that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl); @@ -155,7 +155,7 @@ // ### public initialize: function initialize(force) { - !this.initPromise || force ? this._initialize() : this.initPromise; + return !this.initPromise || force ? this._initialize() : this.initPromise; }, add: function add(data) { @@ -202,7 +202,7 @@ } }, - reset: function reset() { + clear: function clear() { this.index.reset(); }, diff --git a/test/bloodhound_spec.js b/test/bloodhound_spec.js index 88753a36..55079c58 100644 --- a/test/bloodhound_spec.js +++ b/test/bloodhound_spec.js @@ -10,6 +10,92 @@ describe('Bloodhound', function() { clearAjaxRequests(); }); + describe('#initialize', function() { + beforeEach(function() { + this.bloodhound = new Bloodhound({ + datumTokenizer: datumTokenizer, + queryTokenizer: queryTokenizer, + local: fixtures.data.simple + }); + + spyOn(this.bloodhound, '_initialize').andCallThrough(); + }); + + it('should not support reinitialization by default', function() { + var p1, p2; + + p1 = this.bloodhound.initialize(); + p2 = this.bloodhound.initialize(); + + expect(p1).toBe(p2); + expect(this.bloodhound._initialize.callCount).toBe(1); + }); + + it('should reinitialize if reintialize flag is true', function() { + var p1, p2; + + p1 = this.bloodhound.initialize(); + p2 = this.bloodhound.initialize(true); + + expect(p1).not.toBe(p2); + expect(this.bloodhound._initialize.callCount).toBe(2); + }); + }); + + describe('#add', function() { + it('should add datums to search index', function() { + var spy = jasmine.createSpy(); + + this.bloodhound = new Bloodhound({ + datumTokenizer: datumTokenizer, + queryTokenizer: queryTokenizer, + local: [] + }); + this.bloodhound.initialize(); + this.bloodhound.add(fixtures.data.simple); + + this.bloodhound.get('big', spy); + + expect(spy).toHaveBeenCalledWith([ + { value: 'big' }, + { value: 'bigger' }, + { value: 'biggest' } + ]); + }); + }); + + describe('#clear', function() { + it('should remove all datums to search index', function() { + var spy = jasmine.createSpy(); + + this.bloodhound = new Bloodhound({ + datumTokenizer: datumTokenizer, + queryTokenizer: queryTokenizer, + local: fixtures.data.simple + }); + this.bloodhound.initialize(); + this.bloodhound.clear(); + + this.bloodhound.get('big', spy); + + expect(spy).toHaveBeenCalledWith([]); + }); + }); + + describe('#clearPrefetchCache', function() { + it('should clear persistent storage', function() { + this.bloodhound = new Bloodhound({ + datumTokenizer: datumTokenizer, + queryTokenizer: queryTokenizer, + prefetch: '/test' + }); + this.bloodhound.initialize(); + this.bloodhound.clearPrefetchCache(); + + expect(this.bloodhound.storage.clear).toHaveBeenCalled() + }); + }); + describe('local', function() { describe('when local is an array', function() { beforeEach(function() { @@ -140,6 +226,20 @@ describe('Bloodhound', function() { ]); }); + it('should clear preexisting data', function() { + this.bloodhound = new Bloodhound({ + datumTokenizer: datumTokenizer, + queryTokenizer: queryTokenizer, + prefetch: '/test' + }); + + spyOn(this.bloodhound, 'clear'); + this.bloodhound.initialize(); + + mostRecentAjaxRequest().response(fixtures.ajaxResps.ok); + expect(this.bloodhound.clear).toHaveBeenCalled(); + }); + it('should filter data if filter is provided', function() { var filterSpy, spy; diff --git a/test/search_index_spec.js b/test/search_index_spec.js index 65655f90..b41e4af2 100644 --- a/test/search_index_spec.js +++ b/test/search_index_spec.js @@ -45,6 +45,13 @@ describe('SearchIndex', function() { expect(this.searchIndex.get('wtf')).toEqual([]); }); + it('#reset should empty the search index', function() { + this.searchIndex.reset(); + expect(this.searchIndex.datums).toEqual([]); + expect(this.searchIndex.trie.ids).toEqual([]); + expect(this.searchIndex.trie.children).toEqual({}); + }); + // helper functions // ---------------- From 13d0210eea0a776d340d61f022d2a2a7df7a7a69 Mon Sep 17 00:00:00 2001 From: Jake Harding Date: Sat, 8 Mar 2014 21:49:56 -0800 Subject: [PATCH 3/4] Update bloodhound docs for new methods. --- doc/bloodhound.md | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/doc/bloodhound.md b/doc/bloodhound.md index dd2cd499..ce20d211 100644 --- a/doc/bloodhound.md +++ b/doc/bloodhound.md @@ -48,7 +48,7 @@ var engine = new Bloodhound({ }); ``` -#### Bloodhound#initialize() +#### Bloodhound#initialize(reinitialize) Kicks off the initialization of the suggestion engine. This includes processing the data provided through `local` and fetching/processing the data provided @@ -63,6 +63,47 @@ promise .fail(function() { console.log('err!'); }); ``` +After the initial call of `initialize`, how subsequent invocations of the method +behave depends on the `reinitialize` argument. If `reinitialize` is falsy, the +method will not execute the initialization logic and will just return the same +jQuery promise returned by the initial invocation. If `reinitialize` is truthy, +the method will behave as if it were being called for the first time. + +```javascript +var promise1 = engine.initialize(); +var promise2 = engine.initialize(); +var promise3 = engine.initialize(true); + +promise1 === promise2; +promise3 !== promise1 && promise3 !== promise2; +``` + +#### Bloodhound#add(datums) + +Takes one argument, `datums`, which is expected to be an array of +[datums](#datums). The passed in datums will get added to the search index that +powers the suggestion engine. + +```javascript +engine.add([{ val: 'one' }, { val: 'two' }]); +``` + +#### Bloodhound#clear() + +Removes all suggestions from the search index. + +```javascript +engine.clear(); +``` + +#### Bloodhound#clearPrefetchCache() + +Removes the prefetch data from local storage. + +```javascript +engine.clearPrefetchCache(); +``` + [jQuery promise]: http://api.jquery.com/Types/#Promise From d4dfcccf8ed521e1db68268cd32416caff881cf8 Mon Sep 17 00:00:00 2001 From: Jake Harding Date: Sat, 8 Mar 2014 22:17:53 -0800 Subject: [PATCH 4/4] Add Bloodhound#clearRemoteCache. --- doc/bloodhound.md | 14 +++++++++++++- src/bloodhound/bloodhound.js | 4 ++++ test/bloodhound_spec.js | 18 +++++++++++++++++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/doc/bloodhound.md b/doc/bloodhound.md index ce20d211..dc7a8144 100644 --- a/doc/bloodhound.md +++ b/doc/bloodhound.md @@ -98,12 +98,24 @@ engine.clear(); #### Bloodhound#clearPrefetchCache() -Removes the prefetch data from local storage. +If you're using `prefetch`, data gets cached in local storage in an effort to +cut down on unnecessary network requests. `clearPrefetchCache` offers a way to +programmatically clear said cache. ```javascript engine.clearPrefetchCache(); ``` +#### Bloodhound#clearRemoteCache() + +If you're using `remote`, Bloodhound will cache the 10 most recent responses +in an effort to provide a better user experience. `clearRemoteCache` offers a +way to programmatically clear said cache. + +```javascript +engine.clearRemoteCache(); +``` + [jQuery promise]: http://api.jquery.com/Types/#Promise diff --git a/src/bloodhound/bloodhound.js b/src/bloodhound/bloodhound.js index 2f4292e4..10b9f775 100644 --- a/src/bloodhound/bloodhound.js +++ b/src/bloodhound/bloodhound.js @@ -210,6 +210,10 @@ this.storage && this.storage.clear(); }, + clearRemoteCache: function clearRemoteCache() { + this.transport && Transport.resetCache(); + }, + ttAdapter: function ttAdapter() { return _.bind(this.get, this); } }); diff --git a/test/bloodhound_spec.js b/test/bloodhound_spec.js index 55079c58..f4cbc1eb 100644 --- a/test/bloodhound_spec.js +++ b/test/bloodhound_spec.js @@ -92,7 +92,23 @@ describe('Bloodhound', function() { this.bloodhound.initialize(); this.bloodhound.clearPrefetchCache(); - expect(this.bloodhound.storage.clear).toHaveBeenCalled() + expect(this.bloodhound.storage.clear).toHaveBeenCalled(); + }); + }); + + describe('#clearRemoteCache', function() { + it('should clear remote request cache', function() { + spyOn(Transport, 'resetCache'); + + this.bloodhound = new Bloodhound({ + datumTokenizer: datumTokenizer, + queryTokenizer: queryTokenizer, + remote: '/test' + }); + this.bloodhound.initialize(); + + this.bloodhound.clearRemoteCache(); + expect(Transport.resetCache).toHaveBeenCalled(); }); });