Blame | Last modification | View Log | RSS feed
/** typeahead.js* https://github.com/twitter/typeahead.js* Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT*/var Typeahead = (function() {'use strict';var attrsKey = 'ttAttrs';// constructor// -----------// THOUGHT: what if datasets could dynamically be added/removed?function Typeahead(o) {var $menu, $input, $hint;o = o || {};if (!o.input) {$.error('missing input');}this.isActivated = false;this.autoselect = !!o.autoselect;this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;this.$node = buildDom(o.input, o.withHint);$menu = this.$node.find('.tt-dropdown-menu');$input = this.$node.find('.tt-input');$hint = this.$node.find('.tt-hint');// #705: if there's scrollable overflow, ie doesn't support// blur cancellations when the scrollbar is clicked//// #351: preventDefault won't cancel blurs in ie <= 8$input.on('blur.tt', function($e) {var active, isActive, hasActive;active = document.activeElement;isActive = $menu.is(active);hasActive = $menu.has(active).length > 0;if (_.isMsie() && (isActive || hasActive)) {$e.preventDefault();// stop immediate in order to prevent Input#_onBlur from// getting exectued$e.stopImmediatePropagation();_.defer(function() { $input.focus(); });}});// #351: prevents input blur due to clicks within dropdown menu$menu.on('mousedown.tt', function($e) { $e.preventDefault(); });this.eventBus = o.eventBus || new EventBus({ el: $input });this.dropdown = new Dropdown({ menu: $menu, datasets: o.datasets }).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);this.input = new Input({ input: $input, hint: $hint }).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);this._setLanguageDirection();}// instance methods// ----------------_.mixin(Typeahead.prototype, {// ### private_onSuggestionClicked: function onSuggestionClicked(type, $el) {var datum;if (datum = this.dropdown.getDatumForSuggestion($el)) {this._select(datum);}},_onCursorMoved: function onCursorMoved() {var datum = this.dropdown.getDatumForCursor();this.input.setInputValue(datum.value, true);this.eventBus.trigger('cursorchanged', datum.raw, datum.datasetName);},_onCursorRemoved: function onCursorRemoved() {this.input.resetInputValue();this._updateHint();},_onDatasetRendered: function onDatasetRendered() {this._updateHint();},_onOpened: function onOpened() {this._updateHint();this.eventBus.trigger('opened');},_onClosed: function onClosed() {this.input.clearHint();this.eventBus.trigger('closed');},_onFocused: function onFocused() {this.isActivated = true;this.dropdown.open();},_onBlurred: function onBlurred() {this.isActivated = false;this.dropdown.empty();this.dropdown.close();},_onEnterKeyed: function onEnterKeyed(type, $e) {var cursorDatum, topSuggestionDatum;cursorDatum = this.dropdown.getDatumForCursor();topSuggestionDatum = this.dropdown.getDatumForTopSuggestion();if (cursorDatum) {this._select(cursorDatum);$e.preventDefault();}else if (this.autoselect && topSuggestionDatum) {this._select(topSuggestionDatum);$e.preventDefault();}},_onTabKeyed: function onTabKeyed(type, $e) {var datum;if (datum = this.dropdown.getDatumForCursor()) {this._select(datum);$e.preventDefault();}else {this._autocomplete(true);}},_onEscKeyed: function onEscKeyed() {this.dropdown.close();this.input.resetInputValue();},_onUpKeyed: function onUpKeyed() {var query = this.input.getQuery();this.dropdown.isEmpty && query.length >= this.minLength ?this.dropdown.update(query) :this.dropdown.moveCursorUp();this.dropdown.open();},_onDownKeyed: function onDownKeyed() {var query = this.input.getQuery();this.dropdown.isEmpty && query.length >= this.minLength ?this.dropdown.update(query) :this.dropdown.moveCursorDown();this.dropdown.open();},_onLeftKeyed: function onLeftKeyed() {this.dir === 'rtl' && this._autocomplete();},_onRightKeyed: function onRightKeyed() {this.dir === 'ltr' && this._autocomplete();},_onQueryChanged: function onQueryChanged(e, query) {this.input.clearHintIfInvalid();query.length >= this.minLength ?this.dropdown.update(query) :this.dropdown.empty();this.dropdown.open();this._setLanguageDirection();},_onWhitespaceChanged: function onWhitespaceChanged() {this._updateHint();this.dropdown.open();},_setLanguageDirection: function setLanguageDirection() {var dir;if (this.dir !== (dir = this.input.getLanguageDirection())) {this.dir = dir;this.$node.css('direction', dir);this.dropdown.setLanguageDirection(dir);}},_updateHint: function updateHint() {var datum, val, query, escapedQuery, frontMatchRegEx, match;datum = this.dropdown.getDatumForTopSuggestion();if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) {val = this.input.getInputValue();query = Input.normalizeQuery(val);escapedQuery = _.escapeRegExChars(query);// match input value, then capture trailing textfrontMatchRegEx = new RegExp('^(?:' + escapedQuery + ')(.+$)', 'i');match = frontMatchRegEx.exec(datum.value);// clear hint if there's no trailing textmatch ? this.input.setHint(val + match[1]) : this.input.clearHint();}else {this.input.clearHint();}},_autocomplete: function autocomplete(laxCursor) {var hint, query, isCursorAtEnd, datum;hint = this.input.getHint();query = this.input.getQuery();isCursorAtEnd = laxCursor || this.input.isCursorAtEnd();if (hint && query !== hint && isCursorAtEnd) {datum = this.dropdown.getDatumForTopSuggestion();datum && this.input.setInputValue(datum.value);this.eventBus.trigger('autocompleted', datum.raw, datum.datasetName);}},_select: function select(datum) {this.input.setQuery(datum.value);this.input.setInputValue(datum.value, true);this._setLanguageDirection();this.eventBus.trigger('selected', datum.raw, datum.datasetName);this.dropdown.close();// #118: allow click event to bubble up to the body before removing// the suggestions otherwise we break event delegation_.defer(_.bind(this.dropdown.empty, this.dropdown));},// ### publicopen: function open() {this.dropdown.open();},close: function close() {this.dropdown.close();},setVal: function setVal(val) {// expect val to be a string, so be safe, and coerceval = _.toStr(val);if (this.isActivated) {this.input.setInputValue(val);}else {this.input.setQuery(val);this.input.setInputValue(val, true);}this._setLanguageDirection();},getVal: function getVal() {return this.input.getQuery();},destroy: function destroy() {this.input.destroy();this.dropdown.destroy();destroyDomStructure(this.$node);this.$node = null;}});return Typeahead;function buildDom(input, withHint) {var $input, $wrapper, $dropdown, $hint;$input = $(input);$wrapper = $(html.wrapper).css(css.wrapper);$dropdown = $(html.dropdown).css(css.dropdown);$hint = $input.clone().css(css.hint).css(getBackgroundStyles($input));$hint.val('').removeData().addClass('tt-hint').removeAttr('id name placeholder required').prop('readonly', true).attr({ autocomplete: 'off', spellcheck: 'false', tabindex: -1 });// store the original values of the attrs that get modified// so modifications can be reverted on destroy$input.data(attrsKey, {dir: $input.attr('dir'),autocomplete: $input.attr('autocomplete'),spellcheck: $input.attr('spellcheck'),style: $input.attr('style')});$input.addClass('tt-input').attr({ autocomplete: 'off', spellcheck: false }).css(withHint ? css.input : css.inputWithNoHint);// ie7 does not like it when dir is set to autotry { !$input.attr('dir') && $input.attr('dir', 'auto'); } catch (e) {}return $input.wrap($wrapper).parent().prepend(withHint ? $hint : null).append($dropdown);}function getBackgroundStyles($el) {return {backgroundAttachment: $el.css('background-attachment'),backgroundClip: $el.css('background-clip'),backgroundColor: $el.css('background-color'),backgroundImage: $el.css('background-image'),backgroundOrigin: $el.css('background-origin'),backgroundPosition: $el.css('background-position'),backgroundRepeat: $el.css('background-repeat'),backgroundSize: $el.css('background-size')};}function destroyDomStructure($node) {var $input = $node.find('.tt-input');// need to remove attrs that weren't previously defined and// revert attrs that originally had a value_.each($input.data(attrsKey), function(val, key) {_.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);});$input.detach().removeData(attrsKey).removeClass('tt-input').insertAfter($node);$node.remove();}})();