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("typeahead.js", [ "jquery" ], function(a0) { 10 return factory(a0); 11 }); 12 } else if (typeof exports === "object") { 13 module.exports = factory(require("jquery")); 14 } else { 15 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 WWW = function() { 155 "use strict"; 156 var defaultClassNames = { 157 wrapper: "twitter-typeahead", 158 input: "tt-input", 159 hint: "tt-hint", 160 menu: "tt-menu", 161 dataset: "tt-dataset", 162 suggestion: "tt-suggestion", 163 selectable: "tt-selectable", 164 empty: "tt-empty", 165 open: "tt-open", 166 cursor: "tt-cursor", 167 highlight: "tt-highlight" 168 }; 169 return build; 170 function build(o) { 171 var www, classes; 172 classes = _.mixin({}, defaultClassNames, o); 173 www = { 174 css: buildCss(), 175 classes: classes, 176 html: buildHtml(classes), 177 selectors: buildSelectors(classes) 178 }; 179 return { 180 css: www.css, 181 html: www.html, 182 classes: www.classes, 183 selectors: www.selectors, 184 mixin: function(o) { 185 _.mixin(o, www); 186 } 187 }; 188 } 189 function buildHtml(c) { 190 return { 191 wrapper: '<span class="' + c.wrapper + '"></span>', 192 menu: '<div class="' + c.menu + '"></div>' 193 }; 194 } 195 function buildSelectors(classes) { 196 var selectors = {}; 197 _.each(classes, function(v, k) { 198 selectors[k] = "." + v; 199 }); 200 return selectors; 201 } 202 function buildCss() { 203 var css = { 204 wrapper: { 205 position: "relative", 206 display: "inline-block" 207 }, 208 hint: { 209 position: "absolute", 210 top: "0", 211 left: "0", 212 borderColor: "transparent", 213 boxShadow: "none", 214 opacity: "1" 215 }, 216 input: { 217 position: "relative", 218 verticalAlign: "top", 219 backgroundColor: "transparent" 220 }, 221 inputWithNoHint: { 222 position: "relative", 223 verticalAlign: "top" 224 }, 225 menu: { 226 position: "absolute", 227 top: "100%", 228 left: "0", 229 zIndex: "100", 230 display: "none" 231 }, 232 ltr: { 233 left: "0", 234 right: "auto" 235 }, 236 rtl: { 237 left: "auto", 238 right: " 0" 239 } 240 }; 241 if (_.isMsie()) { 242 _.mixin(css.input, { 243 backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" 244 }); 245 } 246 return css; 247 } 248 }(); 249 var EventBus = function() { 250 "use strict"; 251 var namespace, deprecationMap; 252 namespace = "typeahead:"; 253 deprecationMap = { 254 render: "rendered", 255 cursorchange: "cursorchanged", 256 select: "selected", 257 autocomplete: "autocompleted" 258 }; 259 function EventBus(o) { 260 if (!o || !o.el) { 261 $.error("EventBus initialized without el"); 262 } 263 this.$el = $(o.el); 264 } 265 _.mixin(EventBus.prototype, { 266 _trigger: function(type, args) { 267 var $e; 268 $e = $.Event(namespace + type); 269 (args = args || []).unshift($e); 270 this.$el.trigger.apply(this.$el, args); 271 return $e; 272 }, 273 before: function(type) { 274 var args, $e; 275 args = [].slice.call(arguments, 1); 276 $e = this._trigger("before" + type, args); 277 return $e.isDefaultPrevented(); 278 }, 279 trigger: function(type) { 280 var deprecatedType; 281 this._trigger(type, [].slice.call(arguments, 1)); 282 if (deprecatedType = deprecationMap[type]) { 283 this._trigger(deprecatedType, [].slice.call(arguments, 1)); 284 } 285 } 286 }); 287 return EventBus; 288 }(); 289 var EventEmitter = function() { 290 "use strict"; 291 var splitter = /\s+/, nextTick = getNextTick(); 292 return { 293 onSync: onSync, 294 onAsync: onAsync, 295 off: off, 296 trigger: trigger 297 }; 298 function on(method, types, cb, context) { 299 var type; 300 if (!cb) { 301 return this; 302 } 303 types = types.split(splitter); 304 cb = context ? bindContext(cb, context) : cb; 305 this._callbacks = this._callbacks || {}; 306 while (type = types.shift()) { 307 this._callbacks[type] = this._callbacks[type] || { 308 sync: [], 309 async: [] 310 }; 311 this._callbacks[type][method].push(cb); 312 } 313 return this; 314 } 315 function onAsync(types, cb, context) { 316 return on.call(this, "async", types, cb, context); 317 } 318 function onSync(types, cb, context) { 319 return on.call(this, "sync", types, cb, context); 320 } 321 function off(types) { 322 var type; 323 if (!this._callbacks) { 324 return this; 325 } 326 types = types.split(splitter); 327 while (type = types.shift()) { 328 delete this._callbacks[type]; 329 } 330 return this; 331 } 332 function trigger(types) { 333 var type, callbacks, args, syncFlush, asyncFlush; 334 if (!this._callbacks) { 335 return this; 336 } 337 types = types.split(splitter); 338 args = [].slice.call(arguments, 1); 339 while ((type = types.shift()) && (callbacks = this._callbacks[type])) { 340 syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); 341 asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); 342 syncFlush() && nextTick(asyncFlush); 343 } 344 return this; 345 } 346 function getFlush(callbacks, context, args) { 347 return flush; 348 function flush() { 349 var cancelled; 350 for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) { 351 cancelled = callbacks[i].apply(context, args) === false; 352 } 353 return !cancelled; 354 } 355 } 356 function getNextTick() { 357 var nextTickFn; 358 if (window.setImmediate) { 359 nextTickFn = function nextTickSetImmediate(fn) { 360 setImmediate(function() { 361 fn(); 362 }); 363 }; 364 } else { 365 nextTickFn = function nextTickSetTimeout(fn) { 366 setTimeout(function() { 367 fn(); 368 }, 0); 369 }; 370 } 371 return nextTickFn; 372 } 373 function bindContext(fn, context) { 374 return fn.bind ? fn.bind(context) : function() { 375 fn.apply(context, [].slice.call(arguments, 0)); 376 }; 377 } 378 }(); 379 var highlight = function(doc) { 380 "use strict"; 381 var defaults = { 382 node: null, 383 pattern: null, 384 tagName: "strong", 385 className: null, 386 wordsOnly: false, 387 caseSensitive: false 388 }; 389 return function hightlight(o) { 390 var regex; 391 o = _.mixin({}, defaults, o); 392 if (!o.node || !o.pattern) { 393 return; 394 } 395 o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; 396 regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); 397 traverse(o.node, hightlightTextNode); 398 function hightlightTextNode(textNode) { 399 var match, patternNode, wrapperNode; 400 if (match = regex.exec(textNode.data)) { 401 wrapperNode = doc.createElement(o.tagName); 402 o.className && (wrapperNode.className = o.className); 403 patternNode = textNode.splitText(match.index); 404 patternNode.splitText(match[0].length); 405 wrapperNode.appendChild(patternNode.cloneNode(true)); 406 textNode.parentNode.replaceChild(wrapperNode, patternNode); 407 } 408 return !!match; 409 } 410 function traverse(el, hightlightTextNode) { 411 var childNode, TEXT_NODE_TYPE = 3; 412 for (var i = 0; i < el.childNodes.length; i++) { 413 childNode = el.childNodes[i]; 414 if (childNode.nodeType === TEXT_NODE_TYPE) { 415 i += hightlightTextNode(childNode) ? 1 : 0; 416 } else { 417 traverse(childNode, hightlightTextNode); 418 } 419 } 420 } 421 }; 422 function getRegex(patterns, caseSensitive, wordsOnly) { 423 var escapedPatterns = [], regexStr; 424 for (var i = 0, len = patterns.length; i < len; i++) { 425 escapedPatterns.push(_.escapeRegExChars(patterns[i])); 426 } 427 regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; 428 return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); 429 } 430 }(window.document); 431 var Input = function() { 432 "use strict"; 433 var specialKeyCodeMap; 434 specialKeyCodeMap = { 435 9: "tab", 436 27: "esc", 437 37: "left", 438 39: "right", 439 13: "enter", 440 38: "up", 441 40: "down" 442 }; 443 function Input(o, www) { 444 o = o || {}; 445 if (!o.input) { 446 $.error("input is missing"); 447 } 448 www.mixin(this); 449 this.$hint = $(o.hint); 450 this.$input = $(o.input); 451 this.query = this.$input.val(); 452 this.queryWhenFocused = this.hasFocus() ? this.query : null; 453 this.$overflowHelper = buildOverflowHelper(this.$input); 454 this._checkLanguageDirection(); 455 if (this.$hint.length === 0) { 456 this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; 457 } 458 } 459 Input.normalizeQuery = function(str) { 460 return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " "); 461 }; 462 _.mixin(Input.prototype, EventEmitter, { 463 _onBlur: function onBlur() { 464 this.resetInputValue(); 465 this.trigger("blurred"); 466 }, 467 _onFocus: function onFocus() { 468 this.queryWhenFocused = this.query; 469 this.trigger("focused"); 470 }, 471 _onKeydown: function onKeydown($e) { 472 var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; 473 this._managePreventDefault(keyName, $e); 474 if (keyName && this._shouldTrigger(keyName, $e)) { 475 this.trigger(keyName + "Keyed", $e); 476 } 477 }, 478 _onInput: function onInput() { 479 this._setQuery(this.getInputValue()); 480 this.clearHintIfInvalid(); 481 this._checkLanguageDirection(); 482 }, 483 _managePreventDefault: function managePreventDefault(keyName, $e) { 484 var preventDefault; 485 switch (keyName) { 486 case "up": 487 case "down": 488 preventDefault = !withModifier($e); 489 break; 490 491 default: 492 preventDefault = false; 493 } 494 preventDefault && $e.preventDefault(); 495 }, 496 _shouldTrigger: function shouldTrigger(keyName, $e) { 497 var trigger; 498 switch (keyName) { 499 case "tab": 500 trigger = !withModifier($e); 501 break; 502 503 default: 504 trigger = true; 505 } 506 return trigger; 507 }, 508 _checkLanguageDirection: function checkLanguageDirection() { 509 var dir = (this.$input.css("direction") || "ltr").toLowerCase(); 510 if (this.dir !== dir) { 511 this.dir = dir; 512 this.$hint.attr("dir", dir); 513 this.trigger("langDirChanged", dir); 514 } 515 }, 516 _setQuery: function setQuery(val, silent) { 517 var areEquivalent, hasDifferentWhitespace; 518 areEquivalent = areQueriesEquivalent(val, this.query); 519 hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false; 520 this.query = val; 521 if (!silent && !areEquivalent) { 522 this.trigger("queryChanged", this.query); 523 } else if (!silent && hasDifferentWhitespace) { 524 this.trigger("whitespaceChanged", this.query); 525 } 526 }, 527 bind: function() { 528 var that = this, onBlur, onFocus, onKeydown, onInput; 529 onBlur = _.bind(this._onBlur, this); 530 onFocus = _.bind(this._onFocus, this); 531 onKeydown = _.bind(this._onKeydown, this); 532 onInput = _.bind(this._onInput, this); 533 this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); 534 if (!_.isMsie() || _.isMsie() > 9) { 535 this.$input.on("input.tt", onInput); 536 } else { 537 this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { 538 if (specialKeyCodeMap[$e.which || $e.keyCode]) { 539 return; 540 } 541 _.defer(_.bind(that._onInput, that, $e)); 542 }); 543 } 544 return this; 545 }, 546 focus: function focus() { 547 this.$input.focus(); 548 }, 549 blur: function blur() { 550 this.$input.blur(); 551 }, 552 getLangDir: function getLangDir() { 553 return this.dir; 554 }, 555 getQuery: function getQuery() { 556 return this.query || ""; 557 }, 558 setQuery: function setQuery(val, silent) { 559 this.setInputValue(val); 560 this._setQuery(val, silent); 561 }, 562 hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() { 563 return this.query !== this.queryWhenFocused; 564 }, 565 getInputValue: function getInputValue() { 566 return this.$input.val(); 567 }, 568 setInputValue: function setInputValue(value) { 569 this.$input.val(value); 570 this.clearHintIfInvalid(); 571 this._checkLanguageDirection(); 572 }, 573 resetInputValue: function resetInputValue() { 574 this.setInputValue(this.query); 575 }, 576 getHint: function getHint() { 577 return this.$hint.val(); 578 }, 579 setHint: function setHint(value) { 580 this.$hint.val(value); 581 }, 582 clearHint: function clearHint() { 583 this.setHint(""); 584 }, 585 clearHintIfInvalid: function clearHintIfInvalid() { 586 var val, hint, valIsPrefixOfHint, isValid; 587 val = this.getInputValue(); 588 hint = this.getHint(); 589 valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; 590 isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); 591 !isValid && this.clearHint(); 592 }, 593 hasFocus: function hasFocus() { 594 return this.$input.is(":focus"); 595 }, 596 hasOverflow: function hasOverflow() { 597 var constraint = this.$input.width() - 2; 598 this.$overflowHelper.text(this.getInputValue()); 599 return this.$overflowHelper.width() >= constraint; 600 }, 601 isCursorAtEnd: function() { 602 var valueLength, selectionStart, range; 603 valueLength = this.$input.val().length; 604 selectionStart = this.$input[0].selectionStart; 605 if (_.isNumber(selectionStart)) { 606 return selectionStart === valueLength; 607 } else if (document.selection) { 608 range = document.selection.createRange(); 609 range.moveStart("character", -valueLength); 610 return valueLength === range.text.length; 611 } 612 return true; 613 }, 614 destroy: function destroy() { 615 this.$hint.off(".tt"); 616 this.$input.off(".tt"); 617 this.$overflowHelper.remove(); 618 this.$hint = this.$input = this.$overflowHelper = $("<div>"); 619 } 620 }); 621 return Input; 622 function buildOverflowHelper($input) { 623 return $('<pre aria-hidden="true"></pre>').css({ 624 position: "absolute", 625 visibility: "hidden", 626 whiteSpace: "pre", 627 fontFamily: $input.css("font-family"), 628 fontSize: $input.css("font-size"), 629 fontStyle: $input.css("font-style"), 630 fontVariant: $input.css("font-variant"), 631 fontWeight: $input.css("font-weight"), 632 wordSpacing: $input.css("word-spacing"), 633 letterSpacing: $input.css("letter-spacing"), 634 textIndent: $input.css("text-indent"), 635 textRendering: $input.css("text-rendering"), 636 textTransform: $input.css("text-transform") 637 }).insertAfter($input); 638 } 639 function areQueriesEquivalent(a, b) { 640 return Input.normalizeQuery(a) === Input.normalizeQuery(b); 641 } 642 function withModifier($e) { 643 return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; 644 } 645 }(); 646 var Dataset = function() { 647 "use strict"; 648 var keys, nameGenerator; 649 keys = { 650 val: "tt-selectable-display", 651 obj: "tt-selectable-object" 652 }; 653 nameGenerator = _.getIdGenerator(); 654 function Dataset(o, www) { 655 o = o || {}; 656 o.templates = o.templates || {}; 657 o.templates.notFound = o.templates.notFound || o.templates.empty; 658 if (!o.source) { 659 $.error("missing source"); 660 } 661 if (!o.node) { 662 $.error("missing node"); 663 } 664 if (o.name && !isValidName(o.name)) { 665 $.error("invalid dataset name: " + o.name); 666 } 667 www.mixin(this); 668 this.highlight = !!o.highlight; 669 this.name = o.name || nameGenerator(); 670 this.limit = o.limit || 5; 671 this.displayFn = getDisplayFn(o.display || o.displayKey); 672 this.templates = getTemplates(o.templates, this.displayFn); 673 this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source; 674 this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async; 675 this._resetLastSuggestion(); 676 this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name); 677 } 678 Dataset.extractData = function extractData(el) { 679 var $el = $(el); 680 if ($el.data(keys.obj)) { 681 return { 682 val: $el.data(keys.val) || "", 683 obj: $el.data(keys.obj) || null 684 }; 685 } 686 return null; 687 }; 688 _.mixin(Dataset.prototype, EventEmitter, { 689 _overwrite: function overwrite(query, suggestions) { 690 suggestions = suggestions || []; 691 if (suggestions.length) { 692 this._renderSuggestions(query, suggestions); 693 } else if (this.async && this.templates.pending) { 694 this._renderPending(query); 695 } else if (!this.async && this.templates.notFound) { 696 this._renderNotFound(query); 697 } else { 698 this._empty(); 699 } 700 this.trigger("rendered", this.name, suggestions, false); 701 }, 702 _append: function append(query, suggestions) { 703 suggestions = suggestions || []; 704 if (suggestions.length && this.$lastSuggestion.length) { 705 this._appendSuggestions(query, suggestions); 706 } else if (suggestions.length) { 707 this._renderSuggestions(query, suggestions); 708 } else if (!this.$lastSuggestion.length && this.templates.notFound) { 709 this._renderNotFound(query); 710 } 711 this.trigger("rendered", this.name, suggestions, true); 712 }, 713 _renderSuggestions: function renderSuggestions(query, suggestions) { 714 var $fragment; 715 $fragment = this._getSuggestionsFragment(query, suggestions); 716 this.$lastSuggestion = $fragment.children().last(); 717 this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions)); 718 }, 719 _appendSuggestions: function appendSuggestions(query, suggestions) { 720 var $fragment, $lastSuggestion; 721 $fragment = this._getSuggestionsFragment(query, suggestions); 722 $lastSuggestion = $fragment.children().last(); 723 this.$lastSuggestion.after($fragment); 724 this.$lastSuggestion = $lastSuggestion; 725 }, 726 _renderPending: function renderPending(query) { 727 var template = this.templates.pending; 728 this._resetLastSuggestion(); 729 template && this.$el.html(template({ 730 query: query, 731 dataset: this.name 732 })); 733 }, 734 _renderNotFound: function renderNotFound(query) { 735 var template = this.templates.notFound; 736 this._resetLastSuggestion(); 737 template && this.$el.html(template({ 738 query: query, 739 dataset: this.name 740 })); 741 }, 742 _empty: function empty() { 743 this.$el.empty(); 744 this._resetLastSuggestion(); 745 }, 746 _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) { 747 var that = this, fragment; 748 fragment = document.createDocumentFragment(); 749 _.each(suggestions, function getSuggestionNode(suggestion) { 750 var $el, context; 751 context = that._injectQuery(query, suggestion); 752 $el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable); 753 fragment.appendChild($el[0]); 754 }); 755 this.highlight && highlight({ 756 className: this.classes.highlight, 757 node: fragment, 758 pattern: query 759 }); 760 return $(fragment); 761 }, 762 _getFooter: function getFooter(query, suggestions) { 763 return this.templates.footer ? this.templates.footer({ 764 query: query, 765 suggestions: suggestions, 766 dataset: this.name 767 }) : null; 768 }, 769 _getHeader: function getHeader(query, suggestions) { 770 return this.templates.header ? this.templates.header({ 771 query: query, 772 suggestions: suggestions, 773 dataset: this.name 774 }) : null; 775 }, 776 _resetLastSuggestion: function resetLastSuggestion() { 777 this.$lastSuggestion = $(); 778 }, 779 _injectQuery: function injectQuery(query, obj) { 780 return _.isObject(obj) ? _.mixin({ 781 _query: query 782 }, obj) : obj; 783 }, 784 update: function update(query) { 785 var that = this, canceled = false, syncCalled = false, rendered = 0; 786 this.cancel(); 787 this.cancel = function cancel() { 788 canceled = true; 789 that.cancel = $.noop; 790 that.async && that.trigger("asyncCanceled", query); 791 }; 792 this.source(query, sync, async); 793 !syncCalled && sync([]); 794 function sync(suggestions) { 795 if (syncCalled) { 796 return; 797 } 798 syncCalled = true; 799 suggestions = (suggestions || []).slice(0, that.limit); 800 rendered = suggestions.length; 801 that._overwrite(query, suggestions); 802 if (rendered < that.limit && that.async) { 803 that.trigger("asyncRequested", query); 804 } 805 } 806 function async(suggestions) { 807 suggestions = suggestions || []; 808 if (!canceled && rendered < that.limit) { 809 that.cancel = $.noop; 810 rendered += suggestions.length; 811 812 // HACK: because we don't have a synchronous way of 813 // retrieving results, we use the async function every 814 // time we update the drop-down; however, the typeahead 815 // does some internal book-keeping which means that we 816 // only get the additional items in the drop-down when 817 // the next set of results is fetched, instead of all 818 // of them (it appears to implicitly track which 819 // results have already been shown in the drop-down); by 820 // forcing an overwrite, we see all of the new results 821 // every time we fetch a set of suggestions 822 //that._append(query, suggestions.slice(0, that.limit - rendered)); 823 that._overwrite(query, suggestions); 824 825 that.async && that.trigger("asyncReceived", query); 826 } 827 } 828 }, 829 cancel: $.noop, 830 clear: function clear() { 831 this._empty(); 832 this.cancel(); 833 this.trigger("cleared"); 834 }, 835 isEmpty: function isEmpty() { 836 return this.$el.is(":empty"); 837 }, 838 destroy: function destroy() { 839 this.$el = $("<div>"); 840 } 841 }); 842 return Dataset; 843 function getDisplayFn(display) { 844 display = display || _.stringify; 845 return _.isFunction(display) ? display : displayFn; 846 function displayFn(obj) { 847 return obj[display]; 848 } 849 } 850 function getTemplates(templates, displayFn) { 851 return { 852 notFound: templates.notFound && _.templatify(templates.notFound), 853 pending: templates.pending && _.templatify(templates.pending), 854 header: templates.header && _.templatify(templates.header), 855 footer: templates.footer && _.templatify(templates.footer), 856 suggestion: templates.suggestion || suggestionTemplate 857 }; 858 function suggestionTemplate(context) { 859 return $("<div>").text(displayFn(context)); 860 } 861 } 862 function isValidName(str) { 863 return /^[_a-zA-Z0-9-]+$/.test(str); 864 } 865 }(); 866 var Menu = function() { 867 "use strict"; 868 function Menu(o, www) { 869 var that = this; 870 o = o || {}; 871 if (!o.node) { 872 $.error("node is required"); 873 } 874 www.mixin(this); 875 this.$node = $(o.node); 876 this.query = null; 877 this.datasets = _.map(o.datasets, initializeDataset); 878 function initializeDataset(oDataset) { 879 var node = that.$node.find(oDataset.node).first(); 880 oDataset.node = node.length ? node : $("<div>").appendTo(that.$node); 881 return new Dataset(oDataset, www); 882 } 883 } 884 _.mixin(Menu.prototype, EventEmitter, { 885 _onSelectableClick: function onSelectableClick($e) { 886 this.trigger("selectableClicked", $($e.currentTarget)); 887 }, 888 _onRendered: function onRendered(type, dataset, suggestions, async) { 889 this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); 890 this.trigger("datasetRendered", dataset, suggestions, async); 891 }, 892 _onCleared: function onCleared() { 893 this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); 894 this.trigger("datasetCleared"); 895 }, 896 _propagate: function propagate() { 897 this.trigger.apply(this, arguments); 898 }, 899 _allDatasetsEmpty: function allDatasetsEmpty() { 900 return _.every(this.datasets, isDatasetEmpty); 901 function isDatasetEmpty(dataset) { 902 return dataset.isEmpty(); 903 } 904 }, 905 _getSelectables: function getSelectables() { 906 return this.$node.find(this.selectors.selectable); 907 }, 908 _removeCursor: function _removeCursor() { 909 var $selectable = this.getActiveSelectable(); 910 $selectable && $selectable.removeClass(this.classes.cursor); 911 }, 912 _ensureVisible: function ensureVisible($el) { 913 var elTop, elBottom, nodeScrollTop, nodeHeight; 914 elTop = $el.position().top; 915 elBottom = elTop + $el.outerHeight(true); 916 nodeScrollTop = this.$node.scrollTop(); 917 nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10); 918 if (elTop < 0) { 919 this.$node.scrollTop(nodeScrollTop + elTop); 920 } else if (nodeHeight < elBottom) { 921 this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight)); 922 } 923 }, 924 bind: function() { 925 var that = this, onSelectableClick; 926 onSelectableClick = _.bind(this._onSelectableClick, this); 927 this.$node.on("click.tt", this.selectors.selectable, onSelectableClick); 928 _.each(this.datasets, function(dataset) { 929 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); 930 }); 931 return this; 932 }, 933 isOpen: function isOpen() { 934 return this.$node.hasClass(this.classes.open); 935 }, 936 open: function open() { 937 this.$node.addClass(this.classes.open); 938 }, 939 close: function close() { 940 this.$node.removeClass(this.classes.open); 941 this._removeCursor(); 942 }, 943 setLanguageDirection: function setLanguageDirection(dir) { 944 this.$node.attr("dir", dir); 945 }, 946 selectableRelativeToCursor: function selectableRelativeToCursor(delta) { 947 var $selectables, $oldCursor, oldIndex, newIndex; 948 $oldCursor = this.getActiveSelectable(); 949 $selectables = this._getSelectables(); 950 oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1; 951 newIndex = oldIndex + delta; 952 newIndex = (newIndex + 1) % ($selectables.length + 1) - 1; 953 newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex; 954 return newIndex === -1 ? null : $selectables.eq(newIndex); 955 }, 956 setCursor: function setCursor($selectable) { 957 this._removeCursor(); 958 if ($selectable = $selectable && $selectable.first()) { 959 $selectable.addClass(this.classes.cursor); 960 this._ensureVisible($selectable); 961 } 962 }, 963 getSelectableData: function getSelectableData($el) { 964 return $el && $el.length ? Dataset.extractData($el) : null; 965 }, 966 getActiveSelectable: function getActiveSelectable() { 967 var $selectable = this._getSelectables().filter(this.selectors.cursor).first(); 968 return $selectable.length ? $selectable : null; 969 }, 970 getTopSelectable: function getTopSelectable() { 971 var $selectable = this._getSelectables().first(); 972 return $selectable.length ? $selectable : null; 973 }, 974 update: function update(query) { 975 var isValidUpdate = query !== this.query; 976 if (isValidUpdate) { 977 this.query = query; 978 _.each(this.datasets, updateDataset); 979 } 980 return isValidUpdate; 981 function updateDataset(dataset) { 982 dataset.update(query); 983 } 984 }, 985 empty: function empty() { 986 _.each(this.datasets, clearDataset); 987 this.query = null; 988 this.$node.addClass(this.classes.empty); 989 function clearDataset(dataset) { 990 dataset.clear(); 991 } 992 }, 993 destroy: function destroy() { 994 this.$node.off(".tt"); 995 this.$node = $("<div>"); 996 _.each(this.datasets, destroyDataset); 997 function destroyDataset(dataset) { 998 dataset.destroy(); 999 } 1000 } 1001 }); 1002 return Menu; 1003 }(); 1004 var DefaultMenu = function() { 1005 "use strict"; 1006 var s = Menu.prototype; 1007 function DefaultMenu() { 1008 Menu.apply(this, [].slice.call(arguments, 0)); 1009 } 1010 _.mixin(DefaultMenu.prototype, Menu.prototype, { 1011 open: function open() { 1012 !this._allDatasetsEmpty() && this._show(); 1013 return s.open.apply(this, [].slice.call(arguments, 0)); 1014 }, 1015 close: function close() { 1016 this._hide(); 1017 return s.close.apply(this, [].slice.call(arguments, 0)); 1018 }, 1019 _onRendered: function onRendered() { 1020 if (this._allDatasetsEmpty()) { 1021 this._hide(); 1022 } else { 1023 this.isOpen() && this._show(); 1024 } 1025 return s._onRendered.apply(this, [].slice.call(arguments, 0)); 1026 }, 1027 _onCleared: function onCleared() { 1028 if (this._allDatasetsEmpty()) { 1029 this._hide(); 1030 } else { 1031 this.isOpen() && this._show(); 1032 } 1033 return s._onCleared.apply(this, [].slice.call(arguments, 0)); 1034 }, 1035 setLanguageDirection: function setLanguageDirection(dir) { 1036 this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl); 1037 return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0)); 1038 }, 1039 _hide: function hide() { 1040 this.$node.hide(); 1041 }, 1042 _show: function show() { 1043 this.$node.css("display", "block"); 1044 } 1045 }); 1046 return DefaultMenu; 1047 }(); 1048 var Typeahead = function() { 1049 "use strict"; 1050 function Typeahead(o, www) { 1051 var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged; 1052 o = o || {}; 1053 if (!o.input) { 1054 $.error("missing input"); 1055 } 1056 if (!o.menu) { 1057 $.error("missing menu"); 1058 } 1059 if (!o.eventBus) { 1060 $.error("missing event bus"); 1061 } 1062 www.mixin(this); 1063 this.eventBus = o.eventBus; 1064 this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; 1065 this.input = o.input; 1066 this.menu = o.menu; 1067 this.enabled = true; 1068 this.active = false; 1069 this.input.hasFocus() && this.activate(); 1070 this.dir = this.input.getLangDir(); 1071 this._hacks(); 1072 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); 1073 onFocused = c(this, "activate", "open", "_onFocused"); 1074 onBlurred = c(this, "deactivate", "_onBlurred"); 1075 onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed"); 1076 onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed"); 1077 onEscKeyed = c(this, "isActive", "_onEscKeyed"); 1078 onUpKeyed = c(this, "isActive", "open", "_onUpKeyed"); 1079 onDownKeyed = c(this, "isActive", "open", "_onDownKeyed"); 1080 onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed"); 1081 onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed"); 1082 onQueryChanged = c(this, "_openIfActive", "_onQueryChanged"); 1083 onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged"); 1084 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); 1085 } 1086 _.mixin(Typeahead.prototype, { 1087 _hacks: function hacks() { 1088 var $input, $menu; 1089 $input = this.input.$input || $("<div>"); 1090 $menu = this.menu.$node || $("<div>"); 1091 $input.on("blur.tt", function($e) { 1092 var active, isActive, hasActive; 1093 active = document.activeElement; 1094 isActive = $menu.is(active); 1095 hasActive = $menu.has(active).length > 0; 1096 if (_.isMsie() && (isActive || hasActive)) { 1097 $e.preventDefault(); 1098 $e.stopImmediatePropagation(); 1099 _.defer(function() { 1100 $input.focus(); 1101 }); 1102 } 1103 }); 1104 $menu.on("mousedown.tt", function($e) { 1105 $e.preventDefault(); 1106 }); 1107 }, 1108 _onSelectableClicked: function onSelectableClicked(type, $el) { 1109 this.select($el); 1110 }, 1111 _onDatasetCleared: function onDatasetCleared() { 1112 this._updateHint(); 1113 }, 1114 _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) { 1115 this._updateHint(); 1116 this.eventBus.trigger("render", suggestions, async, dataset); 1117 }, 1118 _onAsyncRequested: function onAsyncRequested(type, dataset, query) { 1119 this.eventBus.trigger("asyncrequest", query, dataset); 1120 }, 1121 _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) { 1122 this.eventBus.trigger("asynccancel", query, dataset); 1123 }, 1124 _onAsyncReceived: function onAsyncReceived(type, dataset, query) { 1125 this.eventBus.trigger("asyncreceive", query, dataset); 1126 }, 1127 _onFocused: function onFocused() { 1128 this._minLengthMet() && this.menu.update(this.input.getQuery()); 1129 }, 1130 _onBlurred: function onBlurred() { 1131 if (this.input.hasQueryChangedSinceLastFocus()) { 1132 this.eventBus.trigger("change", this.input.getQuery()); 1133 } 1134 }, 1135 _onEnterKeyed: function onEnterKeyed(type, $e) { 1136 var $selectable; 1137 if ($selectable = this.menu.getActiveSelectable()) { 1138 this.select($selectable) && $e.preventDefault(); 1139 } 1140 }, 1141 _onTabKeyed: function onTabKeyed(type, $e) { 1142 var $selectable; 1143 if ($selectable = this.menu.getActiveSelectable()) { 1144 this.select($selectable) && $e.preventDefault(); 1145 } else if ($selectable = this.menu.getTopSelectable()) { 1146 this.autocomplete($selectable) && $e.preventDefault(); 1147 } 1148 }, 1149 _onEscKeyed: function onEscKeyed() { 1150 this.close(); 1151 }, 1152 _onUpKeyed: function onUpKeyed() { 1153 this.moveCursor(-1); 1154 }, 1155 _onDownKeyed: function onDownKeyed() { 1156 this.moveCursor(+1); 1157 }, 1158 _onLeftKeyed: function onLeftKeyed() { 1159 if (this.dir === "rtl" && this.input.isCursorAtEnd()) { 1160 this.autocomplete(this.menu.getTopSelectable()); 1161 } 1162 }, 1163 _onRightKeyed: function onRightKeyed() { 1164 if (this.dir === "ltr" && this.input.isCursorAtEnd()) { 1165 this.autocomplete(this.menu.getTopSelectable()); 1166 } 1167 }, 1168 _onQueryChanged: function onQueryChanged(e, query) { 1169 this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty(); 1170 }, 1171 _onWhitespaceChanged: function onWhitespaceChanged() { 1172 this._updateHint(); 1173 }, 1174 _onLangDirChanged: function onLangDirChanged(e, dir) { 1175 if (this.dir !== dir) { 1176 this.dir = dir; 1177 this.menu.setLanguageDirection(dir); 1178 } 1179 }, 1180 _openIfActive: function openIfActive() { 1181 this.isActive() && this.open(); 1182 }, 1183 _minLengthMet: function minLengthMet(query) { 1184 query = _.isString(query) ? query : this.input.getQuery() || ""; 1185 return query.length >= this.minLength; 1186 }, 1187 _updateHint: function updateHint() { 1188 var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match; 1189 $selectable = this.menu.getTopSelectable(); 1190 data = this.menu.getSelectableData($selectable); 1191 val = this.input.getInputValue(); 1192 if (data && !_.isBlankString(val) && !this.input.hasOverflow()) { 1193 query = Input.normalizeQuery(val); 1194 escapedQuery = _.escapeRegExChars(query); 1195 frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); 1196 match = frontMatchRegEx.exec(data.val); 1197 match && this.input.setHint(val + match[1]); 1198 } else { 1199 this.input.clearHint(); 1200 } 1201 }, 1202 isEnabled: function isEnabled() { 1203 return this.enabled; 1204 }, 1205 enable: function enable() { 1206 this.enabled = true; 1207 }, 1208 disable: function disable() { 1209 this.enabled = false; 1210 }, 1211 isActive: function isActive() { 1212 return this.active; 1213 }, 1214 activate: function activate() { 1215 if (this.isActive()) { 1216 return true; 1217 } else if (!this.isEnabled() || this.eventBus.before("active")) { 1218 return false; 1219 } else { 1220 this.active = true; 1221 this.eventBus.trigger("active"); 1222 return true; 1223 } 1224 }, 1225 deactivate: function deactivate() { 1226 if (!this.isActive()) { 1227 return true; 1228 } else if (this.eventBus.before("idle")) { 1229 return false; 1230 } else { 1231 this.active = false; 1232 this.close(); 1233 this.eventBus.trigger("idle"); 1234 return true; 1235 } 1236 }, 1237 isOpen: function isOpen() { 1238 return this.menu.isOpen(); 1239 }, 1240 open: function open() { 1241 if (!this.isOpen() && !this.eventBus.before("open")) { 1242 this.menu.open(); 1243 this._updateHint(); 1244 this.eventBus.trigger("open"); 1245 } 1246 return this.isOpen(); 1247 }, 1248 close: function close() { 1249 if (this.isOpen() && !this.eventBus.before("close")) { 1250 this.menu.close(); 1251 this.input.clearHint(); 1252 this.input.resetInputValue(); 1253 this.eventBus.trigger("close"); 1254 } 1255 return !this.isOpen(); 1256 }, 1257 setVal: function setVal(val) { 1258 this.input.setQuery(_.toStr(val)); 1259 }, 1260 getVal: function getVal() { 1261 return this.input.getQuery(); 1262 }, 1263 select: function select($selectable) { 1264 var data = this.menu.getSelectableData($selectable); 1265 if (data && !this.eventBus.before("select", data.obj)) { 1266 this.input.setQuery(data.val, true); 1267 this.eventBus.trigger("select", data.obj); 1268 this.close(); 1269 return true; 1270 } 1271 return false; 1272 }, 1273 autocomplete: function autocomplete($selectable) { 1274 var query, data, isValid; 1275 query = this.input.getQuery(); 1276 data = this.menu.getSelectableData($selectable); 1277 isValid = data && query !== data.val; 1278 if (isValid && !this.eventBus.before("autocomplete", data.obj)) { 1279 this.input.setQuery(data.val); 1280 this.eventBus.trigger("autocomplete", data.obj); 1281 return true; 1282 } 1283 return false; 1284 }, 1285 moveCursor: function moveCursor(delta) { 1286 var query, $candidate, data, payload, cancelMove; 1287 query = this.input.getQuery(); 1288 $candidate = this.menu.selectableRelativeToCursor(delta); 1289 data = this.menu.getSelectableData($candidate); 1290 payload = data ? data.obj : null; 1291 cancelMove = this._minLengthMet() && this.menu.update(query); 1292 if (!cancelMove && !this.eventBus.before("cursorchange", payload)) { 1293 this.menu.setCursor($candidate); 1294 if (data) { 1295 this.input.setInputValue(data.val); 1296 } else { 1297 this.input.resetInputValue(); 1298 this._updateHint(); 1299 } 1300 this.eventBus.trigger("cursorchange", payload); 1301 return true; 1302 } 1303 return false; 1304 }, 1305 destroy: function destroy() { 1306 this.input.destroy(); 1307 this.menu.destroy(); 1308 } 1309 }); 1310 return Typeahead; 1311 function c(ctx) { 1312 var methods = [].slice.call(arguments, 1); 1313 return function() { 1314 var args = [].slice.call(arguments); 1315 _.each(methods, function(method) { 1316 return ctx[method].apply(ctx, args); 1317 }); 1318 }; 1319 } 1320 }(); 1321 (function() { 1322 "use strict"; 1323 var old, keys, methods; 1324 old = $.fn.typeahead; 1325 keys = { 1326 www: "tt-www", 1327 attrs: "tt-attrs", 1328 typeahead: "tt-typeahead" 1329 }; 1330 methods = { 1331 initialize: function initialize(o, datasets) { 1332 var www; 1333 datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); 1334 o = o || {}; 1335 www = WWW(o.classNames); 1336 return this.each(attach); 1337 function attach() { 1338 var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor; 1339 _.each(datasets, function(d) { 1340 d.highlight = !!o.highlight; 1341 }); 1342 $input = $(this); 1343 $wrapper = $(www.html.wrapper); 1344 $hint = $elOrNull(o.hint); 1345 $menu = $elOrNull(o.menu); 1346 defaultHint = o.hint !== false && !$hint; 1347 defaultMenu = o.menu !== false && !$menu; 1348 defaultHint && ($hint = buildHintFromInput($input, www)); 1349 defaultMenu && ($menu = $(www.html.menu).css(www.css.menu)); 1350 $hint && $hint.val(""); 1351 $input = prepInput($input, www); 1352 if (defaultHint || defaultMenu) { 1353 $wrapper.css(www.css.wrapper); 1354 $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint); 1355 $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null); 1356 } 1357 MenuConstructor = defaultMenu ? DefaultMenu : Menu; 1358 eventBus = new EventBus({ 1359 el: $input 1360 }); 1361 input = new Input({ 1362 hint: $hint, 1363 input: $input 1364 }, www); 1365 menu = new MenuConstructor({ 1366 node: $menu, 1367 datasets: datasets 1368 }, www); 1369 typeahead = new Typeahead({ 1370 input: input, 1371 menu: menu, 1372 eventBus: eventBus, 1373 minLength: o.minLength 1374 }, www); 1375 $input.data(keys.www, www); 1376 $input.data(keys.typeahead, typeahead); 1377 } 1378 }, 1379 isEnabled: function isEnabled() { 1380 var enabled; 1381 ttEach(this.first(), function(t) { 1382 enabled = t.isEnabled(); 1383 }); 1384 return enabled; 1385 }, 1386 enable: function enable() { 1387 ttEach(this, function(t) { 1388 t.enable(); 1389 }); 1390 return this; 1391 }, 1392 disable: function disable() { 1393 ttEach(this, function(t) { 1394 t.disable(); 1395 }); 1396 return this; 1397 }, 1398 isActive: function isActive() { 1399 var active; 1400 ttEach(this.first(), function(t) { 1401 active = t.isActive(); 1402 }); 1403 return active; 1404 }, 1405 activate: function activate() { 1406 ttEach(this, function(t) { 1407 t.activate(); 1408 }); 1409 return this; 1410 }, 1411 deactivate: function deactivate() { 1412 ttEach(this, function(t) { 1413 t.deactivate(); 1414 }); 1415 return this; 1416 }, 1417 isOpen: function isOpen() { 1418 var open; 1419 ttEach(this.first(), function(t) { 1420 open = t.isOpen(); 1421 }); 1422 return open; 1423 }, 1424 open: function open() { 1425 ttEach(this, function(t) { 1426 t.open(); 1427 }); 1428 return this; 1429 }, 1430 close: function close() { 1431 ttEach(this, function(t) { 1432 t.close(); 1433 }); 1434 return this; 1435 }, 1436 select: function select(el) { 1437 var success = false, $el = $(el); 1438 ttEach(this.first(), function(t) { 1439 success = t.select($el); 1440 }); 1441 return success; 1442 }, 1443 autocomplete: function autocomplete(el) { 1444 var success = false, $el = $(el); 1445 ttEach(this.first(), function(t) { 1446 success = t.autocomplete($el); 1447 }); 1448 return success; 1449 }, 1450 moveCursor: function moveCursoe(delta) { 1451 var success = false; 1452 ttEach(this.first(), function(t) { 1453 success = t.moveCursor(delta); 1454 }); 1455 return success; 1456 }, 1457 val: function val(newVal) { 1458 var query; 1459 if (!arguments.length) { 1460 ttEach(this.first(), function(t) { 1461 query = t.getVal(); 1462 }); 1463 return query; 1464 } else { 1465 ttEach(this, function(t) { 1466 t.setVal(newVal); 1467 }); 1468 return this; 1469 } 1470 }, 1471 destroy: function destroy() { 1472 ttEach(this, function(typeahead, $input) { 1473 revert($input); 1474 typeahead.destroy(); 1475 }); 1476 return this; 1477 } 1478 }; 1479 $.fn.typeahead = function(method) { 1480 if (methods[method]) { 1481 return methods[method].apply(this, [].slice.call(arguments, 1)); 1482 } else { 1483 return methods.initialize.apply(this, arguments); 1484 } 1485 }; 1486 $.fn.typeahead.noConflict = function noConflict() { 1487 $.fn.typeahead = old; 1488 return this; 1489 }; 1490 function ttEach($els, fn) { 1491 $els.each(function() { 1492 var $input = $(this), typeahead; 1493 (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input); 1494 }); 1495 } 1496 function buildHintFromInput($input, www) { 1497 return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({ 1498 autocomplete: "off", 1499 spellcheck: "false", 1500 tabindex: -1 1501 }); 1502 } 1503 function prepInput($input, www) { 1504 $input.data(keys.attrs, { 1505 dir: $input.attr("dir"), 1506 autocomplete: $input.attr("autocomplete"), 1507 spellcheck: $input.attr("spellcheck"), 1508 style: $input.attr("style") 1509 }); 1510 $input.addClass(www.classes.input).attr({ 1511 autocomplete: "off", 1512 spellcheck: false 1513 }); 1514 try { 1515 !$input.attr("dir") && $input.attr("dir", "auto"); 1516 } catch (e) {} 1517 return $input; 1518 } 1519 function getBackgroundStyles($el) { 1520 return { 1521 backgroundAttachment: $el.css("background-attachment"), 1522 backgroundClip: $el.css("background-clip"), 1523 backgroundColor: $el.css("background-color"), 1524 backgroundImage: $el.css("background-image"), 1525 backgroundOrigin: $el.css("background-origin"), 1526 backgroundPosition: $el.css("background-position"), 1527 backgroundRepeat: $el.css("background-repeat"), 1528 backgroundSize: $el.css("background-size") 1529 }; 1530 } 1531 function revert($input) { 1532 var www, $wrapper; 1533 www = $input.data(keys.www); 1534 $wrapper = $input.parent().filter(www.selectors.wrapper); 1535 _.each($input.data(keys.attrs), function(val, key) { 1536 _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); 1537 }); 1538 $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input); 1539 if ($wrapper.length) { 1540 $input.detach().insertAfter($wrapper); 1541 $wrapper.remove(); 1542 } 1543 } 1544 function $elOrNull(obj) { 1545 var isValid, $el; 1546 isValid = _.isJQuery(obj) || _.isElement(obj); 1547 $el = isValid ? $(obj).first() : []; 1548 return $el.length ? $el : null; 1549 } 1550 })(); 1551});