git ssb

0+

k4ml / belajar-js



Commit cc2cba6868e87111234e740f9226b2c1abdfa3b3

added typeahead bundle

Kamal Mustafa committed on 3/18/2015, 7:43:52 AM
Parent: 571e76e3fe6dd53291195d3514920ca86f2dcebe

Files changed

javascripts/typeahead.bundle.jsadded
javascripts/typeahead.bundle.jsView
@@ -1,0 +1,1782 @@
1+/*!
2+ * typeahead.js 0.10.5
3+ * https://github.com/twitter/typeahead.js
4+ * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT
5+ */
6+
7+(function($) {
8+ var _ = function() {
9+ "use strict";
10+ return {
11+ isMsie: function() {
12+ return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
13+ },
14+ isBlankString: function(str) {
15+ return !str || /^\s*$/.test(str);
16+ },
17+ escapeRegExChars: function(str) {
18+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
19+ },
20+ isString: function(obj) {
21+ return typeof obj === "string";
22+ },
23+ isNumber: function(obj) {
24+ return typeof obj === "number";
25+ },
26+ isArray: $.isArray,
27+ isFunction: $.isFunction,
28+ isObject: $.isPlainObject,
29+ isUndefined: function(obj) {
30+ return typeof obj === "undefined";
31+ },
32+ toStr: function toStr(s) {
33+ return _.isUndefined(s) || s === null ? "" : s + "";
34+ },
35+ bind: $.proxy,
36+ each: function(collection, cb) {
37+ $.each(collection, reverseArgs);
38+ function reverseArgs(index, value) {
39+ return cb(value, index);
40+ }
41+ },
42+ map: $.map,
43+ filter: $.grep,
44+ every: function(obj, test) {
45+ var result = true;
46+ if (!obj) {
47+ return result;
48+ }
49+ $.each(obj, function(key, val) {
50+ if (!(result = test.call(null, val, key, obj))) {
51+ return false;
52+ }
53+ });
54+ return !!result;
55+ },
56+ some: function(obj, test) {
57+ var result = false;
58+ if (!obj) {
59+ return result;
60+ }
61+ $.each(obj, function(key, val) {
62+ if (result = test.call(null, val, key, obj)) {
63+ return false;
64+ }
65+ });
66+ return !!result;
67+ },
68+ mixin: $.extend,
69+ getUniqueId: function() {
70+ var counter = 0;
71+ return function() {
72+ return counter++;
73+ };
74+ }(),
75+ templatify: function templatify(obj) {
76+ return $.isFunction(obj) ? obj : template;
77+ function template() {
78+ return String(obj);
79+ }
80+ },
81+ defer: function(fn) {
82+ setTimeout(fn, 0);
83+ },
84+ debounce: function(func, wait, immediate) {
85+ var timeout, result;
86+ return function() {
87+ var context = this, args = arguments, later, callNow;
88+ later = function() {
89+ timeout = null;
90+ if (!immediate) {
91+ result = func.apply(context, args);
92+ }
93+ };
94+ callNow = immediate && !timeout;
95+ clearTimeout(timeout);
96+ timeout = setTimeout(later, wait);
97+ if (callNow) {
98+ result = func.apply(context, args);
99+ }
100+ return result;
101+ };
102+ },
103+ throttle: function(func, wait) {
104+ var context, args, timeout, result, previous, later;
105+ previous = 0;
106+ later = function() {
107+ previous = new Date();
108+ timeout = null;
109+ result = func.apply(context, args);
110+ };
111+ return function() {
112+ var now = new Date(), remaining = wait - (now - previous);
113+ context = this;
114+ args = arguments;
115+ if (remaining <= 0) {
116+ clearTimeout(timeout);
117+ timeout = null;
118+ previous = now;
119+ result = func.apply(context, args);
120+ } else if (!timeout) {
121+ timeout = setTimeout(later, remaining);
122+ }
123+ return result;
124+ };
125+ },
126+ noop: function() {}
127+ };
128+ }();
129+ var VERSION = "0.10.5";
130+ var tokenizers = function() {
131+ "use strict";
132+ return {
133+ nonword: nonword,
134+ whitespace: whitespace,
135+ obj: {
136+ nonword: getObjTokenizer(nonword),
137+ whitespace: getObjTokenizer(whitespace)
138+ }
139+ };
140+ function whitespace(str) {
141+ str = _.toStr(str);
142+ return str ? str.split(/\s+/) : [];
143+ }
144+ function nonword(str) {
145+ str = _.toStr(str);
146+ return str ? str.split(/\W+/) : [];
147+ }
148+ function getObjTokenizer(tokenizer) {
149+ return function setKey() {
150+ var args = [].slice.call(arguments, 0);
151+ return function tokenize(o) {
152+ var tokens = [];
153+ _.each(args, function(k) {
154+ tokens = tokens.concat(tokenizer(_.toStr(o[k])));
155+ });
156+ return tokens;
157+ };
158+ };
159+ }
160+ }();
161+ var LruCache = function() {
162+ "use strict";
163+ function LruCache(maxSize) {
164+ this.maxSize = _.isNumber(maxSize) ? maxSize : 100;
165+ this.reset();
166+ if (this.maxSize <= 0) {
167+ this.set = this.get = $.noop;
168+ }
169+ }
170+ _.mixin(LruCache.prototype, {
171+ set: function set(key, val) {
172+ var tailItem = this.list.tail, node;
173+ if (this.size >= this.maxSize) {
174+ this.list.remove(tailItem);
175+ delete this.hash[tailItem.key];
176+ }
177+ if (node = this.hash[key]) {
178+ node.val = val;
179+ this.list.moveToFront(node);
180+ } else {
181+ node = new Node(key, val);
182+ this.list.add(node);
183+ this.hash[key] = node;
184+ this.size++;
185+ }
186+ },
187+ get: function get(key) {
188+ var node = this.hash[key];
189+ if (node) {
190+ this.list.moveToFront(node);
191+ return node.val;
192+ }
193+ },
194+ reset: function reset() {
195+ this.size = 0;
196+ this.hash = {};
197+ this.list = new List();
198+ }
199+ });
200+ function List() {
201+ this.head = this.tail = null;
202+ }
203+ _.mixin(List.prototype, {
204+ add: function add(node) {
205+ if (this.head) {
206+ node.next = this.head;
207+ this.head.prev = node;
208+ }
209+ this.head = node;
210+ this.tail = this.tail || node;
211+ },
212+ remove: function remove(node) {
213+ node.prev ? node.prev.next = node.next : this.head = node.next;
214+ node.next ? node.next.prev = node.prev : this.tail = node.prev;
215+ },
216+ moveToFront: function(node) {
217+ this.remove(node);
218+ this.add(node);
219+ }
220+ });
221+ function Node(key, val) {
222+ this.key = key;
223+ this.val = val;
224+ this.prev = this.next = null;
225+ }
226+ return LruCache;
227+ }();
228+ var PersistentStorage = function() {
229+ "use strict";
230+ var ls, methods;
231+ try {
232+ ls = window.localStorage;
233+ ls.setItem("~~~", "!");
234+ ls.removeItem("~~~");
235+ } catch (err) {
236+ ls = null;
237+ }
238+ function PersistentStorage(namespace) {
239+ this.prefix = [ "__", namespace, "__" ].join("");
240+ this.ttlKey = "__ttl__";
241+ this.keyMatcher = new RegExp("^" + _.escapeRegExChars(this.prefix));
242+ }
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]);
279+ }
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+ }
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);
297+ return PersistentStorage;
298+ function now() {
299+ return new Date().getTime();
300+ }
301+ function encode(val) {
302+ return JSON.stringify(_.isUndefined(val) ? null : val);
303+ }
304+ function decode(val) {
305+ return JSON.parse(val);
306+ }
307+ }();
308+ var Transport = function() {
309+ "use strict";
310+ var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, sharedCache = new LruCache(10);
311+ function Transport(o) {
312+ o = o || {};
313+ 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;
317+ this._cache = o.cache === false ? new LruCache(0) : sharedCache;
318+ }
319+ Transport.setMaxPendingRequests = function setMaxPendingRequests(num) {
320+ maxPendingRequests = num;
321+ };
322+ Transport.resetCache = function resetCache() {
323+ sharedCache.reset();
324+ };
325+ _.mixin(Transport.prototype, {
326+ _get: function(url, o, cb) {
327+ var that = this, jqXhr;
328+ if (this.cancelled || url !== this.lastUrl) {
329+ return;
330+ }
331+ if (jqXhr = pendingRequests[url]) {
332+ jqXhr.done(done).fail(fail);
333+ } else if (pendingRequestsCount < maxPendingRequests) {
334+ pendingRequestsCount++;
335+ pendingRequests[url] = this._send(url, o).done(done).fail(fail).always(always);
336+ } else {
337+ this.onDeckRequestArgs = [].slice.call(arguments, 0);
338+ }
339+ function done(resp) {
340+ cb && cb(null, resp);
341+ that._cache.set(url, resp);
342+ }
343+ function fail() {
344+ cb && cb(true);
345+ }
346+ function always() {
347+ pendingRequestsCount--;
348+ delete pendingRequests[url];
349+ if (that.onDeckRequestArgs) {
350+ that._get.apply(that, that.onDeckRequestArgs);
351+ that.onDeckRequestArgs = null;
352+ }
353+ }
354+ },
355+ get: function(url, o, cb) {
356+ var resp;
357+ if (_.isFunction(o)) {
358+ cb = o;
359+ o = {};
360+ }
361+ this.cancelled = false;
362+ this.lastUrl = url;
363+ if (resp = this._cache.get(url)) {
364+ _.defer(function() {
365+ cb && cb(null, resp);
366+ });
367+ } else {
368+ this._get(url, o, cb);
369+ }
370+ return !!resp;
371+ },
372+ cancel: function() {
373+ this.cancelled = true;
374+ }
375+ });
376+ 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+ }();
395+ var SearchIndex = function() {
396+ "use strict";
397+ function SearchIndex(o) {
398+ o = o || {};
399+ if (!o.datumTokenizer || !o.queryTokenizer) {
400+ $.error("datumTokenizer and queryTokenizer are both required");
401+ }
402+ this.datumTokenizer = o.datumTokenizer;
403+ this.queryTokenizer = o.queryTokenizer;
404+ this.reset();
405+ }
406+ _.mixin(SearchIndex.prototype, {
407+ bootstrap: function bootstrap(o) {
408+ this.datums = o.datums;
409+ this.trie = o.trie;
410+ },
411+ add: function(data) {
412+ var that = this;
413+ data = _.isArray(data) ? data : [ data ];
414+ _.each(data, function(datum) {
415+ var id, tokens;
416+ id = that.datums.push(datum) - 1;
417+ tokens = normalizeTokens(that.datumTokenizer(datum));
418+ _.each(tokens, function(token) {
419+ var node, chars, ch;
420+ node = that.trie;
421+ chars = token.split("");
422+ while (ch = chars.shift()) {
423+ node = node.children[ch] || (node.children[ch] = newNode());
424+ node.ids.push(id);
425+ }
426+ });
427+ });
428+ },
429+ get: function get(query) {
430+ var that = this, tokens, matches;
431+ tokens = normalizeTokens(this.queryTokenizer(query));
432+ _.each(tokens, function(token) {
433+ var node, chars, ch, ids;
434+ if (matches && matches.length === 0) {
435+ return false;
436+ }
437+ node = that.trie;
438+ chars = token.split("");
439+ while (node && (ch = chars.shift())) {
440+ node = node.children[ch];
441+ }
442+ if (node && chars.length === 0) {
443+ ids = node.ids.slice(0);
444+ matches = matches ? getIntersection(matches, ids) : ids;
445+ } else {
446+ matches = [];
447+ return false;
448+ }
449+ });
450+ return matches ? _.map(unique(matches), function(id) {
451+ return that.datums[id];
452+ }) : [];
453+ },
454+ reset: function reset() {
455+ this.datums = [];
456+ this.trie = newNode();
457+ },
458+ serialize: function serialize() {
459+ return {
460+ datums: this.datums,
461+ trie: this.trie
462+ };
463+ }
464+ });
465+ return SearchIndex;
466+ function normalizeTokens(tokens) {
467+ tokens = _.filter(tokens, function(token) {
468+ return !!token;
469+ });
470+ tokens = _.map(tokens, function(token) {
471+ return token.toLowerCase();
472+ });
473+ return tokens;
474+ }
475+ function newNode() {
476+ return {
477+ ids: [],
478+ children: {}
479+ };
480+ }
481+ function unique(array) {
482+ var seen = {}, uniques = [];
483+ for (var i = 0, len = array.length; i < len; i++) {
484+ if (!seen[array[i]]) {
485+ seen[array[i]] = true;
486+ uniques.push(array[i]);
487+ }
488+ }
489+ return uniques;
490+ }
491+ function getIntersection(arrayA, arrayB) {
492+ var ai = 0, bi = 0, intersection = [];
493+ arrayA = arrayA.sort(compare);
494+ arrayB = arrayB.sort(compare);
495+ var lenArrayA = arrayA.length, lenArrayB = arrayB.length;
496+ while (ai < lenArrayA && bi < lenArrayB) {
497+ if (arrayA[ai] < arrayB[bi]) {
498+ ai++;
499+ } else if (arrayA[ai] > arrayB[bi]) {
500+ bi++;
501+ } else {
502+ intersection.push(arrayA[ai]);
503+ ai++;
504+ bi++;
505+ }
506+ }
507+ return intersection;
508+ function compare(a, b) {
509+ return a - b;
510+ }
511+ }
512+ }();
513+ var oParser = function() {
514+ "use strict";
515+ return {
516+ local: getLocal,
517+ prefetch: getPrefetch,
518+ remote: getRemote
519+ };
520+ function getLocal(o) {
521+ return o.local || null;
522+ }
523+ function getPrefetch(o) {
524+ var prefetch, defaults;
525+ defaults = {
526+ url: null,
527+ thumbprint: "",
528+ ttl: 24 * 60 * 60 * 1e3,
529+ filter: null,
530+ ajax: {}
531+ };
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;
543+ }
544+ function getRemote(o) {
545+ var remote, defaults;
546+ defaults = {
547+ url: null,
548+ cache: true,
549+ wildcard: "%QUERY",
550+ replace: null,
551+ rateLimitBy: "debounce",
552+ rateLimitWait: 300,
553+ send: null,
554+ filter: null,
555+ ajax: {}
556+ };
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");
568+ }
569+ return remote;
570+ function byDebounce(wait) {
571+ return function(fn) {
572+ return _.debounce(fn, wait);
573+ };
574+ }
575+ function byThrottle(wait) {
576+ return function(fn) {
577+ return _.throttle(fn, wait);
578+ };
579+ }
580+ }
581+ }();
582+ (function(root) {
583+ "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;
592+ 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;
603+ this.index = new SearchIndex({
604+ datumTokenizer: o.datumTokenizer,
605+ queryTokenizer: o.queryTokenizer
606+ });
607+ this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null;
608+ }
609+ Bloodhound.noConflict = function noConflict() {
610+ root.Bloodhound = old;
611+ return Bloodhound;
612+ };
613+ Bloodhound.tokenizers = tokenizers;
614+ _.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;
634+ }
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);
641+ }
642+ },
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);
651+ }
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);
659+ }
660+ isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol;
661+ return stored.data && !isExpired ? stored.data : null;
662+ },
663+ _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();
669+ function addLocalToIndex() {
670+ that.add(_.isFunction(local) ? local() : local);
671+ }
672+ },
673+ initialize: function initialize(force) {
674+ return !this.initPromise || force ? this._initialize() : this.initPromise;
675+ },
676+ add: function add(data) {
677+ this.index.add(data);
678+ },
679+ get: function get(query, cb) {
680+ var that = this, matches = [], cacheHit = false;
681+ matches = this.index.get(query);
682+ matches = this.sorter(matches).slice(0, this.limit);
683+ matches.length < this.limit ? cacheHit = this._getFromRemote(query, returnRemoteMatches) : this._cancelLastRemoteRequest();
684+ if (!cacheHit) {
685+ (matches.length > 0 || !this.transport) && cb && cb(matches);
686+ }
687+ function returnRemoteMatches(remoteMatches) {
688+ var matchesWithBackfill = matches.slice(0);
689+ _.each(remoteMatches, function(remoteMatch) {
690+ var isDuplicate;
691+ isDuplicate = _.some(matchesWithBackfill, function(match) {
692+ return that.dupDetector(remoteMatch, match);
693+ });
694+ !isDuplicate && matchesWithBackfill.push(remoteMatch);
695+ return matchesWithBackfill.length < that.limit;
696+ });
697+ cb && cb(that.sorter(matchesWithBackfill));
698+ }
699+ },
700+ clear: function clear() {
701+ this.index.reset();
702+ },
703+ clearPrefetchCache: function clearPrefetchCache() {
704+ this.storage && this.storage.clear();
705+ },
706+ clearRemoteCache: function clearRemoteCache() {
707+ this.transport && Transport.resetCache();
708+ },
709+ ttAdapter: function ttAdapter() {
710+ return _.bind(this.get, this);
711+ }
712+ });
713+ 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+ }();
736+ var css = function() {
737+ "use strict";
738+ var css = {
739+ wrapper: {
740+ position: "relative",
741+ display: "inline-block"
742+ },
743+ hint: {
744+ position: "absolute",
745+ top: "0",
746+ left: "0",
747+ borderColor: "transparent",
748+ boxShadow: "none",
749+ opacity: "1"
750+ },
751+ input: {
752+ position: "relative",
753+ verticalAlign: "top",
754+ backgroundColor: "transparent"
755+ },
756+ inputWithNoHint: {
757+ position: "relative",
758+ verticalAlign: "top"
759+ },
760+ dropdown: {
761+ position: "absolute",
762+ top: "100%",
763+ left: "0",
764+ zIndex: "100",
765+ display: "none"
766+ },
767+ suggestions: {
768+ display: "block"
769+ },
770+ suggestion: {
771+ whiteSpace: "nowrap",
772+ cursor: "pointer"
773+ },
774+ suggestionChild: {
775+ whiteSpace: "normal"
776+ },
777+ ltr: {
778+ left: "0",
779+ right: "auto"
780+ },
781+ rtl: {
782+ left: "auto",
783+ right: " 0"
784+ }
785+ };
786+ if (_.isMsie()) {
787+ _.mixin(css.input, {
788+ backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
789+ });
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+ }
847+ function off(types) {
848+ var type;
849+ if (!this._callbacks) {
850+ return this;
851+ }
852+ types = types.split(splitter);
853+ while (type = types.shift()) {
854+ delete this._callbacks[type];
855+ }
856+ return this;
857+ }
858+ function trigger(types) {
859+ var type, callbacks, args, syncFlush, asyncFlush;
860+ if (!this._callbacks) {
861+ return this;
862+ }
863+ types = types.split(splitter);
864+ args = [].slice.call(arguments, 1);
865+ while ((type = types.shift()) && (callbacks = this._callbacks[type])) {
866+ syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args));
867+ asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args));
868+ syncFlush() && nextTick(asyncFlush);
869+ }
870+ return this;
871+ }
872+ function getFlush(callbacks, context, args) {
873+ return flush;
874+ function flush() {
875+ var cancelled;
876+ for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) {
877+ cancelled = callbacks[i].apply(context, args) === false;
878+ }
879+ return !cancelled;
880+ }
881+ }
882+ function getNextTick() {
883+ var nextTickFn;
884+ if (window.setImmediate) {
885+ nextTickFn = function nextTickSetImmediate(fn) {
886+ setImmediate(function() {
887+ fn();
888+ });
889+ };
890+ } else {
891+ nextTickFn = function nextTickSetTimeout(fn) {
892+ setTimeout(function() {
893+ fn();
894+ }, 0);
895+ };
896+ }
897+ return nextTickFn;
898+ }
899+ function bindContext(fn, context) {
900+ return fn.bind ? fn.bind(context) : function() {
901+ fn.apply(context, [].slice.call(arguments, 0));
902+ };
903+ }
904+ }();
905+ var highlight = function(doc) {
906+ "use strict";
907+ var defaults = {
908+ node: null,
909+ pattern: null,
910+ tagName: "strong",
911+ className: null,
912+ wordsOnly: false,
913+ caseSensitive: false
914+ };
915+ return function hightlight(o) {
916+ var regex;
917+ o = _.mixin({}, defaults, o);
918+ if (!o.node || !o.pattern) {
919+ return;
920+ }
921+ o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ];
922+ regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly);
923+ traverse(o.node, hightlightTextNode);
924+ function hightlightTextNode(textNode) {
925+ var match, patternNode, wrapperNode;
926+ if (match = regex.exec(textNode.data)) {
927+ wrapperNode = doc.createElement(o.tagName);
928+ o.className && (wrapperNode.className = o.className);
929+ patternNode = textNode.splitText(match.index);
930+ patternNode.splitText(match[0].length);
931+ wrapperNode.appendChild(patternNode.cloneNode(true));
932+ textNode.parentNode.replaceChild(wrapperNode, patternNode);
933+ }
934+ return !!match;
935+ }
936+ function traverse(el, hightlightTextNode) {
937+ var childNode, TEXT_NODE_TYPE = 3;
938+ for (var i = 0; i < el.childNodes.length; i++) {
939+ childNode = el.childNodes[i];
940+ if (childNode.nodeType === TEXT_NODE_TYPE) {
941+ i += hightlightTextNode(childNode) ? 1 : 0;
942+ } else {
943+ traverse(childNode, hightlightTextNode);
944+ }
945+ }
946+ }
947+ };
948+ function getRegex(patterns, caseSensitive, wordsOnly) {
949+ var escapedPatterns = [], regexStr;
950+ for (var i = 0, len = patterns.length; i < len; i++) {
951+ escapedPatterns.push(_.escapeRegExChars(patterns[i]));
952+ }
953+ regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")";
954+ return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i");
955+ }
956+ }(window.document);
957+ var Input = function() {
958+ "use strict";
959+ var specialKeyCodeMap;
960+ specialKeyCodeMap = {
961+ 9: "tab",
962+ 27: "esc",
963+ 37: "left",
964+ 39: "right",
965+ 13: "enter",
966+ 38: "up",
967+ 40: "down"
968+ };
969+ function Input(o) {
970+ var that = this, onBlur, onFocus, onKeydown, onInput;
971+ o = o || {};
972+ if (!o.input) {
973+ $.error("input is missing");
974+ }
975+ onBlur = _.bind(this._onBlur, this);
976+ onFocus = _.bind(this._onFocus, this);
977+ onKeydown = _.bind(this._onKeydown, this);
978+ onInput = _.bind(this._onInput, this);
979+ this.$hint = $(o.hint);
980+ this.$input = $(o.input).on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown);
981+ if (this.$hint.length === 0) {
982+ this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop;
983+ }
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+ }
997+ Input.normalizeQuery = function(str) {
998+ return (str || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
999+ };
1000+ _.mixin(Input.prototype, EventEmitter, {
1001+ _onBlur: function onBlur() {
1002+ this.resetInputValue();
1003+ this.trigger("blurred");
1004+ },
1005+ _onFocus: function onFocus() {
1006+ this.trigger("focused");
1007+ },
1008+ _onKeydown: function onKeydown($e) {
1009+ var keyName = specialKeyCodeMap[$e.which || $e.keyCode];
1010+ this._managePreventDefault(keyName, $e);
1011+ if (keyName && this._shouldTrigger(keyName, $e)) {
1012+ this.trigger(keyName + "Keyed", $e);
1013+ }
1014+ },
1015+ _onInput: function onInput() {
1016+ this._checkInputValue();
1017+ },
1018+ _managePreventDefault: function managePreventDefault(keyName, $e) {
1019+ var preventDefault, hintValue, inputValue;
1020+ switch (keyName) {
1021+ case "tab":
1022+ hintValue = this.getHint();
1023+ inputValue = this.getInputValue();
1024+ preventDefault = hintValue && hintValue !== inputValue && !withModifier($e);
1025+ break;
1026+
1027+ case "up":
1028+ case "down":
1029+ preventDefault = !withModifier($e);
1030+ break;
1031+
1032+ default:
1033+ preventDefault = false;
1034+ }
1035+ preventDefault && $e.preventDefault();
1036+ },
1037+ _shouldTrigger: function shouldTrigger(keyName, $e) {
1038+ var trigger;
1039+ switch (keyName) {
1040+ case "tab":
1041+ trigger = !withModifier($e);
1042+ break;
1043+
1044+ default:
1045+ trigger = true;
1046+ }
1047+ return trigger;
1048+ },
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) {
1056+ this.trigger("queryChanged", this.query);
1057+ } else if (hasDifferentWhitespace) {
1058+ this.trigger("whitespaceChanged", this.query);
1059+ }
1060+ },
1061+ focus: function focus() {
1062+ this.$input.focus();
1063+ },
1064+ blur: function blur() {
1065+ this.$input.blur();
1066+ },
1067+ getQuery: function getQuery() {
1068+ return this.query;
1069+ },
1070+ setQuery: function setQuery(query) {
1071+ this.query = query;
1072+ },
1073+ getInputValue: function getInputValue() {
1074+ return this.$input.val();
1075+ },
1076+ setInputValue: function setInputValue(value, silent) {
1077+ this.$input.val(value);
1078+ silent ? this.clearHint() : this._checkInputValue();
1079+ },
1080+ resetInputValue: function resetInputValue() {
1081+ this.setInputValue(this.query, true);
1082+ },
1083+ getHint: function getHint() {
1084+ return this.$hint.val();
1085+ },
1086+ setHint: function setHint(value) {
1087+ this.$hint.val(value);
1088+ },
1089+ clearHint: function clearHint() {
1090+ this.setHint("");
1091+ },
1092+ clearHintIfInvalid: function clearHintIfInvalid() {
1093+ var val, hint, valIsPrefixOfHint, isValid;
1094+ val = this.getInputValue();
1095+ hint = this.getHint();
1096+ valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0;
1097+ isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow();
1098+ !isValid && this.clearHint();
1099+ },
1100+ getLanguageDirection: function getLanguageDirection() {
1101+ return (this.$input.css("direction") || "ltr").toLowerCase();
1102+ },
1103+ hasOverflow: function hasOverflow() {
1104+ var constraint = this.$input.width() - 2;
1105+ this.$overflowHelper.text(this.getInputValue());
1106+ return this.$overflowHelper.width() >= constraint;
1107+ },
1108+ isCursorAtEnd: function() {
1109+ var valueLength, selectionStart, range;
1110+ valueLength = this.$input.val().length;
1111+ selectionStart = this.$input[0].selectionStart;
1112+ if (_.isNumber(selectionStart)) {
1113+ return selectionStart === valueLength;
1114+ } else if (document.selection) {
1115+ range = document.selection.createRange();
1116+ range.moveStart("character", -valueLength);
1117+ return valueLength === range.text.length;
1118+ }
1119+ return true;
1120+ },
1121+ destroy: function destroy() {
1122+ this.$hint.off(".tt");
1123+ this.$input.off(".tt");
1124+ this.$hint = this.$input = this.$overflowHelper = null;
1125+ }
1126+ });
1127+ return Input;
1128+ function buildOverflowHelper($input) {
1129+ return $('<pre aria-hidden="true"></pre>').css({
1130+ position: "absolute",
1131+ visibility: "hidden",
1132+ whiteSpace: "pre",
1133+ fontFamily: $input.css("font-family"),
1134+ fontSize: $input.css("font-size"),
1135+ fontStyle: $input.css("font-style"),
1136+ fontVariant: $input.css("font-variant"),
1137+ fontWeight: $input.css("font-weight"),
1138+ wordSpacing: $input.css("word-spacing"),
1139+ letterSpacing: $input.css("letter-spacing"),
1140+ textIndent: $input.css("text-indent"),
1141+ textRendering: $input.css("text-rendering"),
1142+ textTransform: $input.css("text-transform")
1143+ }).insertAfter($input);
1144+ }
1145+ function areQueriesEquivalent(a, b) {
1146+ return Input.normalizeQuery(a) === Input.normalizeQuery(b);
1147+ }
1148+ function withModifier($e) {
1149+ return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey;
1150+ }
1151+ }();
1152+ var Dataset = function() {
1153+ "use strict";
1154+ var datasetKey = "ttDataset", valueKey = "ttValue", datumKey = "ttDatum";
1155+ function Dataset(o) {
1156+ o = o || {};
1157+ o.templates = o.templates || {};
1158+ if (!o.source) {
1159+ $.error("missing source");
1160+ }
1161+ if (o.name && !isValidName(o.name)) {
1162+ $.error("invalid dataset name: " + o.name);
1163+ }
1164+ this.query = null;
1165+ this.highlight = !!o.highlight;
1166+ this.name = o.name || _.getUniqueId();
1167+ this.source = o.source;
1168+ this.displayFn = getDisplayFn(o.display || o.displayKey);
1169+ this.templates = getTemplates(o.templates, this.displayFn);
1170+ this.$el = $(html.dataset.replace("%CLASS%", this.name));
1171+ }
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);
1180+ };
1181+ _.mixin(Dataset.prototype, EventEmitter, {
1182+ _render: function render(query, suggestions) {
1183+ if (!this.$el) {
1184+ return;
1185+ }
1186+ var that = this, hasSuggestions;
1187+ 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;
1236+ },
1237+ 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);
1245+ }
1246+ }
1247+ },
1248+ cancel: function cancel() {
1249+ this.canceled = true;
1250+ },
1251+ clear: function clear() {
1252+ this.cancel();
1253+ this.$el.empty();
1254+ this.trigger("rendered");
1255+ },
1256+ isEmpty: function isEmpty() {
1257+ return this.$el.is(":empty");
1258+ },
1259+ destroy: function destroy() {
1260+ this.$el = null;
1261+ }
1262+ });
1263+ return Dataset;
1264+ function getDisplayFn(display) {
1265+ display = display || "value";
1266+ return _.isFunction(display) ? display : displayFn;
1267+ function displayFn(obj) {
1268+ return obj[display];
1269+ }
1270+ }
1271+ function getTemplates(templates, displayFn) {
1272+ return {
1273+ empty: templates.empty && _.templatify(templates.empty),
1274+ header: templates.header && _.templatify(templates.header),
1275+ footer: templates.footer && _.templatify(templates.footer),
1276+ suggestion: templates.suggestion || suggestionTemplate
1277+ };
1278+ function suggestionTemplate(context) {
1279+ return "<p>" + displayFn(context) + "</p>";
1280+ }
1281+ }
1282+ function isValidName(str) {
1283+ return /^[_a-zA-Z0-9-]+$/.test(str);
1284+ }
1285+ }();
1286+ var Dropdown = function() {
1287+ "use strict";
1288+ function Dropdown(o) {
1289+ var that = this, onSuggestionClick, onSuggestionMouseEnter, onSuggestionMouseLeave;
1290+ o = o || {};
1291+ if (!o.menu) {
1292+ $.error("menu is required");
1293+ }
1294+ this.isOpen = false;
1295+ this.isEmpty = true;
1296+ 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+ });
1305+ }
1306+ _.mixin(Dropdown.prototype, EventEmitter, {
1307+ _onSuggestionClick: function onSuggestionClick($e) {
1308+ this.trigger("suggestionClicked", $($e.currentTarget));
1309+ },
1310+ _onSuggestionMouseEnter: function onSuggestionMouseEnter($e) {
1311+ this._removeCursor();
1312+ this._setCursor($($e.currentTarget), true);
1313+ },
1314+ _onSuggestionMouseLeave: function onSuggestionMouseLeave() {
1315+ this._removeCursor();
1316+ },
1317+ _onRendered: function onRendered() {
1318+ this.isEmpty = _.every(this.datasets, isDatasetEmpty);
1319+ this.isEmpty ? this._hide() : this.isOpen && this._show();
1320+ this.trigger("datasetRendered");
1321+ function isDatasetEmpty(dataset) {
1322+ return dataset.isEmpty();
1323+ }
1324+ },
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();
1336+ },
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);
1362+ },
1363+ _ensureVisible: function ensureVisible($el) {
1364+ var elTop, elBottom, menuScrollTop, menuHeight;
1365+ elTop = $el.position().top;
1366+ 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);
1369+ if (elTop < 0) {
1370+ this.$menu.scrollTop(menuScrollTop + elTop);
1371+ } else if (menuHeight < elBottom) {
1372+ this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight));
1373+ }
1374+ },
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+ }
1389+ },
1390+ setLanguageDirection: function setLanguageDirection(dir) {
1391+ this.$menu.css(dir === "ltr" ? css.ltr : css.rtl);
1392+ },
1393+ moveCursorUp: function moveCursorUp() {
1394+ this._moveCursor(-1);
1395+ },
1396+ moveCursorDown: function moveCursorDown() {
1397+ this._moveCursor(+1);
1398+ },
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+ };
1407+ }
1408+ return datum;
1409+ },
1410+ getDatumForCursor: function getDatumForCursor() {
1411+ return this.getDatumForSuggestion(this._getCursor().first());
1412+ },
1413+ getDatumForTopSuggestion: function getDatumForTopSuggestion() {
1414+ return this.getDatumForSuggestion(this._getSuggestions().first());
1415+ },
1416+ update: function update(query) {
1417+ _.each(this.datasets, updateDataset);
1418+ function updateDataset(dataset) {
1419+ dataset.update(query);
1420+ }
1421+ },
1422+ empty: function empty() {
1423+ _.each(this.datasets, clearDataset);
1424+ this.isEmpty = true;
1425+ function clearDataset(dataset) {
1426+ dataset.clear();
1427+ }
1428+ },
1429+ isVisible: function isVisible() {
1430+ return this.isOpen && !this.isEmpty;
1431+ },
1432+ destroy: function destroy() {
1433+ this.$menu.off(".tt");
1434+ this.$menu = null;
1435+ _.each(this.datasets, destroyDataset);
1436+ function destroyDataset(dataset) {
1437+ dataset.destroy();
1438+ }
1439+ }
1440+ });
1441+ return Dropdown;
1442+ function initializeDataset(oDataset) {
1443+ return new Dataset(oDataset);
1444+ }
1445+ }();
1446+ var Typeahead = function() {
1447+ "use strict";
1448+ var attrsKey = "ttAttrs";
1449+ function Typeahead(o) {
1450+ var $menu, $input, $hint;
1451+ o = o || {};
1452+ if (!o.input) {
1453+ $.error("missing input");
1454+ }
1455+ this.isActivated = false;
1456+ this.autoselect = !!o.autoselect;
1457+ 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();
1490+ }
1491+ _.mixin(Typeahead.prototype, {
1492+ _onSuggestionClicked: function onSuggestionClicked(type, $el) {
1493+ var datum;
1494+ if (datum = this.dropdown.getDatumForSuggestion($el)) {
1495+ this._select(datum);
1496+ }
1497+ },
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);
1502+ },
1503+ _onCursorRemoved: function onCursorRemoved() {
1504+ this.input.resetInputValue();
1505+ this._updateHint();
1506+ },
1507+ _onDatasetRendered: function onDatasetRendered() {
1508+ this._updateHint();
1509+ },
1510+ _onOpened: function onOpened() {
1511+ this._updateHint();
1512+ this.eventBus.trigger("opened");
1513+ },
1514+ _onClosed: function onClosed() {
1515+ this.input.clearHint();
1516+ this.eventBus.trigger("closed");
1517+ },
1518+ _onFocused: function onFocused() {
1519+ this.isActivated = true;
1520+ this.dropdown.open();
1521+ },
1522+ _onBlurred: function onBlurred() {
1523+ this.isActivated = false;
1524+ this.dropdown.empty();
1525+ this.dropdown.close();
1526+ },
1527+ _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();
1537+ }
1538+ },
1539+ _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);
1546+ }
1547+ },
1548+ _onEscKeyed: function onEscKeyed() {
1549+ this.dropdown.close();
1550+ this.input.resetInputValue();
1551+ },
1552+ _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();
1556+ },
1557+ _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();
1561+ },
1562+ _onLeftKeyed: function onLeftKeyed() {
1563+ this.dir === "rtl" && this._autocomplete();
1564+ },
1565+ _onRightKeyed: function onRightKeyed() {
1566+ this.dir === "ltr" && this._autocomplete();
1567+ },
1568+ _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();
1573+ },
1574+ _onWhitespaceChanged: function onWhitespaceChanged() {
1575+ this._updateHint();
1576+ this.dropdown.open();
1577+ },
1578+ _setLanguageDirection: function setLanguageDirection() {
1579+ var dir;
1580+ if (this.dir !== (dir = this.input.getLanguageDirection())) {
1581+ this.dir = dir;
1582+ this.$node.css("direction", dir);
1583+ this.dropdown.setLanguageDirection(dir);
1584+ }
1585+ },
1586+ _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();
1591+ query = Input.normalizeQuery(val);
1592+ escapedQuery = _.escapeRegExChars(query);
1593+ frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i");
1594+ match = frontMatchRegEx.exec(datum.value);
1595+ match ? this.input.setHint(val + match[1]) : this.input.clearHint();
1596+ } else {
1597+ this.input.clearHint();
1598+ }
1599+ },
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);
1609+ }
1610+ },
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));
1618+ },
1619+ open: function open() {
1620+ this.dropdown.open();
1621+ },
1622+ close: function close() {
1623+ this.dropdown.close();
1624+ },
1625+ setVal: function setVal(val) {
1626+ val = _.toStr(val);
1627+ if (this.isActivated) {
1628+ this.input.setInputValue(val);
1629+ } else {
1630+ this.input.setQuery(val);
1631+ this.input.setInputValue(val, true);
1632+ }
1633+ this._setLanguageDirection();
1634+ },
1635+ getVal: function getVal() {
1636+ return this.input.getQuery();
1637+ },
1638+ destroy: function destroy() {
1639+ this.input.destroy();
1640+ this.dropdown.destroy();
1641+ destroyDomStructure(this.$node);
1642+ this.$node = null;
1643+ }
1644+ });
1645+ 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")
1682+ };
1683+ }
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+ }();
1693+ (function() {
1694+ "use strict";
1695+ var old, typeaheadKey, methods;
1696+ old = $.fn.typeahead;
1697+ typeaheadKey = "ttTypeahead";
1698+ methods = {
1699+ initialize: function initialize(o, datasets) {
1700+ datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1);
1701+ o = o || {};
1702+ return this.each(attach);
1703+ function attach() {
1704+ var $input = $(this), eventBus, typeahead;
1705+ _.each(datasets, function(d) {
1706+ d.highlight = !!o.highlight;
1707+ });
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
1717+ });
1718+ $input.data(typeaheadKey, typeahead);
1719+ }
1720+ },
1721+ 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+ }
1729+ },
1730+ 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+ }
1738+ },
1739+ 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+ }
1752+ return query;
1753+ }
1754+ },
1755+ 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+ }
1764+ }
1765+ };
1766+ $.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));
1773+ } else {
1774+ return methods.initialize.apply(this, arguments);
1775+ }
1776+ };
1777+ $.fn.typeahead.noConflict = function noConflict() {
1778+ $.fn.typeahead = old;
1779+ return this;
1780+ };
1781+ })();
1782+})(window.jQuery);

Built with git-ssb-web