twitter-typeahead-rails 0.9.3 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -15,7 +15,7 @@ Add this line to your application's Gemfile:
15
15
  or
16
16
 
17
17
  gem 'twitter-typeahead-rails', :git => "git://github.com/yourabi/twitter-typeahead-rails.git"
18
-
18
+
19
19
 
20
20
  And then execute:
21
21
 
@@ -33,9 +33,9 @@ Add one of the following to your application.js manifest:
33
33
 
34
34
  ```js
35
35
 
36
- //= require twitter/typeahead
36
+ //= require twitter/typeahead
37
37
 
38
- //= require twitter/typeahead.min
38
+ //= require twitter/typeahead.min
39
39
 
40
40
  ```
41
41
 
@@ -43,13 +43,36 @@ Add one of the following to your application.js manifest:
43
43
  ```js
44
44
 
45
45
  // Twitter typeahead exmaple.
46
- $(document).ready(function() {
47
- $('.typeahead').typeahead( {name: 'planets', local: [ "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune" ] });
46
+
47
+ // instantiate the bloodhound suggestion engine
48
+ var numbers = new Bloodhound({
49
+ datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.num); },
50
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
51
+ local: [
52
+ { num: 'one' },
53
+ { num: 'two' },
54
+ { num: 'three' },
55
+ { num: 'four' },
56
+ { num: 'five' },
57
+ { num: 'six' },
58
+ { num: 'seven' },
59
+ { num: 'eight' },
60
+ { num: 'nine' },
61
+ { num: 'ten' }
62
+ ]
48
63
  });
49
64
 
65
+ // initialize the bloodhound suggestion engine
66
+ numbers.initialize();
67
+
68
+ // instantiate the typeahead UI
69
+ $('.example-numbers .typeahead').typeahead(null, {
70
+ displayKey: 'num',
71
+ source: numbers.ttAdapter()
72
+ });
50
73
  ```
51
74
 
52
- Currently this version tracks version v0.9.3.
75
+ Currently this version tracks version v0.10.1.
53
76
 
54
77
  ## Contributing
55
78
 
@@ -1,7 +1,7 @@
1
1
  module Twitter
2
2
  module Typeahead
3
3
  module Rails
4
- VERSION = "0.9.3"
4
+ VERSION = "0.10.1"
5
5
  end
6
6
  end
7
7
  end
@@ -1,4 +1,4 @@
1
- //=require twitter/typeahead/typeahead.js
1
+ //=require twitter/typeahead/typeahead.bundle.js
2
2
 
3
3
 
4
4
 
@@ -1,4 +1,4 @@
1
- //=require twitter/typeahead/typeahead.min.js
1
+ //=require twitter/typeahead/typeahead.bundle.min.js
2
2
 
3
3
 
4
4
 
@@ -0,0 +1,651 @@
1
+ /*!
2
+ * typeahead.js 0.10.1
3
+ * https://github.com/twitter/typeahead.js
4
+ * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
5
+ */
6
+
7
+ (function($) {
8
+ var _ = {
9
+ isMsie: function() {
10
+ return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
11
+ },
12
+ isBlankString: function(str) {
13
+ return !str || /^\s*$/.test(str);
14
+ },
15
+ escapeRegExChars: function(str) {
16
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
17
+ },
18
+ isString: function(obj) {
19
+ return typeof obj === "string";
20
+ },
21
+ isNumber: function(obj) {
22
+ return typeof obj === "number";
23
+ },
24
+ isArray: $.isArray,
25
+ isFunction: $.isFunction,
26
+ isObject: $.isPlainObject,
27
+ isUndefined: function(obj) {
28
+ return typeof obj === "undefined";
29
+ },
30
+ bind: $.proxy,
31
+ each: function(collection, cb) {
32
+ $.each(collection, reverseArgs);
33
+ function reverseArgs(index, value) {
34
+ return cb(value, index);
35
+ }
36
+ },
37
+ map: $.map,
38
+ filter: $.grep,
39
+ every: function(obj, test) {
40
+ var result = true;
41
+ if (!obj) {
42
+ return result;
43
+ }
44
+ $.each(obj, function(key, val) {
45
+ if (!(result = test.call(null, val, key, obj))) {
46
+ return false;
47
+ }
48
+ });
49
+ return !!result;
50
+ },
51
+ some: function(obj, test) {
52
+ var result = false;
53
+ if (!obj) {
54
+ return result;
55
+ }
56
+ $.each(obj, function(key, val) {
57
+ if (result = test.call(null, val, key, obj)) {
58
+ return false;
59
+ }
60
+ });
61
+ return !!result;
62
+ },
63
+ mixin: $.extend,
64
+ getUniqueId: function() {
65
+ var counter = 0;
66
+ return function() {
67
+ return counter++;
68
+ };
69
+ }(),
70
+ templatify: function templatify(obj) {
71
+ return $.isFunction(obj) ? obj : template;
72
+ function template() {
73
+ return String(obj);
74
+ }
75
+ },
76
+ defer: function(fn) {
77
+ setTimeout(fn, 0);
78
+ },
79
+ debounce: function(func, wait, immediate) {
80
+ var timeout, result;
81
+ return function() {
82
+ var context = this, args = arguments, later, callNow;
83
+ later = function() {
84
+ timeout = null;
85
+ if (!immediate) {
86
+ result = func.apply(context, args);
87
+ }
88
+ };
89
+ callNow = immediate && !timeout;
90
+ clearTimeout(timeout);
91
+ timeout = setTimeout(later, wait);
92
+ if (callNow) {
93
+ result = func.apply(context, args);
94
+ }
95
+ return result;
96
+ };
97
+ },
98
+ throttle: function(func, wait) {
99
+ var context, args, timeout, result, previous, later;
100
+ previous = 0;
101
+ later = function() {
102
+ previous = new Date();
103
+ timeout = null;
104
+ result = func.apply(context, args);
105
+ };
106
+ return function() {
107
+ var now = new Date(), remaining = wait - (now - previous);
108
+ context = this;
109
+ args = arguments;
110
+ if (remaining <= 0) {
111
+ clearTimeout(timeout);
112
+ timeout = null;
113
+ previous = now;
114
+ result = func.apply(context, args);
115
+ } else if (!timeout) {
116
+ timeout = setTimeout(later, remaining);
117
+ }
118
+ return result;
119
+ };
120
+ },
121
+ noop: function() {}
122
+ };
123
+ var VERSION = "0.10.1";
124
+ var LruCache = function(root, undefined) {
125
+ function LruCache(maxSize) {
126
+ this.maxSize = maxSize || 100;
127
+ this.size = 0;
128
+ this.hash = {};
129
+ this.list = new List();
130
+ }
131
+ _.mixin(LruCache.prototype, {
132
+ set: function set(key, val) {
133
+ var tailItem = this.list.tail, node;
134
+ if (this.size >= this.maxSize) {
135
+ this.list.remove(tailItem);
136
+ delete this.hash[tailItem.key];
137
+ }
138
+ if (node = this.hash[key]) {
139
+ node.val = val;
140
+ this.list.moveToFront(node);
141
+ } else {
142
+ node = new Node(key, val);
143
+ this.list.add(node);
144
+ this.hash[key] = node;
145
+ this.size++;
146
+ }
147
+ },
148
+ get: function get(key) {
149
+ var node = this.hash[key];
150
+ if (node) {
151
+ this.list.moveToFront(node);
152
+ return node.val;
153
+ }
154
+ }
155
+ });
156
+ function List() {
157
+ this.head = this.tail = null;
158
+ }
159
+ _.mixin(List.prototype, {
160
+ add: function add(node) {
161
+ if (this.head) {
162
+ node.next = this.head;
163
+ this.head.prev = node;
164
+ }
165
+ this.head = node;
166
+ this.tail = this.tail || node;
167
+ },
168
+ remove: function remove(node) {
169
+ node.prev ? node.prev.next = node.next : this.head = node.next;
170
+ node.next ? node.next.prev = node.prev : this.tail = node.prev;
171
+ },
172
+ moveToFront: function(node) {
173
+ this.remove(node);
174
+ this.add(node);
175
+ }
176
+ });
177
+ function Node(key, val) {
178
+ this.key = key;
179
+ this.val = val;
180
+ this.prev = this.next = null;
181
+ }
182
+ return LruCache;
183
+ }(this);
184
+ var PersistentStorage = function() {
185
+ var ls, methods;
186
+ try {
187
+ ls = window.localStorage;
188
+ ls.setItem("~~~", "!");
189
+ ls.removeItem("~~~");
190
+ } catch (err) {
191
+ ls = null;
192
+ }
193
+ function PersistentStorage(namespace) {
194
+ this.prefix = [ "__", namespace, "__" ].join("");
195
+ this.ttlKey = "__ttl__";
196
+ this.keyMatcher = new RegExp("^" + this.prefix);
197
+ }
198
+ if (ls && window.JSON) {
199
+ methods = {
200
+ _prefix: function(key) {
201
+ return this.prefix + key;
202
+ },
203
+ _ttlKey: function(key) {
204
+ return this._prefix(key) + this.ttlKey;
205
+ },
206
+ get: function(key) {
207
+ if (this.isExpired(key)) {
208
+ this.remove(key);
209
+ }
210
+ return decode(ls.getItem(this._prefix(key)));
211
+ },
212
+ set: function(key, val, ttl) {
213
+ if (_.isNumber(ttl)) {
214
+ ls.setItem(this._ttlKey(key), encode(now() + ttl));
215
+ } else {
216
+ ls.removeItem(this._ttlKey(key));
217
+ }
218
+ return ls.setItem(this._prefix(key), encode(val));
219
+ },
220
+ remove: function(key) {
221
+ ls.removeItem(this._ttlKey(key));
222
+ ls.removeItem(this._prefix(key));
223
+ return this;
224
+ },
225
+ clear: function() {
226
+ var i, key, keys = [], len = ls.length;
227
+ for (i = 0; i < len; i++) {
228
+ if ((key = ls.key(i)).match(this.keyMatcher)) {
229
+ keys.push(key.replace(this.keyMatcher, ""));
230
+ }
231
+ }
232
+ for (i = keys.length; i--; ) {
233
+ this.remove(keys[i]);
234
+ }
235
+ return this;
236
+ },
237
+ isExpired: function(key) {
238
+ var ttl = decode(ls.getItem(this._ttlKey(key)));
239
+ return _.isNumber(ttl) && now() > ttl ? true : false;
240
+ }
241
+ };
242
+ } else {
243
+ methods = {
244
+ get: _.noop,
245
+ set: _.noop,
246
+ remove: _.noop,
247
+ clear: _.noop,
248
+ isExpired: _.noop
249
+ };
250
+ }
251
+ _.mixin(PersistentStorage.prototype, methods);
252
+ return PersistentStorage;
253
+ function now() {
254
+ return new Date().getTime();
255
+ }
256
+ function encode(val) {
257
+ return JSON.stringify(_.isUndefined(val) ? null : val);
258
+ }
259
+ function decode(val) {
260
+ return JSON.parse(val);
261
+ }
262
+ }();
263
+ var Transport = function() {
264
+ var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, requestCache = new LruCache(10);
265
+ function Transport(o) {
266
+ o = o || {};
267
+ this._send = o.transport ? callbackToDeferred(o.transport) : $.ajax;
268
+ this._get = o.rateLimiter ? o.rateLimiter(this._get) : this._get;
269
+ }
270
+ Transport.setMaxPendingRequests = function setMaxPendingRequests(num) {
271
+ maxPendingRequests = num;
272
+ };
273
+ Transport.resetCache = function clearCache() {
274
+ requestCache = new LruCache(10);
275
+ };
276
+ _.mixin(Transport.prototype, {
277
+ _get: function(url, o, cb) {
278
+ var that = this, jqXhr;
279
+ if (jqXhr = pendingRequests[url]) {
280
+ jqXhr.done(done);
281
+ } else if (pendingRequestsCount < maxPendingRequests) {
282
+ pendingRequestsCount++;
283
+ pendingRequests[url] = this._send(url, o).done(done).always(always);
284
+ } else {
285
+ this.onDeckRequestArgs = [].slice.call(arguments, 0);
286
+ }
287
+ function done(resp) {
288
+ cb && cb(resp);
289
+ requestCache.set(url, resp);
290
+ }
291
+ function always() {
292
+ pendingRequestsCount--;
293
+ delete pendingRequests[url];
294
+ if (that.onDeckRequestArgs) {
295
+ that._get.apply(that, that.onDeckRequestArgs);
296
+ that.onDeckRequestArgs = null;
297
+ }
298
+ }
299
+ },
300
+ get: function(url, o, cb) {
301
+ var that = this, resp;
302
+ if (_.isFunction(o)) {
303
+ cb = o;
304
+ o = {};
305
+ }
306
+ if (resp = requestCache.get(url)) {
307
+ _.defer(function() {
308
+ cb && cb(resp);
309
+ });
310
+ } else {
311
+ this._get(url, o, cb);
312
+ }
313
+ return !!resp;
314
+ }
315
+ });
316
+ return Transport;
317
+ function callbackToDeferred(fn) {
318
+ return function customSendWrapper(url, o) {
319
+ var deferred = $.Deferred();
320
+ fn(url, o, onSuccess, onError);
321
+ return deferred;
322
+ function onSuccess(resp) {
323
+ _.defer(function() {
324
+ deferred.resolve(resp);
325
+ });
326
+ }
327
+ function onError(err) {
328
+ _.defer(function() {
329
+ deferred.reject(err);
330
+ });
331
+ }
332
+ };
333
+ }
334
+ }();
335
+ var SearchIndex = function() {
336
+ function SearchIndex(o) {
337
+ o = o || {};
338
+ if (!o.datumTokenizer || !o.queryTokenizer) {
339
+ $.error("datumTokenizer and queryTokenizer are both required");
340
+ }
341
+ this.datumTokenizer = o.datumTokenizer;
342
+ this.queryTokenizer = o.queryTokenizer;
343
+ this.datums = [];
344
+ this.trie = newNode();
345
+ }
346
+ _.mixin(SearchIndex.prototype, {
347
+ bootstrap: function bootstrap(o) {
348
+ this.datums = o.datums;
349
+ this.trie = o.trie;
350
+ },
351
+ add: function(data) {
352
+ var that = this;
353
+ data = _.isArray(data) ? data : [ data ];
354
+ _.each(data, function(datum) {
355
+ var id, tokens;
356
+ id = that.datums.push(datum) - 1;
357
+ tokens = normalizeTokens(that.datumTokenizer(datum));
358
+ _.each(tokens, function(token) {
359
+ var node, chars, ch, ids;
360
+ node = that.trie;
361
+ chars = token.split("");
362
+ while (ch = chars.shift()) {
363
+ node = node.children[ch] || (node.children[ch] = newNode());
364
+ node.ids.push(id);
365
+ }
366
+ });
367
+ });
368
+ },
369
+ get: function get(query) {
370
+ var that = this, tokens, matches;
371
+ tokens = normalizeTokens(this.queryTokenizer(query));
372
+ _.each(tokens, function(token) {
373
+ var node, chars, ch, ids;
374
+ if (matches && matches.length === 0) {
375
+ return false;
376
+ }
377
+ node = that.trie;
378
+ chars = token.split("");
379
+ while (node && (ch = chars.shift())) {
380
+ node = node.children[ch];
381
+ }
382
+ if (node && chars.length === 0) {
383
+ ids = node.ids.slice(0);
384
+ matches = matches ? getIntersection(matches, ids) : ids;
385
+ } else {
386
+ matches = [];
387
+ return false;
388
+ }
389
+ });
390
+ return matches ? _.map(unique(matches), function(id) {
391
+ return that.datums[id];
392
+ }) : [];
393
+ },
394
+ serialize: function serialize() {
395
+ return {
396
+ datums: this.datums,
397
+ trie: this.trie
398
+ };
399
+ }
400
+ });
401
+ return SearchIndex;
402
+ function normalizeTokens(tokens) {
403
+ tokens = _.filter(tokens, function(token) {
404
+ return !!token;
405
+ });
406
+ tokens = _.map(tokens, function(token) {
407
+ return token.toLowerCase();
408
+ });
409
+ return tokens;
410
+ }
411
+ function newNode() {
412
+ return {
413
+ ids: [],
414
+ children: {}
415
+ };
416
+ }
417
+ function unique(array) {
418
+ var seen = {}, uniques = [];
419
+ for (var i = 0; i < array.length; i++) {
420
+ if (!seen[array[i]]) {
421
+ seen[array[i]] = true;
422
+ uniques.push(array[i]);
423
+ }
424
+ }
425
+ return uniques;
426
+ }
427
+ function getIntersection(arrayA, arrayB) {
428
+ var ai = 0, bi = 0, intersection = [];
429
+ arrayA = arrayA.sort(compare);
430
+ arrayB = arrayB.sort(compare);
431
+ while (ai < arrayA.length && bi < arrayB.length) {
432
+ if (arrayA[ai] < arrayB[bi]) {
433
+ ai++;
434
+ } else if (arrayA[ai] > arrayB[bi]) {
435
+ bi++;
436
+ } else {
437
+ intersection.push(arrayA[ai]);
438
+ ai++;
439
+ bi++;
440
+ }
441
+ }
442
+ return intersection;
443
+ function compare(a, b) {
444
+ return a - b;
445
+ }
446
+ }
447
+ }();
448
+ var oParser = function() {
449
+ return {
450
+ local: getLocal,
451
+ prefetch: getPrefetch,
452
+ remote: getRemote
453
+ };
454
+ function getLocal(o) {
455
+ var local = o.local || null;
456
+ if (_.isFunction(local)) {
457
+ local = local.call(null);
458
+ }
459
+ return local;
460
+ }
461
+ function getPrefetch(o) {
462
+ var prefetch, defaults;
463
+ defaults = {
464
+ url: null,
465
+ thumbprint: "",
466
+ ttl: 24 * 60 * 60 * 1e3,
467
+ filter: null,
468
+ ajax: {}
469
+ };
470
+ if (prefetch = o.prefetch || null) {
471
+ prefetch = _.isString(prefetch) ? {
472
+ url: prefetch
473
+ } : prefetch;
474
+ prefetch = _.mixin(defaults, prefetch);
475
+ prefetch.thumbprint = VERSION + prefetch.thumbprint;
476
+ prefetch.ajax.type = prefetch.ajax.type || "GET";
477
+ prefetch.ajax.dataType = prefetch.ajax.dataType || "json";
478
+ !prefetch.url && $.error("prefetch requires url to be set");
479
+ }
480
+ return prefetch;
481
+ }
482
+ function getRemote(o) {
483
+ var remote, defaults;
484
+ defaults = {
485
+ url: null,
486
+ wildcard: "%QUERY",
487
+ replace: null,
488
+ rateLimitBy: "debounce",
489
+ rateLimitWait: 300,
490
+ send: null,
491
+ filter: null,
492
+ ajax: {}
493
+ };
494
+ if (remote = o.remote || null) {
495
+ remote = _.isString(remote) ? {
496
+ url: remote
497
+ } : remote;
498
+ remote = _.mixin(defaults, remote);
499
+ remote.rateLimiter = /^throttle$/i.test(remote.rateLimitBy) ? byThrottle(remote.rateLimitWait) : byDebounce(remote.rateLimitWait);
500
+ remote.ajax.type = remote.ajax.type || "GET";
501
+ remote.ajax.dataType = remote.ajax.dataType || "json";
502
+ delete remote.rateLimitBy;
503
+ delete remote.rateLimitWait;
504
+ !remote.url && $.error("remote requires url to be set");
505
+ }
506
+ return remote;
507
+ function byDebounce(wait) {
508
+ return function(fn) {
509
+ return _.debounce(fn, wait);
510
+ };
511
+ }
512
+ function byThrottle(wait) {
513
+ return function(fn) {
514
+ return _.throttle(fn, wait);
515
+ };
516
+ }
517
+ }
518
+ }();
519
+ var Bloodhound = window.Bloodhound = function() {
520
+ var keys;
521
+ keys = {
522
+ data: "data",
523
+ protocol: "protocol",
524
+ thumbprint: "thumbprint"
525
+ };
526
+ function Bloodhound(o) {
527
+ if (!o || !o.local && !o.prefetch && !o.remote) {
528
+ $.error("one of local, prefetch, or remote is required");
529
+ }
530
+ this.limit = o.limit || 5;
531
+ this.sorter = getSorter(o.sorter);
532
+ this.dupDetector = o.dupDetector || ignoreDuplicates;
533
+ this.local = oParser.local(o);
534
+ this.prefetch = oParser.prefetch(o);
535
+ this.remote = oParser.remote(o);
536
+ this.cacheKey = this.prefetch ? this.prefetch.cacheKey || this.prefetch.url : null;
537
+ this.index = new SearchIndex({
538
+ datumTokenizer: o.datumTokenizer,
539
+ queryTokenizer: o.queryTokenizer
540
+ });
541
+ this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null;
542
+ }
543
+ Bloodhound.tokenizers = {
544
+ whitespace: function whitespaceTokenizer(s) {
545
+ return s.split(/\s+/);
546
+ },
547
+ nonword: function nonwordTokenizer(s) {
548
+ return s.split(/\W+/);
549
+ }
550
+ };
551
+ _.mixin(Bloodhound.prototype, {
552
+ _loadPrefetch: function loadPrefetch(o) {
553
+ var that = this, serialized, deferred;
554
+ if (serialized = this._readFromStorage(o.thumbprint)) {
555
+ this.index.bootstrap(serialized);
556
+ deferred = $.Deferred().resolve();
557
+ } else {
558
+ deferred = $.ajax(o.url, o.ajax).done(handlePrefetchResponse);
559
+ }
560
+ return deferred;
561
+ function handlePrefetchResponse(resp) {
562
+ var filtered;
563
+ filtered = o.filter ? o.filter(resp) : resp;
564
+ that.add(filtered);
565
+ that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl);
566
+ }
567
+ },
568
+ _getFromRemote: function getFromRemote(query, cb) {
569
+ var that = this, url, uriEncodedQuery;
570
+ query = query || "";
571
+ uriEncodedQuery = encodeURIComponent(query);
572
+ url = this.remote.replace ? this.remote.replace(this.remote.url, query) : this.remote.url.replace(this.remote.wildcard, uriEncodedQuery);
573
+ return this.transport.get(url, this.remote.ajax, handleRemoteResponse);
574
+ function handleRemoteResponse(resp) {
575
+ var filtered = that.remote.filter ? that.remote.filter(resp) : resp;
576
+ cb(filtered);
577
+ }
578
+ },
579
+ _saveToStorage: function saveToStorage(data, thumbprint, ttl) {
580
+ if (this.storage) {
581
+ this.storage.set(keys.data, data, ttl);
582
+ this.storage.set(keys.protocol, location.protocol, ttl);
583
+ this.storage.set(keys.thumbprint, thumbprint, ttl);
584
+ }
585
+ },
586
+ _readFromStorage: function readFromStorage(thumbprint) {
587
+ var stored = {}, isExpired;
588
+ if (this.storage) {
589
+ stored.data = this.storage.get(keys.data);
590
+ stored.protocol = this.storage.get(keys.protocol);
591
+ stored.thumbprint = this.storage.get(keys.thumbprint);
592
+ }
593
+ isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol;
594
+ return stored.data && !isExpired ? stored.data : null;
595
+ },
596
+ initialize: function initialize() {
597
+ var that = this, deferred;
598
+ deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve();
599
+ this.local && deferred.done(addLocalToIndex);
600
+ this.transport = this.remote ? new Transport(this.remote) : null;
601
+ this.initialize = function initialize() {
602
+ return deferred.promise();
603
+ };
604
+ return deferred.promise();
605
+ function addLocalToIndex() {
606
+ that.add(that.local);
607
+ }
608
+ },
609
+ add: function add(data) {
610
+ this.index.add(data);
611
+ },
612
+ get: function get(query, cb) {
613
+ var that = this, matches, cacheHit = false;
614
+ matches = this.index.get(query);
615
+ matches = this.sorter(matches).slice(0, this.limit);
616
+ if (matches.length < this.limit && this.transport) {
617
+ cacheHit = this._getFromRemote(query, returnRemoteMatches);
618
+ }
619
+ !cacheHit && cb && cb(matches);
620
+ function returnRemoteMatches(remoteMatches) {
621
+ var matchesWithBackfill = matches.slice(0);
622
+ _.each(remoteMatches, function(remoteMatch) {
623
+ var isDuplicate;
624
+ isDuplicate = _.some(matchesWithBackfill, function(match) {
625
+ return that.dupDetector(remoteMatch, match);
626
+ });
627
+ !isDuplicate && matchesWithBackfill.push(remoteMatch);
628
+ return matchesWithBackfill.length < that.limit;
629
+ });
630
+ cb && cb(that.sorter(matchesWithBackfill));
631
+ }
632
+ },
633
+ ttAdapter: function ttAdapter() {
634
+ return _.bind(this.get, this);
635
+ }
636
+ });
637
+ return Bloodhound;
638
+ function getSorter(sortFn) {
639
+ return _.isFunction(sortFn) ? sort : noSort;
640
+ function sort(array) {
641
+ return array.sort(sortFn);
642
+ }
643
+ function noSort(array) {
644
+ return array;
645
+ }
646
+ }
647
+ function ignoreDuplicates() {
648
+ return false;
649
+ }
650
+ }();
651
+ })(window.jQuery);