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

Sign up to get free protection for your applications and to get access to all the features.
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
+ });