solidus_searchkick 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +5 -0
  3. data/.gitignore +14 -0
  4. data/.travis.yml +18 -0
  5. data/Gemfile +9 -0
  6. data/Gemfile.lock +404 -0
  7. data/LICENSE +26 -0
  8. data/README.md +78 -0
  9. data/Rakefile +21 -0
  10. data/app/assets/javascripts/spree/backend/spree_searchkick.js +2 -0
  11. data/app/assets/javascripts/spree/frontend/spree_searchkick.js +28 -0
  12. data/app/assets/javascripts/spree/frontend/typeahead.bundle.js +2451 -0
  13. data/app/assets/stylesheets/spree/backend/spree_searchkick.css +4 -0
  14. data/app/assets/stylesheets/spree/frontend/spree_searchkick.css +47 -0
  15. data/app/controllers/spree/products_controller_decorator.rb +7 -0
  16. data/app/helpers/spree/products_helper_decorator.rb +9 -0
  17. data/app/models/spree/order_decorator.rb +8 -0
  18. data/app/models/spree/product_decorator.rb +57 -0
  19. data/app/models/spree/property_decorator.rb +7 -0
  20. data/app/models/spree/taxonomy_decorator.rb +7 -0
  21. data/app/overrides/spree/admin/properties/_form/add_filterable_to_property_form.html.erb.deface +8 -0
  22. data/app/overrides/spree/admin/taxonomies/_form/add_filterable_to_taxonomy_form.html.erb.deface +6 -0
  23. data/app/views/spree/shared/_es_filter.html.erb +15 -0
  24. data/app/views/spree/shared/_filters.html.erb +12 -0
  25. data/bin/rails +7 -0
  26. data/config/locales/en.yml +5 -0
  27. data/config/routes.rb +6 -0
  28. data/db/migrate/20150819222417_add_filtrable_to_spree_taxonomies.rb +5 -0
  29. data/db/migrate/20151009155442_add_filterable_to_spree_property.rb +5 -0
  30. data/lib/generators/spree_searchkick/install/install_generator.rb +31 -0
  31. data/lib/solidus_searchkick.rb +2 -0
  32. data/lib/solidus_searchkick/engine.rb +23 -0
  33. data/lib/solidus_searchkick/factories.rb +6 -0
  34. data/lib/solidus_searchkick/version.rb +3 -0
  35. data/lib/spree/core/searchkick_filters.rb +50 -0
  36. data/lib/spree/search/searchkick.rb +58 -0
  37. data/solidus_searchkick.gemspec +38 -0
  38. data/spec/controllers/spree/products_controller_spec.rb +5 -0
  39. data/spec/lib/spree/search/searchkick_spec.rb +43 -0
  40. data/spec/models/spree/order_spec.rb +20 -0
  41. data/spec/models/spree/product_spec.rb +37 -0
  42. data/spec/models/spree/property_spec.rb +11 -0
  43. data/spec/models/spree/taxonomy_spec.rb +11 -0
  44. data/spec/routing/spree/products_routes_spec.rb +20 -0
  45. data/spec/spec_helper.rb +92 -0
  46. metadata +313 -0
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2016 [name of plugin creator]
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name Spree nor the names of its contributors may be used to
13
+ endorse or promote products derived from this software without specific
14
+ prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ [![Build Status](https://travis-ci.org/elevatorup/solidus_searchkick.svg?branch=master)](https://travis-ci.org/elevatorup/solidus_searchkick)
2
+ [![Code Climate](https://codeclimate.com/github/elevatorup/solidus_searchkick/badges/gpa.svg)](https://codeclimate.com/github/elevatorup/solidus_searchkick)
3
+ [![Test Coverage](https://codeclimate.com/github/elevatorup/solidus_searchkick/badges/coverage.svg)](https://codeclimate.com/github/elevatorup/solidus_searchkick/coverage)
4
+
5
+ Solidus + Searchkick
6
+ ===============
7
+
8
+ Add [Elasticsearch](http://elastic.co) goodies to Solidus, powered by [searchkick](http://searchkick.org).
9
+
10
+ Features
11
+ --------
12
+
13
+ * Full search (keyword, in_taxon)
14
+ * Taxons Aggregations (aggs)
15
+ * Search Autocomplete ([Typeahead](https://twitter.github.io/typeahead.js/))
16
+
17
+
18
+ Installation
19
+ ------------
20
+
21
+ Add searchkick and solidus_searchkick to your Gemfile:
22
+
23
+ ```ruby
24
+ gem 'searchkick'
25
+ gem 'solidus_searchkick'
26
+ ```
27
+
28
+ Bundle your dependencies and run the installation generator:
29
+
30
+ ```shell
31
+ bundle
32
+ bundle exec rails g solidus_searchkick:install
33
+
34
+ OR
35
+
36
+ rake solidus_searchkick:install:migrations
37
+ ```
38
+
39
+ [Install elasticsearch](https://www.elastic.co/downloads/elasticsearch)
40
+
41
+ Documentation
42
+ -------------
43
+
44
+ By default, only the `Spree::Product` class is indexed. The following items are indexed by default:
45
+ * name
46
+ * description
47
+ * available? (indexed as `active`)
48
+ * price (needed in order to return products that have price != nil)
49
+ * currency
50
+ * sku
51
+ * orders.complete.count (indexed as `conversions`)
52
+ * taxon_ids
53
+ * taxon_names
54
+ * All Properies
55
+ * All Taxon ids by Taxonomy
56
+
57
+ In order to control what data is indexed, override `Spree::Product#search_data` method. Call `Spree::Product.reindex` after changing this method.
58
+
59
+ To enable or disable taxons filters, go to taxonomy form and change `filterable` boolean.
60
+
61
+ Testing
62
+ -------
63
+
64
+ First bundle your dependencies, then run `rake`. `rake` will default to building the dummy app if it does not exist, then it will run specs. The dummy app can be regenerated by using `rake test_app`.
65
+
66
+ ```shell
67
+ bundle
68
+ bundle exec rake
69
+ ```
70
+
71
+ When testing your applications integration with this extension you may use it's factories.
72
+ Simply add this require statement to your spec_helper:
73
+
74
+ ```ruby
75
+ require 'solidus_searchkick/factories'
76
+ ```
77
+
78
+ Copyright (c) 2016 Jim Smith, released under the New BSD License
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ require 'spree/testing_support/extension_rake'
6
+
7
+ RSpec::Core::RakeTask.new
8
+
9
+ task :default do
10
+ if Dir["spec/dummy"].empty?
11
+ Rake::Task[:test_app].invoke
12
+ Dir.chdir("../../")
13
+ end
14
+ Rake::Task[:spec].invoke
15
+ end
16
+
17
+ desc 'Generates a dummy app for testing'
18
+ task :test_app do
19
+ ENV['LIB_NAME'] = 'solidus_searchkick'
20
+ Rake::Task['extension:test_app'].invoke
21
+ end
@@ -0,0 +1,2 @@
1
+ // Placeholder manifest file.
2
+ // the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/backend/all.js'
@@ -0,0 +1,28 @@
1
+ // Placeholder manifest file.
2
+ // the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/frontend/all.js'
3
+ //= require_tree .
4
+ $(function () {
5
+
6
+ var products = new Bloodhound({
7
+ datumTokenizer: Bloodhound.tokenizers.whitespace,
8
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
9
+ limit: 10,
10
+ prefetch: Spree.pathFor('autocomplete/products.json'),
11
+ remote: {
12
+ url: Spree.pathFor('autocomplete/products.json?keywords=%QUERY'),
13
+ wildcard: '%QUERY'
14
+ }
15
+ });
16
+
17
+ products.initialize();
18
+
19
+ // passing in `null` for the `options` arguments will result in the default
20
+ // options being used
21
+ $('#keywords').typeahead({
22
+ minLength: 2,
23
+ highlight: true
24
+ }, {
25
+ name: 'products',
26
+ source: products
27
+ });
28
+ });
@@ -0,0 +1,2451 @@
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(root, factory) {
8
+ if (typeof define === "function" && define.amd) {
9
+ define("bloodhound", [ "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($) {
18
+ var _ = function() {
19
+ "use strict";
20
+ return {
21
+ isMsie: function() {
22
+ return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
23
+ },
24
+ isBlankString: function(str) {
25
+ return !str || /^\s*$/.test(str);
26
+ },
27
+ escapeRegExChars: function(str) {
28
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
29
+ },
30
+ isString: function(obj) {
31
+ return typeof obj === "string";
32
+ },
33
+ isNumber: function(obj) {
34
+ return typeof obj === "number";
35
+ },
36
+ isArray: $.isArray,
37
+ isFunction: $.isFunction,
38
+ isObject: $.isPlainObject,
39
+ isUndefined: function(obj) {
40
+ return typeof obj === "undefined";
41
+ },
42
+ isElement: function(obj) {
43
+ return !!(obj && obj.nodeType === 1);
44
+ },
45
+ isJQuery: function(obj) {
46
+ return obj instanceof $;
47
+ },
48
+ toStr: function toStr(s) {
49
+ return _.isUndefined(s) || s === null ? "" : s + "";
50
+ },
51
+ bind: $.proxy,
52
+ each: function(collection, cb) {
53
+ $.each(collection, reverseArgs);
54
+ function reverseArgs(index, value) {
55
+ return cb(value, index);
56
+ }
57
+ },
58
+ map: $.map,
59
+ filter: $.grep,
60
+ every: function(obj, test) {
61
+ var result = true;
62
+ if (!obj) {
63
+ return result;
64
+ }
65
+ $.each(obj, function(key, val) {
66
+ if (!(result = test.call(null, val, key, obj))) {
67
+ return false;
68
+ }
69
+ });
70
+ return !!result;
71
+ },
72
+ some: function(obj, test) {
73
+ var result = false;
74
+ if (!obj) {
75
+ return result;
76
+ }
77
+ $.each(obj, function(key, val) {
78
+ if (result = test.call(null, val, key, obj)) {
79
+ return false;
80
+ }
81
+ });
82
+ return !!result;
83
+ },
84
+ mixin: $.extend,
85
+ identity: function(x) {
86
+ return x;
87
+ },
88
+ clone: function(obj) {
89
+ return $.extend(true, {}, obj);
90
+ },
91
+ getIdGenerator: function() {
92
+ var counter = 0;
93
+ return function() {
94
+ return counter++;
95
+ };
96
+ },
97
+ templatify: function templatify(obj) {
98
+ return $.isFunction(obj) ? obj : template;
99
+ function template() {
100
+ return String(obj);
101
+ }
102
+ },
103
+ defer: function(fn) {
104
+ setTimeout(fn, 0);
105
+ },
106
+ debounce: function(func, wait, immediate) {
107
+ var timeout, result;
108
+ return function() {
109
+ var context = this, args = arguments, later, callNow;
110
+ later = function() {
111
+ timeout = null;
112
+ if (!immediate) {
113
+ result = func.apply(context, args);
114
+ }
115
+ };
116
+ callNow = immediate && !timeout;
117
+ clearTimeout(timeout);
118
+ timeout = setTimeout(later, wait);
119
+ if (callNow) {
120
+ result = func.apply(context, args);
121
+ }
122
+ return result;
123
+ };
124
+ },
125
+ throttle: function(func, wait) {
126
+ var context, args, timeout, result, previous, later;
127
+ previous = 0;
128
+ later = function() {
129
+ previous = new Date();
130
+ timeout = null;
131
+ result = func.apply(context, args);
132
+ };
133
+ return function() {
134
+ var now = new Date(), remaining = wait - (now - previous);
135
+ context = this;
136
+ args = arguments;
137
+ if (remaining <= 0) {
138
+ clearTimeout(timeout);
139
+ timeout = null;
140
+ previous = now;
141
+ result = func.apply(context, args);
142
+ } else if (!timeout) {
143
+ timeout = setTimeout(later, remaining);
144
+ }
145
+ return result;
146
+ };
147
+ },
148
+ stringify: function(val) {
149
+ return _.isString(val) ? val : JSON.stringify(val);
150
+ },
151
+ noop: function() {}
152
+ };
153
+ }();
154
+ var VERSION = "0.11.1";
155
+ var tokenizers = function() {
156
+ "use strict";
157
+ return {
158
+ nonword: nonword,
159
+ whitespace: whitespace,
160
+ obj: {
161
+ nonword: getObjTokenizer(nonword),
162
+ whitespace: getObjTokenizer(whitespace)
163
+ }
164
+ };
165
+ function whitespace(str) {
166
+ str = _.toStr(str);
167
+ return str ? str.split(/\s+/) : [];
168
+ }
169
+ function nonword(str) {
170
+ str = _.toStr(str);
171
+ return str ? str.split(/\W+/) : [];
172
+ }
173
+ function getObjTokenizer(tokenizer) {
174
+ return function setKey(keys) {
175
+ keys = _.isArray(keys) ? keys : [].slice.call(arguments, 0);
176
+ return function tokenize(o) {
177
+ var tokens = [];
178
+ _.each(keys, function(k) {
179
+ tokens = tokens.concat(tokenizer(_.toStr(o[k])));
180
+ });
181
+ return tokens;
182
+ };
183
+ };
184
+ }
185
+ }();
186
+ var LruCache = function() {
187
+ "use strict";
188
+ function LruCache(maxSize) {
189
+ this.maxSize = _.isNumber(maxSize) ? maxSize : 100;
190
+ this.reset();
191
+ if (this.maxSize <= 0) {
192
+ this.set = this.get = $.noop;
193
+ }
194
+ }
195
+ _.mixin(LruCache.prototype, {
196
+ set: function set(key, val) {
197
+ var tailItem = this.list.tail, node;
198
+ if (this.size >= this.maxSize) {
199
+ this.list.remove(tailItem);
200
+ delete this.hash[tailItem.key];
201
+ this.size--;
202
+ }
203
+ if (node = this.hash[key]) {
204
+ node.val = val;
205
+ this.list.moveToFront(node);
206
+ } else {
207
+ node = new Node(key, val);
208
+ this.list.add(node);
209
+ this.hash[key] = node;
210
+ this.size++;
211
+ }
212
+ },
213
+ get: function get(key) {
214
+ var node = this.hash[key];
215
+ if (node) {
216
+ this.list.moveToFront(node);
217
+ return node.val;
218
+ }
219
+ },
220
+ reset: function reset() {
221
+ this.size = 0;
222
+ this.hash = {};
223
+ this.list = new List();
224
+ }
225
+ });
226
+ function List() {
227
+ this.head = this.tail = null;
228
+ }
229
+ _.mixin(List.prototype, {
230
+ add: function add(node) {
231
+ if (this.head) {
232
+ node.next = this.head;
233
+ this.head.prev = node;
234
+ }
235
+ this.head = node;
236
+ this.tail = this.tail || node;
237
+ },
238
+ remove: function remove(node) {
239
+ node.prev ? node.prev.next = node.next : this.head = node.next;
240
+ node.next ? node.next.prev = node.prev : this.tail = node.prev;
241
+ },
242
+ moveToFront: function(node) {
243
+ this.remove(node);
244
+ this.add(node);
245
+ }
246
+ });
247
+ function Node(key, val) {
248
+ this.key = key;
249
+ this.val = val;
250
+ this.prev = this.next = null;
251
+ }
252
+ return LruCache;
253
+ }();
254
+ var PersistentStorage = function() {
255
+ "use strict";
256
+ var LOCAL_STORAGE;
257
+ try {
258
+ LOCAL_STORAGE = window.localStorage;
259
+ LOCAL_STORAGE.setItem("~~~", "!");
260
+ LOCAL_STORAGE.removeItem("~~~");
261
+ } catch (err) {
262
+ LOCAL_STORAGE = null;
263
+ }
264
+ function PersistentStorage(namespace, override) {
265
+ this.prefix = [ "__", namespace, "__" ].join("");
266
+ this.ttlKey = "__ttl__";
267
+ this.keyMatcher = new RegExp("^" + _.escapeRegExChars(this.prefix));
268
+ this.ls = override || LOCAL_STORAGE;
269
+ !this.ls && this._noop();
270
+ }
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();
288
+ }
289
+ }
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
+ });
322
+ return PersistentStorage;
323
+ function now() {
324
+ return new Date().getTime();
325
+ }
326
+ function encode(val) {
327
+ return JSON.stringify(_.isUndefined(val) ? null : val);
328
+ }
329
+ function decode(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;
340
+ }
341
+ }();
342
+ var Transport = function() {
343
+ "use strict";
344
+ var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, sharedCache = new LruCache(10);
345
+ function Transport(o) {
346
+ o = o || {};
347
+ this.cancelled = false;
348
+ this.lastReq = null;
349
+ this._send = o.transport;
350
+ this._get = o.limiter ? o.limiter(this._get) : this._get;
351
+ this._cache = o.cache === false ? new LruCache(0) : sharedCache;
352
+ }
353
+ Transport.setMaxPendingRequests = function setMaxPendingRequests(num) {
354
+ maxPendingRequests = num;
355
+ };
356
+ Transport.resetCache = function resetCache() {
357
+ sharedCache.reset();
358
+ };
359
+ _.mixin(Transport.prototype, {
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) {
368
+ return;
369
+ }
370
+ if (jqXhr = pendingRequests[fingerprint]) {
371
+ jqXhr.done(done).fail(fail);
372
+ } else if (pendingRequestsCount < maxPendingRequests) {
373
+ pendingRequestsCount++;
374
+ pendingRequests[fingerprint] = this._send(o).done(done).fail(fail).always(always);
375
+ } else {
376
+ this.onDeckRequestArgs = [].slice.call(arguments, 0);
377
+ }
378
+ function done(resp) {
379
+ cb(null, resp);
380
+ that._cache.set(fingerprint, resp);
381
+ }
382
+ function fail() {
383
+ cb(true);
384
+ }
385
+ function always() {
386
+ pendingRequestsCount--;
387
+ delete pendingRequests[fingerprint];
388
+ if (that.onDeckRequestArgs) {
389
+ that._get.apply(that, that.onDeckRequestArgs);
390
+ that.onDeckRequestArgs = null;
391
+ }
392
+ }
393
+ },
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);
401
+ this.cancelled = false;
402
+ this.lastReq = fingerprint;
403
+ if (resp = this._cache.get(fingerprint)) {
404
+ cb(null, resp);
405
+ } else {
406
+ this._get(o, cb);
407
+ }
408
+ },
409
+ cancel: function() {
410
+ this.cancelled = true;
411
+ }
412
+ });
413
+ return Transport;
414
+ }();
415
+ var SearchIndex = window.SearchIndex = function() {
416
+ "use strict";
417
+ var CHILDREN = "c", IDS = "i";
418
+ function SearchIndex(o) {
419
+ o = o || {};
420
+ if (!o.datumTokenizer || !o.queryTokenizer) {
421
+ $.error("datumTokenizer and queryTokenizer are both required");
422
+ }
423
+ this.identify = o.identify || _.stringify;
424
+ this.datumTokenizer = o.datumTokenizer;
425
+ this.queryTokenizer = o.queryTokenizer;
426
+ this.reset();
427
+ }
428
+ _.mixin(SearchIndex.prototype, {
429
+ bootstrap: function bootstrap(o) {
430
+ this.datums = o.datums;
431
+ this.trie = o.trie;
432
+ },
433
+ add: function(data) {
434
+ var that = this;
435
+ data = _.isArray(data) ? data : [ data ];
436
+ _.each(data, function(datum) {
437
+ var id, tokens;
438
+ that.datums[id = that.identify(datum)] = datum;
439
+ tokens = normalizeTokens(that.datumTokenizer(datum));
440
+ _.each(tokens, function(token) {
441
+ var node, chars, ch;
442
+ node = that.trie;
443
+ chars = token.split("");
444
+ while (ch = chars.shift()) {
445
+ node = node[CHILDREN][ch] || (node[CHILDREN][ch] = newNode());
446
+ node[IDS].push(id);
447
+ }
448
+ });
449
+ });
450
+ },
451
+ get: function get(ids) {
452
+ var that = this;
453
+ return _.map(ids, function(id) {
454
+ return that.datums[id];
455
+ });
456
+ },
457
+ search: function search(query) {
458
+ var that = this, tokens, matches;
459
+ tokens = normalizeTokens(this.queryTokenizer(query));
460
+ _.each(tokens, function(token) {
461
+ var node, chars, ch, ids;
462
+ if (matches && matches.length === 0) {
463
+ return false;
464
+ }
465
+ node = that.trie;
466
+ chars = token.split("");
467
+ while (node && (ch = chars.shift())) {
468
+ node = node[CHILDREN][ch];
469
+ }
470
+ if (node && chars.length === 0) {
471
+ ids = node[IDS].slice(0);
472
+ matches = matches ? getIntersection(matches, ids) : ids;
473
+ } else {
474
+ matches = [];
475
+ return false;
476
+ }
477
+ });
478
+ return matches ? _.map(unique(matches), function(id) {
479
+ return that.datums[id];
480
+ }) : [];
481
+ },
482
+ all: function all() {
483
+ var values = [];
484
+ for (var key in this.datums) {
485
+ values.push(this.datums[key]);
486
+ }
487
+ return values;
488
+ },
489
+ reset: function reset() {
490
+ this.datums = {};
491
+ this.trie = newNode();
492
+ },
493
+ serialize: function serialize() {
494
+ return {
495
+ datums: this.datums,
496
+ trie: this.trie
497
+ };
498
+ }
499
+ });
500
+ return SearchIndex;
501
+ function normalizeTokens(tokens) {
502
+ tokens = _.filter(tokens, function(token) {
503
+ return !!token;
504
+ });
505
+ tokens = _.map(tokens, function(token) {
506
+ return token.toLowerCase();
507
+ });
508
+ return tokens;
509
+ }
510
+ function newNode() {
511
+ var node = {};
512
+ node[IDS] = [];
513
+ node[CHILDREN] = {};
514
+ return node;
515
+ }
516
+ function unique(array) {
517
+ var seen = {}, uniques = [];
518
+ for (var i = 0, len = array.length; i < len; i++) {
519
+ if (!seen[array[i]]) {
520
+ seen[array[i]] = true;
521
+ uniques.push(array[i]);
522
+ }
523
+ }
524
+ return uniques;
525
+ }
526
+ function getIntersection(arrayA, arrayB) {
527
+ var ai = 0, bi = 0, intersection = [];
528
+ arrayA = arrayA.sort();
529
+ arrayB = arrayB.sort();
530
+ var lenArrayA = arrayA.length, lenArrayB = arrayB.length;
531
+ while (ai < lenArrayA && bi < lenArrayB) {
532
+ if (arrayA[ai] < arrayB[bi]) {
533
+ ai++;
534
+ } else if (arrayA[ai] > arrayB[bi]) {
535
+ bi++;
536
+ } else {
537
+ intersection.push(arrayA[ai]);
538
+ ai++;
539
+ bi++;
540
+ }
541
+ }
542
+ return intersection;
543
+ }
544
+ }();
545
+ var Prefetch = function() {
546
+ "use strict";
547
+ var keys;
548
+ keys = {
549
+ data: "data",
550
+ protocol: "protocol",
551
+ thumbprint: "thumbprint"
552
+ };
553
+ function Prefetch(o) {
554
+ this.url = o.url;
555
+ this.ttl = o.ttl;
556
+ this.cache = o.cache;
557
+ this.prepare = o.prepare;
558
+ this.transform = o.transform;
559
+ this.transport = o.transport;
560
+ this.thumbprint = o.thumbprint;
561
+ this.storage = new PersistentStorage(o.cacheKey);
562
+ }
563
+ _.mixin(Prefetch.prototype, {
564
+ _settings: function settings() {
565
+ return {
566
+ url: this.url,
567
+ type: "GET",
568
+ dataType: "json"
569
+ };
570
+ },
571
+ store: function store(data) {
572
+ if (!this.cache) {
573
+ return;
574
+ }
575
+ this.storage.set(keys.data, data, this.ttl);
576
+ this.storage.set(keys.protocol, location.protocol, this.ttl);
577
+ this.storage.set(keys.thumbprint, this.thumbprint, this.ttl);
578
+ },
579
+ fromCache: function fromCache() {
580
+ var stored = {}, isExpired;
581
+ if (!this.cache) {
582
+ return null;
583
+ }
584
+ stored.data = this.storage.get(keys.data);
585
+ stored.protocol = this.storage.get(keys.protocol);
586
+ stored.thumbprint = this.storage.get(keys.thumbprint);
587
+ isExpired = stored.thumbprint !== this.thumbprint || stored.protocol !== location.protocol;
588
+ return stored.data && !isExpired ? stored.data : null;
589
+ },
590
+ fromNetwork: function(cb) {
591
+ var that = this, settings;
592
+ if (!cb) {
593
+ return;
594
+ }
595
+ settings = this.prepare(this._settings());
596
+ this.transport(settings).fail(onError).done(onResponse);
597
+ function onError() {
598
+ cb(true);
599
+ }
600
+ function onResponse(resp) {
601
+ cb(null, that.transform(resp));
602
+ }
603
+ },
604
+ clear: function clear() {
605
+ this.storage.clear();
606
+ return this;
607
+ }
608
+ });
609
+ return Prefetch;
610
+ }();
611
+ var Remote = function() {
612
+ "use strict";
613
+ function Remote(o) {
614
+ this.url = o.url;
615
+ this.prepare = o.prepare;
616
+ this.transform = o.transform;
617
+ this.transport = new Transport({
618
+ cache: o.cache,
619
+ limiter: o.limiter,
620
+ transport: o.transport
621
+ });
622
+ }
623
+ _.mixin(Remote.prototype, {
624
+ _settings: function settings() {
625
+ return {
626
+ url: this.url,
627
+ type: "GET",
628
+ dataType: "json"
629
+ };
630
+ },
631
+ get: function get(query, cb) {
632
+ var that = this, settings;
633
+ if (!cb) {
634
+ return;
635
+ }
636
+ query = query || "";
637
+ settings = this.prepare(query, this._settings());
638
+ return this.transport.get(settings, onResponse);
639
+ function onResponse(err, resp) {
640
+ err ? cb([]) : cb(that.transform(resp));
641
+ }
642
+ },
643
+ cancelLastRequest: function cancelLastRequest() {
644
+ this.transport.cancel();
645
+ }
646
+ });
647
+ return Remote;
648
+ }();
649
+ var oParser = function() {
650
+ "use strict";
651
+ return function parse(o) {
652
+ var defaults, sorter;
653
+ defaults = {
654
+ initialize: true,
655
+ identify: _.stringify,
656
+ datumTokenizer: null,
657
+ queryTokenizer: null,
658
+ sufficient: 5,
659
+ sorter: null,
660
+ local: [],
661
+ prefetch: null,
662
+ remote: null
663
+ };
664
+ o = _.mixin(defaults, o || {});
665
+ !o.datumTokenizer && $.error("datumTokenizer is required");
666
+ !o.queryTokenizer && $.error("queryTokenizer is required");
667
+ sorter = o.sorter;
668
+ o.sorter = sorter ? function(x) {
669
+ return x.sort(sorter);
670
+ } : _.identity;
671
+ o.local = _.isFunction(o.local) ? o.local() : o.local;
672
+ o.prefetch = parsePrefetch(o.prefetch);
673
+ o.remote = parseRemote(o.remote);
674
+ return o;
675
+ };
676
+ function parsePrefetch(o) {
677
+ var defaults;
678
+ if (!o) {
679
+ return null;
680
+ }
681
+ defaults = {
682
+ url: null,
683
+ ttl: 24 * 60 * 60 * 1e3,
684
+ cache: true,
685
+ cacheKey: null,
686
+ thumbprint: "",
687
+ prepare: _.identity,
688
+ transform: _.identity,
689
+ transport: null
690
+ };
691
+ o = _.isString(o) ? {
692
+ url: o
693
+ } : o;
694
+ o = _.mixin(defaults, o);
695
+ !o.url && $.error("prefetch requires url to be set");
696
+ o.transform = o.filter || o.transform;
697
+ o.cacheKey = o.cacheKey || o.url;
698
+ o.thumbprint = VERSION + o.thumbprint;
699
+ o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax;
700
+ return o;
701
+ }
702
+ function parseRemote(o) {
703
+ var defaults;
704
+ if (!o) {
705
+ return;
706
+ }
707
+ defaults = {
708
+ url: null,
709
+ cache: true,
710
+ prepare: null,
711
+ replace: null,
712
+ wildcard: null,
713
+ limiter: null,
714
+ rateLimitBy: "debounce",
715
+ rateLimitWait: 300,
716
+ transform: _.identity,
717
+ transport: null
718
+ };
719
+ o = _.isString(o) ? {
720
+ url: o
721
+ } : o;
722
+ o = _.mixin(defaults, o);
723
+ !o.url && $.error("remote requires url to be set");
724
+ o.transform = o.filter || o.transform;
725
+ o.prepare = toRemotePrepare(o);
726
+ o.limiter = toLimiter(o);
727
+ o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax;
728
+ delete o.replace;
729
+ delete o.wildcard;
730
+ delete o.rateLimitBy;
731
+ delete o.rateLimitWait;
732
+ return o;
733
+ }
734
+ function toRemotePrepare(o) {
735
+ var prepare, replace, wildcard;
736
+ prepare = o.prepare;
737
+ replace = o.replace;
738
+ wildcard = o.wildcard;
739
+ if (prepare) {
740
+ return prepare;
741
+ }
742
+ if (replace) {
743
+ prepare = prepareByReplace;
744
+ } else if (o.wildcard) {
745
+ prepare = prepareByWildcard;
746
+ } else {
747
+ prepare = idenityPrepare;
748
+ }
749
+ return prepare;
750
+ function prepareByReplace(query, settings) {
751
+ settings.url = replace(settings.url, query);
752
+ return settings;
753
+ }
754
+ function prepareByWildcard(query, settings) {
755
+ settings.url = settings.url.replace(wildcard, encodeURIComponent(query));
756
+ return settings;
757
+ }
758
+ function idenityPrepare(query, settings) {
759
+ return settings;
760
+ }
761
+ }
762
+ function toLimiter(o) {
763
+ var limiter, method, wait;
764
+ limiter = o.limiter;
765
+ method = o.rateLimitBy;
766
+ wait = o.rateLimitWait;
767
+ if (!limiter) {
768
+ limiter = /^throttle$/i.test(method) ? throttle(wait) : debounce(wait);
769
+ }
770
+ return limiter;
771
+ function debounce(wait) {
772
+ return function debounce(fn) {
773
+ return _.debounce(fn, wait);
774
+ };
775
+ }
776
+ function throttle(wait) {
777
+ return function throttle(fn) {
778
+ return _.throttle(fn, wait);
779
+ };
780
+ }
781
+ }
782
+ function callbackToDeferred(fn) {
783
+ return function wrapper(o) {
784
+ var deferred = $.Deferred();
785
+ fn(o, onSuccess, onError);
786
+ return deferred;
787
+ function onSuccess(resp) {
788
+ _.defer(function() {
789
+ deferred.resolve(resp);
790
+ });
791
+ }
792
+ function onError(err) {
793
+ _.defer(function() {
794
+ deferred.reject(err);
795
+ });
796
+ }
797
+ };
798
+ }
799
+ }();
800
+ var Bloodhound = function() {
801
+ "use strict";
802
+ var old;
803
+ old = window && window.Bloodhound;
804
+ function Bloodhound(o) {
805
+ o = oParser(o);
806
+ this.sorter = o.sorter;
807
+ this.identify = o.identify;
808
+ this.sufficient = o.sufficient;
809
+ this.local = o.local;
810
+ this.remote = o.remote ? new Remote(o.remote) : null;
811
+ this.prefetch = o.prefetch ? new Prefetch(o.prefetch) : null;
812
+ this.index = new SearchIndex({
813
+ identify: this.identify,
814
+ datumTokenizer: o.datumTokenizer,
815
+ queryTokenizer: o.queryTokenizer
816
+ });
817
+ o.initialize !== false && this.initialize();
818
+ }
819
+ Bloodhound.noConflict = function noConflict() {
820
+ window && (window.Bloodhound = old);
821
+ return Bloodhound;
822
+ };
823
+ Bloodhound.tokenizers = tokenizers;
824
+ _.mixin(Bloodhound.prototype, {
825
+ __ttAdapter: function ttAdapter() {
826
+ var that = this;
827
+ return this.remote ? withAsync : withoutAsync;
828
+ function withAsync(query, sync, async) {
829
+ return that.search(query, sync, async);
830
+ }
831
+ function withoutAsync(query, sync) {
832
+ return that.search(query, sync);
833
+ }
834
+ },
835
+ _loadPrefetch: function loadPrefetch() {
836
+ var that = this, deferred, serialized;
837
+ deferred = $.Deferred();
838
+ if (!this.prefetch) {
839
+ deferred.resolve();
840
+ } else if (serialized = this.prefetch.fromCache()) {
841
+ this.index.bootstrap(serialized);
842
+ deferred.resolve();
843
+ } else {
844
+ this.prefetch.fromNetwork(done);
845
+ }
846
+ return deferred.promise();
847
+ function done(err, data) {
848
+ if (err) {
849
+ return deferred.reject();
850
+ }
851
+ that.add(data);
852
+ that.prefetch.store(that.index.serialize());
853
+ deferred.resolve();
854
+ }
855
+ },
856
+ _initialize: function initialize() {
857
+ var that = this, deferred;
858
+ this.clear();
859
+ (this.initPromise = this._loadPrefetch()).done(addLocalToIndex);
860
+ return this.initPromise;
861
+ function addLocalToIndex() {
862
+ that.add(that.local);
863
+ }
864
+ },
865
+ initialize: function initialize(force) {
866
+ return !this.initPromise || force ? this._initialize() : this.initPromise;
867
+ },
868
+ add: function add(data) {
869
+ this.index.add(data);
870
+ return this;
871
+ },
872
+ get: function get(ids) {
873
+ ids = _.isArray(ids) ? ids : [].slice.call(arguments);
874
+ return this.index.get(ids);
875
+ },
876
+ search: function search(query, sync, async) {
877
+ var that = this, local;
878
+ local = this.sorter(this.index.search(query));
879
+ sync(this.remote ? local.slice() : local);
880
+ if (this.remote && local.length < this.sufficient) {
881
+ this.remote.get(query, processRemote);
882
+ } else if (this.remote) {
883
+ this.remote.cancelLastRequest();
884
+ }
885
+ return this;
886
+ function processRemote(remote) {
887
+ var nonDuplicates = [];
888
+ _.each(remote, function(r) {
889
+ !_.some(local, function(l) {
890
+ return that.identify(r) === that.identify(l);
891
+ }) && nonDuplicates.push(r);
892
+ });
893
+ async && async(nonDuplicates);
894
+ }
895
+ },
896
+ all: function all() {
897
+ return this.index.all();
898
+ },
899
+ clear: function clear() {
900
+ this.index.reset();
901
+ return this;
902
+ },
903
+ clearPrefetchCache: function clearPrefetchCache() {
904
+ this.prefetch && this.prefetch.clear();
905
+ return this;
906
+ },
907
+ clearRemoteCache: function clearRemoteCache() {
908
+ Transport.resetCache();
909
+ return this;
910
+ },
911
+ ttAdapter: function ttAdapter() {
912
+ return this.__ttAdapter();
913
+ }
914
+ });
915
+ return Bloodhound;
916
+ }();
917
+ return Bloodhound;
918
+ });
919
+
920
+ (function(root, factory) {
921
+ if (typeof define === "function" && define.amd) {
922
+ define("typeahead.js", [ "jquery" ], function(a0) {
923
+ return factory(a0);
924
+ });
925
+ } else if (typeof exports === "object") {
926
+ module.exports = factory(require("jquery"));
927
+ } else {
928
+ factory(jQuery);
929
+ }
930
+ })(this, function($) {
931
+ var _ = function() {
932
+ "use strict";
933
+ return {
934
+ isMsie: function() {
935
+ return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
936
+ },
937
+ isBlankString: function(str) {
938
+ return !str || /^\s*$/.test(str);
939
+ },
940
+ escapeRegExChars: function(str) {
941
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
942
+ },
943
+ isString: function(obj) {
944
+ return typeof obj === "string";
945
+ },
946
+ isNumber: function(obj) {
947
+ return typeof obj === "number";
948
+ },
949
+ isArray: $.isArray,
950
+ isFunction: $.isFunction,
951
+ isObject: $.isPlainObject,
952
+ isUndefined: function(obj) {
953
+ return typeof obj === "undefined";
954
+ },
955
+ isElement: function(obj) {
956
+ return !!(obj && obj.nodeType === 1);
957
+ },
958
+ isJQuery: function(obj) {
959
+ return obj instanceof $;
960
+ },
961
+ toStr: function toStr(s) {
962
+ return _.isUndefined(s) || s === null ? "" : s + "";
963
+ },
964
+ bind: $.proxy,
965
+ each: function(collection, cb) {
966
+ $.each(collection, reverseArgs);
967
+ function reverseArgs(index, value) {
968
+ return cb(value, index);
969
+ }
970
+ },
971
+ map: $.map,
972
+ filter: $.grep,
973
+ every: function(obj, test) {
974
+ var result = true;
975
+ if (!obj) {
976
+ return result;
977
+ }
978
+ $.each(obj, function(key, val) {
979
+ if (!(result = test.call(null, val, key, obj))) {
980
+ return false;
981
+ }
982
+ });
983
+ return !!result;
984
+ },
985
+ some: function(obj, test) {
986
+ var result = false;
987
+ if (!obj) {
988
+ return result;
989
+ }
990
+ $.each(obj, function(key, val) {
991
+ if (result = test.call(null, val, key, obj)) {
992
+ return false;
993
+ }
994
+ });
995
+ return !!result;
996
+ },
997
+ mixin: $.extend,
998
+ identity: function(x) {
999
+ return x;
1000
+ },
1001
+ clone: function(obj) {
1002
+ return $.extend(true, {}, obj);
1003
+ },
1004
+ getIdGenerator: function() {
1005
+ var counter = 0;
1006
+ return function() {
1007
+ return counter++;
1008
+ };
1009
+ },
1010
+ templatify: function templatify(obj) {
1011
+ return $.isFunction(obj) ? obj : template;
1012
+ function template() {
1013
+ return String(obj);
1014
+ }
1015
+ },
1016
+ defer: function(fn) {
1017
+ setTimeout(fn, 0);
1018
+ },
1019
+ debounce: function(func, wait, immediate) {
1020
+ var timeout, result;
1021
+ return function() {
1022
+ var context = this, args = arguments, later, callNow;
1023
+ later = function() {
1024
+ timeout = null;
1025
+ if (!immediate) {
1026
+ result = func.apply(context, args);
1027
+ }
1028
+ };
1029
+ callNow = immediate && !timeout;
1030
+ clearTimeout(timeout);
1031
+ timeout = setTimeout(later, wait);
1032
+ if (callNow) {
1033
+ result = func.apply(context, args);
1034
+ }
1035
+ return result;
1036
+ };
1037
+ },
1038
+ throttle: function(func, wait) {
1039
+ var context, args, timeout, result, previous, later;
1040
+ previous = 0;
1041
+ later = function() {
1042
+ previous = new Date();
1043
+ timeout = null;
1044
+ result = func.apply(context, args);
1045
+ };
1046
+ return function() {
1047
+ var now = new Date(), remaining = wait - (now - previous);
1048
+ context = this;
1049
+ args = arguments;
1050
+ if (remaining <= 0) {
1051
+ clearTimeout(timeout);
1052
+ timeout = null;
1053
+ previous = now;
1054
+ result = func.apply(context, args);
1055
+ } else if (!timeout) {
1056
+ timeout = setTimeout(later, remaining);
1057
+ }
1058
+ return result;
1059
+ };
1060
+ },
1061
+ stringify: function(val) {
1062
+ return _.isString(val) ? val : JSON.stringify(val);
1063
+ },
1064
+ noop: function() {}
1065
+ };
1066
+ }();
1067
+ var WWW = function() {
1068
+ "use strict";
1069
+ var defaultClassNames = {
1070
+ wrapper: "twitter-typeahead",
1071
+ input: "tt-input",
1072
+ hint: "tt-hint",
1073
+ menu: "tt-menu",
1074
+ dataset: "tt-dataset",
1075
+ suggestion: "tt-suggestion",
1076
+ selectable: "tt-selectable",
1077
+ empty: "tt-empty",
1078
+ open: "tt-open",
1079
+ cursor: "tt-cursor",
1080
+ highlight: "tt-highlight"
1081
+ };
1082
+ return build;
1083
+ function build(o) {
1084
+ var www, classes;
1085
+ classes = _.mixin({}, defaultClassNames, o);
1086
+ www = {
1087
+ css: buildCss(),
1088
+ classes: classes,
1089
+ html: buildHtml(classes),
1090
+ selectors: buildSelectors(classes)
1091
+ };
1092
+ return {
1093
+ css: www.css,
1094
+ html: www.html,
1095
+ classes: www.classes,
1096
+ selectors: www.selectors,
1097
+ mixin: function(o) {
1098
+ _.mixin(o, www);
1099
+ }
1100
+ };
1101
+ }
1102
+ function buildHtml(c) {
1103
+ return {
1104
+ wrapper: '<span class="' + c.wrapper + '"></span>',
1105
+ menu: '<div class="' + c.menu + '"></div>'
1106
+ };
1107
+ }
1108
+ function buildSelectors(classes) {
1109
+ var selectors = {};
1110
+ _.each(classes, function(v, k) {
1111
+ selectors[k] = "." + v;
1112
+ });
1113
+ return selectors;
1114
+ }
1115
+ function buildCss() {
1116
+ var css = {
1117
+ wrapper: {
1118
+ position: "relative",
1119
+ display: "inline-block"
1120
+ },
1121
+ hint: {
1122
+ position: "absolute",
1123
+ top: "0",
1124
+ left: "0",
1125
+ borderColor: "transparent",
1126
+ boxShadow: "none",
1127
+ opacity: "1"
1128
+ },
1129
+ input: {
1130
+ position: "relative",
1131
+ verticalAlign: "top",
1132
+ backgroundColor: "transparent"
1133
+ },
1134
+ inputWithNoHint: {
1135
+ position: "relative",
1136
+ verticalAlign: "top"
1137
+ },
1138
+ menu: {
1139
+ position: "absolute",
1140
+ top: "100%",
1141
+ left: "0",
1142
+ zIndex: "100",
1143
+ display: "none"
1144
+ },
1145
+ ltr: {
1146
+ left: "0",
1147
+ right: "auto"
1148
+ },
1149
+ rtl: {
1150
+ left: "auto",
1151
+ right: " 0"
1152
+ }
1153
+ };
1154
+ if (_.isMsie()) {
1155
+ _.mixin(css.input, {
1156
+ backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
1157
+ });
1158
+ }
1159
+ return css;
1160
+ }
1161
+ }();
1162
+ var EventBus = function() {
1163
+ "use strict";
1164
+ var namespace, deprecationMap;
1165
+ namespace = "typeahead:";
1166
+ deprecationMap = {
1167
+ render: "rendered",
1168
+ cursorchange: "cursorchanged",
1169
+ select: "selected",
1170
+ autocomplete: "autocompleted"
1171
+ };
1172
+ function EventBus(o) {
1173
+ if (!o || !o.el) {
1174
+ $.error("EventBus initialized without el");
1175
+ }
1176
+ this.$el = $(o.el);
1177
+ }
1178
+ _.mixin(EventBus.prototype, {
1179
+ _trigger: function(type, args) {
1180
+ var $e;
1181
+ $e = $.Event(namespace + type);
1182
+ (args = args || []).unshift($e);
1183
+ this.$el.trigger.apply(this.$el, args);
1184
+ return $e;
1185
+ },
1186
+ before: function(type) {
1187
+ var args, $e;
1188
+ args = [].slice.call(arguments, 1);
1189
+ $e = this._trigger("before" + type, args);
1190
+ return $e.isDefaultPrevented();
1191
+ },
1192
+ trigger: function(type) {
1193
+ var deprecatedType;
1194
+ this._trigger(type, [].slice.call(arguments, 1));
1195
+ if (deprecatedType = deprecationMap[type]) {
1196
+ this._trigger(deprecatedType, [].slice.call(arguments, 1));
1197
+ }
1198
+ }
1199
+ });
1200
+ return EventBus;
1201
+ }();
1202
+ var EventEmitter = function() {
1203
+ "use strict";
1204
+ var splitter = /\s+/, nextTick = getNextTick();
1205
+ return {
1206
+ onSync: onSync,
1207
+ onAsync: onAsync,
1208
+ off: off,
1209
+ trigger: trigger
1210
+ };
1211
+ function on(method, types, cb, context) {
1212
+ var type;
1213
+ if (!cb) {
1214
+ return this;
1215
+ }
1216
+ types = types.split(splitter);
1217
+ cb = context ? bindContext(cb, context) : cb;
1218
+ this._callbacks = this._callbacks || {};
1219
+ while (type = types.shift()) {
1220
+ this._callbacks[type] = this._callbacks[type] || {
1221
+ sync: [],
1222
+ async: []
1223
+ };
1224
+ this._callbacks[type][method].push(cb);
1225
+ }
1226
+ return this;
1227
+ }
1228
+ function onAsync(types, cb, context) {
1229
+ return on.call(this, "async", types, cb, context);
1230
+ }
1231
+ function onSync(types, cb, context) {
1232
+ return on.call(this, "sync", types, cb, context);
1233
+ }
1234
+ function off(types) {
1235
+ var type;
1236
+ if (!this._callbacks) {
1237
+ return this;
1238
+ }
1239
+ types = types.split(splitter);
1240
+ while (type = types.shift()) {
1241
+ delete this._callbacks[type];
1242
+ }
1243
+ return this;
1244
+ }
1245
+ function trigger(types) {
1246
+ var type, callbacks, args, syncFlush, asyncFlush;
1247
+ if (!this._callbacks) {
1248
+ return this;
1249
+ }
1250
+ types = types.split(splitter);
1251
+ args = [].slice.call(arguments, 1);
1252
+ while ((type = types.shift()) && (callbacks = this._callbacks[type])) {
1253
+ syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args));
1254
+ asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args));
1255
+ syncFlush() && nextTick(asyncFlush);
1256
+ }
1257
+ return this;
1258
+ }
1259
+ function getFlush(callbacks, context, args) {
1260
+ return flush;
1261
+ function flush() {
1262
+ var cancelled;
1263
+ for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) {
1264
+ cancelled = callbacks[i].apply(context, args) === false;
1265
+ }
1266
+ return !cancelled;
1267
+ }
1268
+ }
1269
+ function getNextTick() {
1270
+ var nextTickFn;
1271
+ if (window.setImmediate) {
1272
+ nextTickFn = function nextTickSetImmediate(fn) {
1273
+ setImmediate(function() {
1274
+ fn();
1275
+ });
1276
+ };
1277
+ } else {
1278
+ nextTickFn = function nextTickSetTimeout(fn) {
1279
+ setTimeout(function() {
1280
+ fn();
1281
+ }, 0);
1282
+ };
1283
+ }
1284
+ return nextTickFn;
1285
+ }
1286
+ function bindContext(fn, context) {
1287
+ return fn.bind ? fn.bind(context) : function() {
1288
+ fn.apply(context, [].slice.call(arguments, 0));
1289
+ };
1290
+ }
1291
+ }();
1292
+ var highlight = function(doc) {
1293
+ "use strict";
1294
+ var defaults = {
1295
+ node: null,
1296
+ pattern: null,
1297
+ tagName: "strong",
1298
+ className: null,
1299
+ wordsOnly: false,
1300
+ caseSensitive: false
1301
+ };
1302
+ return function hightlight(o) {
1303
+ var regex;
1304
+ o = _.mixin({}, defaults, o);
1305
+ if (!o.node || !o.pattern) {
1306
+ return;
1307
+ }
1308
+ o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ];
1309
+ regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly);
1310
+ traverse(o.node, hightlightTextNode);
1311
+ function hightlightTextNode(textNode) {
1312
+ var match, patternNode, wrapperNode;
1313
+ if (match = regex.exec(textNode.data)) {
1314
+ wrapperNode = doc.createElement(o.tagName);
1315
+ o.className && (wrapperNode.className = o.className);
1316
+ patternNode = textNode.splitText(match.index);
1317
+ patternNode.splitText(match[0].length);
1318
+ wrapperNode.appendChild(patternNode.cloneNode(true));
1319
+ textNode.parentNode.replaceChild(wrapperNode, patternNode);
1320
+ }
1321
+ return !!match;
1322
+ }
1323
+ function traverse(el, hightlightTextNode) {
1324
+ var childNode, TEXT_NODE_TYPE = 3;
1325
+ for (var i = 0; i < el.childNodes.length; i++) {
1326
+ childNode = el.childNodes[i];
1327
+ if (childNode.nodeType === TEXT_NODE_TYPE) {
1328
+ i += hightlightTextNode(childNode) ? 1 : 0;
1329
+ } else {
1330
+ traverse(childNode, hightlightTextNode);
1331
+ }
1332
+ }
1333
+ }
1334
+ };
1335
+ function getRegex(patterns, caseSensitive, wordsOnly) {
1336
+ var escapedPatterns = [], regexStr;
1337
+ for (var i = 0, len = patterns.length; i < len; i++) {
1338
+ escapedPatterns.push(_.escapeRegExChars(patterns[i]));
1339
+ }
1340
+ regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")";
1341
+ return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i");
1342
+ }
1343
+ }(window.document);
1344
+ var Input = function() {
1345
+ "use strict";
1346
+ var specialKeyCodeMap;
1347
+ specialKeyCodeMap = {
1348
+ 9: "tab",
1349
+ 27: "esc",
1350
+ 37: "left",
1351
+ 39: "right",
1352
+ 13: "enter",
1353
+ 38: "up",
1354
+ 40: "down"
1355
+ };
1356
+ function Input(o, www) {
1357
+ o = o || {};
1358
+ if (!o.input) {
1359
+ $.error("input is missing");
1360
+ }
1361
+ www.mixin(this);
1362
+ this.$hint = $(o.hint);
1363
+ this.$input = $(o.input);
1364
+ this.query = this.$input.val();
1365
+ this.queryWhenFocused = this.hasFocus() ? this.query : null;
1366
+ this.$overflowHelper = buildOverflowHelper(this.$input);
1367
+ this._checkLanguageDirection();
1368
+ if (this.$hint.length === 0) {
1369
+ this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop;
1370
+ }
1371
+ }
1372
+ Input.normalizeQuery = function(str) {
1373
+ return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
1374
+ };
1375
+ _.mixin(Input.prototype, EventEmitter, {
1376
+ _onBlur: function onBlur() {
1377
+ this.resetInputValue();
1378
+ this.trigger("blurred");
1379
+ },
1380
+ _onFocus: function onFocus() {
1381
+ this.queryWhenFocused = this.query;
1382
+ this.trigger("focused");
1383
+ },
1384
+ _onKeydown: function onKeydown($e) {
1385
+ var keyName = specialKeyCodeMap[$e.which || $e.keyCode];
1386
+ this._managePreventDefault(keyName, $e);
1387
+ if (keyName && this._shouldTrigger(keyName, $e)) {
1388
+ this.trigger(keyName + "Keyed", $e);
1389
+ }
1390
+ },
1391
+ _onInput: function onInput() {
1392
+ this._setQuery(this.getInputValue());
1393
+ this.clearHintIfInvalid();
1394
+ this._checkLanguageDirection();
1395
+ },
1396
+ _managePreventDefault: function managePreventDefault(keyName, $e) {
1397
+ var preventDefault;
1398
+ switch (keyName) {
1399
+ case "up":
1400
+ case "down":
1401
+ preventDefault = !withModifier($e);
1402
+ break;
1403
+
1404
+ default:
1405
+ preventDefault = false;
1406
+ }
1407
+ preventDefault && $e.preventDefault();
1408
+ },
1409
+ _shouldTrigger: function shouldTrigger(keyName, $e) {
1410
+ var trigger;
1411
+ switch (keyName) {
1412
+ case "tab":
1413
+ trigger = !withModifier($e);
1414
+ break;
1415
+
1416
+ default:
1417
+ trigger = true;
1418
+ }
1419
+ return trigger;
1420
+ },
1421
+ _checkLanguageDirection: function checkLanguageDirection() {
1422
+ var dir = (this.$input.css("direction") || "ltr").toLowerCase();
1423
+ if (this.dir !== dir) {
1424
+ this.dir = dir;
1425
+ this.$hint.attr("dir", dir);
1426
+ this.trigger("langDirChanged", dir);
1427
+ }
1428
+ },
1429
+ _setQuery: function setQuery(val, silent) {
1430
+ var areEquivalent, hasDifferentWhitespace;
1431
+ areEquivalent = areQueriesEquivalent(val, this.query);
1432
+ hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false;
1433
+ this.query = val;
1434
+ if (!silent && !areEquivalent) {
1435
+ this.trigger("queryChanged", this.query);
1436
+ } else if (!silent && hasDifferentWhitespace) {
1437
+ this.trigger("whitespaceChanged", this.query);
1438
+ }
1439
+ },
1440
+ bind: function() {
1441
+ var that = this, onBlur, onFocus, onKeydown, onInput;
1442
+ onBlur = _.bind(this._onBlur, this);
1443
+ onFocus = _.bind(this._onFocus, this);
1444
+ onKeydown = _.bind(this._onKeydown, this);
1445
+ onInput = _.bind(this._onInput, this);
1446
+ this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown);
1447
+ if (!_.isMsie() || _.isMsie() > 9) {
1448
+ this.$input.on("input.tt", onInput);
1449
+ } else {
1450
+ this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) {
1451
+ if (specialKeyCodeMap[$e.which || $e.keyCode]) {
1452
+ return;
1453
+ }
1454
+ _.defer(_.bind(that._onInput, that, $e));
1455
+ });
1456
+ }
1457
+ return this;
1458
+ },
1459
+ focus: function focus() {
1460
+ this.$input.focus();
1461
+ },
1462
+ blur: function blur() {
1463
+ this.$input.blur();
1464
+ },
1465
+ getLangDir: function getLangDir() {
1466
+ return this.dir;
1467
+ },
1468
+ getQuery: function getQuery() {
1469
+ return this.query || "";
1470
+ },
1471
+ setQuery: function setQuery(val, silent) {
1472
+ this.setInputValue(val);
1473
+ this._setQuery(val, silent);
1474
+ },
1475
+ hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() {
1476
+ return this.query !== this.queryWhenFocused;
1477
+ },
1478
+ getInputValue: function getInputValue() {
1479
+ return this.$input.val();
1480
+ },
1481
+ setInputValue: function setInputValue(value) {
1482
+ this.$input.val(value);
1483
+ this.clearHintIfInvalid();
1484
+ this._checkLanguageDirection();
1485
+ },
1486
+ resetInputValue: function resetInputValue() {
1487
+ this.setInputValue(this.query);
1488
+ },
1489
+ getHint: function getHint() {
1490
+ return this.$hint.val();
1491
+ },
1492
+ setHint: function setHint(value) {
1493
+ this.$hint.val(value);
1494
+ },
1495
+ clearHint: function clearHint() {
1496
+ this.setHint("");
1497
+ },
1498
+ clearHintIfInvalid: function clearHintIfInvalid() {
1499
+ var val, hint, valIsPrefixOfHint, isValid;
1500
+ val = this.getInputValue();
1501
+ hint = this.getHint();
1502
+ valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0;
1503
+ isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow();
1504
+ !isValid && this.clearHint();
1505
+ },
1506
+ hasFocus: function hasFocus() {
1507
+ return this.$input.is(":focus");
1508
+ },
1509
+ hasOverflow: function hasOverflow() {
1510
+ var constraint = this.$input.width() - 2;
1511
+ this.$overflowHelper.text(this.getInputValue());
1512
+ return this.$overflowHelper.width() >= constraint;
1513
+ },
1514
+ isCursorAtEnd: function() {
1515
+ var valueLength, selectionStart, range;
1516
+ valueLength = this.$input.val().length;
1517
+ selectionStart = this.$input[0].selectionStart;
1518
+ if (_.isNumber(selectionStart)) {
1519
+ return selectionStart === valueLength;
1520
+ } else if (document.selection) {
1521
+ range = document.selection.createRange();
1522
+ range.moveStart("character", -valueLength);
1523
+ return valueLength === range.text.length;
1524
+ }
1525
+ return true;
1526
+ },
1527
+ destroy: function destroy() {
1528
+ this.$hint.off(".tt");
1529
+ this.$input.off(".tt");
1530
+ this.$overflowHelper.remove();
1531
+ this.$hint = this.$input = this.$overflowHelper = $("<div>");
1532
+ }
1533
+ });
1534
+ return Input;
1535
+ function buildOverflowHelper($input) {
1536
+ return $('<pre aria-hidden="true"></pre>').css({
1537
+ position: "absolute",
1538
+ visibility: "hidden",
1539
+ whiteSpace: "pre",
1540
+ fontFamily: $input.css("font-family"),
1541
+ fontSize: $input.css("font-size"),
1542
+ fontStyle: $input.css("font-style"),
1543
+ fontVariant: $input.css("font-variant"),
1544
+ fontWeight: $input.css("font-weight"),
1545
+ wordSpacing: $input.css("word-spacing"),
1546
+ letterSpacing: $input.css("letter-spacing"),
1547
+ textIndent: $input.css("text-indent"),
1548
+ textRendering: $input.css("text-rendering"),
1549
+ textTransform: $input.css("text-transform")
1550
+ }).insertAfter($input);
1551
+ }
1552
+ function areQueriesEquivalent(a, b) {
1553
+ return Input.normalizeQuery(a) === Input.normalizeQuery(b);
1554
+ }
1555
+ function withModifier($e) {
1556
+ return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey;
1557
+ }
1558
+ }();
1559
+ var Dataset = function() {
1560
+ "use strict";
1561
+ var keys, nameGenerator;
1562
+ keys = {
1563
+ val: "tt-selectable-display",
1564
+ obj: "tt-selectable-object"
1565
+ };
1566
+ nameGenerator = _.getIdGenerator();
1567
+ function Dataset(o, www) {
1568
+ o = o || {};
1569
+ o.templates = o.templates || {};
1570
+ o.templates.notFound = o.templates.notFound || o.templates.empty;
1571
+ if (!o.source) {
1572
+ $.error("missing source");
1573
+ }
1574
+ if (!o.node) {
1575
+ $.error("missing node");
1576
+ }
1577
+ if (o.name && !isValidName(o.name)) {
1578
+ $.error("invalid dataset name: " + o.name);
1579
+ }
1580
+ www.mixin(this);
1581
+ this.highlight = !!o.highlight;
1582
+ this.name = o.name || nameGenerator();
1583
+ this.limit = o.limit || 5;
1584
+ this.displayFn = getDisplayFn(o.display || o.displayKey);
1585
+ this.templates = getTemplates(o.templates, this.displayFn);
1586
+ this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source;
1587
+ this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async;
1588
+ this._resetLastSuggestion();
1589
+ this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name);
1590
+ }
1591
+ Dataset.extractData = function extractData(el) {
1592
+ var $el = $(el);
1593
+ if ($el.data(keys.obj)) {
1594
+ return {
1595
+ val: $el.data(keys.val) || "",
1596
+ obj: $el.data(keys.obj) || null
1597
+ };
1598
+ }
1599
+ return null;
1600
+ };
1601
+ _.mixin(Dataset.prototype, EventEmitter, {
1602
+ _overwrite: function overwrite(query, suggestions) {
1603
+ suggestions = suggestions || [];
1604
+ if (suggestions.length) {
1605
+ this._renderSuggestions(query, suggestions);
1606
+ } else if (this.async && this.templates.pending) {
1607
+ this._renderPending(query);
1608
+ } else if (!this.async && this.templates.notFound) {
1609
+ this._renderNotFound(query);
1610
+ } else {
1611
+ this._empty();
1612
+ }
1613
+ this.trigger("rendered", this.name, suggestions, false);
1614
+ },
1615
+ _append: function append(query, suggestions) {
1616
+ suggestions = suggestions || [];
1617
+ if (suggestions.length && this.$lastSuggestion.length) {
1618
+ this._appendSuggestions(query, suggestions);
1619
+ } else if (suggestions.length) {
1620
+ this._renderSuggestions(query, suggestions);
1621
+ } else if (!this.$lastSuggestion.length && this.templates.notFound) {
1622
+ this._renderNotFound(query);
1623
+ }
1624
+ this.trigger("rendered", this.name, suggestions, true);
1625
+ },
1626
+ _renderSuggestions: function renderSuggestions(query, suggestions) {
1627
+ var $fragment;
1628
+ $fragment = this._getSuggestionsFragment(query, suggestions);
1629
+ this.$lastSuggestion = $fragment.children().last();
1630
+ this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions));
1631
+ },
1632
+ _appendSuggestions: function appendSuggestions(query, suggestions) {
1633
+ var $fragment, $lastSuggestion;
1634
+ $fragment = this._getSuggestionsFragment(query, suggestions);
1635
+ $lastSuggestion = $fragment.children().last();
1636
+ this.$lastSuggestion.after($fragment);
1637
+ this.$lastSuggestion = $lastSuggestion;
1638
+ },
1639
+ _renderPending: function renderPending(query) {
1640
+ var template = this.templates.pending;
1641
+ this._resetLastSuggestion();
1642
+ template && this.$el.html(template({
1643
+ query: query,
1644
+ dataset: this.name
1645
+ }));
1646
+ },
1647
+ _renderNotFound: function renderNotFound(query) {
1648
+ var template = this.templates.notFound;
1649
+ this._resetLastSuggestion();
1650
+ template && this.$el.html(template({
1651
+ query: query,
1652
+ dataset: this.name
1653
+ }));
1654
+ },
1655
+ _empty: function empty() {
1656
+ this.$el.empty();
1657
+ this._resetLastSuggestion();
1658
+ },
1659
+ _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) {
1660
+ var that = this, fragment;
1661
+ fragment = document.createDocumentFragment();
1662
+ _.each(suggestions, function getSuggestionNode(suggestion) {
1663
+ var $el, context;
1664
+ context = that._injectQuery(query, suggestion);
1665
+ $el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable);
1666
+ fragment.appendChild($el[0]);
1667
+ });
1668
+ this.highlight && highlight({
1669
+ className: this.classes.highlight,
1670
+ node: fragment,
1671
+ pattern: query
1672
+ });
1673
+ return $(fragment);
1674
+ },
1675
+ _getFooter: function getFooter(query, suggestions) {
1676
+ return this.templates.footer ? this.templates.footer({
1677
+ query: query,
1678
+ suggestions: suggestions,
1679
+ dataset: this.name
1680
+ }) : null;
1681
+ },
1682
+ _getHeader: function getHeader(query, suggestions) {
1683
+ return this.templates.header ? this.templates.header({
1684
+ query: query,
1685
+ suggestions: suggestions,
1686
+ dataset: this.name
1687
+ }) : null;
1688
+ },
1689
+ _resetLastSuggestion: function resetLastSuggestion() {
1690
+ this.$lastSuggestion = $();
1691
+ },
1692
+ _injectQuery: function injectQuery(query, obj) {
1693
+ return _.isObject(obj) ? _.mixin({
1694
+ _query: query
1695
+ }, obj) : obj;
1696
+ },
1697
+ update: function update(query) {
1698
+ var that = this, canceled = false, syncCalled = false, rendered = 0;
1699
+ this.cancel();
1700
+ this.cancel = function cancel() {
1701
+ canceled = true;
1702
+ that.cancel = $.noop;
1703
+ that.async && that.trigger("asyncCanceled", query);
1704
+ };
1705
+ this.source(query, sync, async);
1706
+ !syncCalled && sync([]);
1707
+ function sync(suggestions) {
1708
+ if (syncCalled) {
1709
+ return;
1710
+ }
1711
+ syncCalled = true;
1712
+ suggestions = (suggestions || []).slice(0, that.limit);
1713
+ rendered = suggestions.length;
1714
+ that._overwrite(query, suggestions);
1715
+ if (rendered < that.limit && that.async) {
1716
+ that.trigger("asyncRequested", query);
1717
+ }
1718
+ }
1719
+ function async(suggestions) {
1720
+ suggestions = suggestions || [];
1721
+ if (!canceled && rendered < that.limit) {
1722
+ that.cancel = $.noop;
1723
+ rendered += suggestions.length;
1724
+ that._append(query, suggestions.slice(0, that.limit - rendered));
1725
+ that.async && that.trigger("asyncReceived", query);
1726
+ }
1727
+ }
1728
+ },
1729
+ cancel: $.noop,
1730
+ clear: function clear() {
1731
+ this._empty();
1732
+ this.cancel();
1733
+ this.trigger("cleared");
1734
+ },
1735
+ isEmpty: function isEmpty() {
1736
+ return this.$el.is(":empty");
1737
+ },
1738
+ destroy: function destroy() {
1739
+ this.$el = $("<div>");
1740
+ }
1741
+ });
1742
+ return Dataset;
1743
+ function getDisplayFn(display) {
1744
+ display = display || _.stringify;
1745
+ return _.isFunction(display) ? display : displayFn;
1746
+ function displayFn(obj) {
1747
+ return obj[display];
1748
+ }
1749
+ }
1750
+ function getTemplates(templates, displayFn) {
1751
+ return {
1752
+ notFound: templates.notFound && _.templatify(templates.notFound),
1753
+ pending: templates.pending && _.templatify(templates.pending),
1754
+ header: templates.header && _.templatify(templates.header),
1755
+ footer: templates.footer && _.templatify(templates.footer),
1756
+ suggestion: templates.suggestion || suggestionTemplate
1757
+ };
1758
+ function suggestionTemplate(context) {
1759
+ return $("<div>").text(displayFn(context));
1760
+ }
1761
+ }
1762
+ function isValidName(str) {
1763
+ return /^[_a-zA-Z0-9-]+$/.test(str);
1764
+ }
1765
+ }();
1766
+ var Menu = function() {
1767
+ "use strict";
1768
+ function Menu(o, www) {
1769
+ var that = this;
1770
+ o = o || {};
1771
+ if (!o.node) {
1772
+ $.error("node is required");
1773
+ }
1774
+ www.mixin(this);
1775
+ this.$node = $(o.node);
1776
+ this.query = null;
1777
+ this.datasets = _.map(o.datasets, initializeDataset);
1778
+ function initializeDataset(oDataset) {
1779
+ var node = that.$node.find(oDataset.node).first();
1780
+ oDataset.node = node.length ? node : $("<div>").appendTo(that.$node);
1781
+ return new Dataset(oDataset, www);
1782
+ }
1783
+ }
1784
+ _.mixin(Menu.prototype, EventEmitter, {
1785
+ _onSelectableClick: function onSelectableClick($e) {
1786
+ this.trigger("selectableClicked", $($e.currentTarget));
1787
+ },
1788
+ _onRendered: function onRendered(type, dataset, suggestions, async) {
1789
+ this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
1790
+ this.trigger("datasetRendered", dataset, suggestions, async);
1791
+ },
1792
+ _onCleared: function onCleared() {
1793
+ this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
1794
+ this.trigger("datasetCleared");
1795
+ },
1796
+ _propagate: function propagate() {
1797
+ this.trigger.apply(this, arguments);
1798
+ },
1799
+ _allDatasetsEmpty: function allDatasetsEmpty() {
1800
+ return _.every(this.datasets, isDatasetEmpty);
1801
+ function isDatasetEmpty(dataset) {
1802
+ return dataset.isEmpty();
1803
+ }
1804
+ },
1805
+ _getSelectables: function getSelectables() {
1806
+ return this.$node.find(this.selectors.selectable);
1807
+ },
1808
+ _removeCursor: function _removeCursor() {
1809
+ var $selectable = this.getActiveSelectable();
1810
+ $selectable && $selectable.removeClass(this.classes.cursor);
1811
+ },
1812
+ _ensureVisible: function ensureVisible($el) {
1813
+ var elTop, elBottom, nodeScrollTop, nodeHeight;
1814
+ elTop = $el.position().top;
1815
+ elBottom = elTop + $el.outerHeight(true);
1816
+ nodeScrollTop = this.$node.scrollTop();
1817
+ nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10);
1818
+ if (elTop < 0) {
1819
+ this.$node.scrollTop(nodeScrollTop + elTop);
1820
+ } else if (nodeHeight < elBottom) {
1821
+ this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight));
1822
+ }
1823
+ },
1824
+ bind: function() {
1825
+ var that = this, onSelectableClick;
1826
+ onSelectableClick = _.bind(this._onSelectableClick, this);
1827
+ this.$node.on("click.tt", this.selectors.selectable, onSelectableClick);
1828
+ _.each(this.datasets, function(dataset) {
1829
+ 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);
1830
+ });
1831
+ return this;
1832
+ },
1833
+ isOpen: function isOpen() {
1834
+ return this.$node.hasClass(this.classes.open);
1835
+ },
1836
+ open: function open() {
1837
+ this.$node.addClass(this.classes.open);
1838
+ },
1839
+ close: function close() {
1840
+ this.$node.removeClass(this.classes.open);
1841
+ this._removeCursor();
1842
+ },
1843
+ setLanguageDirection: function setLanguageDirection(dir) {
1844
+ this.$node.attr("dir", dir);
1845
+ },
1846
+ selectableRelativeToCursor: function selectableRelativeToCursor(delta) {
1847
+ var $selectables, $oldCursor, oldIndex, newIndex;
1848
+ $oldCursor = this.getActiveSelectable();
1849
+ $selectables = this._getSelectables();
1850
+ oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1;
1851
+ newIndex = oldIndex + delta;
1852
+ newIndex = (newIndex + 1) % ($selectables.length + 1) - 1;
1853
+ newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex;
1854
+ return newIndex === -1 ? null : $selectables.eq(newIndex);
1855
+ },
1856
+ setCursor: function setCursor($selectable) {
1857
+ this._removeCursor();
1858
+ if ($selectable = $selectable && $selectable.first()) {
1859
+ $selectable.addClass(this.classes.cursor);
1860
+ this._ensureVisible($selectable);
1861
+ }
1862
+ },
1863
+ getSelectableData: function getSelectableData($el) {
1864
+ return $el && $el.length ? Dataset.extractData($el) : null;
1865
+ },
1866
+ getActiveSelectable: function getActiveSelectable() {
1867
+ var $selectable = this._getSelectables().filter(this.selectors.cursor).first();
1868
+ return $selectable.length ? $selectable : null;
1869
+ },
1870
+ getTopSelectable: function getTopSelectable() {
1871
+ var $selectable = this._getSelectables().first();
1872
+ return $selectable.length ? $selectable : null;
1873
+ },
1874
+ update: function update(query) {
1875
+ var isValidUpdate = query !== this.query;
1876
+ if (isValidUpdate) {
1877
+ this.query = query;
1878
+ _.each(this.datasets, updateDataset);
1879
+ }
1880
+ return isValidUpdate;
1881
+ function updateDataset(dataset) {
1882
+ dataset.update(query);
1883
+ }
1884
+ },
1885
+ empty: function empty() {
1886
+ _.each(this.datasets, clearDataset);
1887
+ this.query = null;
1888
+ this.$node.addClass(this.classes.empty);
1889
+ function clearDataset(dataset) {
1890
+ dataset.clear();
1891
+ }
1892
+ },
1893
+ destroy: function destroy() {
1894
+ this.$node.off(".tt");
1895
+ this.$node = $("<div>");
1896
+ _.each(this.datasets, destroyDataset);
1897
+ function destroyDataset(dataset) {
1898
+ dataset.destroy();
1899
+ }
1900
+ }
1901
+ });
1902
+ return Menu;
1903
+ }();
1904
+ var DefaultMenu = function() {
1905
+ "use strict";
1906
+ var s = Menu.prototype;
1907
+ function DefaultMenu() {
1908
+ Menu.apply(this, [].slice.call(arguments, 0));
1909
+ }
1910
+ _.mixin(DefaultMenu.prototype, Menu.prototype, {
1911
+ open: function open() {
1912
+ !this._allDatasetsEmpty() && this._show();
1913
+ return s.open.apply(this, [].slice.call(arguments, 0));
1914
+ },
1915
+ close: function close() {
1916
+ this._hide();
1917
+ return s.close.apply(this, [].slice.call(arguments, 0));
1918
+ },
1919
+ _onRendered: function onRendered() {
1920
+ if (this._allDatasetsEmpty()) {
1921
+ this._hide();
1922
+ } else {
1923
+ this.isOpen() && this._show();
1924
+ }
1925
+ return s._onRendered.apply(this, [].slice.call(arguments, 0));
1926
+ },
1927
+ _onCleared: function onCleared() {
1928
+ if (this._allDatasetsEmpty()) {
1929
+ this._hide();
1930
+ } else {
1931
+ this.isOpen() && this._show();
1932
+ }
1933
+ return s._onCleared.apply(this, [].slice.call(arguments, 0));
1934
+ },
1935
+ setLanguageDirection: function setLanguageDirection(dir) {
1936
+ this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl);
1937
+ return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0));
1938
+ },
1939
+ _hide: function hide() {
1940
+ this.$node.hide();
1941
+ },
1942
+ _show: function show() {
1943
+ this.$node.css("display", "block");
1944
+ }
1945
+ });
1946
+ return DefaultMenu;
1947
+ }();
1948
+ var Typeahead = function() {
1949
+ "use strict";
1950
+ function Typeahead(o, www) {
1951
+ var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged;
1952
+ o = o || {};
1953
+ if (!o.input) {
1954
+ $.error("missing input");
1955
+ }
1956
+ if (!o.menu) {
1957
+ $.error("missing menu");
1958
+ }
1959
+ if (!o.eventBus) {
1960
+ $.error("missing event bus");
1961
+ }
1962
+ www.mixin(this);
1963
+ this.eventBus = o.eventBus;
1964
+ this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;
1965
+ this.input = o.input;
1966
+ this.menu = o.menu;
1967
+ this.enabled = true;
1968
+ this.active = false;
1969
+ this.input.hasFocus() && this.activate();
1970
+ this.dir = this.input.getLangDir();
1971
+ this._hacks();
1972
+ 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);
1973
+ onFocused = c(this, "activate", "open", "_onFocused");
1974
+ onBlurred = c(this, "deactivate", "_onBlurred");
1975
+ onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed");
1976
+ onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed");
1977
+ onEscKeyed = c(this, "isActive", "_onEscKeyed");
1978
+ onUpKeyed = c(this, "isActive", "open", "_onUpKeyed");
1979
+ onDownKeyed = c(this, "isActive", "open", "_onDownKeyed");
1980
+ onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed");
1981
+ onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed");
1982
+ onQueryChanged = c(this, "_openIfActive", "_onQueryChanged");
1983
+ onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged");
1984
+ 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);
1985
+ }
1986
+ _.mixin(Typeahead.prototype, {
1987
+ _hacks: function hacks() {
1988
+ var $input, $menu;
1989
+ $input = this.input.$input || $("<div>");
1990
+ $menu = this.menu.$node || $("<div>");
1991
+ $input.on("blur.tt", function($e) {
1992
+ var active, isActive, hasActive;
1993
+ active = document.activeElement;
1994
+ isActive = $menu.is(active);
1995
+ hasActive = $menu.has(active).length > 0;
1996
+ if (_.isMsie() && (isActive || hasActive)) {
1997
+ $e.preventDefault();
1998
+ $e.stopImmediatePropagation();
1999
+ _.defer(function() {
2000
+ $input.focus();
2001
+ });
2002
+ }
2003
+ });
2004
+ $menu.on("mousedown.tt", function($e) {
2005
+ $e.preventDefault();
2006
+ });
2007
+ },
2008
+ _onSelectableClicked: function onSelectableClicked(type, $el) {
2009
+ this.select($el);
2010
+ },
2011
+ _onDatasetCleared: function onDatasetCleared() {
2012
+ this._updateHint();
2013
+ },
2014
+ _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) {
2015
+ this._updateHint();
2016
+ this.eventBus.trigger("render", suggestions, async, dataset);
2017
+ },
2018
+ _onAsyncRequested: function onAsyncRequested(type, dataset, query) {
2019
+ this.eventBus.trigger("asyncrequest", query, dataset);
2020
+ },
2021
+ _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) {
2022
+ this.eventBus.trigger("asynccancel", query, dataset);
2023
+ },
2024
+ _onAsyncReceived: function onAsyncReceived(type, dataset, query) {
2025
+ this.eventBus.trigger("asyncreceive", query, dataset);
2026
+ },
2027
+ _onFocused: function onFocused() {
2028
+ this._minLengthMet() && this.menu.update(this.input.getQuery());
2029
+ },
2030
+ _onBlurred: function onBlurred() {
2031
+ if (this.input.hasQueryChangedSinceLastFocus()) {
2032
+ this.eventBus.trigger("change", this.input.getQuery());
2033
+ }
2034
+ },
2035
+ _onEnterKeyed: function onEnterKeyed(type, $e) {
2036
+ var $selectable;
2037
+ if ($selectable = this.menu.getActiveSelectable()) {
2038
+ this.select($selectable) && $e.preventDefault();
2039
+ }
2040
+ },
2041
+ _onTabKeyed: function onTabKeyed(type, $e) {
2042
+ var $selectable;
2043
+ if ($selectable = this.menu.getActiveSelectable()) {
2044
+ this.select($selectable) && $e.preventDefault();
2045
+ } else if ($selectable = this.menu.getTopSelectable()) {
2046
+ this.autocomplete($selectable) && $e.preventDefault();
2047
+ }
2048
+ },
2049
+ _onEscKeyed: function onEscKeyed() {
2050
+ this.close();
2051
+ },
2052
+ _onUpKeyed: function onUpKeyed() {
2053
+ this.moveCursor(-1);
2054
+ },
2055
+ _onDownKeyed: function onDownKeyed() {
2056
+ this.moveCursor(+1);
2057
+ },
2058
+ _onLeftKeyed: function onLeftKeyed() {
2059
+ if (this.dir === "rtl" && this.input.isCursorAtEnd()) {
2060
+ this.autocomplete(this.menu.getTopSelectable());
2061
+ }
2062
+ },
2063
+ _onRightKeyed: function onRightKeyed() {
2064
+ if (this.dir === "ltr" && this.input.isCursorAtEnd()) {
2065
+ this.autocomplete(this.menu.getTopSelectable());
2066
+ }
2067
+ },
2068
+ _onQueryChanged: function onQueryChanged(e, query) {
2069
+ this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty();
2070
+ },
2071
+ _onWhitespaceChanged: function onWhitespaceChanged() {
2072
+ this._updateHint();
2073
+ },
2074
+ _onLangDirChanged: function onLangDirChanged(e, dir) {
2075
+ if (this.dir !== dir) {
2076
+ this.dir = dir;
2077
+ this.menu.setLanguageDirection(dir);
2078
+ }
2079
+ },
2080
+ _openIfActive: function openIfActive() {
2081
+ this.isActive() && this.open();
2082
+ },
2083
+ _minLengthMet: function minLengthMet(query) {
2084
+ query = _.isString(query) ? query : this.input.getQuery() || "";
2085
+ return query.length >= this.minLength;
2086
+ },
2087
+ _updateHint: function updateHint() {
2088
+ var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match;
2089
+ $selectable = this.menu.getTopSelectable();
2090
+ data = this.menu.getSelectableData($selectable);
2091
+ val = this.input.getInputValue();
2092
+ if (data && !_.isBlankString(val) && !this.input.hasOverflow()) {
2093
+ query = Input.normalizeQuery(val);
2094
+ escapedQuery = _.escapeRegExChars(query);
2095
+ frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i");
2096
+ match = frontMatchRegEx.exec(data.val);
2097
+ match && this.input.setHint(val + match[1]);
2098
+ } else {
2099
+ this.input.clearHint();
2100
+ }
2101
+ },
2102
+ isEnabled: function isEnabled() {
2103
+ return this.enabled;
2104
+ },
2105
+ enable: function enable() {
2106
+ this.enabled = true;
2107
+ },
2108
+ disable: function disable() {
2109
+ this.enabled = false;
2110
+ },
2111
+ isActive: function isActive() {
2112
+ return this.active;
2113
+ },
2114
+ activate: function activate() {
2115
+ if (this.isActive()) {
2116
+ return true;
2117
+ } else if (!this.isEnabled() || this.eventBus.before("active")) {
2118
+ return false;
2119
+ } else {
2120
+ this.active = true;
2121
+ this.eventBus.trigger("active");
2122
+ return true;
2123
+ }
2124
+ },
2125
+ deactivate: function deactivate() {
2126
+ if (!this.isActive()) {
2127
+ return true;
2128
+ } else if (this.eventBus.before("idle")) {
2129
+ return false;
2130
+ } else {
2131
+ this.active = false;
2132
+ this.close();
2133
+ this.eventBus.trigger("idle");
2134
+ return true;
2135
+ }
2136
+ },
2137
+ isOpen: function isOpen() {
2138
+ return this.menu.isOpen();
2139
+ },
2140
+ open: function open() {
2141
+ if (!this.isOpen() && !this.eventBus.before("open")) {
2142
+ this.menu.open();
2143
+ this._updateHint();
2144
+ this.eventBus.trigger("open");
2145
+ }
2146
+ return this.isOpen();
2147
+ },
2148
+ close: function close() {
2149
+ if (this.isOpen() && !this.eventBus.before("close")) {
2150
+ this.menu.close();
2151
+ this.input.clearHint();
2152
+ this.input.resetInputValue();
2153
+ this.eventBus.trigger("close");
2154
+ }
2155
+ return !this.isOpen();
2156
+ },
2157
+ setVal: function setVal(val) {
2158
+ this.input.setQuery(_.toStr(val));
2159
+ },
2160
+ getVal: function getVal() {
2161
+ return this.input.getQuery();
2162
+ },
2163
+ select: function select($selectable) {
2164
+ var data = this.menu.getSelectableData($selectable);
2165
+ if (data && !this.eventBus.before("select", data.obj)) {
2166
+ this.input.setQuery(data.val, true);
2167
+ this.eventBus.trigger("select", data.obj);
2168
+ this.close();
2169
+ return true;
2170
+ }
2171
+ return false;
2172
+ },
2173
+ autocomplete: function autocomplete($selectable) {
2174
+ var query, data, isValid;
2175
+ query = this.input.getQuery();
2176
+ data = this.menu.getSelectableData($selectable);
2177
+ isValid = data && query !== data.val;
2178
+ if (isValid && !this.eventBus.before("autocomplete", data.obj)) {
2179
+ this.input.setQuery(data.val);
2180
+ this.eventBus.trigger("autocomplete", data.obj);
2181
+ return true;
2182
+ }
2183
+ return false;
2184
+ },
2185
+ moveCursor: function moveCursor(delta) {
2186
+ var query, $candidate, data, payload, cancelMove;
2187
+ query = this.input.getQuery();
2188
+ $candidate = this.menu.selectableRelativeToCursor(delta);
2189
+ data = this.menu.getSelectableData($candidate);
2190
+ payload = data ? data.obj : null;
2191
+ cancelMove = this._minLengthMet() && this.menu.update(query);
2192
+ if (!cancelMove && !this.eventBus.before("cursorchange", payload)) {
2193
+ this.menu.setCursor($candidate);
2194
+ if (data) {
2195
+ this.input.setInputValue(data.val);
2196
+ } else {
2197
+ this.input.resetInputValue();
2198
+ this._updateHint();
2199
+ }
2200
+ this.eventBus.trigger("cursorchange", payload);
2201
+ return true;
2202
+ }
2203
+ return false;
2204
+ },
2205
+ destroy: function destroy() {
2206
+ this.input.destroy();
2207
+ this.menu.destroy();
2208
+ }
2209
+ });
2210
+ return Typeahead;
2211
+ function c(ctx) {
2212
+ var methods = [].slice.call(arguments, 1);
2213
+ return function() {
2214
+ var args = [].slice.call(arguments);
2215
+ _.each(methods, function(method) {
2216
+ return ctx[method].apply(ctx, args);
2217
+ });
2218
+ };
2219
+ }
2220
+ }();
2221
+ (function() {
2222
+ "use strict";
2223
+ var old, keys, methods;
2224
+ old = $.fn.typeahead;
2225
+ keys = {
2226
+ www: "tt-www",
2227
+ attrs: "tt-attrs",
2228
+ typeahead: "tt-typeahead"
2229
+ };
2230
+ methods = {
2231
+ initialize: function initialize(o, datasets) {
2232
+ var www;
2233
+ datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1);
2234
+ o = o || {};
2235
+ www = WWW(o.classNames);
2236
+ return this.each(attach);
2237
+ function attach() {
2238
+ var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor;
2239
+ _.each(datasets, function(d) {
2240
+ d.highlight = !!o.highlight;
2241
+ });
2242
+ $input = $(this);
2243
+ $wrapper = $(www.html.wrapper);
2244
+ $hint = $elOrNull(o.hint);
2245
+ $menu = $elOrNull(o.menu);
2246
+ defaultHint = o.hint !== false && !$hint;
2247
+ defaultMenu = o.menu !== false && !$menu;
2248
+ defaultHint && ($hint = buildHintFromInput($input, www));
2249
+ defaultMenu && ($menu = $(www.html.menu).css(www.css.menu));
2250
+ $hint && $hint.val("");
2251
+ $input = prepInput($input, www);
2252
+ if (defaultHint || defaultMenu) {
2253
+ $wrapper.css(www.css.wrapper);
2254
+ $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint);
2255
+ $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null);
2256
+ }
2257
+ MenuConstructor = defaultMenu ? DefaultMenu : Menu;
2258
+ eventBus = new EventBus({
2259
+ el: $input
2260
+ });
2261
+ input = new Input({
2262
+ hint: $hint,
2263
+ input: $input
2264
+ }, www);
2265
+ menu = new MenuConstructor({
2266
+ node: $menu,
2267
+ datasets: datasets
2268
+ }, www);
2269
+ typeahead = new Typeahead({
2270
+ input: input,
2271
+ menu: menu,
2272
+ eventBus: eventBus,
2273
+ minLength: o.minLength
2274
+ }, www);
2275
+ $input.data(keys.www, www);
2276
+ $input.data(keys.typeahead, typeahead);
2277
+ }
2278
+ },
2279
+ isEnabled: function isEnabled() {
2280
+ var enabled;
2281
+ ttEach(this.first(), function(t) {
2282
+ enabled = t.isEnabled();
2283
+ });
2284
+ return enabled;
2285
+ },
2286
+ enable: function enable() {
2287
+ ttEach(this, function(t) {
2288
+ t.enable();
2289
+ });
2290
+ return this;
2291
+ },
2292
+ disable: function disable() {
2293
+ ttEach(this, function(t) {
2294
+ t.disable();
2295
+ });
2296
+ return this;
2297
+ },
2298
+ isActive: function isActive() {
2299
+ var active;
2300
+ ttEach(this.first(), function(t) {
2301
+ active = t.isActive();
2302
+ });
2303
+ return active;
2304
+ },
2305
+ activate: function activate() {
2306
+ ttEach(this, function(t) {
2307
+ t.activate();
2308
+ });
2309
+ return this;
2310
+ },
2311
+ deactivate: function deactivate() {
2312
+ ttEach(this, function(t) {
2313
+ t.deactivate();
2314
+ });
2315
+ return this;
2316
+ },
2317
+ isOpen: function isOpen() {
2318
+ var open;
2319
+ ttEach(this.first(), function(t) {
2320
+ open = t.isOpen();
2321
+ });
2322
+ return open;
2323
+ },
2324
+ open: function open() {
2325
+ ttEach(this, function(t) {
2326
+ t.open();
2327
+ });
2328
+ return this;
2329
+ },
2330
+ close: function close() {
2331
+ ttEach(this, function(t) {
2332
+ t.close();
2333
+ });
2334
+ return this;
2335
+ },
2336
+ select: function select(el) {
2337
+ var success = false, $el = $(el);
2338
+ ttEach(this.first(), function(t) {
2339
+ success = t.select($el);
2340
+ });
2341
+ return success;
2342
+ },
2343
+ autocomplete: function autocomplete(el) {
2344
+ var success = false, $el = $(el);
2345
+ ttEach(this.first(), function(t) {
2346
+ success = t.autocomplete($el);
2347
+ });
2348
+ return success;
2349
+ },
2350
+ moveCursor: function moveCursoe(delta) {
2351
+ var success = false;
2352
+ ttEach(this.first(), function(t) {
2353
+ success = t.moveCursor(delta);
2354
+ });
2355
+ return success;
2356
+ },
2357
+ val: function val(newVal) {
2358
+ var query;
2359
+ if (!arguments.length) {
2360
+ ttEach(this.first(), function(t) {
2361
+ query = t.getVal();
2362
+ });
2363
+ return query;
2364
+ } else {
2365
+ ttEach(this, function(t) {
2366
+ t.setVal(newVal);
2367
+ });
2368
+ return this;
2369
+ }
2370
+ },
2371
+ destroy: function destroy() {
2372
+ ttEach(this, function(typeahead, $input) {
2373
+ revert($input);
2374
+ typeahead.destroy();
2375
+ });
2376
+ return this;
2377
+ }
2378
+ };
2379
+ $.fn.typeahead = function(method) {
2380
+ if (methods[method]) {
2381
+ return methods[method].apply(this, [].slice.call(arguments, 1));
2382
+ } else {
2383
+ return methods.initialize.apply(this, arguments);
2384
+ }
2385
+ };
2386
+ $.fn.typeahead.noConflict = function noConflict() {
2387
+ $.fn.typeahead = old;
2388
+ return this;
2389
+ };
2390
+ function ttEach($els, fn) {
2391
+ $els.each(function() {
2392
+ var $input = $(this), typeahead;
2393
+ (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input);
2394
+ });
2395
+ }
2396
+ function buildHintFromInput($input, www) {
2397
+ return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({
2398
+ autocomplete: "off",
2399
+ spellcheck: "false",
2400
+ tabindex: -1
2401
+ });
2402
+ }
2403
+ function prepInput($input, www) {
2404
+ $input.data(keys.attrs, {
2405
+ dir: $input.attr("dir"),
2406
+ autocomplete: $input.attr("autocomplete"),
2407
+ spellcheck: $input.attr("spellcheck"),
2408
+ style: $input.attr("style")
2409
+ });
2410
+ $input.addClass(www.classes.input).attr({
2411
+ autocomplete: "off",
2412
+ spellcheck: false
2413
+ });
2414
+ try {
2415
+ !$input.attr("dir") && $input.attr("dir", "auto");
2416
+ } catch (e) {}
2417
+ return $input;
2418
+ }
2419
+ function getBackgroundStyles($el) {
2420
+ return {
2421
+ backgroundAttachment: $el.css("background-attachment"),
2422
+ backgroundClip: $el.css("background-clip"),
2423
+ backgroundColor: $el.css("background-color"),
2424
+ backgroundImage: $el.css("background-image"),
2425
+ backgroundOrigin: $el.css("background-origin"),
2426
+ backgroundPosition: $el.css("background-position"),
2427
+ backgroundRepeat: $el.css("background-repeat"),
2428
+ backgroundSize: $el.css("background-size")
2429
+ };
2430
+ }
2431
+ function revert($input) {
2432
+ var www, $wrapper;
2433
+ www = $input.data(keys.www);
2434
+ $wrapper = $input.parent().filter(www.selectors.wrapper);
2435
+ _.each($input.data(keys.attrs), function(val, key) {
2436
+ _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);
2437
+ });
2438
+ $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input);
2439
+ if ($wrapper.length) {
2440
+ $input.detach().insertAfter($wrapper);
2441
+ $wrapper.remove();
2442
+ }
2443
+ }
2444
+ function $elOrNull(obj) {
2445
+ var isValid, $el;
2446
+ isValid = _.isJQuery(obj) || _.isElement(obj);
2447
+ $el = isValid ? $(obj).first() : [];
2448
+ return $el.length ? $el : null;
2449
+ }
2450
+ })();
2451
+ });