Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add clear and reset functionality to Bloodhound #703

Merged
merged 4 commits into from
Mar 9, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion doc/bloodhound.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -63,6 +63,59 @@ 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()

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();
```

<!-- section links -->

[jQuery promise]: http://api.jquery.com/Types/#Promise
Expand Down
54 changes: 30 additions & 24 deletions src/bloodhound/bloodhound.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,9 @@
return deferred;

function handlePrefetchResponse(resp) {
var filtered;

filtered = o.filter ? o.filter(resp) : resp;
that.add(filtered);
// 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);
}
Expand All @@ -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);
}
},

Expand Down Expand Up @@ -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) {
return !this.initPromise || force ? this._initialize() : this.initPromise;
},

add: function add(data) {
Expand Down Expand Up @@ -208,6 +202,18 @@
}
},

clear: function clear() {
this.index.reset();
},

clearPrefetchCache: function clearPrefetchCache() {
this.storage && this.storage.clear();
},

clearRemoteCache: function clearRemoteCache() {
this.transport && Transport.resetCache();
},

ttAdapter: function ttAdapter() { return _.bind(this.get, this); }
});

Expand Down
8 changes: 6 additions & 2 deletions src/bloodhound/search_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ var SearchIndex = (function() {
this.datumTokenizer = o.datumTokenizer;
this.queryTokenizer = o.queryTokenizer;

this.datums = [];
this.trie = newNode();
this.reset();
}

// instance methods
Expand Down Expand Up @@ -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 };
}
Expand Down
116 changes: 116 additions & 0 deletions test/bloodhound_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,108 @@ 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('#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();
});
});

describe('local', function() {
describe('when local is an array', function() {
beforeEach(function() {
Expand Down Expand Up @@ -140,6 +242,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;

Expand Down
7 changes: 7 additions & 0 deletions test/search_index_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ----------------

Expand Down