twitter-typeahead-rails 0.10.1 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -46,7 +46,7 @@ Add one of the following to your application.js manifest:
46
46
 
47
47
  // instantiate the bloodhound suggestion engine
48
48
  var numbers = new Bloodhound({
49
- datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.num); },
49
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('num'),
50
50
  queryTokenizer: Bloodhound.tokenizers.whitespace,
51
51
  local: [
52
52
  { num: 'one' },
@@ -72,7 +72,7 @@ $('.example-numbers .typeahead').typeahead(null, {
72
72
  });
73
73
  ```
74
74
 
75
- Currently this version tracks version v0.10.1.
75
+ Currently this version tracks version v0.10.2.
76
76
 
77
77
  ## Contributing
78
78
 
@@ -1,7 +1,7 @@
1
1
  module Twitter
2
2
  module Typeahead
3
3
  module Rails
4
- VERSION = "0.10.1"
4
+ VERSION = "0.10.2"
5
5
  end
6
6
  end
7
7
  end
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * typeahead.js 0.10.1
2
+ * typeahead.js 0.10.2
3
3
  * https://github.com/twitter/typeahead.js
4
- * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
4
+ * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT
5
5
  */
6
6
 
7
7
  (function($) {
@@ -120,8 +120,31 @@
120
120
  },
121
121
  noop: function() {}
122
122
  };
123
- var VERSION = "0.10.1";
124
- var LruCache = function(root, undefined) {
123
+ var VERSION = "0.10.2";
124
+ var tokenizers = function(root) {
125
+ return {
126
+ nonword: nonword,
127
+ whitespace: whitespace,
128
+ obj: {
129
+ nonword: getObjTokenizer(nonword),
130
+ whitespace: getObjTokenizer(whitespace)
131
+ }
132
+ };
133
+ function whitespace(s) {
134
+ return s.split(/\s+/);
135
+ }
136
+ function nonword(s) {
137
+ return s.split(/\W+/);
138
+ }
139
+ function getObjTokenizer(tokenizer) {
140
+ return function setKey(key) {
141
+ return function tokenize(o) {
142
+ return tokenizer(o[key]);
143
+ };
144
+ };
145
+ }
146
+ }();
147
+ var LruCache = function() {
125
148
  function LruCache(maxSize) {
126
149
  this.maxSize = maxSize || 100;
127
150
  this.size = 0;
@@ -180,7 +203,7 @@
180
203
  this.prev = this.next = null;
181
204
  }
182
205
  return LruCache;
183
- }(this);
206
+ }();
184
207
  var PersistentStorage = function() {
185
208
  var ls, methods;
186
209
  try {
@@ -277,17 +300,20 @@
277
300
  _get: function(url, o, cb) {
278
301
  var that = this, jqXhr;
279
302
  if (jqXhr = pendingRequests[url]) {
280
- jqXhr.done(done);
303
+ jqXhr.done(done).fail(fail);
281
304
  } else if (pendingRequestsCount < maxPendingRequests) {
282
305
  pendingRequestsCount++;
283
- pendingRequests[url] = this._send(url, o).done(done).always(always);
306
+ pendingRequests[url] = this._send(url, o).done(done).fail(fail).always(always);
284
307
  } else {
285
308
  this.onDeckRequestArgs = [].slice.call(arguments, 0);
286
309
  }
287
310
  function done(resp) {
288
- cb && cb(resp);
311
+ cb && cb(null, resp);
289
312
  requestCache.set(url, resp);
290
313
  }
314
+ function fail() {
315
+ cb && cb(true);
316
+ }
291
317
  function always() {
292
318
  pendingRequestsCount--;
293
319
  delete pendingRequests[url];
@@ -298,14 +324,14 @@
298
324
  }
299
325
  },
300
326
  get: function(url, o, cb) {
301
- var that = this, resp;
327
+ var resp;
302
328
  if (_.isFunction(o)) {
303
329
  cb = o;
304
330
  o = {};
305
331
  }
306
332
  if (resp = requestCache.get(url)) {
307
333
  _.defer(function() {
308
- cb && cb(resp);
334
+ cb && cb(null, resp);
309
335
  });
310
336
  } else {
311
337
  this._get(url, o, cb);
@@ -340,8 +366,7 @@
340
366
  }
341
367
  this.datumTokenizer = o.datumTokenizer;
342
368
  this.queryTokenizer = o.queryTokenizer;
343
- this.datums = [];
344
- this.trie = newNode();
369
+ this.reset();
345
370
  }
346
371
  _.mixin(SearchIndex.prototype, {
347
372
  bootstrap: function bootstrap(o) {
@@ -356,7 +381,7 @@
356
381
  id = that.datums.push(datum) - 1;
357
382
  tokens = normalizeTokens(that.datumTokenizer(datum));
358
383
  _.each(tokens, function(token) {
359
- var node, chars, ch, ids;
384
+ var node, chars, ch;
360
385
  node = that.trie;
361
386
  chars = token.split("");
362
387
  while (ch = chars.shift()) {
@@ -391,6 +416,10 @@
391
416
  return that.datums[id];
392
417
  }) : [];
393
418
  },
419
+ reset: function reset() {
420
+ this.datums = [];
421
+ this.trie = newNode();
422
+ },
394
423
  serialize: function serialize() {
395
424
  return {
396
425
  datums: this.datums,
@@ -452,11 +481,7 @@
452
481
  remote: getRemote
453
482
  };
454
483
  function getLocal(o) {
455
- var local = o.local || null;
456
- if (_.isFunction(local)) {
457
- local = local.call(null);
458
- }
459
- return local;
484
+ return o.local || null;
460
485
  }
461
486
  function getPrefetch(o) {
462
487
  var prefetch, defaults;
@@ -516,13 +541,15 @@
516
541
  }
517
542
  }
518
543
  }();
519
- var Bloodhound = window.Bloodhound = function() {
520
- var keys;
544
+ (function(root) {
545
+ var old, keys;
546
+ old = root.Bloodhound;
521
547
  keys = {
522
548
  data: "data",
523
549
  protocol: "protocol",
524
550
  thumbprint: "thumbprint"
525
551
  };
552
+ root.Bloodhound = Bloodhound;
526
553
  function Bloodhound(o) {
527
554
  if (!o || !o.local && !o.prefetch && !o.remote) {
528
555
  $.error("one of local, prefetch, or remote is required");
@@ -540,14 +567,11 @@
540
567
  });
541
568
  this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null;
542
569
  }
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
- }
570
+ Bloodhound.noConflict = function noConflict() {
571
+ root.Bloodhound = old;
572
+ return Bloodhound;
550
573
  };
574
+ Bloodhound.tokenizers = tokenizers;
551
575
  _.mixin(Bloodhound.prototype, {
552
576
  _loadPrefetch: function loadPrefetch(o) {
553
577
  var that = this, serialized, deferred;
@@ -559,9 +583,8 @@
559
583
  }
560
584
  return deferred;
561
585
  function handlePrefetchResponse(resp) {
562
- var filtered;
563
- filtered = o.filter ? o.filter(resp) : resp;
564
- that.add(filtered);
586
+ that.clear();
587
+ that.add(o.filter ? o.filter(resp) : resp);
565
588
  that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl);
566
589
  }
567
590
  },
@@ -571,9 +594,8 @@
571
594
  uriEncodedQuery = encodeURIComponent(query);
572
595
  url = this.remote.replace ? this.remote.replace(this.remote.url, query) : this.remote.url.replace(this.remote.wildcard, uriEncodedQuery);
573
596
  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);
597
+ function handleRemoteResponse(err, resp) {
598
+ err ? cb([]) : cb(that.remote.filter ? that.remote.filter(resp) : resp);
577
599
  }
578
600
  },
579
601
  _saveToStorage: function saveToStorage(data, thumbprint, ttl) {
@@ -593,30 +615,32 @@
593
615
  isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol;
594
616
  return stored.data && !isExpired ? stored.data : null;
595
617
  },
596
- initialize: function initialize() {
597
- var that = this, deferred;
618
+ _initialize: function initialize() {
619
+ var that = this, local = this.local, deferred;
598
620
  deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve();
599
- this.local && deferred.done(addLocalToIndex);
621
+ local && deferred.done(addLocalToIndex);
600
622
  this.transport = this.remote ? new Transport(this.remote) : null;
601
- this.initialize = function initialize() {
602
- return deferred.promise();
603
- };
604
- return deferred.promise();
623
+ return this.initPromise = deferred.promise();
605
624
  function addLocalToIndex() {
606
- that.add(that.local);
625
+ that.add(_.isFunction(local) ? local() : local);
607
626
  }
608
627
  },
628
+ initialize: function initialize(force) {
629
+ return !this.initPromise || force ? this._initialize() : this.initPromise;
630
+ },
609
631
  add: function add(data) {
610
632
  this.index.add(data);
611
633
  },
612
634
  get: function get(query, cb) {
613
- var that = this, matches, cacheHit = false;
635
+ var that = this, matches = [], cacheHit = false;
614
636
  matches = this.index.get(query);
615
637
  matches = this.sorter(matches).slice(0, this.limit);
616
638
  if (matches.length < this.limit && this.transport) {
617
639
  cacheHit = this._getFromRemote(query, returnRemoteMatches);
618
640
  }
619
- !cacheHit && cb && cb(matches);
641
+ if (!cacheHit) {
642
+ (matches.length > 0 || !this.transport) && cb && cb(matches);
643
+ }
620
644
  function returnRemoteMatches(remoteMatches) {
621
645
  var matchesWithBackfill = matches.slice(0);
622
646
  _.each(remoteMatches, function(remoteMatch) {
@@ -630,6 +654,15 @@
630
654
  cb && cb(that.sorter(matchesWithBackfill));
631
655
  }
632
656
  },
657
+ clear: function clear() {
658
+ this.index.reset();
659
+ },
660
+ clearPrefetchCache: function clearPrefetchCache() {
661
+ this.storage && this.storage.clear();
662
+ },
663
+ clearRemoteCache: function clearRemoteCache() {
664
+ this.transport && Transport.resetCache();
665
+ },
633
666
  ttAdapter: function ttAdapter() {
634
667
  return _.bind(this.get, this);
635
668
  }
@@ -647,5 +680,5 @@
647
680
  function ignoreDuplicates() {
648
681
  return false;
649
682
  }
650
- }();
683
+ })(this);
651
684
  })(window.jQuery);
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * typeahead.js 0.10.1
2
+ * typeahead.js 0.10.2
3
3
  * https://github.com/twitter/typeahead.js
4
- * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
4
+ * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT
5
5
  */
6
6
 
7
7
  (function($) {
@@ -120,8 +120,31 @@
120
120
  },
121
121
  noop: function() {}
122
122
  };
123
- var VERSION = "0.10.1";
124
- var LruCache = function(root, undefined) {
123
+ var VERSION = "0.10.2";
124
+ var tokenizers = function(root) {
125
+ return {
126
+ nonword: nonword,
127
+ whitespace: whitespace,
128
+ obj: {
129
+ nonword: getObjTokenizer(nonword),
130
+ whitespace: getObjTokenizer(whitespace)
131
+ }
132
+ };
133
+ function whitespace(s) {
134
+ return s.split(/\s+/);
135
+ }
136
+ function nonword(s) {
137
+ return s.split(/\W+/);
138
+ }
139
+ function getObjTokenizer(tokenizer) {
140
+ return function setKey(key) {
141
+ return function tokenize(o) {
142
+ return tokenizer(o[key]);
143
+ };
144
+ };
145
+ }
146
+ }();
147
+ var LruCache = function() {
125
148
  function LruCache(maxSize) {
126
149
  this.maxSize = maxSize || 100;
127
150
  this.size = 0;
@@ -180,7 +203,7 @@
180
203
  this.prev = this.next = null;
181
204
  }
182
205
  return LruCache;
183
- }(this);
206
+ }();
184
207
  var PersistentStorage = function() {
185
208
  var ls, methods;
186
209
  try {
@@ -277,17 +300,20 @@
277
300
  _get: function(url, o, cb) {
278
301
  var that = this, jqXhr;
279
302
  if (jqXhr = pendingRequests[url]) {
280
- jqXhr.done(done);
303
+ jqXhr.done(done).fail(fail);
281
304
  } else if (pendingRequestsCount < maxPendingRequests) {
282
305
  pendingRequestsCount++;
283
- pendingRequests[url] = this._send(url, o).done(done).always(always);
306
+ pendingRequests[url] = this._send(url, o).done(done).fail(fail).always(always);
284
307
  } else {
285
308
  this.onDeckRequestArgs = [].slice.call(arguments, 0);
286
309
  }
287
310
  function done(resp) {
288
- cb && cb(resp);
311
+ cb && cb(null, resp);
289
312
  requestCache.set(url, resp);
290
313
  }
314
+ function fail() {
315
+ cb && cb(true);
316
+ }
291
317
  function always() {
292
318
  pendingRequestsCount--;
293
319
  delete pendingRequests[url];
@@ -298,14 +324,14 @@
298
324
  }
299
325
  },
300
326
  get: function(url, o, cb) {
301
- var that = this, resp;
327
+ var resp;
302
328
  if (_.isFunction(o)) {
303
329
  cb = o;
304
330
  o = {};
305
331
  }
306
332
  if (resp = requestCache.get(url)) {
307
333
  _.defer(function() {
308
- cb && cb(resp);
334
+ cb && cb(null, resp);
309
335
  });
310
336
  } else {
311
337
  this._get(url, o, cb);
@@ -340,8 +366,7 @@
340
366
  }
341
367
  this.datumTokenizer = o.datumTokenizer;
342
368
  this.queryTokenizer = o.queryTokenizer;
343
- this.datums = [];
344
- this.trie = newNode();
369
+ this.reset();
345
370
  }
346
371
  _.mixin(SearchIndex.prototype, {
347
372
  bootstrap: function bootstrap(o) {
@@ -356,7 +381,7 @@
356
381
  id = that.datums.push(datum) - 1;
357
382
  tokens = normalizeTokens(that.datumTokenizer(datum));
358
383
  _.each(tokens, function(token) {
359
- var node, chars, ch, ids;
384
+ var node, chars, ch;
360
385
  node = that.trie;
361
386
  chars = token.split("");
362
387
  while (ch = chars.shift()) {
@@ -391,6 +416,10 @@
391
416
  return that.datums[id];
392
417
  }) : [];
393
418
  },
419
+ reset: function reset() {
420
+ this.datums = [];
421
+ this.trie = newNode();
422
+ },
394
423
  serialize: function serialize() {
395
424
  return {
396
425
  datums: this.datums,
@@ -452,11 +481,7 @@
452
481
  remote: getRemote
453
482
  };
454
483
  function getLocal(o) {
455
- var local = o.local || null;
456
- if (_.isFunction(local)) {
457
- local = local.call(null);
458
- }
459
- return local;
484
+ return o.local || null;
460
485
  }
461
486
  function getPrefetch(o) {
462
487
  var prefetch, defaults;
@@ -516,13 +541,15 @@
516
541
  }
517
542
  }
518
543
  }();
519
- var Bloodhound = window.Bloodhound = function() {
520
- var keys;
544
+ (function(root) {
545
+ var old, keys;
546
+ old = root.Bloodhound;
521
547
  keys = {
522
548
  data: "data",
523
549
  protocol: "protocol",
524
550
  thumbprint: "thumbprint"
525
551
  };
552
+ root.Bloodhound = Bloodhound;
526
553
  function Bloodhound(o) {
527
554
  if (!o || !o.local && !o.prefetch && !o.remote) {
528
555
  $.error("one of local, prefetch, or remote is required");
@@ -540,14 +567,11 @@
540
567
  });
541
568
  this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null;
542
569
  }
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
- }
570
+ Bloodhound.noConflict = function noConflict() {
571
+ root.Bloodhound = old;
572
+ return Bloodhound;
550
573
  };
574
+ Bloodhound.tokenizers = tokenizers;
551
575
  _.mixin(Bloodhound.prototype, {
552
576
  _loadPrefetch: function loadPrefetch(o) {
553
577
  var that = this, serialized, deferred;
@@ -559,9 +583,8 @@
559
583
  }
560
584
  return deferred;
561
585
  function handlePrefetchResponse(resp) {
562
- var filtered;
563
- filtered = o.filter ? o.filter(resp) : resp;
564
- that.add(filtered);
586
+ that.clear();
587
+ that.add(o.filter ? o.filter(resp) : resp);
565
588
  that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl);
566
589
  }
567
590
  },
@@ -571,9 +594,8 @@
571
594
  uriEncodedQuery = encodeURIComponent(query);
572
595
  url = this.remote.replace ? this.remote.replace(this.remote.url, query) : this.remote.url.replace(this.remote.wildcard, uriEncodedQuery);
573
596
  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);
597
+ function handleRemoteResponse(err, resp) {
598
+ err ? cb([]) : cb(that.remote.filter ? that.remote.filter(resp) : resp);
577
599
  }
578
600
  },
579
601
  _saveToStorage: function saveToStorage(data, thumbprint, ttl) {
@@ -593,30 +615,32 @@
593
615
  isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol;
594
616
  return stored.data && !isExpired ? stored.data : null;
595
617
  },
596
- initialize: function initialize() {
597
- var that = this, deferred;
618
+ _initialize: function initialize() {
619
+ var that = this, local = this.local, deferred;
598
620
  deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve();
599
- this.local && deferred.done(addLocalToIndex);
621
+ local && deferred.done(addLocalToIndex);
600
622
  this.transport = this.remote ? new Transport(this.remote) : null;
601
- this.initialize = function initialize() {
602
- return deferred.promise();
603
- };
604
- return deferred.promise();
623
+ return this.initPromise = deferred.promise();
605
624
  function addLocalToIndex() {
606
- that.add(that.local);
625
+ that.add(_.isFunction(local) ? local() : local);
607
626
  }
608
627
  },
628
+ initialize: function initialize(force) {
629
+ return !this.initPromise || force ? this._initialize() : this.initPromise;
630
+ },
609
631
  add: function add(data) {
610
632
  this.index.add(data);
611
633
  },
612
634
  get: function get(query, cb) {
613
- var that = this, matches, cacheHit = false;
635
+ var that = this, matches = [], cacheHit = false;
614
636
  matches = this.index.get(query);
615
637
  matches = this.sorter(matches).slice(0, this.limit);
616
638
  if (matches.length < this.limit && this.transport) {
617
639
  cacheHit = this._getFromRemote(query, returnRemoteMatches);
618
640
  }
619
- !cacheHit && cb && cb(matches);
641
+ if (!cacheHit) {
642
+ (matches.length > 0 || !this.transport) && cb && cb(matches);
643
+ }
620
644
  function returnRemoteMatches(remoteMatches) {
621
645
  var matchesWithBackfill = matches.slice(0);
622
646
  _.each(remoteMatches, function(remoteMatch) {
@@ -630,6 +654,15 @@
630
654
  cb && cb(that.sorter(matchesWithBackfill));
631
655
  }
632
656
  },
657
+ clear: function clear() {
658
+ this.index.reset();
659
+ },
660
+ clearPrefetchCache: function clearPrefetchCache() {
661
+ this.storage && this.storage.clear();
662
+ },
663
+ clearRemoteCache: function clearRemoteCache() {
664
+ this.transport && Transport.resetCache();
665
+ },
633
666
  ttAdapter: function ttAdapter() {
634
667
  return _.bind(this.get, this);
635
668
  }
@@ -647,13 +680,13 @@
647
680
  function ignoreDuplicates() {
648
681
  return false;
649
682
  }
650
- }();
683
+ })(this);
651
684
  var html = {
652
685
  wrapper: '<span class="twitter-typeahead"></span>',
653
686
  dropdown: '<span class="tt-dropdown-menu"></span>',
654
687
  dataset: '<div class="tt-dataset-%CLASS%"></div>',
655
688
  suggestions: '<span class="tt-suggestions"></span>',
656
- suggestion: '<div class="tt-suggestion">%BODY%</div>'
689
+ suggestion: '<div class="tt-suggestion"></div>'
657
690
  };
658
691
  var css = {
659
692
  wrapper: {
@@ -771,7 +804,7 @@
771
804
  return this;
772
805
  }
773
806
  function trigger(types) {
774
- var that = this, type, callbacks, args, syncFlush, asyncFlush;
807
+ var type, callbacks, args, syncFlush, asyncFlush;
775
808
  if (!this._callbacks) {
776
809
  return this;
777
810
  }
@@ -795,7 +828,7 @@
795
828
  }
796
829
  }
797
830
  function getNextTick() {
798
- var nextTickFn, messageChannel;
831
+ var nextTickFn;
799
832
  if (window.setImmediate) {
800
833
  nextTickFn = function nextTickSetImmediate(fn) {
801
834
  setImmediate(function() {
@@ -892,7 +925,7 @@
892
925
  this.$hint = $(o.hint);
893
926
  this.$input = $(o.input).on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown);
894
927
  if (this.$hint.length === 0) {
895
- this.setHintValue = this.getHintValue = this.clearHint = _.noop;
928
+ this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop;
896
929
  }
897
930
  if (!_.isMsie()) {
898
931
  this.$input.on("input.tt", onInput);
@@ -911,11 +944,11 @@
911
944
  return (str || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
912
945
  };
913
946
  _.mixin(Input.prototype, EventEmitter, {
914
- _onBlur: function onBlur($e) {
947
+ _onBlur: function onBlur() {
915
948
  this.resetInputValue();
916
949
  this.trigger("blurred");
917
950
  },
918
- _onFocus: function onFocus($e) {
951
+ _onFocus: function onFocus() {
919
952
  this.trigger("focused");
920
953
  },
921
954
  _onKeydown: function onKeydown($e) {
@@ -925,14 +958,14 @@
925
958
  this.trigger(keyName + "Keyed", $e);
926
959
  }
927
960
  },
928
- _onInput: function onInput($e) {
961
+ _onInput: function onInput() {
929
962
  this._checkInputValue();
930
963
  },
931
964
  _managePreventDefault: function managePreventDefault(keyName, $e) {
932
965
  var preventDefault, hintValue, inputValue;
933
966
  switch (keyName) {
934
967
  case "tab":
935
- hintValue = this.getHintValue();
968
+ hintValue = this.getHint();
936
969
  inputValue = this.getInputValue();
937
970
  preventDefault = hintValue && hintValue !== inputValue && !withModifier($e);
938
971
  break;
@@ -987,19 +1020,27 @@
987
1020
  },
988
1021
  setInputValue: function setInputValue(value, silent) {
989
1022
  this.$input.val(value);
990
- !silent && this._checkInputValue();
1023
+ silent ? this.clearHint() : this._checkInputValue();
991
1024
  },
992
- getHintValue: function getHintValue() {
1025
+ resetInputValue: function resetInputValue() {
1026
+ this.setInputValue(this.query, true);
1027
+ },
1028
+ getHint: function getHint() {
993
1029
  return this.$hint.val();
994
1030
  },
995
- setHintValue: function setHintValue(value) {
1031
+ setHint: function setHint(value) {
996
1032
  this.$hint.val(value);
997
1033
  },
998
- resetInputValue: function resetInputValue() {
999
- this.$input.val(this.query);
1000
- },
1001
1034
  clearHint: function clearHint() {
1002
- this.$hint.val("");
1035
+ this.setHint("");
1036
+ },
1037
+ clearHintIfInvalid: function clearHintIfInvalid() {
1038
+ var val, hint, valIsPrefixOfHint, isValid;
1039
+ val = this.getInputValue();
1040
+ hint = this.getHint();
1041
+ valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0;
1042
+ isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow();
1043
+ !isValid && this.clearHint();
1003
1044
  },
1004
1045
  getLanguageDirection: function getLanguageDirection() {
1005
1046
  return (this.$input.css("direction") || "ltr").toLowerCase();
@@ -1033,7 +1074,7 @@
1033
1074
  return $('<pre aria-hidden="true"></pre>').css({
1034
1075
  position: "absolute",
1035
1076
  visibility: "hidden",
1036
- whiteSpace: "nowrap",
1077
+ whiteSpace: "pre",
1037
1078
  fontFamily: $input.css("font-family"),
1038
1079
  fontSize: $input.css("font-size"),
1039
1080
  fontStyle: $input.css("font-style"),
@@ -1112,10 +1153,8 @@
1112
1153
  });
1113
1154
  return $suggestions;
1114
1155
  function getSuggestionNode(suggestion) {
1115
- var $el, innerHtml, outerHtml;
1116
- innerHtml = that.templates.suggestion(suggestion);
1117
- outerHtml = html.suggestion.replace("%BODY%", innerHtml);
1118
- $el = $(outerHtml).data(datasetKey, that.name).data(valueKey, that.displayFn(suggestion)).data(datumKey, suggestion);
1156
+ var $el;
1157
+ $el = $(html.suggestion).append(that.templates.suggestion(suggestion)).data(datasetKey, that.name).data(valueKey, that.displayFn(suggestion)).data(datumKey, suggestion);
1119
1158
  $el.children().each(function() {
1120
1159
  $(this).css(css.suggestionChild);
1121
1160
  });
@@ -1141,13 +1180,21 @@
1141
1180
  update: function update(query) {
1142
1181
  var that = this;
1143
1182
  this.query = query;
1144
- this.source(query, renderIfQueryIsSame);
1145
- function renderIfQueryIsSame(suggestions) {
1146
- query === that.query && that._render(query, suggestions);
1183
+ this.canceled = false;
1184
+ this.source(query, render);
1185
+ function render(suggestions) {
1186
+ if (!that.canceled && query === that.query) {
1187
+ that._render(query, suggestions);
1188
+ }
1147
1189
  }
1148
1190
  },
1191
+ cancel: function cancel() {
1192
+ this.canceled = true;
1193
+ },
1149
1194
  clear: function clear() {
1150
- this._render(this.query || "");
1195
+ this.cancel();
1196
+ this.$el.empty();
1197
+ this.trigger("rendered");
1151
1198
  },
1152
1199
  isEmpty: function isEmpty() {
1153
1200
  return this.$el.is(":empty");
@@ -1206,7 +1253,7 @@
1206
1253
  this._removeCursor();
1207
1254
  this._setCursor($($e.currentTarget), true);
1208
1255
  },
1209
- _onSuggestionMouseLeave: function onSuggestionMouseLeave($e) {
1256
+ _onSuggestionMouseLeave: function onSuggestionMouseLeave() {
1210
1257
  this._removeCursor();
1211
1258
  },
1212
1259
  _onRendered: function onRendered() {
@@ -1341,17 +1388,34 @@
1341
1388
  var Typeahead = function() {
1342
1389
  var attrsKey = "ttAttrs";
1343
1390
  function Typeahead(o) {
1344
- var $menu, $input, $hint, datasets;
1391
+ var $menu, $input, $hint;
1345
1392
  o = o || {};
1346
1393
  if (!o.input) {
1347
1394
  $.error("missing input");
1348
1395
  }
1396
+ this.isActivated = false;
1349
1397
  this.autoselect = !!o.autoselect;
1350
1398
  this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;
1351
1399
  this.$node = buildDomStructure(o.input, o.withHint);
1352
1400
  $menu = this.$node.find(".tt-dropdown-menu");
1353
1401
  $input = this.$node.find(".tt-input");
1354
1402
  $hint = this.$node.find(".tt-hint");
1403
+ $input.on("blur.tt", function($e) {
1404
+ var active, isActive, hasActive;
1405
+ active = document.activeElement;
1406
+ isActive = $menu.is(active);
1407
+ hasActive = $menu.has(active).length > 0;
1408
+ if (_.isMsie() && (isActive || hasActive)) {
1409
+ $e.preventDefault();
1410
+ $e.stopImmediatePropagation();
1411
+ _.defer(function() {
1412
+ $input.focus();
1413
+ });
1414
+ }
1415
+ });
1416
+ $menu.on("mousedown.tt", function($e) {
1417
+ $e.preventDefault();
1418
+ });
1355
1419
  this.eventBus = o.eventBus || new EventBus({
1356
1420
  el: $input
1357
1421
  });
@@ -1363,15 +1427,7 @@
1363
1427
  input: $input,
1364
1428
  hint: $hint
1365
1429
  }).onSync("focused", this._onFocused, this).onSync("blurred", this._onBlurred, this).onSync("enterKeyed", this._onEnterKeyed, this).onSync("tabKeyed", this._onTabKeyed, this).onSync("escKeyed", this._onEscKeyed, this).onSync("upKeyed", this._onUpKeyed, this).onSync("downKeyed", this._onDownKeyed, this).onSync("leftKeyed", this._onLeftKeyed, this).onSync("rightKeyed", this._onRightKeyed, this).onSync("queryChanged", this._onQueryChanged, this).onSync("whitespaceChanged", this._onWhitespaceChanged, this);
1366
- $menu.on("mousedown.tt", function($e) {
1367
- if (_.isMsie() && _.isMsie() < 9) {
1368
- $input[0].onbeforedeactivate = function() {
1369
- window.event.returnValue = false;
1370
- $input[0].onbeforedeactivate = null;
1371
- };
1372
- }
1373
- $e.preventDefault();
1374
- });
1430
+ this._setLanguageDirection();
1375
1431
  }
1376
1432
  _.mixin(Typeahead.prototype, {
1377
1433
  _onSuggestionClicked: function onSuggestionClicked(type, $el) {
@@ -1382,7 +1438,6 @@
1382
1438
  },
1383
1439
  _onCursorMoved: function onCursorMoved() {
1384
1440
  var datum = this.dropdown.getDatumForCursor();
1385
- this.input.clearHint();
1386
1441
  this.input.setInputValue(datum.value, true);
1387
1442
  this.eventBus.trigger("cursorchanged", datum.raw, datum.datasetName);
1388
1443
  },
@@ -1402,10 +1457,12 @@
1402
1457
  this.eventBus.trigger("closed");
1403
1458
  },
1404
1459
  _onFocused: function onFocused() {
1405
- this.dropdown.empty();
1460
+ this.isActivated = true;
1406
1461
  this.dropdown.open();
1407
1462
  },
1408
1463
  _onBlurred: function onBlurred() {
1464
+ this.isActivated = false;
1465
+ this.dropdown.empty();
1409
1466
  this.dropdown.close();
1410
1467
  },
1411
1468
  _onEnterKeyed: function onEnterKeyed(type, $e) {
@@ -1426,7 +1483,7 @@
1426
1483
  this._select(datum);
1427
1484
  $e.preventDefault();
1428
1485
  } else {
1429
- this._autocomplete();
1486
+ this._autocomplete(true);
1430
1487
  }
1431
1488
  },
1432
1489
  _onEscKeyed: function onEscKeyed() {
@@ -1435,19 +1492,13 @@
1435
1492
  },
1436
1493
  _onUpKeyed: function onUpKeyed() {
1437
1494
  var query = this.input.getQuery();
1438
- if (!this.dropdown.isOpen && query.length >= this.minLength) {
1439
- this.dropdown.update(query);
1440
- }
1495
+ this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorUp();
1441
1496
  this.dropdown.open();
1442
- this.dropdown.moveCursorUp();
1443
1497
  },
1444
1498
  _onDownKeyed: function onDownKeyed() {
1445
1499
  var query = this.input.getQuery();
1446
- if (!this.dropdown.isOpen && query.length >= this.minLength) {
1447
- this.dropdown.update(query);
1448
- }
1500
+ this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorDown();
1449
1501
  this.dropdown.open();
1450
- this.dropdown.moveCursorDown();
1451
1502
  },
1452
1503
  _onLeftKeyed: function onLeftKeyed() {
1453
1504
  this.dir === "rtl" && this._autocomplete();
@@ -1456,9 +1507,8 @@
1456
1507
  this.dir === "ltr" && this._autocomplete();
1457
1508
  },
1458
1509
  _onQueryChanged: function onQueryChanged(e, query) {
1459
- this.input.clearHint();
1460
- this.dropdown.empty();
1461
- query.length >= this.minLength && this.dropdown.update(query);
1510
+ this.input.clearHintIfInvalid();
1511
+ query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.empty();
1462
1512
  this.dropdown.open();
1463
1513
  this._setLanguageDirection();
1464
1514
  },
@@ -1475,29 +1525,31 @@
1475
1525
  }
1476
1526
  },
1477
1527
  _updateHint: function updateHint() {
1478
- var datum, inputValue, query, escapedQuery, frontMatchRegEx, match;
1528
+ var datum, val, query, escapedQuery, frontMatchRegEx, match;
1479
1529
  datum = this.dropdown.getDatumForTopSuggestion();
1480
1530
  if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) {
1481
- inputValue = this.input.getInputValue();
1482
- query = Input.normalizeQuery(inputValue);
1531
+ val = this.input.getInputValue();
1532
+ query = Input.normalizeQuery(val);
1483
1533
  escapedQuery = _.escapeRegExChars(query);
1484
- frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i");
1534
+ frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i");
1485
1535
  match = frontMatchRegEx.exec(datum.value);
1486
- this.input.setHintValue(inputValue + (match ? match[1] : ""));
1536
+ match ? this.input.setHint(val + match[1]) : this.input.clearHint();
1537
+ } else {
1538
+ this.input.clearHint();
1487
1539
  }
1488
1540
  },
1489
- _autocomplete: function autocomplete() {
1490
- var hint, query, datum;
1491
- hint = this.input.getHintValue();
1541
+ _autocomplete: function autocomplete(laxCursor) {
1542
+ var hint, query, isCursorAtEnd, datum;
1543
+ hint = this.input.getHint();
1492
1544
  query = this.input.getQuery();
1493
- if (hint && query !== hint && this.input.isCursorAtEnd()) {
1545
+ isCursorAtEnd = laxCursor || this.input.isCursorAtEnd();
1546
+ if (hint && query !== hint && isCursorAtEnd) {
1494
1547
  datum = this.dropdown.getDatumForTopSuggestion();
1495
1548
  datum && this.input.setInputValue(datum.value);
1496
1549
  this.eventBus.trigger("autocompleted", datum.raw, datum.datasetName);
1497
1550
  }
1498
1551
  },
1499
1552
  _select: function select(datum) {
1500
- this.input.clearHint();
1501
1553
  this.input.setQuery(datum.value);
1502
1554
  this.input.setInputValue(datum.value, true);
1503
1555
  this._setLanguageDirection();
@@ -1511,11 +1563,17 @@
1511
1563
  close: function close() {
1512
1564
  this.dropdown.close();
1513
1565
  },
1514
- getQuery: function getQuery() {
1515
- return this.input.getQuery();
1566
+ setVal: function setVal(val) {
1567
+ if (this.isActivated) {
1568
+ this.input.setInputValue(val);
1569
+ } else {
1570
+ this.input.setQuery(val);
1571
+ this.input.setInputValue(val, true);
1572
+ }
1573
+ this._setLanguageDirection();
1516
1574
  },
1517
- setQuery: function setQuery(val) {
1518
- this.input.setInputValue(val);
1575
+ getVal: function getVal() {
1576
+ return this.input.getQuery();
1519
1577
  },
1520
1578
  destroy: function destroy() {
1521
1579
  this.input.destroy();
@@ -1617,17 +1675,17 @@
1617
1675
  }
1618
1676
  },
1619
1677
  val: function val(newVal) {
1620
- return !arguments.length ? getQuery(this.first()) : this.each(setQuery);
1621
- function setQuery() {
1678
+ return !arguments.length ? getVal(this.first()) : this.each(setVal);
1679
+ function setVal() {
1622
1680
  var $input = $(this), typeahead;
1623
1681
  if (typeahead = $input.data(typeaheadKey)) {
1624
- typeahead.setQuery(newVal);
1682
+ typeahead.setVal(newVal);
1625
1683
  }
1626
1684
  }
1627
- function getQuery($input) {
1685
+ function getVal($input) {
1628
1686
  var typeahead, query;
1629
1687
  if (typeahead = $input.data(typeaheadKey)) {
1630
- query = typeahead.getQuery();
1688
+ query = typeahead.getVal();
1631
1689
  }
1632
1690
  return query;
1633
1691
  }