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.
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * typeahead.js 0.11.1
3
+ * https://github.com/twitter/typeahead.js
4
+ * Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT
5
+ */
6
+
7
+ !function(a,b){"function"==typeof define&&define.amd?define(["jquery"],function(c){return a.Bloodhound=b(c)}):"object"==typeof exports?module.exports=b(require("jquery")):a.Bloodhound=b(jQuery)}(this,function(a){var b=function(){"use strict";return{isMsie:function(){return/(msie|trident)/i.test(navigator.userAgent)?navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2]:!1},isBlankString:function(a){return!a||/^\s*$/.test(a)},escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(a){return"string"==typeof a},isNumber:function(a){return"number"==typeof a},isArray:a.isArray,isFunction:a.isFunction,isObject:a.isPlainObject,isUndefined:function(a){return"undefined"==typeof a},isElement:function(a){return!(!a||1!==a.nodeType)},isJQuery:function(b){return b instanceof a},toStr:function(a){return b.isUndefined(a)||null===a?"":a+""},bind:a.proxy,each:function(b,c){function d(a,b){return c(b,a)}a.each(b,d)},map:a.map,filter:a.grep,every:function(b,c){var d=!0;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?void 0:!1}),!!d):d},some:function(b,c){var d=!1;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?!1:void 0}),!!d):d},mixin:a.extend,identity:function(a){return a},clone:function(b){return a.extend(!0,{},b)},getIdGenerator:function(){var a=0;return function(){return a++}},templatify:function(b){function c(){return String(b)}return a.isFunction(b)?b:c},defer:function(a){setTimeout(a,0)},debounce:function(a,b,c){var d,e;return function(){var f,g,h=this,i=arguments;return f=function(){d=null,c||(e=a.apply(h,i))},g=c&&!d,clearTimeout(d),d=setTimeout(f,b),g&&(e=a.apply(h,i)),e}},throttle:function(a,b){var c,d,e,f,g,h;return g=0,h=function(){g=new Date,e=null,f=a.apply(c,d)},function(){var i=new Date,j=b-(i-g);return c=this,d=arguments,0>=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},stringify:function(a){return b.isString(a)?a:JSON.stringify(a)},noop:function(){}}}(),c="0.11.1",d=function(){"use strict";function a(a){return a=b.toStr(a),a?a.split(/\s+/):[]}function c(a){return a=b.toStr(a),a?a.split(/\W+/):[]}function d(a){return function(c){return c=b.isArray(c)?c:[].slice.call(arguments,0),function(d){var e=[];return b.each(c,function(c){e=e.concat(a(b.toStr(d[c])))}),e}}}return{nonword:c,whitespace:a,obj:{nonword:d(c),whitespace:d(a)}}}(),e=function(){"use strict";function c(c){this.maxSize=b.isNumber(c)?c:100,this.reset(),this.maxSize<=0&&(this.set=this.get=a.noop)}function d(){this.head=this.tail=null}function e(a,b){this.key=a,this.val=b,this.prev=this.next=null}return b.mixin(c.prototype,{set:function(a,b){var c,d=this.list.tail;this.size>=this.maxSize&&(this.list.remove(d),delete this.hash[d.key],this.size--),(c=this.hash[a])?(c.val=b,this.list.moveToFront(c)):(c=new e(a,b),this.list.add(c),this.hash[a]=c,this.size++)},get:function(a){var b=this.hash[a];return b?(this.list.moveToFront(b),b.val):void 0},reset:function(){this.size=0,this.hash={},this.list=new d}}),b.mixin(d.prototype,{add:function(a){this.head&&(a.next=this.head,this.head.prev=a),this.head=a,this.tail=this.tail||a},remove:function(a){a.prev?a.prev.next=a.next:this.head=a.next,a.next?a.next.prev=a.prev:this.tail=a.prev},moveToFront:function(a){this.remove(a),this.add(a)}}),c}(),f=function(){"use strict";function c(a,c){this.prefix=["__",a,"__"].join(""),this.ttlKey="__ttl__",this.keyMatcher=new RegExp("^"+b.escapeRegExChars(this.prefix)),this.ls=c||h,!this.ls&&this._noop()}function d(){return(new Date).getTime()}function e(a){return JSON.stringify(b.isUndefined(a)?null:a)}function f(b){return a.parseJSON(b)}function g(a){var b,c,d=[],e=h.length;for(b=0;e>b;b++)(c=h.key(b)).match(a)&&d.push(c.replace(a,""));return d}var h;try{h=window.localStorage,h.setItem("~~~","!"),h.removeItem("~~~")}catch(i){h=null}return b.mixin(c.prototype,{_prefix:function(a){return this.prefix+a},_ttlKey:function(a){return this._prefix(a)+this.ttlKey},_noop:function(){this.get=this.set=this.remove=this.clear=this.isExpired=b.noop},_safeSet:function(a,b){try{this.ls.setItem(a,b)}catch(c){"QuotaExceededError"===c.name&&(this.clear(),this._noop())}},get:function(a){return this.isExpired(a)&&this.remove(a),f(this.ls.getItem(this._prefix(a)))},set:function(a,c,f){return b.isNumber(f)?this._safeSet(this._ttlKey(a),e(d()+f)):this.ls.removeItem(this._ttlKey(a)),this._safeSet(this._prefix(a),e(c))},remove:function(a){return this.ls.removeItem(this._ttlKey(a)),this.ls.removeItem(this._prefix(a)),this},clear:function(){var a,b=g(this.keyMatcher);for(a=b.length;a--;)this.remove(b[a]);return this},isExpired:function(a){var c=f(this.ls.getItem(this._ttlKey(a)));return b.isNumber(c)&&d()>c?!0:!1}}),c}(),g=function(){"use strict";function c(a){a=a||{},this.cancelled=!1,this.lastReq=null,this._send=a.transport,this._get=a.limiter?a.limiter(this._get):this._get,this._cache=a.cache===!1?new e(0):h}var d=0,f={},g=6,h=new e(10);return c.setMaxPendingRequests=function(a){g=a},c.resetCache=function(){h.reset()},b.mixin(c.prototype,{_fingerprint:function(b){return b=b||{},b.url+b.type+a.param(b.data||{})},_get:function(a,b){function c(a){b(null,a),k._cache.set(i,a)}function e(){b(!0)}function h(){d--,delete f[i],k.onDeckRequestArgs&&(k._get.apply(k,k.onDeckRequestArgs),k.onDeckRequestArgs=null)}var i,j,k=this;i=this._fingerprint(a),this.cancelled||i!==this.lastReq||((j=f[i])?j.done(c).fail(e):g>d?(d++,f[i]=this._send(a).done(c).fail(e).always(h)):this.onDeckRequestArgs=[].slice.call(arguments,0))},get:function(c,d){var e,f;d=d||a.noop,c=b.isString(c)?{url:c}:c||{},f=this._fingerprint(c),this.cancelled=!1,this.lastReq=f,(e=this._cache.get(f))?d(null,e):this._get(c,d)},cancel:function(){this.cancelled=!0}}),c}(),h=window.SearchIndex=function(){"use strict";function c(c){c=c||{},c.datumTokenizer&&c.queryTokenizer||a.error("datumTokenizer and queryTokenizer are both required"),this.identify=c.identify||b.stringify,this.datumTokenizer=c.datumTokenizer,this.queryTokenizer=c.queryTokenizer,this.matchAnyQueryToken=c.matchAnyQueryToken,this.reset()}function d(a){return a=b.filter(a,function(a){return!!a}),a=b.map(a,function(a){return a.toLowerCase()})}function e(){var a={};return a[i]=[],a[h]={},a}function f(a){for(var b={},c=[],d=0,e=a.length;e>d;d++)b[a[d]]||(b[a[d]]=!0,c.push(a[d]));return c}function g(a,b){var c=0,d=0,e=[];a=a.sort(),b=b.sort();for(var f=a.length,g=b.length;f>c&&g>d;)a[c]<b[d]?c++:a[c]>b[d]?d++:(e.push(a[c]),c++,d++);return e}var h="c",i="i";return b.mixin(c.prototype,{bootstrap:function(a){this.datums=a.datums,this.trie=a.trie},add:function(a){var c=this;a=b.isArray(a)?a:[a],b.each(a,function(a){var f,g;c.datums[f=c.identify(a)]=a,g=d(c.datumTokenizer(a)),b.each(g,function(a){var b,d,g;for(b=c.trie,d=a.split("");g=d.shift();)b=b[h][g]||(b[h][g]=e()),b[i].push(f)})})},get:function(a){var c=this;return b.map(a,function(a){return c.datums[a]})},search:function(a){var c,e,j=this;return c=d(this.queryTokenizer(a)),b.each(c,function(a){var b,c,d,f;if(e&&0===e.length&&!j.matchAnyQueryToken)return!1;for(b=j.trie,c=a.split("");b&&(d=c.shift());)b=b[h][d];if(b&&0===c.length)f=b[i].slice(0),e=e?g(e,f):f;else if(!j.matchAnyQueryToken)return e=[],!1}),e?b.map(f(e),function(a){return j.datums[a]}):[]},all:function(){var a=[];for(var b in this.datums)a.push(this.datums[b]);return a},reset:function(){this.datums={},this.trie=e()},serialize:function(){return{datums:this.datums,trie:this.trie}}}),c}(),i=function(){"use strict";function a(a){this.url=a.url,this.ttl=a.ttl,this.cache=a.cache,this.prepare=a.prepare,this.transform=a.transform,this.transport=a.transport,this.thumbprint=a.thumbprint,this.storage=new f(a.cacheKey)}var c;return c={data:"data",protocol:"protocol",thumbprint:"thumbprint"},b.mixin(a.prototype,{_settings:function(){return{url:this.url,type:"GET",dataType:"json"}},store:function(a){this.cache&&(this.storage.set(c.data,a,this.ttl),this.storage.set(c.protocol,location.protocol,this.ttl),this.storage.set(c.thumbprint,this.thumbprint,this.ttl))},fromCache:function(){var a,b={};return this.cache?(b.data=this.storage.get(c.data),b.protocol=this.storage.get(c.protocol),b.thumbprint=this.storage.get(c.thumbprint),a=b.thumbprint!==this.thumbprint||b.protocol!==location.protocol,b.data&&!a?b.data:null):null},fromNetwork:function(a){function b(){a(!0)}function c(b){a(null,e.transform(b))}var d,e=this;a&&(d=this.prepare(this._settings()),this.transport(d).fail(b).done(c))},clear:function(){return this.storage.clear(),this}}),a}(),j=function(){"use strict";function a(a){this.url=a.url,this.prepare=a.prepare,this.transform=a.transform,this.indexResponse=a.indexResponse,this.transport=new g({cache:a.cache,limiter:a.limiter,transport:a.transport})}return b.mixin(a.prototype,{_settings:function(){return{url:this.url,type:"GET",dataType:"json"}},get:function(a,b){function c(a,c){b(a?[]:e.transform(c))}var d,e=this;if(b)return a=a||"",d=this.prepare(a,this._settings()),this.transport.get(d,c)},cancelLastRequest:function(){this.transport.cancel()}}),a}(),k=function(){"use strict";function d(d){var e;return d?(e={url:null,ttl:864e5,cache:!0,cacheKey:null,thumbprint:"",prepare:b.identity,transform:b.identity,transport:null},d=b.isString(d)?{url:d}:d,d=b.mixin(e,d),!d.url&&a.error("prefetch requires url to be set"),d.transform=d.filter||d.transform,d.cacheKey=d.cacheKey||d.url,d.thumbprint=c+d.thumbprint,d.transport=d.transport?h(d.transport):a.ajax,d):null}function e(c){var d;if(c)return d={url:null,cache:!0,prepare:null,replace:null,wildcard:null,limiter:null,rateLimitBy:"debounce",rateLimitWait:300,transform:b.identity,transport:null},c=b.isString(c)?{url:c}:c,c=b.mixin(d,c),!c.url&&a.error("remote requires url to be set"),c.transform=c.filter||c.transform,c.prepare=f(c),c.limiter=g(c),c.transport=c.transport?h(c.transport):a.ajax,delete c.replace,delete c.wildcard,delete c.rateLimitBy,delete c.rateLimitWait,c}function f(a){function b(a,b){return b.url=f(b.url,a),b}function c(a,b){return b.url=b.url.replace(g,encodeURIComponent(a)),b}function d(a,b){return b}var e,f,g;return e=a.prepare,f=a.replace,g=a.wildcard,e?e:e=f?b:a.wildcard?c:d}function g(a){function c(a){return function(c){return b.debounce(c,a)}}function d(a){return function(c){return b.throttle(c,a)}}var e,f,g;return e=a.limiter,f=a.rateLimitBy,g=a.rateLimitWait,e||(e=/^throttle$/i.test(f)?d(g):c(g)),e}function h(c){return function(d){function e(a){b.defer(function(){g.resolve(a)})}function f(a){b.defer(function(){g.reject(a)})}var g=a.Deferred();return c(d,e,f),g}}return function(c){var f,g;return f={initialize:!0,identify:b.stringify,datumTokenizer:null,queryTokenizer:null,matchAnyQueryToken:!1,sufficient:5,indexRemote:!1,sorter:null,local:[],prefetch:null,remote:null},c=b.mixin(f,c||{}),!c.datumTokenizer&&a.error("datumTokenizer is required"),!c.queryTokenizer&&a.error("queryTokenizer is required"),g=c.sorter,c.sorter=g?function(a){return a.sort(g)}:b.identity,c.local=b.isFunction(c.local)?c.local():c.local,c.prefetch=d(c.prefetch),c.remote=e(c.remote),c}}(),l=function(){"use strict";function c(a){a=k(a),this.sorter=a.sorter,this.identify=a.identify,this.sufficient=a.sufficient,this.indexRemote=a.indexRemote,this.local=a.local,this.remote=a.remote?new j(a.remote):null,this.prefetch=a.prefetch?new i(a.prefetch):null,this.index=new h({identify:this.identify,datumTokenizer:a.datumTokenizer,queryTokenizer:a.queryTokenizer}),a.initialize!==!1&&this.initialize()}var e;return e=window&&window.Bloodhound,c.noConflict=function(){return window&&(window.Bloodhound=e),c},c.tokenizers=d,b.mixin(c.prototype,{__ttAdapter:function(){function a(a,b,d){return c.search(a,b,d)}function b(a,b){return c.search(a,b)}var c=this;return this.remote?a:b},_loadPrefetch:function(){function b(a,b){return a?c.reject():(e.add(b),e.prefetch.store(e.index.serialize()),void c.resolve())}var c,d,e=this;return c=a.Deferred(),this.prefetch?(d=this.prefetch.fromCache())?(this.index.bootstrap(d),c.resolve()):this.prefetch.fromNetwork(b):c.resolve(),c.promise()},_initialize:function(){function a(){b.add(b.local)}var b=this;return this.clear(),(this.initPromise=this._loadPrefetch()).done(a),this.initPromise},initialize:function(a){return!this.initPromise||a?this._initialize():this.initPromise},add:function(a){return this.index.add(a),this},get:function(a){return a=b.isArray(a)?a:[].slice.call(arguments),this.index.get(a)},search:function(a,c,d){function e(a){var c=[];b.each(a,function(a){!b.some(f,function(b){return g.identify(a)===g.identify(b)})&&c.push(a)}),g.indexRemote&&g.add(c),d(c)}var f,g=this;return c=c||b.noop,d=d||b.noop,f=this.sorter(this.index.search(a)),c(this.remote?f.slice():f),this.remote&&f.length<this.sufficient?this.remote.get(a,e):this.remote&&this.remote.cancelLastRequest(),this},all:function(){return this.index.all()},clear:function(){return this.index.reset(),this},clearPrefetchCache:function(){return this.prefetch&&this.prefetch.clear(),this},clearRemoteCache:function(){return g.resetCache(),this},ttAdapter:function(){return this.__ttAdapter()}}),c}();return l});
@@ -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;
763
+ }
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);
568
775
  }
569
- return remote;
570
- function byDebounce(wait) {
571
- return function(fn) {
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,175 +874,373 @@
675
874
  },
676
875
  add: function add(data) {
677
876
  this.index.add(data);
877
+ return this;
678
878
  },
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;
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
- 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
- };
735
926
  }();
736
- var css = function() {
927
+ return Bloodhound;
928
+ });
929
+
930
+ (function(root, factory) {
931
+ if (typeof define === "function" && define.amd) {
932
+ define([ "jquery" ], function(a0) {
933
+ return factory(a0);
934
+ });
935
+ } else if (typeof exports === "object") {
936
+ module.exports = factory(require("jquery"));
937
+ } else {
938
+ factory(jQuery);
939
+ }
940
+ })(this, function($) {
941
+ var _ = function() {
737
942
  "use strict";
738
- var css = {
739
- wrapper: {
740
- position: "relative",
741
- display: "inline-block"
943
+ return {
944
+ isMsie: function() {
945
+ return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
742
946
  },
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"
947
+ isBlankString: function(str) {
948
+ return !str || /^\s*$/.test(str);
766
949
  },
767
- suggestions: {
768
- display: "block"
950
+ escapeRegExChars: function(str) {
951
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
769
952
  },
770
- suggestion: {
771
- whiteSpace: "nowrap",
772
- cursor: "pointer"
953
+ isString: function(obj) {
954
+ return typeof obj === "string";
773
955
  },
774
- suggestionChild: {
775
- whiteSpace: "normal"
956
+ isNumber: function(obj) {
957
+ return typeof obj === "number";
776
958
  },
777
- ltr: {
778
- left: "0",
779
- right: "auto"
959
+ isArray: $.isArray,
960
+ isFunction: $.isFunction,
961
+ isObject: $.isPlainObject,
962
+ isUndefined: function(obj) {
963
+ return typeof obj === "undefined";
780
964
  },
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
- });
790
- }
791
- if (_.isMsie() && _.isMsie() <= 7) {
792
- _.mixin(css.input, {
793
- marginTop: "-1px"
794
- });
795
- }
796
- return css;
797
- }();
798
- var EventBus = function() {
799
- "use strict";
800
- var namespace = "typeahead:";
801
- function EventBus(o) {
802
- if (!o || !o.el) {
803
- $.error("EventBus initialized without el");
804
- }
805
- this.$el = $(o.el);
806
- }
807
- _.mixin(EventBus.prototype, {
808
- trigger: function(type) {
809
- var args = [].slice.call(arguments, 1);
810
- this.$el.trigger(namespace + type, args);
811
- }
812
- });
813
- return EventBus;
814
- }();
815
- var EventEmitter = function() {
816
- "use strict";
817
- var splitter = /\s+/, nextTick = getNextTick();
818
- return {
819
- onSync: onSync,
820
- onAsync: onAsync,
821
- off: off,
822
- trigger: trigger
823
- };
824
- function on(method, types, cb, context) {
825
- var type;
826
- if (!cb) {
827
- return this;
828
- }
829
- types = types.split(splitter);
830
- cb = context ? bindContext(cb, context) : cb;
831
- this._callbacks = this._callbacks || {};
832
- while (type = types.shift()) {
833
- this._callbacks[type] = this._callbacks[type] || {
834
- sync: [],
835
- async: []
836
- };
837
- this._callbacks[type][method].push(cb);
838
- }
839
- return this;
840
- }
841
- function onAsync(types, cb, context) {
842
- return on.call(this, "async", types, cb, context);
843
- }
844
- function onSync(types, cb, context) {
845
- return on.call(this, "sync", types, cb, context);
846
- }
965
+ isElement: function(obj) {
966
+ return !!(obj && obj.nodeType === 1);
967
+ },
968
+ isJQuery: function(obj) {
969
+ return obj instanceof $;
970
+ },
971
+ toStr: function toStr(s) {
972
+ return _.isUndefined(s) || s === null ? "" : s + "";
973
+ },
974
+ bind: $.proxy,
975
+ each: function(collection, cb) {
976
+ $.each(collection, reverseArgs);
977
+ function reverseArgs(index, value) {
978
+ return cb(value, index);
979
+ }
980
+ },
981
+ map: $.map,
982
+ filter: $.grep,
983
+ every: function(obj, test) {
984
+ var result = true;
985
+ if (!obj) {
986
+ return result;
987
+ }
988
+ $.each(obj, function(key, val) {
989
+ if (!(result = test.call(null, val, key, obj))) {
990
+ return false;
991
+ }
992
+ });
993
+ return !!result;
994
+ },
995
+ some: function(obj, test) {
996
+ var result = false;
997
+ if (!obj) {
998
+ return result;
999
+ }
1000
+ $.each(obj, function(key, val) {
1001
+ if (result = test.call(null, val, key, obj)) {
1002
+ return false;
1003
+ }
1004
+ });
1005
+ return !!result;
1006
+ },
1007
+ mixin: $.extend,
1008
+ identity: function(x) {
1009
+ return x;
1010
+ },
1011
+ clone: function(obj) {
1012
+ return $.extend(true, {}, obj);
1013
+ },
1014
+ getIdGenerator: function() {
1015
+ var counter = 0;
1016
+ return function() {
1017
+ return counter++;
1018
+ };
1019
+ },
1020
+ templatify: function templatify(obj) {
1021
+ return $.isFunction(obj) ? obj : template;
1022
+ function template() {
1023
+ return String(obj);
1024
+ }
1025
+ },
1026
+ defer: function(fn) {
1027
+ setTimeout(fn, 0);
1028
+ },
1029
+ debounce: function(func, wait, immediate) {
1030
+ var timeout, result;
1031
+ return function() {
1032
+ var context = this, args = arguments, later, callNow;
1033
+ later = function() {
1034
+ timeout = null;
1035
+ if (!immediate) {
1036
+ result = func.apply(context, args);
1037
+ }
1038
+ };
1039
+ callNow = immediate && !timeout;
1040
+ clearTimeout(timeout);
1041
+ timeout = setTimeout(later, wait);
1042
+ if (callNow) {
1043
+ result = func.apply(context, args);
1044
+ }
1045
+ return result;
1046
+ };
1047
+ },
1048
+ throttle: function(func, wait) {
1049
+ var context, args, timeout, result, previous, later;
1050
+ previous = 0;
1051
+ later = function() {
1052
+ previous = new Date();
1053
+ timeout = null;
1054
+ result = func.apply(context, args);
1055
+ };
1056
+ return function() {
1057
+ var now = new Date(), remaining = wait - (now - previous);
1058
+ context = this;
1059
+ args = arguments;
1060
+ if (remaining <= 0) {
1061
+ clearTimeout(timeout);
1062
+ timeout = null;
1063
+ previous = now;
1064
+ result = func.apply(context, args);
1065
+ } else if (!timeout) {
1066
+ timeout = setTimeout(later, remaining);
1067
+ }
1068
+ return result;
1069
+ };
1070
+ },
1071
+ stringify: function(val) {
1072
+ return _.isString(val) ? val : JSON.stringify(val);
1073
+ },
1074
+ noop: function() {}
1075
+ };
1076
+ }();
1077
+ var WWW = function() {
1078
+ "use strict";
1079
+ var defaultClassNames = {
1080
+ wrapper: "twitter-typeahead",
1081
+ input: "tt-input",
1082
+ hint: "tt-hint",
1083
+ menu: "tt-menu",
1084
+ dataset: "tt-dataset",
1085
+ suggestion: "tt-suggestion",
1086
+ selectable: "tt-selectable",
1087
+ empty: "tt-empty",
1088
+ open: "tt-open",
1089
+ cursor: "tt-cursor",
1090
+ highlight: "tt-highlight"
1091
+ };
1092
+ return build;
1093
+ function build(o) {
1094
+ var www, classes;
1095
+ classes = _.mixin({}, defaultClassNames, o);
1096
+ www = {
1097
+ css: buildCss(),
1098
+ classes: classes,
1099
+ html: buildHtml(classes),
1100
+ selectors: buildSelectors(classes)
1101
+ };
1102
+ return {
1103
+ css: www.css,
1104
+ html: www.html,
1105
+ classes: www.classes,
1106
+ selectors: www.selectors,
1107
+ mixin: function(o) {
1108
+ _.mixin(o, www);
1109
+ }
1110
+ };
1111
+ }
1112
+ function buildHtml(c) {
1113
+ return {
1114
+ wrapper: '<span class="' + c.wrapper + '"></span>',
1115
+ menu: '<div class="' + c.menu + '"></div>'
1116
+ };
1117
+ }
1118
+ function buildSelectors(classes) {
1119
+ var selectors = {};
1120
+ _.each(classes, function(v, k) {
1121
+ selectors[k] = "." + v;
1122
+ });
1123
+ return selectors;
1124
+ }
1125
+ function buildCss() {
1126
+ var css = {
1127
+ wrapper: {
1128
+ position: "relative",
1129
+ display: "inline-block"
1130
+ },
1131
+ hint: {
1132
+ position: "absolute",
1133
+ top: "0",
1134
+ left: "0",
1135
+ borderColor: "transparent",
1136
+ boxShadow: "none",
1137
+ opacity: "1"
1138
+ },
1139
+ input: {
1140
+ position: "relative",
1141
+ verticalAlign: "top",
1142
+ backgroundColor: "transparent"
1143
+ },
1144
+ inputWithNoHint: {
1145
+ position: "relative",
1146
+ verticalAlign: "top"
1147
+ },
1148
+ menu: {
1149
+ position: "absolute",
1150
+ top: "100%",
1151
+ left: "0",
1152
+ zIndex: "100",
1153
+ display: "none"
1154
+ },
1155
+ ltr: {
1156
+ left: "0",
1157
+ right: "auto"
1158
+ },
1159
+ rtl: {
1160
+ left: "auto",
1161
+ right: " 0"
1162
+ }
1163
+ };
1164
+ if (_.isMsie()) {
1165
+ _.mixin(css.input, {
1166
+ backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
1167
+ });
1168
+ }
1169
+ return css;
1170
+ }
1171
+ }();
1172
+ var EventBus = function() {
1173
+ "use strict";
1174
+ var namespace, deprecationMap;
1175
+ namespace = "typeahead:";
1176
+ deprecationMap = {
1177
+ render: "rendered",
1178
+ cursorchange: "cursorchanged",
1179
+ select: "selected",
1180
+ autocomplete: "autocompleted"
1181
+ };
1182
+ function EventBus(o) {
1183
+ if (!o || !o.el) {
1184
+ $.error("EventBus initialized without el");
1185
+ }
1186
+ this.$el = $(o.el);
1187
+ }
1188
+ _.mixin(EventBus.prototype, {
1189
+ _trigger: function(type, args) {
1190
+ var $e;
1191
+ $e = $.Event(namespace + type);
1192
+ (args = args || []).unshift($e);
1193
+ this.$el.trigger.apply(this.$el, args);
1194
+ return $e;
1195
+ },
1196
+ before: function(type) {
1197
+ var args, $e;
1198
+ args = [].slice.call(arguments, 1);
1199
+ $e = this._trigger("before" + type, args);
1200
+ return $e.isDefaultPrevented();
1201
+ },
1202
+ trigger: function(type) {
1203
+ var deprecatedType;
1204
+ this._trigger(type, [].slice.call(arguments, 1));
1205
+ if (deprecatedType = deprecationMap[type]) {
1206
+ this._trigger(deprecatedType, [].slice.call(arguments, 1));
1207
+ }
1208
+ }
1209
+ });
1210
+ return EventBus;
1211
+ }();
1212
+ var EventEmitter = function() {
1213
+ "use strict";
1214
+ var splitter = /\s+/, nextTick = getNextTick();
1215
+ return {
1216
+ onSync: onSync,
1217
+ onAsync: onAsync,
1218
+ off: off,
1219
+ trigger: trigger
1220
+ };
1221
+ function on(method, types, cb, context) {
1222
+ var type;
1223
+ if (!cb) {
1224
+ return this;
1225
+ }
1226
+ types = types.split(splitter);
1227
+ cb = context ? bindContext(cb, context) : cb;
1228
+ this._callbacks = this._callbacks || {};
1229
+ while (type = types.shift()) {
1230
+ this._callbacks[type] = this._callbacks[type] || {
1231
+ sync: [],
1232
+ async: []
1233
+ };
1234
+ this._callbacks[type][method].push(cb);
1235
+ }
1236
+ return this;
1237
+ }
1238
+ function onAsync(types, cb, context) {
1239
+ return on.call(this, "async", types, cb, context);
1240
+ }
1241
+ function onSync(types, cb, context) {
1242
+ return on.call(this, "sync", types, cb, context);
1243
+ }
847
1244
  function off(types) {
848
1245
  var type;
849
1246
  if (!this._callbacks) {
@@ -966,36 +1363,24 @@
966
1363
  38: "up",
967
1364
  40: "down"
968
1365
  };
969
- function Input(o) {
970
- var that = this, onBlur, onFocus, onKeydown, onInput;
1366
+ function Input(o, www) {
971
1367
  o = o || {};
972
1368
  if (!o.input) {
973
1369
  $.error("input is missing");
974
1370
  }
975
- onBlur = _.bind(this._onBlur, this);
976
- onFocus = _.bind(this._onFocus, this);
977
- onKeydown = _.bind(this._onKeydown, this);
978
- onInput = _.bind(this._onInput, this);
1371
+ www.mixin(this);
979
1372
  this.$hint = $(o.hint);
980
- this.$input = $(o.input).on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown);
1373
+ this.$input = $(o.input);
1374
+ this.query = this.$input.val();
1375
+ this.queryWhenFocused = this.hasFocus() ? this.query : null;
1376
+ this.$overflowHelper = buildOverflowHelper(this.$input);
1377
+ this._checkLanguageDirection();
981
1378
  if (this.$hint.length === 0) {
982
1379
  this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop;
983
1380
  }
984
- if (!_.isMsie()) {
985
- this.$input.on("input.tt", onInput);
986
- } else {
987
- this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) {
988
- if (specialKeyCodeMap[$e.which || $e.keyCode]) {
989
- return;
990
- }
991
- _.defer(_.bind(that._onInput, that, $e));
992
- });
993
- }
994
- this.query = this.$input.val();
995
- this.$overflowHelper = buildOverflowHelper(this.$input);
996
1381
  }
997
1382
  Input.normalizeQuery = function(str) {
998
- return (str || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
1383
+ return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
999
1384
  };
1000
1385
  _.mixin(Input.prototype, EventEmitter, {
1001
1386
  _onBlur: function onBlur() {
@@ -1003,6 +1388,7 @@
1003
1388
  this.trigger("blurred");
1004
1389
  },
1005
1390
  _onFocus: function onFocus() {
1391
+ this.queryWhenFocused = this.query;
1006
1392
  this.trigger("focused");
1007
1393
  },
1008
1394
  _onKeydown: function onKeydown($e) {
@@ -1013,17 +1399,13 @@
1013
1399
  }
1014
1400
  },
1015
1401
  _onInput: function onInput() {
1016
- this._checkInputValue();
1402
+ this._setQuery(this.getInputValue());
1403
+ this.clearHintIfInvalid();
1404
+ this._checkLanguageDirection();
1017
1405
  },
1018
1406
  _managePreventDefault: function managePreventDefault(keyName, $e) {
1019
- var preventDefault, hintValue, inputValue;
1407
+ var preventDefault;
1020
1408
  switch (keyName) {
1021
- case "tab":
1022
- hintValue = this.getHint();
1023
- inputValue = this.getInputValue();
1024
- preventDefault = hintValue && hintValue !== inputValue && !withModifier($e);
1025
- break;
1026
-
1027
1409
  case "up":
1028
1410
  case "down":
1029
1411
  preventDefault = !withModifier($e);
@@ -1046,39 +1428,73 @@
1046
1428
  }
1047
1429
  return trigger;
1048
1430
  },
1049
- _checkInputValue: function checkInputValue() {
1050
- var inputValue, areEquivalent, hasDifferentWhitespace;
1051
- inputValue = this.getInputValue();
1052
- areEquivalent = areQueriesEquivalent(inputValue, this.query);
1053
- hasDifferentWhitespace = areEquivalent ? this.query.length !== inputValue.length : false;
1054
- this.query = inputValue;
1055
- if (!areEquivalent) {
1431
+ _checkLanguageDirection: function checkLanguageDirection() {
1432
+ var dir = (this.$input.css("direction") || "ltr").toLowerCase();
1433
+ if (this.dir !== dir) {
1434
+ this.dir = dir;
1435
+ this.$hint.attr("dir", dir);
1436
+ this.trigger("langDirChanged", dir);
1437
+ }
1438
+ },
1439
+ _setQuery: function setQuery(val, silent) {
1440
+ var areEquivalent, hasDifferentWhitespace;
1441
+ areEquivalent = areQueriesEquivalent(val, this.query);
1442
+ hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false;
1443
+ this.query = val;
1444
+ if (!silent && !areEquivalent) {
1056
1445
  this.trigger("queryChanged", this.query);
1057
- } else if (hasDifferentWhitespace) {
1446
+ } else if (!silent && hasDifferentWhitespace) {
1058
1447
  this.trigger("whitespaceChanged", this.query);
1059
1448
  }
1060
1449
  },
1450
+ bind: function() {
1451
+ var that = this, onBlur, onFocus, onKeydown, onInput;
1452
+ onBlur = _.bind(this._onBlur, this);
1453
+ onFocus = _.bind(this._onFocus, this);
1454
+ onKeydown = _.bind(this._onKeydown, this);
1455
+ onInput = _.bind(this._onInput, this);
1456
+ this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown);
1457
+ if (!_.isMsie() || _.isMsie() > 9) {
1458
+ this.$input.on("input.tt", onInput);
1459
+ } else {
1460
+ this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) {
1461
+ if (specialKeyCodeMap[$e.which || $e.keyCode]) {
1462
+ return;
1463
+ }
1464
+ _.defer(_.bind(that._onInput, that, $e));
1465
+ });
1466
+ }
1467
+ return this;
1468
+ },
1061
1469
  focus: function focus() {
1062
1470
  this.$input.focus();
1063
1471
  },
1064
1472
  blur: function blur() {
1065
1473
  this.$input.blur();
1066
1474
  },
1475
+ getLangDir: function getLangDir() {
1476
+ return this.dir;
1477
+ },
1067
1478
  getQuery: function getQuery() {
1068
- return this.query;
1479
+ return this.query || "";
1480
+ },
1481
+ setQuery: function setQuery(val, silent) {
1482
+ this.setInputValue(val);
1483
+ this._setQuery(val, silent);
1069
1484
  },
1070
- setQuery: function setQuery(query) {
1071
- this.query = query;
1485
+ hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() {
1486
+ return this.query !== this.queryWhenFocused;
1072
1487
  },
1073
1488
  getInputValue: function getInputValue() {
1074
1489
  return this.$input.val();
1075
1490
  },
1076
- setInputValue: function setInputValue(value, silent) {
1491
+ setInputValue: function setInputValue(value) {
1077
1492
  this.$input.val(value);
1078
- silent ? this.clearHint() : this._checkInputValue();
1493
+ this.clearHintIfInvalid();
1494
+ this._checkLanguageDirection();
1079
1495
  },
1080
1496
  resetInputValue: function resetInputValue() {
1081
- this.setInputValue(this.query, true);
1497
+ this.setInputValue(this.query);
1082
1498
  },
1083
1499
  getHint: function getHint() {
1084
1500
  return this.$hint.val();
@@ -1097,8 +1513,8 @@
1097
1513
  isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow();
1098
1514
  !isValid && this.clearHint();
1099
1515
  },
1100
- getLanguageDirection: function getLanguageDirection() {
1101
- return (this.$input.css("direction") || "ltr").toLowerCase();
1516
+ hasFocus: function hasFocus() {
1517
+ return this.$input.is(":focus");
1102
1518
  },
1103
1519
  hasOverflow: function hasOverflow() {
1104
1520
  var constraint = this.$input.width() - 2;
@@ -1121,7 +1537,8 @@
1121
1537
  destroy: function destroy() {
1122
1538
  this.$hint.off(".tt");
1123
1539
  this.$input.off(".tt");
1124
- this.$hint = this.$input = this.$overflowHelper = null;
1540
+ this.$overflowHelper.remove();
1541
+ this.$hint = this.$input = this.$overflowHelper = $("<div>");
1125
1542
  }
1126
1543
  });
1127
1544
  return Input;
@@ -1151,118 +1568,190 @@
1151
1568
  }();
1152
1569
  var Dataset = function() {
1153
1570
  "use strict";
1154
- var datasetKey = "ttDataset", valueKey = "ttValue", datumKey = "ttDatum";
1155
- function Dataset(o) {
1571
+ var keys, nameGenerator;
1572
+ keys = {
1573
+ val: "tt-selectable-display",
1574
+ obj: "tt-selectable-object"
1575
+ };
1576
+ nameGenerator = _.getIdGenerator();
1577
+ function Dataset(o, www) {
1156
1578
  o = o || {};
1157
1579
  o.templates = o.templates || {};
1580
+ o.templates.notFound = o.templates.notFound || o.templates.empty;
1158
1581
  if (!o.source) {
1159
1582
  $.error("missing source");
1160
1583
  }
1584
+ if (!o.node) {
1585
+ $.error("missing node");
1586
+ }
1161
1587
  if (o.name && !isValidName(o.name)) {
1162
1588
  $.error("invalid dataset name: " + o.name);
1163
1589
  }
1164
- this.query = null;
1590
+ www.mixin(this);
1165
1591
  this.highlight = !!o.highlight;
1166
- this.name = o.name || _.getUniqueId();
1167
- this.source = o.source;
1592
+ this.name = o.name || nameGenerator();
1593
+ this.limit = o.limit || 5;
1168
1594
  this.displayFn = getDisplayFn(o.display || o.displayKey);
1169
1595
  this.templates = getTemplates(o.templates, this.displayFn);
1170
- this.$el = $(html.dataset.replace("%CLASS%", this.name));
1596
+ this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source;
1597
+ this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async;
1598
+ this._resetLastSuggestion();
1599
+ this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name);
1171
1600
  }
1172
- Dataset.extractDatasetName = function extractDatasetName(el) {
1173
- return $(el).data(datasetKey);
1174
- };
1175
- Dataset.extractValue = function extractDatum(el) {
1176
- return $(el).data(valueKey);
1177
- };
1178
- Dataset.extractDatum = function extractDatum(el) {
1179
- return $(el).data(datumKey);
1601
+ Dataset.extractData = function extractData(el) {
1602
+ var $el = $(el);
1603
+ if ($el.data(keys.obj)) {
1604
+ return {
1605
+ val: $el.data(keys.val) || "",
1606
+ obj: $el.data(keys.obj) || null
1607
+ };
1608
+ }
1609
+ return null;
1180
1610
  };
1181
1611
  _.mixin(Dataset.prototype, EventEmitter, {
1182
- _render: function render(query, suggestions) {
1183
- if (!this.$el) {
1184
- return;
1185
- }
1186
- var that = this, hasSuggestions;
1612
+ _overwrite: function overwrite(query, suggestions) {
1613
+ suggestions = suggestions || [];
1614
+ if (suggestions.length) {
1615
+ this._renderSuggestions(query, suggestions);
1616
+ } else if (this.async && this.templates.pending) {
1617
+ this._renderPending(query);
1618
+ } else if (!this.async && this.templates.notFound) {
1619
+ this._renderNotFound(query);
1620
+ } else {
1621
+ this._empty();
1622
+ }
1623
+ this.trigger("rendered", this.name, suggestions, false);
1624
+ },
1625
+ _append: function append(query, suggestions) {
1626
+ suggestions = suggestions || [];
1627
+ if (suggestions.length && this.$lastSuggestion.length) {
1628
+ this._appendSuggestions(query, suggestions);
1629
+ } else if (suggestions.length) {
1630
+ this._renderSuggestions(query, suggestions);
1631
+ } else if (!this.$lastSuggestion.length && this.templates.notFound) {
1632
+ this._renderNotFound(query);
1633
+ }
1634
+ this.trigger("rendered", this.name, suggestions, true);
1635
+ },
1636
+ _renderSuggestions: function renderSuggestions(query, suggestions) {
1637
+ var $fragment;
1638
+ $fragment = this._getSuggestionsFragment(query, suggestions);
1639
+ this.$lastSuggestion = $fragment.children().last();
1640
+ this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions));
1641
+ },
1642
+ _appendSuggestions: function appendSuggestions(query, suggestions) {
1643
+ var $fragment, $lastSuggestion;
1644
+ $fragment = this._getSuggestionsFragment(query, suggestions);
1645
+ $lastSuggestion = $fragment.children().last();
1646
+ this.$lastSuggestion.after($fragment);
1647
+ this.$lastSuggestion = $lastSuggestion;
1648
+ },
1649
+ _renderPending: function renderPending(query) {
1650
+ var template = this.templates.pending;
1651
+ this._resetLastSuggestion();
1652
+ template && this.$el.html(template({
1653
+ query: query,
1654
+ dataset: this.name
1655
+ }));
1656
+ },
1657
+ _renderNotFound: function renderNotFound(query) {
1658
+ var template = this.templates.notFound;
1659
+ this._resetLastSuggestion();
1660
+ template && this.$el.html(template({
1661
+ query: query,
1662
+ dataset: this.name
1663
+ }));
1664
+ },
1665
+ _empty: function empty() {
1187
1666
  this.$el.empty();
1188
- hasSuggestions = suggestions && suggestions.length;
1189
- if (!hasSuggestions && this.templates.empty) {
1190
- this.$el.html(getEmptyHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null);
1191
- } else if (hasSuggestions) {
1192
- this.$el.html(getSuggestionsHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null);
1193
- }
1194
- this.trigger("rendered");
1195
- function getEmptyHtml() {
1196
- return that.templates.empty({
1197
- query: query,
1198
- isEmpty: true
1199
- });
1200
- }
1201
- function getSuggestionsHtml() {
1202
- var $suggestions, nodes;
1203
- $suggestions = $(html.suggestions).css(css.suggestions);
1204
- nodes = _.map(suggestions, getSuggestionNode);
1205
- $suggestions.append.apply($suggestions, nodes);
1206
- that.highlight && highlight({
1207
- className: "tt-highlight",
1208
- node: $suggestions[0],
1209
- pattern: query
1210
- });
1211
- return $suggestions;
1212
- function getSuggestionNode(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);
1215
- $el.children().each(function() {
1216
- $(this).css(css.suggestionChild);
1217
- });
1218
- return $el;
1219
- }
1220
- }
1221
- function getHeaderHtml() {
1222
- return that.templates.header({
1223
- query: query,
1224
- isEmpty: !hasSuggestions
1225
- });
1226
- }
1227
- function getFooterHtml() {
1228
- return that.templates.footer({
1229
- query: query,
1230
- isEmpty: !hasSuggestions
1231
- });
1232
- }
1233
- },
1234
- getRoot: function getRoot() {
1235
- return this.$el;
1667
+ this._resetLastSuggestion();
1668
+ },
1669
+ _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) {
1670
+ var that = this, fragment;
1671
+ fragment = document.createDocumentFragment();
1672
+ _.each(suggestions, function getSuggestionNode(suggestion) {
1673
+ var $el, context;
1674
+ context = that._injectQuery(query, suggestion);
1675
+ $el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable);
1676
+ fragment.appendChild($el[0]);
1677
+ });
1678
+ this.highlight && highlight({
1679
+ className: this.classes.highlight,
1680
+ node: fragment,
1681
+ pattern: query
1682
+ });
1683
+ return $(fragment);
1684
+ },
1685
+ _getFooter: function getFooter(query, suggestions) {
1686
+ return this.templates.footer ? this.templates.footer({
1687
+ query: query,
1688
+ suggestions: suggestions,
1689
+ dataset: this.name
1690
+ }) : null;
1691
+ },
1692
+ _getHeader: function getHeader(query, suggestions) {
1693
+ return this.templates.header ? this.templates.header({
1694
+ query: query,
1695
+ suggestions: suggestions,
1696
+ dataset: this.name
1697
+ }) : null;
1698
+ },
1699
+ _resetLastSuggestion: function resetLastSuggestion() {
1700
+ this.$lastSuggestion = $();
1701
+ },
1702
+ _injectQuery: function injectQuery(query, obj) {
1703
+ return _.isObject(obj) ? _.mixin({
1704
+ _query: query
1705
+ }, obj) : obj;
1236
1706
  },
1237
1707
  update: function update(query) {
1238
- var that = this;
1239
- this.query = query;
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);
1708
+ var that = this, canceled = false, syncCalled = false, rendered = 0;
1709
+ this.cancel();
1710
+ this.cancel = function cancel() {
1711
+ canceled = true;
1712
+ that.cancel = $.noop;
1713
+ that.async && that.trigger("asyncCanceled", query);
1714
+ };
1715
+ this.source(query, sync, async);
1716
+ !syncCalled && sync([]);
1717
+ function sync(suggestions) {
1718
+ if (syncCalled) {
1719
+ return;
1720
+ }
1721
+ syncCalled = true;
1722
+ suggestions = (suggestions || []).slice(0, that.limit);
1723
+ rendered = suggestions.length;
1724
+ that._overwrite(query, suggestions);
1725
+ if (rendered < that.limit && that.async) {
1726
+ that.trigger("asyncRequested", query);
1727
+ }
1728
+ }
1729
+ function async(suggestions) {
1730
+ suggestions = suggestions || [];
1731
+ if (!canceled && rendered < that.limit) {
1732
+ that.cancel = $.noop;
1733
+ that._append(query, suggestions.slice(0, that.limit - rendered));
1734
+ rendered += suggestions.length;
1735
+ that.async && that.trigger("asyncReceived", query);
1245
1736
  }
1246
1737
  }
1247
1738
  },
1248
- cancel: function cancel() {
1249
- this.canceled = true;
1250
- },
1739
+ cancel: $.noop,
1251
1740
  clear: function clear() {
1741
+ this._empty();
1252
1742
  this.cancel();
1253
- this.$el.empty();
1254
- this.trigger("rendered");
1743
+ this.trigger("cleared");
1255
1744
  },
1256
1745
  isEmpty: function isEmpty() {
1257
1746
  return this.$el.is(":empty");
1258
1747
  },
1259
1748
  destroy: function destroy() {
1260
- this.$el = null;
1749
+ this.$el = $("<div>");
1261
1750
  }
1262
1751
  });
1263
1752
  return Dataset;
1264
1753
  function getDisplayFn(display) {
1265
- display = display || "value";
1754
+ display = display || _.stringify;
1266
1755
  return _.isFunction(display) ? display : displayFn;
1267
1756
  function displayFn(obj) {
1268
1757
  return obj[display];
@@ -1270,506 +1759,637 @@
1270
1759
  }
1271
1760
  function getTemplates(templates, displayFn) {
1272
1761
  return {
1273
- empty: templates.empty && _.templatify(templates.empty),
1762
+ notFound: templates.notFound && _.templatify(templates.notFound),
1763
+ pending: templates.pending && _.templatify(templates.pending),
1274
1764
  header: templates.header && _.templatify(templates.header),
1275
1765
  footer: templates.footer && _.templatify(templates.footer),
1276
1766
  suggestion: templates.suggestion || suggestionTemplate
1277
1767
  };
1278
1768
  function suggestionTemplate(context) {
1279
- return "<p>" + displayFn(context) + "</p>";
1769
+ return $("<div>").text(displayFn(context));
1280
1770
  }
1281
1771
  }
1282
1772
  function isValidName(str) {
1283
1773
  return /^[_a-zA-Z0-9-]+$/.test(str);
1284
1774
  }
1285
1775
  }();
1286
- var Dropdown = function() {
1776
+ var Menu = function() {
1287
1777
  "use strict";
1288
- function Dropdown(o) {
1289
- var that = this, onSuggestionClick, onSuggestionMouseEnter, onSuggestionMouseLeave;
1778
+ function Menu(o, www) {
1779
+ var that = this;
1290
1780
  o = o || {};
1291
- if (!o.menu) {
1292
- $.error("menu is required");
1781
+ if (!o.node) {
1782
+ $.error("node is required");
1293
1783
  }
1294
- this.isOpen = false;
1295
- this.isEmpty = true;
1784
+ www.mixin(this);
1785
+ this.$node = $(o.node);
1786
+ this.query = null;
1296
1787
  this.datasets = _.map(o.datasets, initializeDataset);
1297
- onSuggestionClick = _.bind(this._onSuggestionClick, this);
1298
- onSuggestionMouseEnter = _.bind(this._onSuggestionMouseEnter, this);
1299
- onSuggestionMouseLeave = _.bind(this._onSuggestionMouseLeave, this);
1300
- this.$menu = $(o.menu).on("click.tt", ".tt-suggestion", onSuggestionClick).on("mouseenter.tt", ".tt-suggestion", onSuggestionMouseEnter).on("mouseleave.tt", ".tt-suggestion", onSuggestionMouseLeave);
1301
- _.each(this.datasets, function(dataset) {
1302
- that.$menu.append(dataset.getRoot());
1303
- dataset.onSync("rendered", that._onRendered, that);
1304
- });
1788
+ function initializeDataset(oDataset) {
1789
+ var node = that.$node.find(oDataset.node).first();
1790
+ oDataset.node = node.length ? node : $("<div>").appendTo(that.$node);
1791
+ return new Dataset(oDataset, www);
1792
+ }
1305
1793
  }
1306
- _.mixin(Dropdown.prototype, EventEmitter, {
1307
- _onSuggestionClick: function onSuggestionClick($e) {
1308
- this.trigger("suggestionClicked", $($e.currentTarget));
1794
+ _.mixin(Menu.prototype, EventEmitter, {
1795
+ _onSelectableClick: function onSelectableClick($e) {
1796
+ this.trigger("selectableClicked", $($e.currentTarget));
1309
1797
  },
1310
- _onSuggestionMouseEnter: function onSuggestionMouseEnter($e) {
1311
- this._removeCursor();
1312
- this._setCursor($($e.currentTarget), true);
1798
+ _onRendered: function onRendered(type, dataset, suggestions, async) {
1799
+ this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
1800
+ this.trigger("datasetRendered", dataset, suggestions, async);
1313
1801
  },
1314
- _onSuggestionMouseLeave: function onSuggestionMouseLeave() {
1315
- this._removeCursor();
1802
+ _onCleared: function onCleared() {
1803
+ this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
1804
+ this.trigger("datasetCleared");
1316
1805
  },
1317
- _onRendered: function onRendered() {
1318
- this.isEmpty = _.every(this.datasets, isDatasetEmpty);
1319
- this.isEmpty ? this._hide() : this.isOpen && this._show();
1320
- this.trigger("datasetRendered");
1806
+ _propagate: function propagate() {
1807
+ this.trigger.apply(this, arguments);
1808
+ },
1809
+ _allDatasetsEmpty: function allDatasetsEmpty() {
1810
+ return _.every(this.datasets, isDatasetEmpty);
1321
1811
  function isDatasetEmpty(dataset) {
1322
1812
  return dataset.isEmpty();
1323
1813
  }
1324
1814
  },
1325
- _hide: function() {
1326
- this.$menu.hide();
1327
- },
1328
- _show: function() {
1329
- this.$menu.css("display", "block");
1330
- },
1331
- _getSuggestions: function getSuggestions() {
1332
- return this.$menu.find(".tt-suggestion");
1333
- },
1334
- _getCursor: function getCursor() {
1335
- return this.$menu.find(".tt-cursor").first();
1815
+ _getSelectables: function getSelectables() {
1816
+ return this.$node.find(this.selectors.selectable);
1336
1817
  },
1337
- _setCursor: function setCursor($el, silent) {
1338
- $el.first().addClass("tt-cursor");
1339
- !silent && this.trigger("cursorMoved");
1340
- },
1341
- _removeCursor: function removeCursor() {
1342
- this._getCursor().removeClass("tt-cursor");
1343
- },
1344
- _moveCursor: function moveCursor(increment) {
1345
- var $suggestions, $oldCursor, newCursorIndex, $newCursor;
1346
- if (!this.isOpen) {
1347
- return;
1348
- }
1349
- $oldCursor = this._getCursor();
1350
- $suggestions = this._getSuggestions();
1351
- this._removeCursor();
1352
- newCursorIndex = $suggestions.index($oldCursor) + increment;
1353
- newCursorIndex = (newCursorIndex + 1) % ($suggestions.length + 1) - 1;
1354
- if (newCursorIndex === -1) {
1355
- this.trigger("cursorRemoved");
1356
- return;
1357
- } else if (newCursorIndex < -1) {
1358
- newCursorIndex = $suggestions.length - 1;
1359
- }
1360
- this._setCursor($newCursor = $suggestions.eq(newCursorIndex));
1361
- this._ensureVisible($newCursor);
1818
+ _removeCursor: function _removeCursor() {
1819
+ var $selectable = this.getActiveSelectable();
1820
+ $selectable && $selectable.removeClass(this.classes.cursor);
1362
1821
  },
1363
1822
  _ensureVisible: function ensureVisible($el) {
1364
- var elTop, elBottom, menuScrollTop, menuHeight;
1823
+ var elTop, elBottom, nodeScrollTop, nodeHeight;
1365
1824
  elTop = $el.position().top;
1366
1825
  elBottom = elTop + $el.outerHeight(true);
1367
- menuScrollTop = this.$menu.scrollTop();
1368
- menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10);
1826
+ nodeScrollTop = this.$node.scrollTop();
1827
+ nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10);
1369
1828
  if (elTop < 0) {
1370
- this.$menu.scrollTop(menuScrollTop + elTop);
1371
- } else if (menuHeight < elBottom) {
1372
- this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight));
1829
+ this.$node.scrollTop(nodeScrollTop + elTop);
1830
+ } else if (nodeHeight < elBottom) {
1831
+ this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight));
1373
1832
  }
1374
1833
  },
1375
- close: function close() {
1376
- if (this.isOpen) {
1377
- this.isOpen = false;
1378
- this._removeCursor();
1379
- this._hide();
1380
- this.trigger("closed");
1381
- }
1382
- },
1383
- open: function open() {
1384
- if (!this.isOpen) {
1385
- this.isOpen = true;
1386
- !this.isEmpty && this._show();
1387
- this.trigger("opened");
1388
- }
1834
+ bind: function() {
1835
+ var that = this, onSelectableClick;
1836
+ onSelectableClick = _.bind(this._onSelectableClick, this);
1837
+ this.$node.on("click.tt", this.selectors.selectable, onSelectableClick);
1838
+ _.each(this.datasets, function(dataset) {
1839
+ dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that);
1840
+ });
1841
+ return this;
1389
1842
  },
1390
- setLanguageDirection: function setLanguageDirection(dir) {
1391
- this.$menu.css(dir === "ltr" ? css.ltr : css.rtl);
1843
+ isOpen: function isOpen() {
1844
+ return this.$node.hasClass(this.classes.open);
1392
1845
  },
1393
- moveCursorUp: function moveCursorUp() {
1394
- this._moveCursor(-1);
1846
+ open: function open() {
1847
+ this.$node.scrollTop(0);
1848
+ this.$node.addClass(this.classes.open);
1395
1849
  },
1396
- moveCursorDown: function moveCursorDown() {
1397
- this._moveCursor(+1);
1850
+ close: function close() {
1851
+ this.$node.removeClass(this.classes.open);
1852
+ this._removeCursor();
1398
1853
  },
1399
- getDatumForSuggestion: function getDatumForSuggestion($el) {
1400
- var datum = null;
1401
- if ($el.length) {
1402
- datum = {
1403
- raw: Dataset.extractDatum($el),
1404
- value: Dataset.extractValue($el),
1405
- datasetName: Dataset.extractDatasetName($el)
1406
- };
1854
+ setLanguageDirection: function setLanguageDirection(dir) {
1855
+ this.$node.attr("dir", dir);
1856
+ },
1857
+ selectableRelativeToCursor: function selectableRelativeToCursor(delta) {
1858
+ var $selectables, $oldCursor, oldIndex, newIndex;
1859
+ $oldCursor = this.getActiveSelectable();
1860
+ $selectables = this._getSelectables();
1861
+ oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1;
1862
+ newIndex = oldIndex + delta;
1863
+ newIndex = (newIndex + 1) % ($selectables.length + 1) - 1;
1864
+ newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex;
1865
+ return newIndex === -1 ? null : $selectables.eq(newIndex);
1866
+ },
1867
+ setCursor: function setCursor($selectable) {
1868
+ this._removeCursor();
1869
+ if ($selectable = $selectable && $selectable.first()) {
1870
+ $selectable.addClass(this.classes.cursor);
1871
+ this._ensureVisible($selectable);
1407
1872
  }
1408
- return datum;
1409
1873
  },
1410
- getDatumForCursor: function getDatumForCursor() {
1411
- return this.getDatumForSuggestion(this._getCursor().first());
1874
+ getSelectableData: function getSelectableData($el) {
1875
+ return $el && $el.length ? Dataset.extractData($el) : null;
1876
+ },
1877
+ getActiveSelectable: function getActiveSelectable() {
1878
+ var $selectable = this._getSelectables().filter(this.selectors.cursor).first();
1879
+ return $selectable.length ? $selectable : null;
1412
1880
  },
1413
- getDatumForTopSuggestion: function getDatumForTopSuggestion() {
1414
- return this.getDatumForSuggestion(this._getSuggestions().first());
1881
+ getTopSelectable: function getTopSelectable() {
1882
+ var $selectable = this._getSelectables().first();
1883
+ return $selectable.length ? $selectable : null;
1415
1884
  },
1416
1885
  update: function update(query) {
1417
- _.each(this.datasets, updateDataset);
1886
+ var isValidUpdate = query !== this.query;
1887
+ if (isValidUpdate) {
1888
+ this.query = query;
1889
+ _.each(this.datasets, updateDataset);
1890
+ }
1891
+ return isValidUpdate;
1418
1892
  function updateDataset(dataset) {
1419
1893
  dataset.update(query);
1420
1894
  }
1421
1895
  },
1422
1896
  empty: function empty() {
1423
1897
  _.each(this.datasets, clearDataset);
1424
- this.isEmpty = true;
1898
+ this.query = null;
1899
+ this.$node.addClass(this.classes.empty);
1425
1900
  function clearDataset(dataset) {
1426
1901
  dataset.clear();
1427
1902
  }
1428
1903
  },
1429
- isVisible: function isVisible() {
1430
- return this.isOpen && !this.isEmpty;
1431
- },
1432
1904
  destroy: function destroy() {
1433
- this.$menu.off(".tt");
1434
- this.$menu = null;
1905
+ this.$node.off(".tt");
1906
+ this.$node = $("<div>");
1435
1907
  _.each(this.datasets, destroyDataset);
1436
1908
  function destroyDataset(dataset) {
1437
1909
  dataset.destroy();
1438
1910
  }
1439
1911
  }
1440
1912
  });
1441
- return Dropdown;
1442
- function initializeDataset(oDataset) {
1443
- return new Dataset(oDataset);
1913
+ return Menu;
1914
+ }();
1915
+ var DefaultMenu = function() {
1916
+ "use strict";
1917
+ var s = Menu.prototype;
1918
+ function DefaultMenu() {
1919
+ Menu.apply(this, [].slice.call(arguments, 0));
1444
1920
  }
1921
+ _.mixin(DefaultMenu.prototype, Menu.prototype, {
1922
+ open: function open() {
1923
+ !this._allDatasetsEmpty() && this._show();
1924
+ return s.open.apply(this, [].slice.call(arguments, 0));
1925
+ },
1926
+ close: function close() {
1927
+ this._hide();
1928
+ return s.close.apply(this, [].slice.call(arguments, 0));
1929
+ },
1930
+ _onRendered: function onRendered() {
1931
+ if (this._allDatasetsEmpty()) {
1932
+ this._hide();
1933
+ } else {
1934
+ this.isOpen() && this._show();
1935
+ }
1936
+ return s._onRendered.apply(this, [].slice.call(arguments, 0));
1937
+ },
1938
+ _onCleared: function onCleared() {
1939
+ if (this._allDatasetsEmpty()) {
1940
+ this._hide();
1941
+ } else {
1942
+ this.isOpen() && this._show();
1943
+ }
1944
+ return s._onCleared.apply(this, [].slice.call(arguments, 0));
1945
+ },
1946
+ setLanguageDirection: function setLanguageDirection(dir) {
1947
+ this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl);
1948
+ return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0));
1949
+ },
1950
+ _hide: function hide() {
1951
+ this.$node.hide();
1952
+ },
1953
+ _show: function show() {
1954
+ this.$node.css("display", "block");
1955
+ }
1956
+ });
1957
+ return DefaultMenu;
1445
1958
  }();
1446
1959
  var Typeahead = function() {
1447
1960
  "use strict";
1448
- var attrsKey = "ttAttrs";
1449
- function Typeahead(o) {
1450
- var $menu, $input, $hint;
1961
+ function Typeahead(o, www) {
1962
+ var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged;
1451
1963
  o = o || {};
1452
1964
  if (!o.input) {
1453
1965
  $.error("missing input");
1454
1966
  }
1455
- this.isActivated = false;
1456
- this.autoselect = !!o.autoselect;
1967
+ if (!o.menu) {
1968
+ $.error("missing menu");
1969
+ }
1970
+ if (!o.eventBus) {
1971
+ $.error("missing event bus");
1972
+ }
1973
+ www.mixin(this);
1974
+ this.eventBus = o.eventBus;
1457
1975
  this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;
1458
- this.$node = buildDom(o.input, o.withHint);
1459
- $menu = this.$node.find(".tt-dropdown-menu");
1460
- $input = this.$node.find(".tt-input");
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
- });
1478
- this.eventBus = o.eventBus || new EventBus({
1479
- el: $input
1480
- });
1481
- this.dropdown = new Dropdown({
1482
- menu: $menu,
1483
- datasets: o.datasets
1484
- }).onSync("suggestionClicked", this._onSuggestionClicked, this).onSync("cursorMoved", this._onCursorMoved, this).onSync("cursorRemoved", this._onCursorRemoved, this).onSync("opened", this._onOpened, this).onSync("closed", this._onClosed, this).onAsync("datasetRendered", this._onDatasetRendered, this);
1485
- this.input = new Input({
1486
- input: $input,
1487
- hint: $hint
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);
1489
- this._setLanguageDirection();
1976
+ this.input = o.input;
1977
+ this.menu = o.menu;
1978
+ this.enabled = true;
1979
+ this.active = false;
1980
+ this.input.hasFocus() && this.activate();
1981
+ this.dir = this.input.getLangDir();
1982
+ this._hacks();
1983
+ this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this);
1984
+ onFocused = c(this, "activate", "open", "_onFocused");
1985
+ onBlurred = c(this, "deactivate", "_onBlurred");
1986
+ onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed");
1987
+ onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed");
1988
+ onEscKeyed = c(this, "isActive", "_onEscKeyed");
1989
+ onUpKeyed = c(this, "isActive", "open", "_onUpKeyed");
1990
+ onDownKeyed = c(this, "isActive", "open", "_onDownKeyed");
1991
+ onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed");
1992
+ onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed");
1993
+ onQueryChanged = c(this, "_openIfActive", "_onQueryChanged");
1994
+ onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged");
1995
+ this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this);
1490
1996
  }
1491
1997
  _.mixin(Typeahead.prototype, {
1492
- _onSuggestionClicked: function onSuggestionClicked(type, $el) {
1493
- var datum;
1494
- if (datum = this.dropdown.getDatumForSuggestion($el)) {
1495
- this._select(datum);
1496
- }
1998
+ _hacks: function hacks() {
1999
+ var $input, $menu;
2000
+ $input = this.input.$input || $("<div>");
2001
+ $menu = this.menu.$node || $("<div>");
2002
+ $input.on("blur.tt", function($e) {
2003
+ var active, isActive, hasActive;
2004
+ active = document.activeElement;
2005
+ isActive = $menu.is(active);
2006
+ hasActive = $menu.has(active).length > 0;
2007
+ if (_.isMsie() && (isActive || hasActive)) {
2008
+ $e.preventDefault();
2009
+ $e.stopImmediatePropagation();
2010
+ _.defer(function() {
2011
+ $input.focus();
2012
+ });
2013
+ }
2014
+ });
2015
+ $menu.on("mousedown.tt", function($e) {
2016
+ $e.preventDefault();
2017
+ });
1497
2018
  },
1498
- _onCursorMoved: function onCursorMoved() {
1499
- var datum = this.dropdown.getDatumForCursor();
1500
- this.input.setInputValue(datum.value, true);
1501
- this.eventBus.trigger("cursorchanged", datum.raw, datum.datasetName);
2019
+ _onSelectableClicked: function onSelectableClicked(type, $el) {
2020
+ this.select($el);
1502
2021
  },
1503
- _onCursorRemoved: function onCursorRemoved() {
1504
- this.input.resetInputValue();
2022
+ _onDatasetCleared: function onDatasetCleared() {
1505
2023
  this._updateHint();
1506
2024
  },
1507
- _onDatasetRendered: function onDatasetRendered() {
2025
+ _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) {
1508
2026
  this._updateHint();
2027
+ this.eventBus.trigger("render", suggestions, async, dataset);
1509
2028
  },
1510
- _onOpened: function onOpened() {
1511
- this._updateHint();
1512
- this.eventBus.trigger("opened");
2029
+ _onAsyncRequested: function onAsyncRequested(type, dataset, query) {
2030
+ this.eventBus.trigger("asyncrequest", query, dataset);
2031
+ },
2032
+ _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) {
2033
+ this.eventBus.trigger("asynccancel", query, dataset);
1513
2034
  },
1514
- _onClosed: function onClosed() {
1515
- this.input.clearHint();
1516
- this.eventBus.trigger("closed");
2035
+ _onAsyncReceived: function onAsyncReceived(type, dataset, query) {
2036
+ this.eventBus.trigger("asyncreceive", query, dataset);
1517
2037
  },
1518
2038
  _onFocused: function onFocused() {
1519
- this.isActivated = true;
1520
- this.dropdown.open();
2039
+ this._minLengthMet() && this.menu.update(this.input.getQuery());
1521
2040
  },
1522
2041
  _onBlurred: function onBlurred() {
1523
- this.isActivated = false;
1524
- this.dropdown.empty();
1525
- this.dropdown.close();
2042
+ if (this.input.hasQueryChangedSinceLastFocus()) {
2043
+ this.eventBus.trigger("change", this.input.getQuery());
2044
+ }
1526
2045
  },
1527
2046
  _onEnterKeyed: function onEnterKeyed(type, $e) {
1528
- var cursorDatum, topSuggestionDatum;
1529
- cursorDatum = this.dropdown.getDatumForCursor();
1530
- topSuggestionDatum = this.dropdown.getDatumForTopSuggestion();
1531
- if (cursorDatum) {
1532
- this._select(cursorDatum);
1533
- $e.preventDefault();
1534
- } else if (this.autoselect && topSuggestionDatum) {
1535
- this._select(topSuggestionDatum);
1536
- $e.preventDefault();
2047
+ var $selectable;
2048
+ if ($selectable = this.menu.getActiveSelectable()) {
2049
+ this.select($selectable) && $e.preventDefault();
1537
2050
  }
1538
2051
  },
1539
2052
  _onTabKeyed: function onTabKeyed(type, $e) {
1540
- var datum;
1541
- if (datum = this.dropdown.getDatumForCursor()) {
1542
- this._select(datum);
1543
- $e.preventDefault();
1544
- } else {
1545
- this._autocomplete(true);
2053
+ var $selectable;
2054
+ if ($selectable = this.menu.getActiveSelectable()) {
2055
+ this.select($selectable) && $e.preventDefault();
2056
+ } else if ($selectable = this.menu.getTopSelectable()) {
2057
+ this.autocomplete($selectable) && $e.preventDefault();
1546
2058
  }
1547
2059
  },
1548
2060
  _onEscKeyed: function onEscKeyed() {
1549
- this.dropdown.close();
1550
- this.input.resetInputValue();
2061
+ this.close();
1551
2062
  },
1552
2063
  _onUpKeyed: function onUpKeyed() {
1553
- var query = this.input.getQuery();
1554
- this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorUp();
1555
- this.dropdown.open();
2064
+ this.moveCursor(-1);
1556
2065
  },
1557
2066
  _onDownKeyed: function onDownKeyed() {
1558
- var query = this.input.getQuery();
1559
- this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorDown();
1560
- this.dropdown.open();
2067
+ this.moveCursor(+1);
1561
2068
  },
1562
2069
  _onLeftKeyed: function onLeftKeyed() {
1563
- this.dir === "rtl" && this._autocomplete();
2070
+ if (this.dir === "rtl" && this.input.isCursorAtEnd()) {
2071
+ this.autocomplete(this.menu.getTopSelectable());
2072
+ }
1564
2073
  },
1565
2074
  _onRightKeyed: function onRightKeyed() {
1566
- this.dir === "ltr" && this._autocomplete();
2075
+ if (this.dir === "ltr" && this.input.isCursorAtEnd()) {
2076
+ this.autocomplete(this.menu.getTopSelectable());
2077
+ }
1567
2078
  },
1568
2079
  _onQueryChanged: function onQueryChanged(e, query) {
1569
- this.input.clearHintIfInvalid();
1570
- query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.empty();
1571
- this.dropdown.open();
1572
- this._setLanguageDirection();
2080
+ this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty();
1573
2081
  },
1574
2082
  _onWhitespaceChanged: function onWhitespaceChanged() {
1575
2083
  this._updateHint();
1576
- this.dropdown.open();
1577
2084
  },
1578
- _setLanguageDirection: function setLanguageDirection() {
1579
- var dir;
1580
- if (this.dir !== (dir = this.input.getLanguageDirection())) {
2085
+ _onLangDirChanged: function onLangDirChanged(e, dir) {
2086
+ if (this.dir !== dir) {
1581
2087
  this.dir = dir;
1582
- this.$node.css("direction", dir);
1583
- this.dropdown.setLanguageDirection(dir);
2088
+ this.menu.setLanguageDirection(dir);
1584
2089
  }
1585
2090
  },
2091
+ _openIfActive: function openIfActive() {
2092
+ this.isActive() && this.open();
2093
+ },
2094
+ _minLengthMet: function minLengthMet(query) {
2095
+ query = _.isString(query) ? query : this.input.getQuery() || "";
2096
+ return query.length >= this.minLength;
2097
+ },
1586
2098
  _updateHint: function updateHint() {
1587
- var datum, val, query, escapedQuery, frontMatchRegEx, match;
1588
- datum = this.dropdown.getDatumForTopSuggestion();
1589
- if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) {
1590
- val = this.input.getInputValue();
2099
+ var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match;
2100
+ $selectable = this.menu.getTopSelectable();
2101
+ data = this.menu.getSelectableData($selectable);
2102
+ val = this.input.getInputValue();
2103
+ if (data && !_.isBlankString(val) && !this.input.hasOverflow()) {
1591
2104
  query = Input.normalizeQuery(val);
1592
2105
  escapedQuery = _.escapeRegExChars(query);
1593
2106
  frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i");
1594
- match = frontMatchRegEx.exec(datum.value);
1595
- match ? this.input.setHint(val + match[1]) : this.input.clearHint();
2107
+ match = frontMatchRegEx.exec(data.val);
2108
+ match && this.input.setHint(val + match[1]);
1596
2109
  } else {
1597
2110
  this.input.clearHint();
1598
2111
  }
1599
2112
  },
1600
- _autocomplete: function autocomplete(laxCursor) {
1601
- var hint, query, isCursorAtEnd, datum;
1602
- hint = this.input.getHint();
1603
- query = this.input.getQuery();
1604
- isCursorAtEnd = laxCursor || this.input.isCursorAtEnd();
1605
- if (hint && query !== hint && isCursorAtEnd) {
1606
- datum = this.dropdown.getDatumForTopSuggestion();
1607
- datum && this.input.setInputValue(datum.value);
1608
- this.eventBus.trigger("autocompleted", datum.raw, datum.datasetName);
2113
+ isEnabled: function isEnabled() {
2114
+ return this.enabled;
2115
+ },
2116
+ enable: function enable() {
2117
+ this.enabled = true;
2118
+ },
2119
+ disable: function disable() {
2120
+ this.enabled = false;
2121
+ },
2122
+ isActive: function isActive() {
2123
+ return this.active;
2124
+ },
2125
+ activate: function activate() {
2126
+ if (this.isActive()) {
2127
+ return true;
2128
+ } else if (!this.isEnabled() || this.eventBus.before("active")) {
2129
+ return false;
2130
+ } else {
2131
+ this.active = true;
2132
+ this.eventBus.trigger("active");
2133
+ return true;
1609
2134
  }
1610
2135
  },
1611
- _select: function select(datum) {
1612
- this.input.setQuery(datum.value);
1613
- this.input.setInputValue(datum.value, true);
1614
- this._setLanguageDirection();
1615
- this.eventBus.trigger("selected", datum.raw, datum.datasetName);
1616
- this.dropdown.close();
1617
- _.defer(_.bind(this.dropdown.empty, this.dropdown));
2136
+ deactivate: function deactivate() {
2137
+ if (!this.isActive()) {
2138
+ return true;
2139
+ } else if (this.eventBus.before("idle")) {
2140
+ return false;
2141
+ } else {
2142
+ this.active = false;
2143
+ this.close();
2144
+ this.eventBus.trigger("idle");
2145
+ return true;
2146
+ }
2147
+ },
2148
+ isOpen: function isOpen() {
2149
+ return this.menu.isOpen();
1618
2150
  },
1619
2151
  open: function open() {
1620
- this.dropdown.open();
2152
+ if (!this.isOpen() && !this.eventBus.before("open")) {
2153
+ this.menu.open();
2154
+ this._updateHint();
2155
+ this.eventBus.trigger("open");
2156
+ }
2157
+ return this.isOpen();
1621
2158
  },
1622
2159
  close: function close() {
1623
- this.dropdown.close();
2160
+ if (this.isOpen() && !this.eventBus.before("close")) {
2161
+ this.menu.close();
2162
+ this.input.clearHint();
2163
+ this.input.resetInputValue();
2164
+ this.eventBus.trigger("close");
2165
+ }
2166
+ return !this.isOpen();
1624
2167
  },
1625
2168
  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();
2169
+ this.input.setQuery(_.toStr(val));
1634
2170
  },
1635
2171
  getVal: function getVal() {
1636
2172
  return this.input.getQuery();
1637
2173
  },
2174
+ select: function select($selectable) {
2175
+ var data = this.menu.getSelectableData($selectable);
2176
+ if (data && !this.eventBus.before("select", data.obj)) {
2177
+ this.input.setQuery(data.val, true);
2178
+ this.eventBus.trigger("select", data.obj);
2179
+ this.close();
2180
+ return true;
2181
+ }
2182
+ return false;
2183
+ },
2184
+ autocomplete: function autocomplete($selectable) {
2185
+ var query, data, isValid;
2186
+ query = this.input.getQuery();
2187
+ data = this.menu.getSelectableData($selectable);
2188
+ isValid = data && query !== data.val;
2189
+ if (isValid && !this.eventBus.before("autocomplete", data.obj)) {
2190
+ this.input.setQuery(data.val);
2191
+ this.eventBus.trigger("autocomplete", data.obj);
2192
+ return true;
2193
+ }
2194
+ return false;
2195
+ },
2196
+ moveCursor: function moveCursor(delta) {
2197
+ var query, $candidate, data, payload, cancelMove;
2198
+ query = this.input.getQuery();
2199
+ $candidate = this.menu.selectableRelativeToCursor(delta);
2200
+ data = this.menu.getSelectableData($candidate);
2201
+ payload = data ? data.obj : null;
2202
+ cancelMove = this._minLengthMet() && this.menu.update(query);
2203
+ if (!cancelMove && !this.eventBus.before("cursorchange", payload)) {
2204
+ this.menu.setCursor($candidate);
2205
+ if (data) {
2206
+ this.input.setInputValue(data.val);
2207
+ } else {
2208
+ this.input.resetInputValue();
2209
+ this._updateHint();
2210
+ }
2211
+ this.eventBus.trigger("cursorchange", payload);
2212
+ return true;
2213
+ }
2214
+ return false;
2215
+ },
1638
2216
  destroy: function destroy() {
1639
2217
  this.input.destroy();
1640
- this.dropdown.destroy();
1641
- destroyDomStructure(this.$node);
1642
- this.$node = null;
2218
+ this.menu.destroy();
1643
2219
  }
1644
2220
  });
1645
2221
  return Typeahead;
1646
- function buildDom(input, withHint) {
1647
- var $input, $wrapper, $dropdown, $hint;
1648
- $input = $(input);
1649
- $wrapper = $(html.wrapper).css(css.wrapper);
1650
- $dropdown = $(html.dropdown).css(css.dropdown);
1651
- $hint = $input.clone().css(css.hint).css(getBackgroundStyles($input));
1652
- $hint.val("").removeData().addClass("tt-hint").removeAttr("id name placeholder required").prop("readonly", true).attr({
1653
- autocomplete: "off",
1654
- spellcheck: "false",
1655
- tabindex: -1
1656
- });
1657
- $input.data(attrsKey, {
1658
- dir: $input.attr("dir"),
1659
- autocomplete: $input.attr("autocomplete"),
1660
- spellcheck: $input.attr("spellcheck"),
1661
- style: $input.attr("style")
1662
- });
1663
- $input.addClass("tt-input").attr({
1664
- autocomplete: "off",
1665
- spellcheck: false
1666
- }).css(withHint ? css.input : css.inputWithNoHint);
1667
- try {
1668
- !$input.attr("dir") && $input.attr("dir", "auto");
1669
- } catch (e) {}
1670
- return $input.wrap($wrapper).parent().prepend(withHint ? $hint : null).append($dropdown);
1671
- }
1672
- function getBackgroundStyles($el) {
1673
- return {
1674
- backgroundAttachment: $el.css("background-attachment"),
1675
- backgroundClip: $el.css("background-clip"),
1676
- backgroundColor: $el.css("background-color"),
1677
- backgroundImage: $el.css("background-image"),
1678
- backgroundOrigin: $el.css("background-origin"),
1679
- backgroundPosition: $el.css("background-position"),
1680
- backgroundRepeat: $el.css("background-repeat"),
1681
- backgroundSize: $el.css("background-size")
2222
+ function c(ctx) {
2223
+ var methods = [].slice.call(arguments, 1);
2224
+ return function() {
2225
+ var args = [].slice.call(arguments);
2226
+ _.each(methods, function(method) {
2227
+ return ctx[method].apply(ctx, args);
2228
+ });
1682
2229
  };
1683
2230
  }
1684
- function destroyDomStructure($node) {
1685
- var $input = $node.find(".tt-input");
1686
- _.each($input.data(attrsKey), function(val, key) {
1687
- _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);
1688
- });
1689
- $input.detach().removeData(attrsKey).removeClass("tt-input").insertAfter($node);
1690
- $node.remove();
1691
- }
1692
2231
  }();
1693
2232
  (function() {
1694
2233
  "use strict";
1695
- var old, typeaheadKey, methods;
2234
+ var old, keys, methods;
1696
2235
  old = $.fn.typeahead;
1697
- typeaheadKey = "ttTypeahead";
2236
+ keys = {
2237
+ www: "tt-www",
2238
+ attrs: "tt-attrs",
2239
+ typeahead: "tt-typeahead"
2240
+ };
1698
2241
  methods = {
1699
2242
  initialize: function initialize(o, datasets) {
2243
+ var www;
1700
2244
  datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1);
1701
2245
  o = o || {};
2246
+ www = WWW(o.classNames);
1702
2247
  return this.each(attach);
1703
2248
  function attach() {
1704
- var $input = $(this), eventBus, typeahead;
2249
+ var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor;
1705
2250
  _.each(datasets, function(d) {
1706
2251
  d.highlight = !!o.highlight;
1707
2252
  });
1708
- typeahead = new Typeahead({
1709
- input: $input,
1710
- eventBus: eventBus = new EventBus({
1711
- el: $input
1712
- }),
1713
- withHint: _.isUndefined(o.hint) ? true : !!o.hint,
1714
- minLength: o.minLength,
1715
- autoselect: o.autoselect,
1716
- datasets: datasets
2253
+ $input = $(this);
2254
+ $wrapper = $(www.html.wrapper);
2255
+ $hint = $elOrNull(o.hint);
2256
+ $menu = $elOrNull(o.menu);
2257
+ defaultHint = o.hint !== false && !$hint;
2258
+ defaultMenu = o.menu !== false && !$menu;
2259
+ defaultHint && ($hint = buildHintFromInput($input, www));
2260
+ defaultMenu && ($menu = $(www.html.menu).css(www.css.menu));
2261
+ $hint && $hint.val("");
2262
+ $input = prepInput($input, www);
2263
+ if (defaultHint || defaultMenu) {
2264
+ $wrapper.css(www.css.wrapper);
2265
+ $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint);
2266
+ $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null);
2267
+ }
2268
+ MenuConstructor = defaultMenu ? DefaultMenu : Menu;
2269
+ eventBus = new EventBus({
2270
+ el: $input
1717
2271
  });
1718
- $input.data(typeaheadKey, typeahead);
1719
- }
2272
+ input = new Input({
2273
+ hint: $hint,
2274
+ input: $input
2275
+ }, www);
2276
+ menu = new MenuConstructor({
2277
+ node: $menu,
2278
+ datasets: datasets
2279
+ }, www);
2280
+ typeahead = new Typeahead({
2281
+ input: input,
2282
+ menu: menu,
2283
+ eventBus: eventBus,
2284
+ minLength: o.minLength
2285
+ }, www);
2286
+ $input.data(keys.www, www);
2287
+ $input.data(keys.typeahead, typeahead);
2288
+ }
2289
+ },
2290
+ isEnabled: function isEnabled() {
2291
+ var enabled;
2292
+ ttEach(this.first(), function(t) {
2293
+ enabled = t.isEnabled();
2294
+ });
2295
+ return enabled;
2296
+ },
2297
+ enable: function enable() {
2298
+ ttEach(this, function(t) {
2299
+ t.enable();
2300
+ });
2301
+ return this;
2302
+ },
2303
+ disable: function disable() {
2304
+ ttEach(this, function(t) {
2305
+ t.disable();
2306
+ });
2307
+ return this;
2308
+ },
2309
+ isActive: function isActive() {
2310
+ var active;
2311
+ ttEach(this.first(), function(t) {
2312
+ active = t.isActive();
2313
+ });
2314
+ return active;
2315
+ },
2316
+ activate: function activate() {
2317
+ ttEach(this, function(t) {
2318
+ t.activate();
2319
+ });
2320
+ return this;
2321
+ },
2322
+ deactivate: function deactivate() {
2323
+ ttEach(this, function(t) {
2324
+ t.deactivate();
2325
+ });
2326
+ return this;
2327
+ },
2328
+ isOpen: function isOpen() {
2329
+ var open;
2330
+ ttEach(this.first(), function(t) {
2331
+ open = t.isOpen();
2332
+ });
2333
+ return open;
1720
2334
  },
1721
2335
  open: function open() {
1722
- return this.each(openTypeahead);
1723
- function openTypeahead() {
1724
- var $input = $(this), typeahead;
1725
- if (typeahead = $input.data(typeaheadKey)) {
1726
- typeahead.open();
1727
- }
1728
- }
2336
+ ttEach(this, function(t) {
2337
+ t.open();
2338
+ });
2339
+ return this;
1729
2340
  },
1730
2341
  close: function close() {
1731
- return this.each(closeTypeahead);
1732
- function closeTypeahead() {
1733
- var $input = $(this), typeahead;
1734
- if (typeahead = $input.data(typeaheadKey)) {
1735
- typeahead.close();
1736
- }
1737
- }
2342
+ ttEach(this, function(t) {
2343
+ t.close();
2344
+ });
2345
+ return this;
2346
+ },
2347
+ select: function select(el) {
2348
+ var success = false, $el = $(el);
2349
+ ttEach(this.first(), function(t) {
2350
+ success = t.select($el);
2351
+ });
2352
+ return success;
2353
+ },
2354
+ autocomplete: function autocomplete(el) {
2355
+ var success = false, $el = $(el);
2356
+ ttEach(this.first(), function(t) {
2357
+ success = t.autocomplete($el);
2358
+ });
2359
+ return success;
2360
+ },
2361
+ moveCursor: function moveCursoe(delta) {
2362
+ var success = false;
2363
+ ttEach(this.first(), function(t) {
2364
+ success = t.moveCursor(delta);
2365
+ });
2366
+ return success;
1738
2367
  },
1739
2368
  val: function val(newVal) {
1740
- return !arguments.length ? getVal(this.first()) : this.each(setVal);
1741
- function setVal() {
1742
- var $input = $(this), typeahead;
1743
- if (typeahead = $input.data(typeaheadKey)) {
1744
- typeahead.setVal(newVal);
1745
- }
1746
- }
1747
- function getVal($input) {
1748
- var typeahead, query;
1749
- if (typeahead = $input.data(typeaheadKey)) {
1750
- query = typeahead.getVal();
1751
- }
2369
+ var query;
2370
+ if (!arguments.length) {
2371
+ ttEach(this.first(), function(t) {
2372
+ query = t.getVal();
2373
+ });
1752
2374
  return query;
2375
+ } else {
2376
+ ttEach(this, function(t) {
2377
+ t.setVal(_.toStr(newVal));
2378
+ });
2379
+ return this;
1753
2380
  }
1754
2381
  },
1755
2382
  destroy: function destroy() {
1756
- return this.each(unattach);
1757
- function unattach() {
1758
- var $input = $(this), typeahead;
1759
- if (typeahead = $input.data(typeaheadKey)) {
1760
- typeahead.destroy();
1761
- $input.removeData(typeaheadKey);
1762
- }
1763
- }
2383
+ ttEach(this, function(typeahead, $input) {
2384
+ revert($input);
2385
+ typeahead.destroy();
2386
+ });
2387
+ return this;
1764
2388
  }
1765
2389
  };
1766
2390
  $.fn.typeahead = function(method) {
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));
2391
+ if (methods[method]) {
2392
+ return methods[method].apply(this, [].slice.call(arguments, 1));
1773
2393
  } else {
1774
2394
  return methods.initialize.apply(this, arguments);
1775
2395
  }
@@ -1778,5 +2398,65 @@
1778
2398
  $.fn.typeahead = old;
1779
2399
  return this;
1780
2400
  };
2401
+ function ttEach($els, fn) {
2402
+ $els.each(function() {
2403
+ var $input = $(this), typeahead;
2404
+ (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input);
2405
+ });
2406
+ }
2407
+ function buildHintFromInput($input, www) {
2408
+ return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({
2409
+ autocomplete: "off",
2410
+ spellcheck: "false",
2411
+ tabindex: -1
2412
+ });
2413
+ }
2414
+ function prepInput($input, www) {
2415
+ $input.data(keys.attrs, {
2416
+ dir: $input.attr("dir"),
2417
+ autocomplete: $input.attr("autocomplete"),
2418
+ spellcheck: $input.attr("spellcheck"),
2419
+ style: $input.attr("style")
2420
+ });
2421
+ $input.addClass(www.classes.input).attr({
2422
+ autocomplete: "off",
2423
+ spellcheck: false
2424
+ });
2425
+ try {
2426
+ !$input.attr("dir") && $input.attr("dir", "auto");
2427
+ } catch (e) {}
2428
+ return $input;
2429
+ }
2430
+ function getBackgroundStyles($el) {
2431
+ return {
2432
+ backgroundAttachment: $el.css("background-attachment"),
2433
+ backgroundClip: $el.css("background-clip"),
2434
+ backgroundColor: $el.css("background-color"),
2435
+ backgroundImage: $el.css("background-image"),
2436
+ backgroundOrigin: $el.css("background-origin"),
2437
+ backgroundPosition: $el.css("background-position"),
2438
+ backgroundRepeat: $el.css("background-repeat"),
2439
+ backgroundSize: $el.css("background-size")
2440
+ };
2441
+ }
2442
+ function revert($input) {
2443
+ var www, $wrapper;
2444
+ www = $input.data(keys.www);
2445
+ $wrapper = $input.parent().filter(www.selectors.wrapper);
2446
+ _.each($input.data(keys.attrs), function(val, key) {
2447
+ _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);
2448
+ });
2449
+ $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input);
2450
+ if ($wrapper.length) {
2451
+ $input.detach().insertAfter($wrapper);
2452
+ $wrapper.remove();
2453
+ }
2454
+ }
2455
+ function $elOrNull(obj) {
2456
+ var isValid, $el;
2457
+ isValid = _.isJQuery(obj) || _.isElement(obj);
2458
+ $el = isValid ? $(obj).first() : [];
2459
+ return $el.length ? $el : null;
2460
+ }
1781
2461
  })();
1782
- })(window.jQuery);
2462
+ });