UNPKG

typeahead-addresspicker

Version:

Example of adding google map API to typeahead jquery plugin to display address autocomplete suggestions.

312 lines (239 loc) 7.97 kB
/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ var Input = (function() { 'use strict'; var specialKeyCodeMap; specialKeyCodeMap = { 9: 'tab', 27: 'esc', 37: 'left', 39: 'right', 13: 'enter', 38: 'up', 40: 'down' }; // constructor // ----------- function Input(o) { var that = this, onBlur, onFocus, onKeydown, onInput; o = o || {}; if (!o.input) { $.error('input is missing'); } // bound functions onBlur = _.bind(this._onBlur, this); onFocus = _.bind(this._onFocus, this); onKeydown = _.bind(this._onKeydown, this); onInput = _.bind(this._onInput, this); this.$hint = $(o.hint); this.$input = $(o.input) .on('blur.tt', onBlur) .on('focus.tt', onFocus) .on('keydown.tt', onKeydown); // if no hint, noop all the hint related functions if (this.$hint.length === 0) { this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; } // ie7 and ie8 don't support the input event // ie9 doesn't fire the input event when characters are removed // not sure if ie10 is compatible if (!_.isMsie()) { this.$input.on('input.tt', onInput); } else { this.$input.on('keydown.tt keypress.tt cut.tt paste.tt', function($e) { // if a special key triggered this, ignore it if (specialKeyCodeMap[$e.which || $e.keyCode]) { return; } // give the browser a chance to update the value of the input // before checking to see if the query changed _.defer(_.bind(that._onInput, that, $e)); }); } // the query defaults to whatever the value of the input is // on initialization, it'll most likely be an empty string this.query = this.$input.val(); // helps with calculating the width of the input's value this.$overflowHelper = buildOverflowHelper(this.$input); } // static methods // -------------- Input.normalizeQuery = function(str) { // strips leading whitespace and condenses all whitespace return (str || '').replace(/^\s*/g, '').replace(/\s{2,}/g, ' '); }; // instance methods // ---------------- _.mixin(Input.prototype, EventEmitter, { // ### private _onBlur: function onBlur() { this.resetInputValue(); this.trigger('blurred'); }, _onFocus: function onFocus() { this.trigger('focused'); }, _onKeydown: function onKeydown($e) { // which is normalized and consistent (but not for ie) var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; this._managePreventDefault(keyName, $e); if (keyName && this._shouldTrigger(keyName, $e)) { this.trigger(keyName + 'Keyed', $e); } }, _onInput: function onInput() { this._checkInputValue(); }, _managePreventDefault: function managePreventDefault(keyName, $e) { var preventDefault, hintValue, inputValue; switch (keyName) { case 'tab': hintValue = this.getHint(); inputValue = this.getInputValue(); preventDefault = hintValue && hintValue !== inputValue && !withModifier($e); break; case 'up': case 'down': preventDefault = !withModifier($e); break; default: preventDefault = false; } preventDefault && $e.preventDefault(); }, _shouldTrigger: function shouldTrigger(keyName, $e) { var trigger; switch (keyName) { case 'tab': trigger = !withModifier($e); break; default: trigger = true; } return trigger; }, _checkInputValue: function checkInputValue() { var inputValue, areEquivalent, hasDifferentWhitespace; inputValue = this.getInputValue(); areEquivalent = areQueriesEquivalent(inputValue, this.query); hasDifferentWhitespace = areEquivalent ? this.query.length !== inputValue.length : false; this.query = inputValue; if (!areEquivalent) { this.trigger('queryChanged', this.query); } else if (hasDifferentWhitespace) { this.trigger('whitespaceChanged', this.query); } }, // ### public focus: function focus() { this.$input.focus(); }, blur: function blur() { this.$input.blur(); }, getQuery: function getQuery() { return this.query; }, setQuery: function setQuery(query) { this.query = query; }, getInputValue: function getInputValue() { return this.$input.val(); }, setInputValue: function setInputValue(value, silent) { this.$input.val(value); // silent prevents any additional events from being triggered silent ? this.clearHint() : this._checkInputValue(); }, resetInputValue: function resetInputValue() { this.setInputValue(this.query, true); }, getHint: function getHint() { return this.$hint.val(); }, setHint: function setHint(value) { this.$hint.val(value); }, clearHint: function clearHint() { this.setHint(''); }, clearHintIfInvalid: function clearHintIfInvalid() { var val, hint, valIsPrefixOfHint, isValid; val = this.getInputValue(); hint = this.getHint(); valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; isValid = val !== '' && valIsPrefixOfHint && !this.hasOverflow(); !isValid && this.clearHint(); }, getLanguageDirection: function getLanguageDirection() { return (this.$input.css('direction') || 'ltr').toLowerCase(); }, hasOverflow: function hasOverflow() { // 2 is arbitrary, just picking a small number to handle edge cases var constraint = this.$input.width() - 2; this.$overflowHelper.text(this.getInputValue()); return this.$overflowHelper.width() >= constraint; }, isCursorAtEnd: function() { var valueLength, selectionStart, range; valueLength = this.$input.val().length; selectionStart = this.$input[0].selectionStart; if (_.isNumber(selectionStart)) { return selectionStart === valueLength; } else if (document.selection) { // NOTE: this won't work unless the input has focus, the good news // is this code should only get called when the input has focus range = document.selection.createRange(); range.moveStart('character', -valueLength); return valueLength === range.text.length; } return true; }, destroy: function destroy() { this.$hint.off('.tt'); this.$input.off('.tt'); this.$hint = this.$input = this.$overflowHelper = null; } }); return Input; // helper functions // ---------------- function buildOverflowHelper($input) { return $('<pre aria-hidden="true"></pre>') .css({ // position helper off-screen position: 'absolute', visibility: 'hidden', // avoid line breaks and whitespace collapsing whiteSpace: 'pre', // use same font css as input to calculate accurate width fontFamily: $input.css('font-family'), fontSize: $input.css('font-size'), fontStyle: $input.css('font-style'), fontVariant: $input.css('font-variant'), fontWeight: $input.css('font-weight'), wordSpacing: $input.css('word-spacing'), letterSpacing: $input.css('letter-spacing'), textIndent: $input.css('text-indent'), textRendering: $input.css('text-rendering'), textTransform: $input.css('text-transform') }) .insertAfter($input); } function areQueriesEquivalent(a, b) { return Input.normalizeQuery(a) === Input.normalizeQuery(b); } function withModifier($e) { return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; } })();