| 15403 |
manish.sha |
1 |
/*
|
|
|
2 |
* typeahead.js
|
|
|
3 |
* https://github.com/twitter/typeahead.js
|
|
|
4 |
* Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT
|
|
|
5 |
*/
|
|
|
6 |
|
|
|
7 |
(function(root) {
|
|
|
8 |
'use strict';
|
|
|
9 |
|
|
|
10 |
var old, keys;
|
|
|
11 |
|
|
|
12 |
old = root.Bloodhound;
|
|
|
13 |
keys = { data: 'data', protocol: 'protocol', thumbprint: 'thumbprint' };
|
|
|
14 |
|
|
|
15 |
// add Bloodhoud to global context
|
|
|
16 |
root.Bloodhound = Bloodhound;
|
|
|
17 |
|
|
|
18 |
// constructor
|
|
|
19 |
// -----------
|
|
|
20 |
|
|
|
21 |
function Bloodhound(o) {
|
|
|
22 |
if (!o || (!o.local && !o.prefetch && !o.remote)) {
|
|
|
23 |
$.error('one of local, prefetch, or remote is required');
|
|
|
24 |
}
|
|
|
25 |
|
|
|
26 |
this.limit = o.limit || 5;
|
|
|
27 |
this.sorter = getSorter(o.sorter);
|
|
|
28 |
this.dupDetector = o.dupDetector || ignoreDuplicates;
|
|
|
29 |
|
|
|
30 |
this.local = oParser.local(o);
|
|
|
31 |
this.prefetch = oParser.prefetch(o);
|
|
|
32 |
this.remote = oParser.remote(o);
|
|
|
33 |
|
|
|
34 |
this.cacheKey = this.prefetch ?
|
|
|
35 |
(this.prefetch.cacheKey || this.prefetch.url) : null;
|
|
|
36 |
|
|
|
37 |
// the backing data structure used for fast pattern matching
|
|
|
38 |
this.index = new SearchIndex({
|
|
|
39 |
datumTokenizer: o.datumTokenizer,
|
|
|
40 |
queryTokenizer: o.queryTokenizer
|
|
|
41 |
});
|
|
|
42 |
|
|
|
43 |
// only initialize storage if there's a cacheKey otherwise
|
|
|
44 |
// loading from storage on subsequent page loads is impossible
|
|
|
45 |
this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null;
|
|
|
46 |
}
|
|
|
47 |
|
|
|
48 |
// static methods
|
|
|
49 |
// --------------
|
|
|
50 |
|
|
|
51 |
Bloodhound.noConflict = function noConflict() {
|
|
|
52 |
root.Bloodhound = old;
|
|
|
53 |
return Bloodhound;
|
|
|
54 |
};
|
|
|
55 |
|
|
|
56 |
Bloodhound.tokenizers = tokenizers;
|
|
|
57 |
|
|
|
58 |
// instance methods
|
|
|
59 |
// ----------------
|
|
|
60 |
|
|
|
61 |
_.mixin(Bloodhound.prototype, {
|
|
|
62 |
|
|
|
63 |
// ### private
|
|
|
64 |
|
|
|
65 |
_loadPrefetch: function loadPrefetch(o) {
|
|
|
66 |
var that = this, serialized, deferred;
|
|
|
67 |
|
|
|
68 |
if (serialized = this._readFromStorage(o.thumbprint)) {
|
|
|
69 |
this.index.bootstrap(serialized);
|
|
|
70 |
deferred = $.Deferred().resolve();
|
|
|
71 |
}
|
|
|
72 |
|
|
|
73 |
else {
|
|
|
74 |
deferred = $.ajax(o.url, o.ajax).done(handlePrefetchResponse);
|
|
|
75 |
}
|
|
|
76 |
|
|
|
77 |
return deferred;
|
|
|
78 |
|
|
|
79 |
function handlePrefetchResponse(resp) {
|
|
|
80 |
// clear to mirror the behavior of bootstrapping
|
|
|
81 |
that.clear();
|
|
|
82 |
that.add(o.filter ? o.filter(resp) : resp);
|
|
|
83 |
|
|
|
84 |
that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl);
|
|
|
85 |
}
|
|
|
86 |
},
|
|
|
87 |
|
|
|
88 |
_getFromRemote: function getFromRemote(query, cb) {
|
|
|
89 |
var that = this, url, uriEncodedQuery;
|
|
|
90 |
|
|
|
91 |
if (!this.transport) { return; }
|
|
|
92 |
|
|
|
93 |
query = query || '';
|
|
|
94 |
uriEncodedQuery = encodeURIComponent(query);
|
|
|
95 |
|
|
|
96 |
url = this.remote.replace ?
|
|
|
97 |
this.remote.replace(this.remote.url, query) :
|
|
|
98 |
this.remote.url.replace(this.remote.wildcard, uriEncodedQuery);
|
|
|
99 |
|
|
|
100 |
return this.transport.get(url, this.remote.ajax, handleRemoteResponse);
|
|
|
101 |
|
|
|
102 |
function handleRemoteResponse(err, resp) {
|
|
|
103 |
err ? cb([]) : cb(that.remote.filter ? that.remote.filter(resp) : resp);
|
|
|
104 |
}
|
|
|
105 |
},
|
|
|
106 |
|
|
|
107 |
_cancelLastRemoteRequest: function cancelLastRemoteRequest() {
|
|
|
108 |
// #149: prevents outdated rate-limited requests from being sent
|
|
|
109 |
this.transport && this.transport.cancel();
|
|
|
110 |
},
|
|
|
111 |
|
|
|
112 |
_saveToStorage: function saveToStorage(data, thumbprint, ttl) {
|
|
|
113 |
if (this.storage) {
|
|
|
114 |
this.storage.set(keys.data, data, ttl);
|
|
|
115 |
this.storage.set(keys.protocol, location.protocol, ttl);
|
|
|
116 |
this.storage.set(keys.thumbprint, thumbprint, ttl);
|
|
|
117 |
}
|
|
|
118 |
},
|
|
|
119 |
|
|
|
120 |
_readFromStorage: function readFromStorage(thumbprint) {
|
|
|
121 |
var stored = {}, isExpired;
|
|
|
122 |
|
|
|
123 |
if (this.storage) {
|
|
|
124 |
stored.data = this.storage.get(keys.data);
|
|
|
125 |
stored.protocol = this.storage.get(keys.protocol);
|
|
|
126 |
stored.thumbprint = this.storage.get(keys.thumbprint);
|
|
|
127 |
}
|
|
|
128 |
// the stored data is considered expired if the thumbprints
|
|
|
129 |
// don't match or if the protocol it was originally stored under
|
|
|
130 |
// has changed
|
|
|
131 |
isExpired = stored.thumbprint !== thumbprint ||
|
|
|
132 |
stored.protocol !== location.protocol;
|
|
|
133 |
|
|
|
134 |
return stored.data && !isExpired ? stored.data : null;
|
|
|
135 |
},
|
|
|
136 |
|
|
|
137 |
_initialize: function initialize() {
|
|
|
138 |
var that = this, local = this.local, deferred;
|
|
|
139 |
|
|
|
140 |
deferred = this.prefetch ?
|
|
|
141 |
this._loadPrefetch(this.prefetch) : $.Deferred().resolve();
|
|
|
142 |
|
|
|
143 |
// make sure local is added to the index after prefetch
|
|
|
144 |
local && deferred.done(addLocalToIndex);
|
|
|
145 |
|
|
|
146 |
this.transport = this.remote ? new Transport(this.remote) : null;
|
|
|
147 |
|
|
|
148 |
return (this.initPromise = deferred.promise());
|
|
|
149 |
|
|
|
150 |
function addLocalToIndex() {
|
|
|
151 |
// local can be a function that returns an array of datums
|
|
|
152 |
that.add(_.isFunction(local) ? local() : local);
|
|
|
153 |
}
|
|
|
154 |
},
|
|
|
155 |
|
|
|
156 |
// ### public
|
|
|
157 |
|
|
|
158 |
initialize: function initialize(force) {
|
|
|
159 |
return !this.initPromise || force ? this._initialize() : this.initPromise;
|
|
|
160 |
},
|
|
|
161 |
|
|
|
162 |
add: function add(data) {
|
|
|
163 |
this.index.add(data);
|
|
|
164 |
},
|
|
|
165 |
|
|
|
166 |
get: function get(query, cb) {
|
|
|
167 |
var that = this, matches = [], cacheHit = false;
|
|
|
168 |
|
|
|
169 |
matches = this.index.get(query);
|
|
|
170 |
matches = this.sorter(matches).slice(0, this.limit);
|
|
|
171 |
|
|
|
172 |
matches.length < this.limit ?
|
|
|
173 |
(cacheHit = this._getFromRemote(query, returnRemoteMatches)) :
|
|
|
174 |
this._cancelLastRemoteRequest();
|
|
|
175 |
|
|
|
176 |
// if a cache hit occurred, skip rendering local matches
|
|
|
177 |
// because the rendering of local/remote matches is already
|
|
|
178 |
// in the event loop
|
|
|
179 |
if (!cacheHit) {
|
|
|
180 |
// only render if there are some local suggestions or we're
|
|
|
181 |
// going to the network to backfill
|
|
|
182 |
(matches.length > 0 || !this.transport) && cb && cb(matches);
|
|
|
183 |
}
|
|
|
184 |
|
|
|
185 |
function returnRemoteMatches(remoteMatches) {
|
|
|
186 |
var matchesWithBackfill = matches.slice(0);
|
|
|
187 |
|
|
|
188 |
_.each(remoteMatches, function(remoteMatch) {
|
|
|
189 |
var isDuplicate;
|
|
|
190 |
|
|
|
191 |
// checks for duplicates
|
|
|
192 |
isDuplicate = _.some(matchesWithBackfill, function(match) {
|
|
|
193 |
return that.dupDetector(remoteMatch, match);
|
|
|
194 |
});
|
|
|
195 |
|
|
|
196 |
!isDuplicate && matchesWithBackfill.push(remoteMatch);
|
|
|
197 |
|
|
|
198 |
// if we're at the limit, we no longer need to process
|
|
|
199 |
// the remote results and can break out of the each loop
|
|
|
200 |
return matchesWithBackfill.length < that.limit;
|
|
|
201 |
});
|
|
|
202 |
cb && cb(that.sorter(matchesWithBackfill));
|
|
|
203 |
}
|
|
|
204 |
},
|
|
|
205 |
|
|
|
206 |
clear: function clear() {
|
|
|
207 |
this.index.reset();
|
|
|
208 |
},
|
|
|
209 |
|
|
|
210 |
clearPrefetchCache: function clearPrefetchCache() {
|
|
|
211 |
this.storage && this.storage.clear();
|
|
|
212 |
},
|
|
|
213 |
|
|
|
214 |
clearRemoteCache: function clearRemoteCache() {
|
|
|
215 |
this.transport && Transport.resetCache();
|
|
|
216 |
},
|
|
|
217 |
|
|
|
218 |
ttAdapter: function ttAdapter() { return _.bind(this.get, this); }
|
|
|
219 |
});
|
|
|
220 |
|
|
|
221 |
return Bloodhound;
|
|
|
222 |
|
|
|
223 |
// helper functions
|
|
|
224 |
// ----------------
|
|
|
225 |
|
|
|
226 |
function getSorter(sortFn) {
|
|
|
227 |
return _.isFunction(sortFn) ? sort : noSort;
|
|
|
228 |
|
|
|
229 |
function sort(array) { return array.sort(sortFn); }
|
|
|
230 |
function noSort(array) { return array; }
|
|
|
231 |
}
|
|
|
232 |
|
|
|
233 |
function ignoreDuplicates() { return false; }
|
|
|
234 |
})(this);
|