twitter-typeahead-rails 0.10.5 → 0.11.1.pre.corejavascript

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.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- MTEyNTc3YTNkMTYwYjViMzNiOGUyYzA1N2FjM2MzYjAwMjI1ZWQ5YQ==
5
- data.tar.gz: !binary |-
6
- MmQ3ZDUzNTQ3ODU3NWU4Njg1ZDY4ZmMzZDE1NDI0YjEwMjg3NjYwNA==
2
+ SHA1:
3
+ metadata.gz: ef1c9d7c03cb7ceb3422f9da077080ca20504e66
4
+ data.tar.gz: 4574d3cb280a3fd7fa8ddb7ac24c9b7ba0b3db2c
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- N2QyNDJjNDQ5MjhkODk4YjM5MTM2NWVkNTAxNzkzMzdjMWRmOWMzZDU1NTQ0
10
- YTgzNTA5YjI1OWMyNThhZTZmZTQ4ODZlNTEzOTUxY2I4ODhlZGFmMGViNGQ1
11
- NTU0MDJiYjhkYWI0ODQ4YTg4MzNhNDg4ZWJjNWJhYTliMDU1YTY=
12
- data.tar.gz: !binary |-
13
- NGFhNTRhNjViNGYxZGI1ZjBiOWE2YWU0NjIxNWE4ZDAyNmViZjcxZTg4NWEy
14
- NWEyMWI3ZTM2MDZhYzY5YThjOTBjMjczYmYzODUzYTQ4OTFhYzczNGUyZmJk
15
- MzgwYWJmMTExMjU1MjRkY2U4ZTA2M2M5MjQ4NTczMmJlNWE4ZGQ=
6
+ metadata.gz: ba47f2ede69b1d391e9d247d216f6ddf8563a6dad88968eef7e0b58b03dfb1c015033661153d18d3d7d497ce171e9f9525040b44ff0c098ec1d53d57a64fb9d7
7
+ data.tar.gz: 0d97243cd0557b7a1d39ca6e60e36cb26eb0b1c5bc11c07055423eeb67cc6eb663f6769e64374a7f82d259c80a6799d4d80619c1bcc4f9fb63567487242ac2b0
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Twitter typeahead.js jquery plugin
2
2
 
3
- This asset gem packages the [twitter typeahead.js](https://github.com/twitter/typeahead.js) jquery plugin for the Rails asset pipeline.
3
+ This asset gem packages the [mantained twitter typeahead.js](https://github.com/corejavascript/typeahead.js) jquery plugin for the Rails asset pipeline.
4
4
 
5
5
  To learn more about typeahead.js read the post [Twitter's engineering blog](http://engineering.twitter.com/2013/02/twitter-typeaheadjs-you-autocomplete-me.html).
6
6
 
@@ -42,7 +42,7 @@ Add one of the following to your application.js manifest:
42
42
 
43
43
  ```js
44
44
 
45
- // Twitter typeahead exmaple.
45
+ // Twitter typeahead example.
46
46
 
47
47
  // instantiate the bloodhound suggestion engine
48
48
  var numbers = new Bloodhound({
@@ -72,7 +72,7 @@ $('.example-numbers .typeahead').typeahead(null, {
72
72
  });
73
73
  ```
74
74
 
75
- Currently this version tracks version v0.10.5.
75
+ Currently this version tracks version v0.11.1.
76
76
 
77
77
  ## Contributing
78
78
 
@@ -81,4 +81,3 @@ Currently this version tracks version v0.10.5.
81
81
  3. Commit your changes (`git commit -am 'Add some feature'`)
82
82
  4. Push to the branch (`git push origin my-new-feature`)
83
83
  5. Create new Pull Request
84
-
@@ -1,7 +1,7 @@
1
1
  module Twitter
2
2
  module Typeahead
3
3
  module Rails
4
- VERSION = "0.10.5"
4
+ VERSION = "0.11.1-corejavascript"
5
5
  end
6
6
  end
7
7
  end
@@ -1,4 +1,2 @@
1
1
  //=require twitter/typeahead/typeahead.bundle.js
2
2
 
3
-
4
-
@@ -1,4 +1,2 @@
1
1
  //=require twitter/typeahead/typeahead.bundle.min.js
2
2
 
3
-
4
-
@@ -1,10 +1,20 @@
1
1
  /*!
2
- * typeahead.js 0.10.5
2
+ * typeahead.js 0.11.1
3
3
  * https://github.com/twitter/typeahead.js
4
- * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT
4
+ * Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT
5
5
  */
6
6
 
7
- (function($) {
7
+ (function(root, factory) {
8
+ if (typeof define === "function" && define.amd) {
9
+ define([ "jquery" ], function(a0) {
10
+ return root["Bloodhound"] = factory(a0);
11
+ });
12
+ } else if (typeof exports === "object") {
13
+ module.exports = factory(require("jquery"));
14
+ } else {
15
+ root["Bloodhound"] = factory(jQuery);
16
+ }
17
+ })(this, function($) {
8
18
  var _ = function() {
9
19
  "use strict";
10
20
  return {
@@ -29,6 +39,12 @@
29
39
  isUndefined: function(obj) {
30
40
  return typeof obj === "undefined";
31
41
  },
42
+ isElement: function(obj) {
43
+ return !!(obj && obj.nodeType === 1);
44
+ },
45
+ isJQuery: function(obj) {
46
+ return obj instanceof $;
47
+ },
32
48
  toStr: function toStr(s) {
33
49
  return _.isUndefined(s) || s === null ? "" : s + "";
34
50
  },
@@ -66,12 +82,18 @@
66
82
  return !!result;
67
83
  },
68
84
  mixin: $.extend,
69
- getUniqueId: function() {
85
+ identity: function(x) {
86
+ return x;
87
+ },
88
+ clone: function(obj) {
89
+ return $.extend(true, {}, obj);
90
+ },
91
+ getIdGenerator: function() {
70
92
  var counter = 0;
71
93
  return function() {
72
94
  return counter++;
73
95
  };
74
- }(),
96
+ },
75
97
  templatify: function templatify(obj) {
76
98
  return $.isFunction(obj) ? obj : template;
77
99
  function template() {
@@ -123,10 +145,13 @@
123
145
  return result;
124
146
  };
125
147
  },
148
+ stringify: function(val) {
149
+ return _.isString(val) ? val : JSON.stringify(val);
150
+ },
126
151
  noop: function() {}
127
152
  };
128
153
  }();
129
- var VERSION = "0.10.5";
154
+ var VERSION = "0.11.1";
130
155
  var tokenizers = function() {
131
156
  "use strict";
132
157
  return {
@@ -146,11 +171,11 @@
146
171
  return str ? str.split(/\W+/) : [];
147
172
  }
148
173
  function getObjTokenizer(tokenizer) {
149
- return function setKey() {
150
- var args = [].slice.call(arguments, 0);
174
+ return function setKey(keys) {
175
+ keys = _.isArray(keys) ? keys : [].slice.call(arguments, 0);
151
176
  return function tokenize(o) {
152
177
  var tokens = [];
153
- _.each(args, function(k) {
178
+ _.each(keys, function(k) {
154
179
  tokens = tokens.concat(tokenizer(_.toStr(o[k])));
155
180
  });
156
181
  return tokens;
@@ -173,6 +198,7 @@
173
198
  if (this.size >= this.maxSize) {
174
199
  this.list.remove(tailItem);
175
200
  delete this.hash[tailItem.key];
201
+ this.size--;
176
202
  }
177
203
  if (node = this.hash[key]) {
178
204
  node.val = val;
@@ -227,73 +253,72 @@
227
253
  }();
228
254
  var PersistentStorage = function() {
229
255
  "use strict";
230
- var ls, methods;
256
+ var LOCAL_STORAGE;
231
257
  try {
232
- ls = window.localStorage;
233
- ls.setItem("~~~", "!");
234
- ls.removeItem("~~~");
258
+ LOCAL_STORAGE = window.localStorage;
259
+ LOCAL_STORAGE.setItem("~~~", "!");
260
+ LOCAL_STORAGE.removeItem("~~~");
235
261
  } catch (err) {
236
- ls = null;
262
+ LOCAL_STORAGE = null;
237
263
  }
238
- function PersistentStorage(namespace) {
264
+ function PersistentStorage(namespace, override) {
239
265
  this.prefix = [ "__", namespace, "__" ].join("");
240
266
  this.ttlKey = "__ttl__";
241
267
  this.keyMatcher = new RegExp("^" + _.escapeRegExChars(this.prefix));
268
+ this.ls = override || LOCAL_STORAGE;
269
+ !this.ls && this._noop();
242
270
  }
243
- if (ls && window.JSON) {
244
- methods = {
245
- _prefix: function(key) {
246
- return this.prefix + key;
247
- },
248
- _ttlKey: function(key) {
249
- return this._prefix(key) + this.ttlKey;
250
- },
251
- get: function(key) {
252
- if (this.isExpired(key)) {
253
- this.remove(key);
254
- }
255
- return decode(ls.getItem(this._prefix(key)));
256
- },
257
- set: function(key, val, ttl) {
258
- if (_.isNumber(ttl)) {
259
- ls.setItem(this._ttlKey(key), encode(now() + ttl));
260
- } else {
261
- ls.removeItem(this._ttlKey(key));
262
- }
263
- return ls.setItem(this._prefix(key), encode(val));
264
- },
265
- remove: function(key) {
266
- ls.removeItem(this._ttlKey(key));
267
- ls.removeItem(this._prefix(key));
268
- return this;
269
- },
270
- clear: function() {
271
- var i, key, keys = [], len = ls.length;
272
- for (i = 0; i < len; i++) {
273
- if ((key = ls.key(i)).match(this.keyMatcher)) {
274
- keys.push(key.replace(this.keyMatcher, ""));
275
- }
276
- }
277
- for (i = keys.length; i--; ) {
278
- this.remove(keys[i]);
271
+ _.mixin(PersistentStorage.prototype, {
272
+ _prefix: function(key) {
273
+ return this.prefix + key;
274
+ },
275
+ _ttlKey: function(key) {
276
+ return this._prefix(key) + this.ttlKey;
277
+ },
278
+ _noop: function() {
279
+ this.get = this.set = this.remove = this.clear = this.isExpired = _.noop;
280
+ },
281
+ _safeSet: function(key, val) {
282
+ try {
283
+ this.ls.setItem(key, val);
284
+ } catch (err) {
285
+ if (err.name === "QuotaExceededError") {
286
+ this.clear();
287
+ this._noop();
279
288
  }
280
- return this;
281
- },
282
- isExpired: function(key) {
283
- var ttl = decode(ls.getItem(this._ttlKey(key)));
284
- return _.isNumber(ttl) && now() > ttl ? true : false;
285
289
  }
286
- };
287
- } else {
288
- methods = {
289
- get: _.noop,
290
- set: _.noop,
291
- remove: _.noop,
292
- clear: _.noop,
293
- isExpired: _.noop
294
- };
295
- }
296
- _.mixin(PersistentStorage.prototype, methods);
290
+ },
291
+ get: function(key) {
292
+ if (this.isExpired(key)) {
293
+ this.remove(key);
294
+ }
295
+ return decode(this.ls.getItem(this._prefix(key)));
296
+ },
297
+ set: function(key, val, ttl) {
298
+ if (_.isNumber(ttl)) {
299
+ this._safeSet(this._ttlKey(key), encode(now() + ttl));
300
+ } else {
301
+ this.ls.removeItem(this._ttlKey(key));
302
+ }
303
+ return this._safeSet(this._prefix(key), encode(val));
304
+ },
305
+ remove: function(key) {
306
+ this.ls.removeItem(this._ttlKey(key));
307
+ this.ls.removeItem(this._prefix(key));
308
+ return this;
309
+ },
310
+ clear: function() {
311
+ var i, keys = gatherMatchingKeys(this.keyMatcher);
312
+ for (i = keys.length; i--; ) {
313
+ this.remove(keys[i]);
314
+ }
315
+ return this;
316
+ },
317
+ isExpired: function(key) {
318
+ var ttl = decode(this.ls.getItem(this._ttlKey(key)));
319
+ return _.isNumber(ttl) && now() > ttl ? true : false;
320
+ }
321
+ });
297
322
  return PersistentStorage;
298
323
  function now() {
299
324
  return new Date().getTime();
@@ -302,7 +327,16 @@
302
327
  return JSON.stringify(_.isUndefined(val) ? null : val);
303
328
  }
304
329
  function decode(val) {
305
- return JSON.parse(val);
330
+ return $.parseJSON(val);
331
+ }
332
+ function gatherMatchingKeys(keyMatcher) {
333
+ var i, key, keys = [], len = LOCAL_STORAGE.length;
334
+ for (i = 0; i < len; i++) {
335
+ if ((key = LOCAL_STORAGE.key(i)).match(keyMatcher)) {
336
+ keys.push(key.replace(keyMatcher, ""));
337
+ }
338
+ }
339
+ return keys;
306
340
  }
307
341
  }();
308
342
  var Transport = function() {
@@ -311,9 +345,9 @@
311
345
  function Transport(o) {
312
346
  o = o || {};
313
347
  this.cancelled = false;
314
- this.lastUrl = null;
315
- this._send = o.transport ? callbackToDeferred(o.transport) : $.ajax;
316
- this._get = o.rateLimiter ? o.rateLimiter(this._get) : this._get;
348
+ this.lastReq = null;
349
+ this._send = o.transport;
350
+ this._get = o.limiter ? o.limiter(this._get) : this._get;
317
351
  this._cache = o.cache === false ? new LruCache(0) : sharedCache;
318
352
  }
319
353
  Transport.setMaxPendingRequests = function setMaxPendingRequests(num) {
@@ -323,84 +357,73 @@
323
357
  sharedCache.reset();
324
358
  };
325
359
  _.mixin(Transport.prototype, {
326
- _get: function(url, o, cb) {
327
- var that = this, jqXhr;
328
- if (this.cancelled || url !== this.lastUrl) {
360
+ _fingerprint: function fingerprint(o) {
361
+ o = o || {};
362
+ return o.url + o.type + $.param(o.data || {});
363
+ },
364
+ _get: function(o, cb) {
365
+ var that = this, fingerprint, jqXhr;
366
+ fingerprint = this._fingerprint(o);
367
+ if (this.cancelled || fingerprint !== this.lastReq) {
329
368
  return;
330
369
  }
331
- if (jqXhr = pendingRequests[url]) {
370
+ if (jqXhr = pendingRequests[fingerprint]) {
332
371
  jqXhr.done(done).fail(fail);
333
372
  } else if (pendingRequestsCount < maxPendingRequests) {
334
373
  pendingRequestsCount++;
335
- pendingRequests[url] = this._send(url, o).done(done).fail(fail).always(always);
374
+ pendingRequests[fingerprint] = this._send(o).done(done).fail(fail).always(always);
336
375
  } else {
337
376
  this.onDeckRequestArgs = [].slice.call(arguments, 0);
338
377
  }
339
378
  function done(resp) {
340
- cb && cb(null, resp);
341
- that._cache.set(url, resp);
379
+ cb(null, resp);
380
+ that._cache.set(fingerprint, resp);
342
381
  }
343
382
  function fail() {
344
- cb && cb(true);
383
+ cb(true);
345
384
  }
346
385
  function always() {
347
386
  pendingRequestsCount--;
348
- delete pendingRequests[url];
387
+ delete pendingRequests[fingerprint];
349
388
  if (that.onDeckRequestArgs) {
350
389
  that._get.apply(that, that.onDeckRequestArgs);
351
390
  that.onDeckRequestArgs = null;
352
391
  }
353
392
  }
354
393
  },
355
- get: function(url, o, cb) {
356
- var resp;
357
- if (_.isFunction(o)) {
358
- cb = o;
359
- o = {};
360
- }
394
+ get: function(o, cb) {
395
+ var resp, fingerprint;
396
+ cb = cb || $.noop;
397
+ o = _.isString(o) ? {
398
+ url: o
399
+ } : o || {};
400
+ fingerprint = this._fingerprint(o);
361
401
  this.cancelled = false;
362
- this.lastUrl = url;
363
- if (resp = this._cache.get(url)) {
364
- _.defer(function() {
365
- cb && cb(null, resp);
366
- });
402
+ this.lastReq = fingerprint;
403
+ if (resp = this._cache.get(fingerprint)) {
404
+ cb(null, resp);
367
405
  } else {
368
- this._get(url, o, cb);
406
+ this._get(o, cb);
369
407
  }
370
- return !!resp;
371
408
  },
372
409
  cancel: function() {
373
410
  this.cancelled = true;
374
411
  }
375
412
  });
376
413
  return Transport;
377
- function callbackToDeferred(fn) {
378
- return function customSendWrapper(url, o) {
379
- var deferred = $.Deferred();
380
- fn(url, o, onSuccess, onError);
381
- return deferred;
382
- function onSuccess(resp) {
383
- _.defer(function() {
384
- deferred.resolve(resp);
385
- });
386
- }
387
- function onError(err) {
388
- _.defer(function() {
389
- deferred.reject(err);
390
- });
391
- }
392
- };
393
- }
394
414
  }();
395
- var SearchIndex = function() {
415
+ var SearchIndex = window.SearchIndex = function() {
396
416
  "use strict";
417
+ var CHILDREN = "c", IDS = "i";
397
418
  function SearchIndex(o) {
398
419
  o = o || {};
399
420
  if (!o.datumTokenizer || !o.queryTokenizer) {
400
421
  $.error("datumTokenizer and queryTokenizer are both required");
401
422
  }
423
+ this.identify = o.identify || _.stringify;
402
424
  this.datumTokenizer = o.datumTokenizer;
403
425
  this.queryTokenizer = o.queryTokenizer;
426
+ this.matchAnyQueryToken = o.matchAnyQueryToken;
404
427
  this.reset();
405
428
  }
406
429
  _.mixin(SearchIndex.prototype, {
@@ -413,46 +436,61 @@
413
436
  data = _.isArray(data) ? data : [ data ];
414
437
  _.each(data, function(datum) {
415
438
  var id, tokens;
416
- id = that.datums.push(datum) - 1;
439
+ that.datums[id = that.identify(datum)] = datum;
417
440
  tokens = normalizeTokens(that.datumTokenizer(datum));
418
441
  _.each(tokens, function(token) {
419
442
  var node, chars, ch;
420
443
  node = that.trie;
421
444
  chars = token.split("");
422
445
  while (ch = chars.shift()) {
423
- node = node.children[ch] || (node.children[ch] = newNode());
424
- node.ids.push(id);
446
+ node = node[CHILDREN][ch] || (node[CHILDREN][ch] = newNode());
447
+ node[IDS].push(id);
425
448
  }
426
449
  });
427
450
  });
428
451
  },
429
- get: function get(query) {
452
+ get: function get(ids) {
453
+ var that = this;
454
+ return _.map(ids, function(id) {
455
+ return that.datums[id];
456
+ });
457
+ },
458
+ search: function search(query) {
430
459
  var that = this, tokens, matches;
431
460
  tokens = normalizeTokens(this.queryTokenizer(query));
432
461
  _.each(tokens, function(token) {
433
462
  var node, chars, ch, ids;
434
- if (matches && matches.length === 0) {
463
+ if (matches && matches.length === 0 && !that.matchAnyQueryToken) {
435
464
  return false;
436
465
  }
437
466
  node = that.trie;
438
467
  chars = token.split("");
439
468
  while (node && (ch = chars.shift())) {
440
- node = node.children[ch];
469
+ node = node[CHILDREN][ch];
441
470
  }
442
471
  if (node && chars.length === 0) {
443
- ids = node.ids.slice(0);
472
+ ids = node[IDS].slice(0);
444
473
  matches = matches ? getIntersection(matches, ids) : ids;
445
474
  } else {
446
- matches = [];
447
- return false;
475
+ if (!that.matchAnyQueryToken) {
476
+ matches = [];
477
+ return false;
478
+ }
448
479
  }
449
480
  });
450
481
  return matches ? _.map(unique(matches), function(id) {
451
482
  return that.datums[id];
452
483
  }) : [];
453
484
  },
485
+ all: function all() {
486
+ var values = [];
487
+ for (var key in this.datums) {
488
+ values.push(this.datums[key]);
489
+ }
490
+ return values;
491
+ },
454
492
  reset: function reset() {
455
- this.datums = [];
493
+ this.datums = {};
456
494
  this.trie = newNode();
457
495
  },
458
496
  serialize: function serialize() {
@@ -473,10 +511,10 @@
473
511
  return tokens;
474
512
  }
475
513
  function newNode() {
476
- return {
477
- ids: [],
478
- children: {}
479
- };
514
+ var node = {};
515
+ node[IDS] = [];
516
+ node[CHILDREN] = {};
517
+ return node;
480
518
  }
481
519
  function unique(array) {
482
520
  var seen = {}, uniques = [];
@@ -490,8 +528,8 @@
490
528
  }
491
529
  function getIntersection(arrayA, arrayB) {
492
530
  var ai = 0, bi = 0, intersection = [];
493
- arrayA = arrayA.sort(compare);
494
- arrayB = arrayB.sort(compare);
531
+ arrayA = arrayA.sort();
532
+ arrayB = arrayB.sort();
495
533
  var lenArrayA = arrayA.length, lenArrayB = arrayB.length;
496
534
  while (ai < lenArrayA && bi < lenArrayB) {
497
535
  if (arrayA[ai] < arrayB[bi]) {
@@ -505,169 +543,330 @@
505
543
  }
506
544
  }
507
545
  return intersection;
508
- function compare(a, b) {
509
- return a - b;
546
+ }
547
+ }();
548
+ var Prefetch = function() {
549
+ "use strict";
550
+ var keys;
551
+ keys = {
552
+ data: "data",
553
+ protocol: "protocol",
554
+ thumbprint: "thumbprint"
555
+ };
556
+ function Prefetch(o) {
557
+ this.url = o.url;
558
+ this.ttl = o.ttl;
559
+ this.cache = o.cache;
560
+ this.prepare = o.prepare;
561
+ this.transform = o.transform;
562
+ this.transport = o.transport;
563
+ this.thumbprint = o.thumbprint;
564
+ this.storage = new PersistentStorage(o.cacheKey);
565
+ }
566
+ _.mixin(Prefetch.prototype, {
567
+ _settings: function settings() {
568
+ return {
569
+ url: this.url,
570
+ type: "GET",
571
+ dataType: "json"
572
+ };
573
+ },
574
+ store: function store(data) {
575
+ if (!this.cache) {
576
+ return;
577
+ }
578
+ this.storage.set(keys.data, data, this.ttl);
579
+ this.storage.set(keys.protocol, location.protocol, this.ttl);
580
+ this.storage.set(keys.thumbprint, this.thumbprint, this.ttl);
581
+ },
582
+ fromCache: function fromCache() {
583
+ var stored = {}, isExpired;
584
+ if (!this.cache) {
585
+ return null;
586
+ }
587
+ stored.data = this.storage.get(keys.data);
588
+ stored.protocol = this.storage.get(keys.protocol);
589
+ stored.thumbprint = this.storage.get(keys.thumbprint);
590
+ isExpired = stored.thumbprint !== this.thumbprint || stored.protocol !== location.protocol;
591
+ return stored.data && !isExpired ? stored.data : null;
592
+ },
593
+ fromNetwork: function(cb) {
594
+ var that = this, settings;
595
+ if (!cb) {
596
+ return;
597
+ }
598
+ settings = this.prepare(this._settings());
599
+ this.transport(settings).fail(onError).done(onResponse);
600
+ function onError() {
601
+ cb(true);
602
+ }
603
+ function onResponse(resp) {
604
+ cb(null, that.transform(resp));
605
+ }
606
+ },
607
+ clear: function clear() {
608
+ this.storage.clear();
609
+ return this;
510
610
  }
611
+ });
612
+ return Prefetch;
613
+ }();
614
+ var Remote = function() {
615
+ "use strict";
616
+ function Remote(o) {
617
+ this.url = o.url;
618
+ this.prepare = o.prepare;
619
+ this.transform = o.transform;
620
+ this.indexResponse = o.indexResponse;
621
+ this.transport = new Transport({
622
+ cache: o.cache,
623
+ limiter: o.limiter,
624
+ transport: o.transport
625
+ });
511
626
  }
627
+ _.mixin(Remote.prototype, {
628
+ _settings: function settings() {
629
+ return {
630
+ url: this.url,
631
+ type: "GET",
632
+ dataType: "json"
633
+ };
634
+ },
635
+ get: function get(query, cb) {
636
+ var that = this, settings;
637
+ if (!cb) {
638
+ return;
639
+ }
640
+ query = query || "";
641
+ settings = this.prepare(query, this._settings());
642
+ return this.transport.get(settings, onResponse);
643
+ function onResponse(err, resp) {
644
+ err ? cb([]) : cb(that.transform(resp));
645
+ }
646
+ },
647
+ cancelLastRequest: function cancelLastRequest() {
648
+ this.transport.cancel();
649
+ }
650
+ });
651
+ return Remote;
512
652
  }();
513
653
  var oParser = function() {
514
654
  "use strict";
515
- return {
516
- local: getLocal,
517
- prefetch: getPrefetch,
518
- remote: getRemote
655
+ return function parse(o) {
656
+ var defaults, sorter;
657
+ defaults = {
658
+ initialize: true,
659
+ identify: _.stringify,
660
+ datumTokenizer: null,
661
+ queryTokenizer: null,
662
+ matchAnyQueryToken: false,
663
+ sufficient: 5,
664
+ indexRemote: false,
665
+ sorter: null,
666
+ local: [],
667
+ prefetch: null,
668
+ remote: null
669
+ };
670
+ o = _.mixin(defaults, o || {});
671
+ !o.datumTokenizer && $.error("datumTokenizer is required");
672
+ !o.queryTokenizer && $.error("queryTokenizer is required");
673
+ sorter = o.sorter;
674
+ o.sorter = sorter ? function(x) {
675
+ return x.sort(sorter);
676
+ } : _.identity;
677
+ o.local = _.isFunction(o.local) ? o.local() : o.local;
678
+ o.prefetch = parsePrefetch(o.prefetch);
679
+ o.remote = parseRemote(o.remote);
680
+ return o;
519
681
  };
520
- function getLocal(o) {
521
- return o.local || null;
522
- }
523
- function getPrefetch(o) {
524
- var prefetch, defaults;
682
+ function parsePrefetch(o) {
683
+ var defaults;
684
+ if (!o) {
685
+ return null;
686
+ }
525
687
  defaults = {
526
688
  url: null,
527
- thumbprint: "",
528
689
  ttl: 24 * 60 * 60 * 1e3,
529
- filter: null,
530
- ajax: {}
690
+ cache: true,
691
+ cacheKey: null,
692
+ thumbprint: "",
693
+ prepare: _.identity,
694
+ transform: _.identity,
695
+ transport: null
531
696
  };
532
- if (prefetch = o.prefetch || null) {
533
- prefetch = _.isString(prefetch) ? {
534
- url: prefetch
535
- } : prefetch;
536
- prefetch = _.mixin(defaults, prefetch);
537
- prefetch.thumbprint = VERSION + prefetch.thumbprint;
538
- prefetch.ajax.type = prefetch.ajax.type || "GET";
539
- prefetch.ajax.dataType = prefetch.ajax.dataType || "json";
540
- !prefetch.url && $.error("prefetch requires url to be set");
541
- }
542
- return prefetch;
697
+ o = _.isString(o) ? {
698
+ url: o
699
+ } : o;
700
+ o = _.mixin(defaults, o);
701
+ !o.url && $.error("prefetch requires url to be set");
702
+ o.transform = o.filter || o.transform;
703
+ o.cacheKey = o.cacheKey || o.url;
704
+ o.thumbprint = VERSION + o.thumbprint;
705
+ o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax;
706
+ return o;
543
707
  }
544
- function getRemote(o) {
545
- var remote, defaults;
708
+ function parseRemote(o) {
709
+ var defaults;
710
+ if (!o) {
711
+ return;
712
+ }
546
713
  defaults = {
547
714
  url: null,
548
715
  cache: true,
549
- wildcard: "%QUERY",
716
+ prepare: null,
550
717
  replace: null,
718
+ wildcard: null,
719
+ limiter: null,
551
720
  rateLimitBy: "debounce",
552
721
  rateLimitWait: 300,
553
- send: null,
554
- filter: null,
555
- ajax: {}
722
+ transform: _.identity,
723
+ transport: null
556
724
  };
557
- if (remote = o.remote || null) {
558
- remote = _.isString(remote) ? {
559
- url: remote
560
- } : remote;
561
- remote = _.mixin(defaults, remote);
562
- remote.rateLimiter = /^throttle$/i.test(remote.rateLimitBy) ? byThrottle(remote.rateLimitWait) : byDebounce(remote.rateLimitWait);
563
- remote.ajax.type = remote.ajax.type || "GET";
564
- remote.ajax.dataType = remote.ajax.dataType || "json";
565
- delete remote.rateLimitBy;
566
- delete remote.rateLimitWait;
567
- !remote.url && $.error("remote requires url to be set");
725
+ o = _.isString(o) ? {
726
+ url: o
727
+ } : o;
728
+ o = _.mixin(defaults, o);
729
+ !o.url && $.error("remote requires url to be set");
730
+ o.transform = o.filter || o.transform;
731
+ o.prepare = toRemotePrepare(o);
732
+ o.limiter = toLimiter(o);
733
+ o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax;
734
+ delete o.replace;
735
+ delete o.wildcard;
736
+ delete o.rateLimitBy;
737
+ delete o.rateLimitWait;
738
+ return o;
739
+ }
740
+ function toRemotePrepare(o) {
741
+ var prepare, replace, wildcard;
742
+ prepare = o.prepare;
743
+ replace = o.replace;
744
+ wildcard = o.wildcard;
745
+ if (prepare) {
746
+ return prepare;
747
+ }
748
+ if (replace) {
749
+ prepare = prepareByReplace;
750
+ } else if (o.wildcard) {
751
+ prepare = prepareByWildcard;
752
+ } else {
753
+ prepare = idenityPrepare;
754
+ }
755
+ return prepare;
756
+ function prepareByReplace(query, settings) {
757
+ settings.url = replace(settings.url, query);
758
+ return settings;
759
+ }
760
+ function prepareByWildcard(query, settings) {
761
+ settings.url = settings.url.replace(wildcard, encodeURIComponent(query));
762
+ return settings;
568
763
  }
569
- return remote;
570
- function byDebounce(wait) {
571
- return function(fn) {
764
+ function idenityPrepare(query, settings) {
765
+ return settings;
766
+ }
767
+ }
768
+ function toLimiter(o) {
769
+ var limiter, method, wait;
770
+ limiter = o.limiter;
771
+ method = o.rateLimitBy;
772
+ wait = o.rateLimitWait;
773
+ if (!limiter) {
774
+ limiter = /^throttle$/i.test(method) ? throttle(wait) : debounce(wait);
775
+ }
776
+ return limiter;
777
+ function debounce(wait) {
778
+ return function debounce(fn) {
572
779
  return _.debounce(fn, wait);
573
780
  };
574
781
  }
575
- function byThrottle(wait) {
576
- return function(fn) {
782
+ function throttle(wait) {
783
+ return function throttle(fn) {
577
784
  return _.throttle(fn, wait);
578
785
  };
579
786
  }
580
787
  }
788
+ function callbackToDeferred(fn) {
789
+ return function wrapper(o) {
790
+ var deferred = $.Deferred();
791
+ fn(o, onSuccess, onError);
792
+ return deferred;
793
+ function onSuccess(resp) {
794
+ _.defer(function() {
795
+ deferred.resolve(resp);
796
+ });
797
+ }
798
+ function onError(err) {
799
+ _.defer(function() {
800
+ deferred.reject(err);
801
+ });
802
+ }
803
+ };
804
+ }
581
805
  }();
582
- (function(root) {
806
+ var Bloodhound = function() {
583
807
  "use strict";
584
- var old, keys;
585
- old = root.Bloodhound;
586
- keys = {
587
- data: "data",
588
- protocol: "protocol",
589
- thumbprint: "thumbprint"
590
- };
591
- root.Bloodhound = Bloodhound;
808
+ var old;
809
+ old = window && window.Bloodhound;
592
810
  function Bloodhound(o) {
593
- if (!o || !o.local && !o.prefetch && !o.remote) {
594
- $.error("one of local, prefetch, or remote is required");
595
- }
596
- this.limit = o.limit || 5;
597
- this.sorter = getSorter(o.sorter);
598
- this.dupDetector = o.dupDetector || ignoreDuplicates;
599
- this.local = oParser.local(o);
600
- this.prefetch = oParser.prefetch(o);
601
- this.remote = oParser.remote(o);
602
- this.cacheKey = this.prefetch ? this.prefetch.cacheKey || this.prefetch.url : null;
811
+ o = oParser(o);
812
+ this.sorter = o.sorter;
813
+ this.identify = o.identify;
814
+ this.sufficient = o.sufficient;
815
+ this.indexRemote = o.indexRemote;
816
+ this.local = o.local;
817
+ this.remote = o.remote ? new Remote(o.remote) : null;
818
+ this.prefetch = o.prefetch ? new Prefetch(o.prefetch) : null;
603
819
  this.index = new SearchIndex({
820
+ identify: this.identify,
604
821
  datumTokenizer: o.datumTokenizer,
605
822
  queryTokenizer: o.queryTokenizer
606
823
  });
607
- this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null;
824
+ o.initialize !== false && this.initialize();
608
825
  }
609
826
  Bloodhound.noConflict = function noConflict() {
610
- root.Bloodhound = old;
827
+ window && (window.Bloodhound = old);
611
828
  return Bloodhound;
612
829
  };
613
830
  Bloodhound.tokenizers = tokenizers;
614
831
  _.mixin(Bloodhound.prototype, {
615
- _loadPrefetch: function loadPrefetch(o) {
616
- var that = this, serialized, deferred;
617
- if (serialized = this._readFromStorage(o.thumbprint)) {
618
- this.index.bootstrap(serialized);
619
- deferred = $.Deferred().resolve();
620
- } else {
621
- deferred = $.ajax(o.url, o.ajax).done(handlePrefetchResponse);
622
- }
623
- return deferred;
624
- function handlePrefetchResponse(resp) {
625
- that.clear();
626
- that.add(o.filter ? o.filter(resp) : resp);
627
- that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl);
628
- }
629
- },
630
- _getFromRemote: function getFromRemote(query, cb) {
631
- var that = this, url, uriEncodedQuery;
632
- if (!this.transport) {
633
- return;
832
+ __ttAdapter: function ttAdapter() {
833
+ var that = this;
834
+ return this.remote ? withAsync : withoutAsync;
835
+ function withAsync(query, sync, async) {
836
+ return that.search(query, sync, async);
634
837
  }
635
- query = query || "";
636
- uriEncodedQuery = encodeURIComponent(query);
637
- url = this.remote.replace ? this.remote.replace(this.remote.url, query) : this.remote.url.replace(this.remote.wildcard, uriEncodedQuery);
638
- return this.transport.get(url, this.remote.ajax, handleRemoteResponse);
639
- function handleRemoteResponse(err, resp) {
640
- err ? cb([]) : cb(that.remote.filter ? that.remote.filter(resp) : resp);
838
+ function withoutAsync(query, sync) {
839
+ return that.search(query, sync);
641
840
  }
642
841
  },
643
- _cancelLastRemoteRequest: function cancelLastRemoteRequest() {
644
- this.transport && this.transport.cancel();
645
- },
646
- _saveToStorage: function saveToStorage(data, thumbprint, ttl) {
647
- if (this.storage) {
648
- this.storage.set(keys.data, data, ttl);
649
- this.storage.set(keys.protocol, location.protocol, ttl);
650
- this.storage.set(keys.thumbprint, thumbprint, ttl);
842
+ _loadPrefetch: function loadPrefetch() {
843
+ var that = this, deferred, serialized;
844
+ deferred = $.Deferred();
845
+ if (!this.prefetch) {
846
+ deferred.resolve();
847
+ } else if (serialized = this.prefetch.fromCache()) {
848
+ this.index.bootstrap(serialized);
849
+ deferred.resolve();
850
+ } else {
851
+ this.prefetch.fromNetwork(done);
651
852
  }
652
- },
653
- _readFromStorage: function readFromStorage(thumbprint) {
654
- var stored = {}, isExpired;
655
- if (this.storage) {
656
- stored.data = this.storage.get(keys.data);
657
- stored.protocol = this.storage.get(keys.protocol);
658
- stored.thumbprint = this.storage.get(keys.thumbprint);
853
+ return deferred.promise();
854
+ function done(err, data) {
855
+ if (err) {
856
+ return deferred.reject();
857
+ }
858
+ that.add(data);
859
+ that.prefetch.store(that.index.serialize());
860
+ deferred.resolve();
659
861
  }
660
- isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol;
661
- return stored.data && !isExpired ? stored.data : null;
662
862
  },
663
863
  _initialize: function initialize() {
664
- var that = this, local = this.local, deferred;
665
- deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve();
666
- local && deferred.done(addLocalToIndex);
667
- this.transport = this.remote ? new Transport(this.remote) : null;
668
- return this.initPromise = deferred.promise();
864
+ var that = this, deferred;
865
+ this.clear();
866
+ (this.initPromise = this._loadPrefetch()).done(addLocalToIndex);
867
+ return this.initPromise;
669
868
  function addLocalToIndex() {
670
- that.add(_.isFunction(local) ? local() : local);
869
+ that.add(that.local);
671
870
  }
672
871
  },
673
872
  initialize: function initialize(force) {
@@ -675,53 +874,55 @@
675
874
  },
676
875
  add: function add(data) {
677
876
  this.index.add(data);
678
- },
679
- get: function get(query, cb) {
680
- var that = this, matches = [], cacheHit = false;
681
- matches = this.index.get(query);
682
- matches = this.sorter(matches).slice(0, this.limit);
683
- matches.length < this.limit ? cacheHit = this._getFromRemote(query, returnRemoteMatches) : this._cancelLastRemoteRequest();
684
- if (!cacheHit) {
685
- (matches.length > 0 || !this.transport) && cb && cb(matches);
686
- }
687
- function returnRemoteMatches(remoteMatches) {
688
- var matchesWithBackfill = matches.slice(0);
689
- _.each(remoteMatches, function(remoteMatch) {
690
- var isDuplicate;
691
- isDuplicate = _.some(matchesWithBackfill, function(match) {
692
- return that.dupDetector(remoteMatch, match);
693
- });
694
- !isDuplicate && matchesWithBackfill.push(remoteMatch);
695
- return matchesWithBackfill.length < that.limit;
877
+ return this;
878
+ },
879
+ get: function get(ids) {
880
+ ids = _.isArray(ids) ? ids : [].slice.call(arguments);
881
+ return this.index.get(ids);
882
+ },
883
+ search: function search(query, sync, async) {
884
+ var that = this, local;
885
+ sync = sync || _.noop;
886
+ async = async || _.noop;
887
+ local = this.sorter(this.index.search(query));
888
+ sync(this.remote ? local.slice() : local);
889
+ if (this.remote && local.length < this.sufficient) {
890
+ this.remote.get(query, processRemote);
891
+ } else if (this.remote) {
892
+ this.remote.cancelLastRequest();
893
+ }
894
+ return this;
895
+ function processRemote(remote) {
896
+ var nonDuplicates = [];
897
+ _.each(remote, function(r) {
898
+ !_.some(local, function(l) {
899
+ return that.identify(r) === that.identify(l);
900
+ }) && nonDuplicates.push(r);
696
901
  });
697
- cb && cb(that.sorter(matchesWithBackfill));
902
+ that.indexRemote && that.add(nonDuplicates);
903
+ async(nonDuplicates);
698
904
  }
699
905
  },
906
+ all: function all() {
907
+ return this.index.all();
908
+ },
700
909
  clear: function clear() {
701
910
  this.index.reset();
911
+ return this;
702
912
  },
703
913
  clearPrefetchCache: function clearPrefetchCache() {
704
- this.storage && this.storage.clear();
914
+ this.prefetch && this.prefetch.clear();
915
+ return this;
705
916
  },
706
917
  clearRemoteCache: function clearRemoteCache() {
707
- this.transport && Transport.resetCache();
918
+ Transport.resetCache();
919
+ return this;
708
920
  },
709
921
  ttAdapter: function ttAdapter() {
710
- return _.bind(this.get, this);
922
+ return this.__ttAdapter();
711
923
  }
712
924
  });
713
925
  return Bloodhound;
714
- function getSorter(sortFn) {
715
- return _.isFunction(sortFn) ? sort : noSort;
716
- function sort(array) {
717
- return array.sort(sortFn);
718
- }
719
- function noSort(array) {
720
- return array;
721
- }
722
- }
723
- function ignoreDuplicates() {
724
- return false;
725
- }
726
- })(this);
727
- })(window.jQuery);
926
+ }();
927
+ return Bloodhound;
928
+ });