typeahead-rails 0.10.1 → 0.10.5

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,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 56dc64b83584b4917ed1adb425bd2058f407e571
4
- data.tar.gz: 7f9545643c384168275c1f56485e96de789566aa
3
+ metadata.gz: 5fe33c4ed40fca145701a4553f63a2dedb05261b
4
+ data.tar.gz: eae13ee831d46ab9fc2ff4168bc8084a5572f77c
5
5
  SHA512:
6
- metadata.gz: 9b8795c6b45604d2baa0307293109213f296706efe85e1d2021fbce061979234d5c030a18918e2359d83ec01d451969c617490b546648185f4e22e11ddf5db0b
7
- data.tar.gz: ae1aed049769bad300c66b1c731514fbd85e4c162671946fb59b863480f528f5027463d587211373bfb3af4838b8786b7f4dc59792bf9b1a9e431edb4184658f
6
+ metadata.gz: 6d49a8259d15787497292011f381f7468cc33fbf2c5b741663fc8f4a8ffbed8e3f32c2c51c10db8f91f84c75030ea802ba6f717bdfced98bacdf18c2426c02ef
7
+ data.tar.gz: 7a4223a7a0146177e173371c494611cf89baadaf999690138365915265b36fac2acdc38d625e5a934f4223b0f73f144af8bcbf54943293a4bd822b5b917b4a24
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
- # Typeahead in JavaScript in Rails3
1
+ # Twitter typeahead for Rails 3
2
2
 
3
- Provides an easy-to-use Rails 3.1 asset for [typeahead.js](http://twitter.github.io/typeahead.js/)
3
+ Provides an easy-to-use Rails 3.1 or higher asset for [typeahead.js](http://twitter.github.io/typeahead.js/)
4
+ This gem includes the standard and minified versions of the assets.
4
5
 
5
6
  ## Installation
6
7
 
@@ -16,11 +17,62 @@ Or install it yourself as:
16
17
 
17
18
  $ gem install typeahead-rails
18
19
 
19
- ## Usage
20
+
21
+ ## Configuration
22
+
23
+ ### Javascript
20
24
 
21
25
  Add the following to your app/assets/javascripts/application.js:
22
26
 
23
- //= require typeahead
27
+ Standard version
28
+ ```js
29
+ //= require typeahead
30
+ ```
31
+
32
+ Minified version :
33
+ ```js
34
+ //= require typeahead.min
35
+ ```
36
+
37
+ ### Css
38
+
39
+ You can use a css example via the asset pipeline (app/assets/stylesheets/application.css) :
40
+ ```css
41
+ *= require typeahead-rails
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ```js
47
+
48
+ // instantiate the bloodhound suggestion engine
49
+ var numbers = new Bloodhound({
50
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('num'),
51
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
52
+ local: [
53
+ { num: 'one' },
54
+ { num: 'two' },
55
+ { num: 'three' },
56
+ { num: 'four' },
57
+ { num: 'five' },
58
+ { num: 'six' },
59
+ { num: 'seven' },
60
+ { num: 'eight' },
61
+ { num: 'nine' },
62
+ { num: 'ten' }
63
+ ]
64
+ });
65
+
66
+ // initialize the bloodhound suggestion engine
67
+ numbers.initialize();
68
+
69
+ // instantiate the typeahead UI
70
+ $('.example-numbers .typeahead').typeahead(null, {
71
+ displayKey: 'num',
72
+ source: numbers.ttAdapter()
73
+ });
74
+ ```
75
+
24
76
 
25
77
  ## Contributing
26
78
 
@@ -1,5 +1,5 @@
1
1
  module Typeahead
2
2
  module Rails
3
- VERSION = '0.10.1'
3
+ VERSION = '0.10.5'
4
4
  end
5
- end
5
+ end
@@ -6,8 +6,8 @@ require 'typeahead/rails/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'typeahead-rails'
8
8
  spec.version = Typeahead::Rails::VERSION
9
- spec.authors = ['Maksim Berjoza']
10
- spec.email = ['torbjon@gmail.com']
9
+ spec.authors = ['Maksim Berjoza', 'Luc Donnet']
10
+ spec.email = ['torbjon@gmail.com', 'luc.donnet@free.fr']
11
11
  spec.description = 'Twitter Typeahead.js with Rails asset pipeline'
12
12
  spec.homepage = 'https://github.com/torbjon/typeahead-rails'
13
13
  spec.summary = 'Twitter Typeahead.js with Rails asset pipeline'
@@ -19,5 +19,5 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = %w(lib vendors)
20
20
 
21
21
  spec.add_development_dependency 'bundler', '~> 1.3'
22
- spec.add_development_dependency 'rake'
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
23
  end
@@ -1,132 +1,171 @@
1
1
  /*!
2
- * typeahead.js 0.10.1
2
+ * typeahead.js 0.10.5
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($) {
8
- var _ = {
9
- isMsie: function() {
10
- return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
11
- },
12
- isBlankString: function(str) {
13
- return !str || /^\s*$/.test(str);
14
- },
15
- escapeRegExChars: function(str) {
16
- return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
17
- },
18
- isString: function(obj) {
19
- return typeof obj === "string";
20
- },
21
- isNumber: function(obj) {
22
- return typeof obj === "number";
23
- },
24
- isArray: $.isArray,
25
- isFunction: $.isFunction,
26
- isObject: $.isPlainObject,
27
- isUndefined: function(obj) {
28
- return typeof obj === "undefined";
29
- },
30
- bind: $.proxy,
31
- each: function(collection, cb) {
32
- $.each(collection, reverseArgs);
33
- function reverseArgs(index, value) {
34
- return cb(value, index);
35
- }
36
- },
37
- map: $.map,
38
- filter: $.grep,
39
- every: function(obj, test) {
40
- var result = true;
41
- if (!obj) {
42
- return result;
43
- }
44
- $.each(obj, function(key, val) {
45
- if (!(result = test.call(null, val, key, obj))) {
46
- return false;
8
+ var _ = function() {
9
+ "use strict";
10
+ return {
11
+ isMsie: function() {
12
+ return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
13
+ },
14
+ isBlankString: function(str) {
15
+ return !str || /^\s*$/.test(str);
16
+ },
17
+ escapeRegExChars: function(str) {
18
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
19
+ },
20
+ isString: function(obj) {
21
+ return typeof obj === "string";
22
+ },
23
+ isNumber: function(obj) {
24
+ return typeof obj === "number";
25
+ },
26
+ isArray: $.isArray,
27
+ isFunction: $.isFunction,
28
+ isObject: $.isPlainObject,
29
+ isUndefined: function(obj) {
30
+ return typeof obj === "undefined";
31
+ },
32
+ toStr: function toStr(s) {
33
+ return _.isUndefined(s) || s === null ? "" : s + "";
34
+ },
35
+ bind: $.proxy,
36
+ each: function(collection, cb) {
37
+ $.each(collection, reverseArgs);
38
+ function reverseArgs(index, value) {
39
+ return cb(value, index);
47
40
  }
48
- });
49
- return !!result;
50
- },
51
- some: function(obj, test) {
52
- var result = false;
53
- if (!obj) {
54
- return result;
55
- }
56
- $.each(obj, function(key, val) {
57
- if (result = test.call(null, val, key, obj)) {
58
- return false;
41
+ },
42
+ map: $.map,
43
+ filter: $.grep,
44
+ every: function(obj, test) {
45
+ var result = true;
46
+ if (!obj) {
47
+ return result;
59
48
  }
60
- });
61
- return !!result;
62
- },
63
- mixin: $.extend,
64
- getUniqueId: function() {
65
- var counter = 0;
66
- return function() {
67
- return counter++;
68
- };
69
- }(),
70
- templatify: function templatify(obj) {
71
- return $.isFunction(obj) ? obj : template;
72
- function template() {
73
- return String(obj);
74
- }
75
- },
76
- defer: function(fn) {
77
- setTimeout(fn, 0);
78
- },
79
- debounce: function(func, wait, immediate) {
80
- var timeout, result;
81
- return function() {
82
- var context = this, args = arguments, later, callNow;
83
- later = function() {
84
- timeout = null;
85
- if (!immediate) {
86
- result = func.apply(context, args);
49
+ $.each(obj, function(key, val) {
50
+ if (!(result = test.call(null, val, key, obj))) {
51
+ return false;
87
52
  }
88
- };
89
- callNow = immediate && !timeout;
90
- clearTimeout(timeout);
91
- timeout = setTimeout(later, wait);
92
- if (callNow) {
93
- result = func.apply(context, args);
53
+ });
54
+ return !!result;
55
+ },
56
+ some: function(obj, test) {
57
+ var result = false;
58
+ if (!obj) {
59
+ return result;
94
60
  }
95
- return result;
96
- };
97
- },
98
- throttle: function(func, wait) {
99
- var context, args, timeout, result, previous, later;
100
- previous = 0;
101
- later = function() {
102
- previous = new Date();
103
- timeout = null;
104
- result = func.apply(context, args);
105
- };
106
- return function() {
107
- var now = new Date(), remaining = wait - (now - previous);
108
- context = this;
109
- args = arguments;
110
- if (remaining <= 0) {
61
+ $.each(obj, function(key, val) {
62
+ if (result = test.call(null, val, key, obj)) {
63
+ return false;
64
+ }
65
+ });
66
+ return !!result;
67
+ },
68
+ mixin: $.extend,
69
+ getUniqueId: function() {
70
+ var counter = 0;
71
+ return function() {
72
+ return counter++;
73
+ };
74
+ }(),
75
+ templatify: function templatify(obj) {
76
+ return $.isFunction(obj) ? obj : template;
77
+ function template() {
78
+ return String(obj);
79
+ }
80
+ },
81
+ defer: function(fn) {
82
+ setTimeout(fn, 0);
83
+ },
84
+ debounce: function(func, wait, immediate) {
85
+ var timeout, result;
86
+ return function() {
87
+ var context = this, args = arguments, later, callNow;
88
+ later = function() {
89
+ timeout = null;
90
+ if (!immediate) {
91
+ result = func.apply(context, args);
92
+ }
93
+ };
94
+ callNow = immediate && !timeout;
111
95
  clearTimeout(timeout);
96
+ timeout = setTimeout(later, wait);
97
+ if (callNow) {
98
+ result = func.apply(context, args);
99
+ }
100
+ return result;
101
+ };
102
+ },
103
+ throttle: function(func, wait) {
104
+ var context, args, timeout, result, previous, later;
105
+ previous = 0;
106
+ later = function() {
107
+ previous = new Date();
112
108
  timeout = null;
113
- previous = now;
114
109
  result = func.apply(context, args);
115
- } else if (!timeout) {
116
- timeout = setTimeout(later, remaining);
117
- }
118
- return result;
110
+ };
111
+ return function() {
112
+ var now = new Date(), remaining = wait - (now - previous);
113
+ context = this;
114
+ args = arguments;
115
+ if (remaining <= 0) {
116
+ clearTimeout(timeout);
117
+ timeout = null;
118
+ previous = now;
119
+ result = func.apply(context, args);
120
+ } else if (!timeout) {
121
+ timeout = setTimeout(later, remaining);
122
+ }
123
+ return result;
124
+ };
125
+ },
126
+ noop: function() {}
127
+ };
128
+ }();
129
+ var VERSION = "0.10.5";
130
+ var tokenizers = function() {
131
+ "use strict";
132
+ return {
133
+ nonword: nonword,
134
+ whitespace: whitespace,
135
+ obj: {
136
+ nonword: getObjTokenizer(nonword),
137
+ whitespace: getObjTokenizer(whitespace)
138
+ }
139
+ };
140
+ function whitespace(str) {
141
+ str = _.toStr(str);
142
+ return str ? str.split(/\s+/) : [];
143
+ }
144
+ function nonword(str) {
145
+ str = _.toStr(str);
146
+ return str ? str.split(/\W+/) : [];
147
+ }
148
+ function getObjTokenizer(tokenizer) {
149
+ return function setKey() {
150
+ var args = [].slice.call(arguments, 0);
151
+ return function tokenize(o) {
152
+ var tokens = [];
153
+ _.each(args, function(k) {
154
+ tokens = tokens.concat(tokenizer(_.toStr(o[k])));
155
+ });
156
+ return tokens;
157
+ };
119
158
  };
120
- },
121
- noop: function() {}
122
- };
123
- var VERSION = "0.10.1";
124
- var LruCache = function(root, undefined) {
159
+ }
160
+ }();
161
+ var LruCache = function() {
162
+ "use strict";
125
163
  function LruCache(maxSize) {
126
- this.maxSize = maxSize || 100;
127
- this.size = 0;
128
- this.hash = {};
129
- this.list = new List();
164
+ this.maxSize = _.isNumber(maxSize) ? maxSize : 100;
165
+ this.reset();
166
+ if (this.maxSize <= 0) {
167
+ this.set = this.get = $.noop;
168
+ }
130
169
  }
131
170
  _.mixin(LruCache.prototype, {
132
171
  set: function set(key, val) {
@@ -151,6 +190,11 @@
151
190
  this.list.moveToFront(node);
152
191
  return node.val;
153
192
  }
193
+ },
194
+ reset: function reset() {
195
+ this.size = 0;
196
+ this.hash = {};
197
+ this.list = new List();
154
198
  }
155
199
  });
156
200
  function List() {
@@ -180,8 +224,9 @@
180
224
  this.prev = this.next = null;
181
225
  }
182
226
  return LruCache;
183
- }(this);
227
+ }();
184
228
  var PersistentStorage = function() {
229
+ "use strict";
185
230
  var ls, methods;
186
231
  try {
187
232
  ls = window.localStorage;
@@ -193,7 +238,7 @@
193
238
  function PersistentStorage(namespace) {
194
239
  this.prefix = [ "__", namespace, "__" ].join("");
195
240
  this.ttlKey = "__ttl__";
196
- this.keyMatcher = new RegExp("^" + this.prefix);
241
+ this.keyMatcher = new RegExp("^" + _.escapeRegExChars(this.prefix));
197
242
  }
198
243
  if (ls && window.JSON) {
199
244
  methods = {
@@ -261,32 +306,42 @@
261
306
  }
262
307
  }();
263
308
  var Transport = function() {
264
- var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, requestCache = new LruCache(10);
309
+ "use strict";
310
+ var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, sharedCache = new LruCache(10);
265
311
  function Transport(o) {
266
312
  o = o || {};
313
+ this.cancelled = false;
314
+ this.lastUrl = null;
267
315
  this._send = o.transport ? callbackToDeferred(o.transport) : $.ajax;
268
316
  this._get = o.rateLimiter ? o.rateLimiter(this._get) : this._get;
317
+ this._cache = o.cache === false ? new LruCache(0) : sharedCache;
269
318
  }
270
319
  Transport.setMaxPendingRequests = function setMaxPendingRequests(num) {
271
320
  maxPendingRequests = num;
272
321
  };
273
- Transport.resetCache = function clearCache() {
274
- requestCache = new LruCache(10);
322
+ Transport.resetCache = function resetCache() {
323
+ sharedCache.reset();
275
324
  };
276
325
  _.mixin(Transport.prototype, {
277
326
  _get: function(url, o, cb) {
278
327
  var that = this, jqXhr;
328
+ if (this.cancelled || url !== this.lastUrl) {
329
+ return;
330
+ }
279
331
  if (jqXhr = pendingRequests[url]) {
280
- jqXhr.done(done);
332
+ jqXhr.done(done).fail(fail);
281
333
  } else if (pendingRequestsCount < maxPendingRequests) {
282
334
  pendingRequestsCount++;
283
- pendingRequests[url] = this._send(url, o).done(done).always(always);
335
+ pendingRequests[url] = this._send(url, o).done(done).fail(fail).always(always);
284
336
  } else {
285
337
  this.onDeckRequestArgs = [].slice.call(arguments, 0);
286
338
  }
287
339
  function done(resp) {
288
- cb && cb(resp);
289
- requestCache.set(url, resp);
340
+ cb && cb(null, resp);
341
+ that._cache.set(url, resp);
342
+ }
343
+ function fail() {
344
+ cb && cb(true);
290
345
  }
291
346
  function always() {
292
347
  pendingRequestsCount--;
@@ -298,19 +353,24 @@
298
353
  }
299
354
  },
300
355
  get: function(url, o, cb) {
301
- var that = this, resp;
356
+ var resp;
302
357
  if (_.isFunction(o)) {
303
358
  cb = o;
304
359
  o = {};
305
360
  }
306
- if (resp = requestCache.get(url)) {
361
+ this.cancelled = false;
362
+ this.lastUrl = url;
363
+ if (resp = this._cache.get(url)) {
307
364
  _.defer(function() {
308
- cb && cb(resp);
365
+ cb && cb(null, resp);
309
366
  });
310
367
  } else {
311
368
  this._get(url, o, cb);
312
369
  }
313
370
  return !!resp;
371
+ },
372
+ cancel: function() {
373
+ this.cancelled = true;
314
374
  }
315
375
  });
316
376
  return Transport;
@@ -333,6 +393,7 @@
333
393
  }
334
394
  }();
335
395
  var SearchIndex = function() {
396
+ "use strict";
336
397
  function SearchIndex(o) {
337
398
  o = o || {};
338
399
  if (!o.datumTokenizer || !o.queryTokenizer) {
@@ -340,8 +401,7 @@
340
401
  }
341
402
  this.datumTokenizer = o.datumTokenizer;
342
403
  this.queryTokenizer = o.queryTokenizer;
343
- this.datums = [];
344
- this.trie = newNode();
404
+ this.reset();
345
405
  }
346
406
  _.mixin(SearchIndex.prototype, {
347
407
  bootstrap: function bootstrap(o) {
@@ -356,7 +416,7 @@
356
416
  id = that.datums.push(datum) - 1;
357
417
  tokens = normalizeTokens(that.datumTokenizer(datum));
358
418
  _.each(tokens, function(token) {
359
- var node, chars, ch, ids;
419
+ var node, chars, ch;
360
420
  node = that.trie;
361
421
  chars = token.split("");
362
422
  while (ch = chars.shift()) {
@@ -391,6 +451,10 @@
391
451
  return that.datums[id];
392
452
  }) : [];
393
453
  },
454
+ reset: function reset() {
455
+ this.datums = [];
456
+ this.trie = newNode();
457
+ },
394
458
  serialize: function serialize() {
395
459
  return {
396
460
  datums: this.datums,
@@ -416,7 +480,7 @@
416
480
  }
417
481
  function unique(array) {
418
482
  var seen = {}, uniques = [];
419
- for (var i = 0; i < array.length; i++) {
483
+ for (var i = 0, len = array.length; i < len; i++) {
420
484
  if (!seen[array[i]]) {
421
485
  seen[array[i]] = true;
422
486
  uniques.push(array[i]);
@@ -428,7 +492,8 @@
428
492
  var ai = 0, bi = 0, intersection = [];
429
493
  arrayA = arrayA.sort(compare);
430
494
  arrayB = arrayB.sort(compare);
431
- while (ai < arrayA.length && bi < arrayB.length) {
495
+ var lenArrayA = arrayA.length, lenArrayB = arrayB.length;
496
+ while (ai < lenArrayA && bi < lenArrayB) {
432
497
  if (arrayA[ai] < arrayB[bi]) {
433
498
  ai++;
434
499
  } else if (arrayA[ai] > arrayB[bi]) {
@@ -446,17 +511,14 @@
446
511
  }
447
512
  }();
448
513
  var oParser = function() {
514
+ "use strict";
449
515
  return {
450
516
  local: getLocal,
451
517
  prefetch: getPrefetch,
452
518
  remote: getRemote
453
519
  };
454
520
  function getLocal(o) {
455
- var local = o.local || null;
456
- if (_.isFunction(local)) {
457
- local = local.call(null);
458
- }
459
- return local;
521
+ return o.local || null;
460
522
  }
461
523
  function getPrefetch(o) {
462
524
  var prefetch, defaults;
@@ -483,6 +545,7 @@
483
545
  var remote, defaults;
484
546
  defaults = {
485
547
  url: null,
548
+ cache: true,
486
549
  wildcard: "%QUERY",
487
550
  replace: null,
488
551
  rateLimitBy: "debounce",
@@ -516,13 +579,16 @@
516
579
  }
517
580
  }
518
581
  }();
519
- var Bloodhound = window.Bloodhound = function() {
520
- var keys;
582
+ (function(root) {
583
+ "use strict";
584
+ var old, keys;
585
+ old = root.Bloodhound;
521
586
  keys = {
522
587
  data: "data",
523
588
  protocol: "protocol",
524
589
  thumbprint: "thumbprint"
525
590
  };
591
+ root.Bloodhound = Bloodhound;
526
592
  function Bloodhound(o) {
527
593
  if (!o || !o.local && !o.prefetch && !o.remote) {
528
594
  $.error("one of local, prefetch, or remote is required");
@@ -540,14 +606,11 @@
540
606
  });
541
607
  this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null;
542
608
  }
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
- }
609
+ Bloodhound.noConflict = function noConflict() {
610
+ root.Bloodhound = old;
611
+ return Bloodhound;
550
612
  };
613
+ Bloodhound.tokenizers = tokenizers;
551
614
  _.mixin(Bloodhound.prototype, {
552
615
  _loadPrefetch: function loadPrefetch(o) {
553
616
  var that = this, serialized, deferred;
@@ -559,23 +622,27 @@
559
622
  }
560
623
  return deferred;
561
624
  function handlePrefetchResponse(resp) {
562
- var filtered;
563
- filtered = o.filter ? o.filter(resp) : resp;
564
- that.add(filtered);
625
+ that.clear();
626
+ that.add(o.filter ? o.filter(resp) : resp);
565
627
  that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl);
566
628
  }
567
629
  },
568
630
  _getFromRemote: function getFromRemote(query, cb) {
569
631
  var that = this, url, uriEncodedQuery;
632
+ if (!this.transport) {
633
+ return;
634
+ }
570
635
  query = query || "";
571
636
  uriEncodedQuery = encodeURIComponent(query);
572
637
  url = this.remote.replace ? this.remote.replace(this.remote.url, query) : this.remote.url.replace(this.remote.wildcard, uriEncodedQuery);
573
638
  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);
639
+ function handleRemoteResponse(err, resp) {
640
+ err ? cb([]) : cb(that.remote.filter ? that.remote.filter(resp) : resp);
577
641
  }
578
642
  },
643
+ _cancelLastRemoteRequest: function cancelLastRemoteRequest() {
644
+ this.transport && this.transport.cancel();
645
+ },
579
646
  _saveToStorage: function saveToStorage(data, thumbprint, ttl) {
580
647
  if (this.storage) {
581
648
  this.storage.set(keys.data, data, ttl);
@@ -593,30 +660,30 @@
593
660
  isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol;
594
661
  return stored.data && !isExpired ? stored.data : null;
595
662
  },
596
- initialize: function initialize() {
597
- var that = this, deferred;
663
+ _initialize: function initialize() {
664
+ var that = this, local = this.local, deferred;
598
665
  deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve();
599
- this.local && deferred.done(addLocalToIndex);
666
+ local && deferred.done(addLocalToIndex);
600
667
  this.transport = this.remote ? new Transport(this.remote) : null;
601
- this.initialize = function initialize() {
602
- return deferred.promise();
603
- };
604
- return deferred.promise();
668
+ return this.initPromise = deferred.promise();
605
669
  function addLocalToIndex() {
606
- that.add(that.local);
670
+ that.add(_.isFunction(local) ? local() : local);
607
671
  }
608
672
  },
673
+ initialize: function initialize(force) {
674
+ return !this.initPromise || force ? this._initialize() : this.initPromise;
675
+ },
609
676
  add: function add(data) {
610
677
  this.index.add(data);
611
678
  },
612
679
  get: function get(query, cb) {
613
- var that = this, matches, cacheHit = false;
680
+ var that = this, matches = [], cacheHit = false;
614
681
  matches = this.index.get(query);
615
682
  matches = this.sorter(matches).slice(0, this.limit);
616
- if (matches.length < this.limit && this.transport) {
617
- cacheHit = this._getFromRemote(query, returnRemoteMatches);
683
+ matches.length < this.limit ? cacheHit = this._getFromRemote(query, returnRemoteMatches) : this._cancelLastRemoteRequest();
684
+ if (!cacheHit) {
685
+ (matches.length > 0 || !this.transport) && cb && cb(matches);
618
686
  }
619
- !cacheHit && cb && cb(matches);
620
687
  function returnRemoteMatches(remoteMatches) {
621
688
  var matchesWithBackfill = matches.slice(0);
622
689
  _.each(remoteMatches, function(remoteMatch) {
@@ -630,6 +697,15 @@
630
697
  cb && cb(that.sorter(matchesWithBackfill));
631
698
  }
632
699
  },
700
+ clear: function clear() {
701
+ this.index.reset();
702
+ },
703
+ clearPrefetchCache: function clearPrefetchCache() {
704
+ this.storage && this.storage.clear();
705
+ },
706
+ clearRemoteCache: function clearRemoteCache() {
707
+ this.transport && Transport.resetCache();
708
+ },
633
709
  ttAdapter: function ttAdapter() {
634
710
  return _.bind(this.get, this);
635
711
  }
@@ -647,72 +723,80 @@
647
723
  function ignoreDuplicates() {
648
724
  return false;
649
725
  }
726
+ })(this);
727
+ var html = function() {
728
+ return {
729
+ wrapper: '<span class="twitter-typeahead"></span>',
730
+ dropdown: '<span class="tt-dropdown-menu"></span>',
731
+ dataset: '<div class="tt-dataset-%CLASS%"></div>',
732
+ suggestions: '<span class="tt-suggestions"></span>',
733
+ suggestion: '<div class="tt-suggestion"></div>'
734
+ };
650
735
  }();
651
- var html = {
652
- wrapper: '<span class="twitter-typeahead"></span>',
653
- dropdown: '<span class="tt-dropdown-menu"></span>',
654
- dataset: '<div class="tt-dataset-%CLASS%"></div>',
655
- suggestions: '<span class="tt-suggestions"></span>',
656
- suggestion: '<div class="tt-suggestion">%BODY%</div>'
657
- };
658
- var css = {
659
- wrapper: {
660
- position: "relative",
661
- display: "inline-block"
662
- },
663
- hint: {
664
- position: "absolute",
665
- top: "0",
666
- left: "0",
667
- borderColor: "transparent",
668
- boxShadow: "none"
669
- },
670
- input: {
671
- position: "relative",
672
- verticalAlign: "top",
673
- backgroundColor: "transparent"
674
- },
675
- inputWithNoHint: {
676
- position: "relative",
677
- verticalAlign: "top"
678
- },
679
- dropdown: {
680
- position: "absolute",
681
- top: "100%",
682
- left: "0",
683
- zIndex: "100",
684
- display: "none"
685
- },
686
- suggestions: {
687
- display: "block"
688
- },
689
- suggestion: {
690
- whiteSpace: "nowrap",
691
- cursor: "pointer"
692
- },
693
- suggestionChild: {
694
- whiteSpace: "normal"
695
- },
696
- ltr: {
697
- left: "0",
698
- right: "auto"
699
- },
700
- rtl: {
701
- left: "auto",
702
- right: " 0"
736
+ var css = function() {
737
+ "use strict";
738
+ var css = {
739
+ wrapper: {
740
+ position: "relative",
741
+ display: "inline-block"
742
+ },
743
+ hint: {
744
+ position: "absolute",
745
+ top: "0",
746
+ left: "0",
747
+ borderColor: "transparent",
748
+ boxShadow: "none",
749
+ opacity: "1"
750
+ },
751
+ input: {
752
+ position: "relative",
753
+ verticalAlign: "top",
754
+ backgroundColor: "transparent"
755
+ },
756
+ inputWithNoHint: {
757
+ position: "relative",
758
+ verticalAlign: "top"
759
+ },
760
+ dropdown: {
761
+ position: "absolute",
762
+ top: "100%",
763
+ left: "0",
764
+ zIndex: "100",
765
+ display: "none"
766
+ },
767
+ suggestions: {
768
+ display: "block"
769
+ },
770
+ suggestion: {
771
+ whiteSpace: "nowrap",
772
+ cursor: "pointer"
773
+ },
774
+ suggestionChild: {
775
+ whiteSpace: "normal"
776
+ },
777
+ ltr: {
778
+ left: "0",
779
+ right: "auto"
780
+ },
781
+ rtl: {
782
+ left: "auto",
783
+ right: " 0"
784
+ }
785
+ };
786
+ if (_.isMsie()) {
787
+ _.mixin(css.input, {
788
+ backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
789
+ });
703
790
  }
704
- };
705
- if (_.isMsie()) {
706
- _.mixin(css.input, {
707
- backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
708
- });
709
- }
710
- if (_.isMsie() && _.isMsie() <= 7) {
711
- _.mixin(css.input, {
712
- marginTop: "-1px"
713
- });
714
- }
791
+ if (_.isMsie() && _.isMsie() <= 7) {
792
+ _.mixin(css.input, {
793
+ marginTop: "-1px"
794
+ });
795
+ }
796
+ return css;
797
+ }();
715
798
  var EventBus = function() {
799
+ "use strict";
716
800
  var namespace = "typeahead:";
717
801
  function EventBus(o) {
718
802
  if (!o || !o.el) {
@@ -729,6 +813,7 @@
729
813
  return EventBus;
730
814
  }();
731
815
  var EventEmitter = function() {
816
+ "use strict";
732
817
  var splitter = /\s+/, nextTick = getNextTick();
733
818
  return {
734
819
  onSync: onSync,
@@ -771,7 +856,7 @@
771
856
  return this;
772
857
  }
773
858
  function trigger(types) {
774
- var that = this, type, callbacks, args, syncFlush, asyncFlush;
859
+ var type, callbacks, args, syncFlush, asyncFlush;
775
860
  if (!this._callbacks) {
776
861
  return this;
777
862
  }
@@ -788,14 +873,14 @@
788
873
  return flush;
789
874
  function flush() {
790
875
  var cancelled;
791
- for (var i = 0; !cancelled && i < callbacks.length; i += 1) {
876
+ for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) {
792
877
  cancelled = callbacks[i].apply(context, args) === false;
793
878
  }
794
879
  return !cancelled;
795
880
  }
796
881
  }
797
882
  function getNextTick() {
798
- var nextTickFn, messageChannel;
883
+ var nextTickFn;
799
884
  if (window.setImmediate) {
800
885
  nextTickFn = function nextTickSetImmediate(fn) {
801
886
  setImmediate(function() {
@@ -818,6 +903,7 @@
818
903
  }
819
904
  }();
820
905
  var highlight = function(doc) {
906
+ "use strict";
821
907
  var defaults = {
822
908
  node: null,
823
909
  pattern: null,
@@ -836,7 +922,7 @@
836
922
  regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly);
837
923
  traverse(o.node, hightlightTextNode);
838
924
  function hightlightTextNode(textNode) {
839
- var match, patternNode;
925
+ var match, patternNode, wrapperNode;
840
926
  if (match = regex.exec(textNode.data)) {
841
927
  wrapperNode = doc.createElement(o.tagName);
842
928
  o.className && (wrapperNode.className = o.className);
@@ -861,7 +947,7 @@
861
947
  };
862
948
  function getRegex(patterns, caseSensitive, wordsOnly) {
863
949
  var escapedPatterns = [], regexStr;
864
- for (var i = 0; i < patterns.length; i++) {
950
+ for (var i = 0, len = patterns.length; i < len; i++) {
865
951
  escapedPatterns.push(_.escapeRegExChars(patterns[i]));
866
952
  }
867
953
  regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")";
@@ -869,6 +955,7 @@
869
955
  }
870
956
  }(window.document);
871
957
  var Input = function() {
958
+ "use strict";
872
959
  var specialKeyCodeMap;
873
960
  specialKeyCodeMap = {
874
961
  9: "tab",
@@ -892,7 +979,7 @@
892
979
  this.$hint = $(o.hint);
893
980
  this.$input = $(o.input).on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown);
894
981
  if (this.$hint.length === 0) {
895
- this.setHintValue = this.getHintValue = this.clearHint = _.noop;
982
+ this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop;
896
983
  }
897
984
  if (!_.isMsie()) {
898
985
  this.$input.on("input.tt", onInput);
@@ -911,11 +998,11 @@
911
998
  return (str || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
912
999
  };
913
1000
  _.mixin(Input.prototype, EventEmitter, {
914
- _onBlur: function onBlur($e) {
1001
+ _onBlur: function onBlur() {
915
1002
  this.resetInputValue();
916
1003
  this.trigger("blurred");
917
1004
  },
918
- _onFocus: function onFocus($e) {
1005
+ _onFocus: function onFocus() {
919
1006
  this.trigger("focused");
920
1007
  },
921
1008
  _onKeydown: function onKeydown($e) {
@@ -925,14 +1012,14 @@
925
1012
  this.trigger(keyName + "Keyed", $e);
926
1013
  }
927
1014
  },
928
- _onInput: function onInput($e) {
1015
+ _onInput: function onInput() {
929
1016
  this._checkInputValue();
930
1017
  },
931
1018
  _managePreventDefault: function managePreventDefault(keyName, $e) {
932
1019
  var preventDefault, hintValue, inputValue;
933
1020
  switch (keyName) {
934
1021
  case "tab":
935
- hintValue = this.getHintValue();
1022
+ hintValue = this.getHint();
936
1023
  inputValue = this.getInputValue();
937
1024
  preventDefault = hintValue && hintValue !== inputValue && !withModifier($e);
938
1025
  break;
@@ -964,8 +1051,9 @@
964
1051
  inputValue = this.getInputValue();
965
1052
  areEquivalent = areQueriesEquivalent(inputValue, this.query);
966
1053
  hasDifferentWhitespace = areEquivalent ? this.query.length !== inputValue.length : false;
1054
+ this.query = inputValue;
967
1055
  if (!areEquivalent) {
968
- this.trigger("queryChanged", this.query = inputValue);
1056
+ this.trigger("queryChanged", this.query);
969
1057
  } else if (hasDifferentWhitespace) {
970
1058
  this.trigger("whitespaceChanged", this.query);
971
1059
  }
@@ -987,19 +1075,27 @@
987
1075
  },
988
1076
  setInputValue: function setInputValue(value, silent) {
989
1077
  this.$input.val(value);
990
- !silent && this._checkInputValue();
1078
+ silent ? this.clearHint() : this._checkInputValue();
1079
+ },
1080
+ resetInputValue: function resetInputValue() {
1081
+ this.setInputValue(this.query, true);
991
1082
  },
992
- getHintValue: function getHintValue() {
1083
+ getHint: function getHint() {
993
1084
  return this.$hint.val();
994
1085
  },
995
- setHintValue: function setHintValue(value) {
1086
+ setHint: function setHint(value) {
996
1087
  this.$hint.val(value);
997
1088
  },
998
- resetInputValue: function resetInputValue() {
999
- this.$input.val(this.query);
1000
- },
1001
1089
  clearHint: function clearHint() {
1002
- this.$hint.val("");
1090
+ this.setHint("");
1091
+ },
1092
+ clearHintIfInvalid: function clearHintIfInvalid() {
1093
+ var val, hint, valIsPrefixOfHint, isValid;
1094
+ val = this.getInputValue();
1095
+ hint = this.getHint();
1096
+ valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0;
1097
+ isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow();
1098
+ !isValid && this.clearHint();
1003
1099
  },
1004
1100
  getLanguageDirection: function getLanguageDirection() {
1005
1101
  return (this.$input.css("direction") || "ltr").toLowerCase();
@@ -1033,7 +1129,7 @@
1033
1129
  return $('<pre aria-hidden="true"></pre>').css({
1034
1130
  position: "absolute",
1035
1131
  visibility: "hidden",
1036
- whiteSpace: "nowrap",
1132
+ whiteSpace: "pre",
1037
1133
  fontFamily: $input.css("font-family"),
1038
1134
  fontSize: $input.css("font-size"),
1039
1135
  fontStyle: $input.css("font-style"),
@@ -1054,6 +1150,7 @@
1054
1150
  }
1055
1151
  }();
1056
1152
  var Dataset = function() {
1153
+ "use strict";
1057
1154
  var datasetKey = "ttDataset", valueKey = "ttValue", datumKey = "ttDatum";
1058
1155
  function Dataset(o) {
1059
1156
  o = o || {};
@@ -1107,15 +1204,14 @@
1107
1204
  nodes = _.map(suggestions, getSuggestionNode);
1108
1205
  $suggestions.append.apply($suggestions, nodes);
1109
1206
  that.highlight && highlight({
1207
+ className: "tt-highlight",
1110
1208
  node: $suggestions[0],
1111
1209
  pattern: query
1112
1210
  });
1113
1211
  return $suggestions;
1114
1212
  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);
1213
+ var $el;
1214
+ $el = $(html.suggestion).append(that.templates.suggestion(suggestion)).data(datasetKey, that.name).data(valueKey, that.displayFn(suggestion)).data(datumKey, suggestion);
1119
1215
  $el.children().each(function() {
1120
1216
  $(this).css(css.suggestionChild);
1121
1217
  });
@@ -1141,13 +1237,21 @@
1141
1237
  update: function update(query) {
1142
1238
  var that = this;
1143
1239
  this.query = query;
1144
- this.source(query, renderIfQueryIsSame);
1145
- function renderIfQueryIsSame(suggestions) {
1146
- query === that.query && that._render(query, suggestions);
1240
+ this.canceled = false;
1241
+ this.source(query, render);
1242
+ function render(suggestions) {
1243
+ if (!that.canceled && query === that.query) {
1244
+ that._render(query, suggestions);
1245
+ }
1147
1246
  }
1148
1247
  },
1248
+ cancel: function cancel() {
1249
+ this.canceled = true;
1250
+ },
1149
1251
  clear: function clear() {
1150
- this._render(this.query || "");
1252
+ this.cancel();
1253
+ this.$el.empty();
1254
+ this.trigger("rendered");
1151
1255
  },
1152
1256
  isEmpty: function isEmpty() {
1153
1257
  return this.$el.is(":empty");
@@ -1180,6 +1284,7 @@
1180
1284
  }
1181
1285
  }();
1182
1286
  var Dropdown = function() {
1287
+ "use strict";
1183
1288
  function Dropdown(o) {
1184
1289
  var that = this, onSuggestionClick, onSuggestionMouseEnter, onSuggestionMouseLeave;
1185
1290
  o = o || {};
@@ -1206,7 +1311,7 @@
1206
1311
  this._removeCursor();
1207
1312
  this._setCursor($($e.currentTarget), true);
1208
1313
  },
1209
- _onSuggestionMouseLeave: function onSuggestionMouseLeave($e) {
1314
+ _onSuggestionMouseLeave: function onSuggestionMouseLeave() {
1210
1315
  this._removeCursor();
1211
1316
  },
1212
1317
  _onRendered: function onRendered() {
@@ -1339,19 +1444,37 @@
1339
1444
  }
1340
1445
  }();
1341
1446
  var Typeahead = function() {
1447
+ "use strict";
1342
1448
  var attrsKey = "ttAttrs";
1343
1449
  function Typeahead(o) {
1344
- var $menu, $input, $hint, datasets;
1450
+ var $menu, $input, $hint;
1345
1451
  o = o || {};
1346
1452
  if (!o.input) {
1347
1453
  $.error("missing input");
1348
1454
  }
1455
+ this.isActivated = false;
1349
1456
  this.autoselect = !!o.autoselect;
1350
1457
  this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;
1351
- this.$node = buildDomStructure(o.input, o.withHint);
1458
+ this.$node = buildDom(o.input, o.withHint);
1352
1459
  $menu = this.$node.find(".tt-dropdown-menu");
1353
1460
  $input = this.$node.find(".tt-input");
1354
1461
  $hint = this.$node.find(".tt-hint");
1462
+ $input.on("blur.tt", function($e) {
1463
+ var active, isActive, hasActive;
1464
+ active = document.activeElement;
1465
+ isActive = $menu.is(active);
1466
+ hasActive = $menu.has(active).length > 0;
1467
+ if (_.isMsie() && (isActive || hasActive)) {
1468
+ $e.preventDefault();
1469
+ $e.stopImmediatePropagation();
1470
+ _.defer(function() {
1471
+ $input.focus();
1472
+ });
1473
+ }
1474
+ });
1475
+ $menu.on("mousedown.tt", function($e) {
1476
+ $e.preventDefault();
1477
+ });
1355
1478
  this.eventBus = o.eventBus || new EventBus({
1356
1479
  el: $input
1357
1480
  });
@@ -1363,15 +1486,7 @@
1363
1486
  input: $input,
1364
1487
  hint: $hint
1365
1488
  }).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
- });
1489
+ this._setLanguageDirection();
1375
1490
  }
1376
1491
  _.mixin(Typeahead.prototype, {
1377
1492
  _onSuggestionClicked: function onSuggestionClicked(type, $el) {
@@ -1382,7 +1497,6 @@
1382
1497
  },
1383
1498
  _onCursorMoved: function onCursorMoved() {
1384
1499
  var datum = this.dropdown.getDatumForCursor();
1385
- this.input.clearHint();
1386
1500
  this.input.setInputValue(datum.value, true);
1387
1501
  this.eventBus.trigger("cursorchanged", datum.raw, datum.datasetName);
1388
1502
  },
@@ -1402,10 +1516,12 @@
1402
1516
  this.eventBus.trigger("closed");
1403
1517
  },
1404
1518
  _onFocused: function onFocused() {
1405
- this.dropdown.empty();
1519
+ this.isActivated = true;
1406
1520
  this.dropdown.open();
1407
1521
  },
1408
1522
  _onBlurred: function onBlurred() {
1523
+ this.isActivated = false;
1524
+ this.dropdown.empty();
1409
1525
  this.dropdown.close();
1410
1526
  },
1411
1527
  _onEnterKeyed: function onEnterKeyed(type, $e) {
@@ -1426,7 +1542,7 @@
1426
1542
  this._select(datum);
1427
1543
  $e.preventDefault();
1428
1544
  } else {
1429
- this._autocomplete();
1545
+ this._autocomplete(true);
1430
1546
  }
1431
1547
  },
1432
1548
  _onEscKeyed: function onEscKeyed() {
@@ -1435,19 +1551,13 @@
1435
1551
  },
1436
1552
  _onUpKeyed: function onUpKeyed() {
1437
1553
  var query = this.input.getQuery();
1438
- if (!this.dropdown.isOpen && query.length >= this.minLength) {
1439
- this.dropdown.update(query);
1440
- }
1554
+ this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorUp();
1441
1555
  this.dropdown.open();
1442
- this.dropdown.moveCursorUp();
1443
1556
  },
1444
1557
  _onDownKeyed: function onDownKeyed() {
1445
1558
  var query = this.input.getQuery();
1446
- if (!this.dropdown.isOpen && query.length >= this.minLength) {
1447
- this.dropdown.update(query);
1448
- }
1559
+ this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorDown();
1449
1560
  this.dropdown.open();
1450
- this.dropdown.moveCursorDown();
1451
1561
  },
1452
1562
  _onLeftKeyed: function onLeftKeyed() {
1453
1563
  this.dir === "rtl" && this._autocomplete();
@@ -1456,9 +1566,8 @@
1456
1566
  this.dir === "ltr" && this._autocomplete();
1457
1567
  },
1458
1568
  _onQueryChanged: function onQueryChanged(e, query) {
1459
- this.input.clearHint();
1460
- this.dropdown.empty();
1461
- query.length >= this.minLength && this.dropdown.update(query);
1569
+ this.input.clearHintIfInvalid();
1570
+ query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.empty();
1462
1571
  this.dropdown.open();
1463
1572
  this._setLanguageDirection();
1464
1573
  },
@@ -1475,29 +1584,31 @@
1475
1584
  }
1476
1585
  },
1477
1586
  _updateHint: function updateHint() {
1478
- var datum, inputValue, query, escapedQuery, frontMatchRegEx, match;
1587
+ var datum, val, query, escapedQuery, frontMatchRegEx, match;
1479
1588
  datum = this.dropdown.getDatumForTopSuggestion();
1480
1589
  if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) {
1481
- inputValue = this.input.getInputValue();
1482
- query = Input.normalizeQuery(inputValue);
1590
+ val = this.input.getInputValue();
1591
+ query = Input.normalizeQuery(val);
1483
1592
  escapedQuery = _.escapeRegExChars(query);
1484
- frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i");
1593
+ frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i");
1485
1594
  match = frontMatchRegEx.exec(datum.value);
1486
- this.input.setHintValue(inputValue + (match ? match[1] : ""));
1595
+ match ? this.input.setHint(val + match[1]) : this.input.clearHint();
1596
+ } else {
1597
+ this.input.clearHint();
1487
1598
  }
1488
1599
  },
1489
- _autocomplete: function autocomplete() {
1490
- var hint, query, datum;
1491
- hint = this.input.getHintValue();
1600
+ _autocomplete: function autocomplete(laxCursor) {
1601
+ var hint, query, isCursorAtEnd, datum;
1602
+ hint = this.input.getHint();
1492
1603
  query = this.input.getQuery();
1493
- if (hint && query !== hint && this.input.isCursorAtEnd()) {
1604
+ isCursorAtEnd = laxCursor || this.input.isCursorAtEnd();
1605
+ if (hint && query !== hint && isCursorAtEnd) {
1494
1606
  datum = this.dropdown.getDatumForTopSuggestion();
1495
1607
  datum && this.input.setInputValue(datum.value);
1496
1608
  this.eventBus.trigger("autocompleted", datum.raw, datum.datasetName);
1497
1609
  }
1498
1610
  },
1499
1611
  _select: function select(datum) {
1500
- this.input.clearHint();
1501
1612
  this.input.setQuery(datum.value);
1502
1613
  this.input.setInputValue(datum.value, true);
1503
1614
  this._setLanguageDirection();
@@ -1511,11 +1622,18 @@
1511
1622
  close: function close() {
1512
1623
  this.dropdown.close();
1513
1624
  },
1514
- getQuery: function getQuery() {
1515
- return this.input.getQuery();
1625
+ setVal: function setVal(val) {
1626
+ val = _.toStr(val);
1627
+ if (this.isActivated) {
1628
+ this.input.setInputValue(val);
1629
+ } else {
1630
+ this.input.setQuery(val);
1631
+ this.input.setInputValue(val, true);
1632
+ }
1633
+ this._setLanguageDirection();
1516
1634
  },
1517
- setQuery: function setQuery(val) {
1518
- this.input.setInputValue(val);
1635
+ getVal: function getVal() {
1636
+ return this.input.getQuery();
1519
1637
  },
1520
1638
  destroy: function destroy() {
1521
1639
  this.input.destroy();
@@ -1525,15 +1643,16 @@
1525
1643
  }
1526
1644
  });
1527
1645
  return Typeahead;
1528
- function buildDomStructure(input, withHint) {
1646
+ function buildDom(input, withHint) {
1529
1647
  var $input, $wrapper, $dropdown, $hint;
1530
1648
  $input = $(input);
1531
1649
  $wrapper = $(html.wrapper).css(css.wrapper);
1532
1650
  $dropdown = $(html.dropdown).css(css.dropdown);
1533
1651
  $hint = $input.clone().css(css.hint).css(getBackgroundStyles($input));
1534
- $hint.val("").removeData().addClass("tt-hint").removeAttr("id name placeholder").prop("disabled", true).attr({
1652
+ $hint.val("").removeData().addClass("tt-hint").removeAttr("id name placeholder required").prop("readonly", true).attr({
1535
1653
  autocomplete: "off",
1536
- spellcheck: "false"
1654
+ spellcheck: "false",
1655
+ tabindex: -1
1537
1656
  });
1538
1657
  $input.data(attrsKey, {
1539
1658
  dir: $input.attr("dir"),
@@ -1572,6 +1691,7 @@
1572
1691
  }
1573
1692
  }();
1574
1693
  (function() {
1694
+ "use strict";
1575
1695
  var old, typeaheadKey, methods;
1576
1696
  old = $.fn.typeahead;
1577
1697
  typeaheadKey = "ttTypeahead";
@@ -1617,17 +1737,17 @@
1617
1737
  }
1618
1738
  },
1619
1739
  val: function val(newVal) {
1620
- return !arguments.length ? getQuery(this.first()) : this.each(setQuery);
1621
- function setQuery() {
1740
+ return !arguments.length ? getVal(this.first()) : this.each(setVal);
1741
+ function setVal() {
1622
1742
  var $input = $(this), typeahead;
1623
1743
  if (typeahead = $input.data(typeaheadKey)) {
1624
- typeahead.setQuery(newVal);
1744
+ typeahead.setVal(newVal);
1625
1745
  }
1626
1746
  }
1627
- function getQuery($input) {
1747
+ function getVal($input) {
1628
1748
  var typeahead, query;
1629
1749
  if (typeahead = $input.data(typeaheadKey)) {
1630
- query = typeahead.getQuery();
1750
+ query = typeahead.getVal();
1631
1751
  }
1632
1752
  return query;
1633
1753
  }
@@ -1644,8 +1764,12 @@
1644
1764
  }
1645
1765
  };
1646
1766
  $.fn.typeahead = function(method) {
1647
- if (methods[method]) {
1648
- return methods[method].apply(this, [].slice.call(arguments, 1));
1767
+ var tts;
1768
+ if (methods[method] && method !== "initialize") {
1769
+ tts = this.filter(function() {
1770
+ return !!$(this).data(typeaheadKey);
1771
+ });
1772
+ return methods[method].apply(tts, [].slice.call(arguments, 1));
1649
1773
  } else {
1650
1774
  return methods.initialize.apply(this, arguments);
1651
1775
  }