typeahead-addresspicker
Version:
Example of adding google map API to typeahead jquery plugin to display address autocomplete suggestions.
246 lines (177 loc) • 6.15 kB
JavaScript
/*
* typeahead.js
* https://github.com/twitter/typeahead.js
* Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT
*/
var Dropdown = (function() {
'use strict';
// constructor
// -----------
function Dropdown(o) {
var that = this, onSuggestionClick, onSuggestionMouseEnter,
onSuggestionMouseLeave;
o = o || {};
if (!o.menu) {
$.error('menu is required');
}
this.isOpen = false;
this.isEmpty = true;
this.datasets = _.map(o.datasets, initializeDataset);
// bound functions
onSuggestionClick = _.bind(this._onSuggestionClick, this);
onSuggestionMouseEnter = _.bind(this._onSuggestionMouseEnter, this);
onSuggestionMouseLeave = _.bind(this._onSuggestionMouseLeave, this);
this.$menu = $(o.menu)
.on('click.tt', '.tt-suggestion', onSuggestionClick)
.on('mouseenter.tt', '.tt-suggestion', onSuggestionMouseEnter)
.on('mouseleave.tt', '.tt-suggestion', onSuggestionMouseLeave);
_.each(this.datasets, function(dataset) {
that.$menu.append(dataset.getRoot());
dataset.onSync('rendered', that._onRendered, that);
});
}
// instance methods
// ----------------
_.mixin(Dropdown.prototype, EventEmitter, {
// ### private
_onSuggestionClick: function onSuggestionClick($e) {
this.trigger('suggestionClicked', $($e.currentTarget));
},
_onSuggestionMouseEnter: function onSuggestionMouseEnter($e) {
this._removeCursor();
this._setCursor($($e.currentTarget), true);
},
_onSuggestionMouseLeave: function onSuggestionMouseLeave() {
this._removeCursor();
},
_onRendered: function onRendered() {
this.isEmpty = _.every(this.datasets, isDatasetEmpty);
this.isEmpty ? this._hide() : (this.isOpen && this._show());
this.trigger('datasetRendered');
function isDatasetEmpty(dataset) { return dataset.isEmpty(); }
},
_hide: function() {
this.$menu.hide();
},
_show: function() {
// can't use jQuery#show because $menu is a span element we want
// display: block; not dislay: inline;
this.$menu.css('display', 'block');
},
_getSuggestions: function getSuggestions() {
return this.$menu.find('.tt-suggestion');
},
_getCursor: function getCursor() {
return this.$menu.find('.tt-cursor').first();
},
_setCursor: function setCursor($el, silent) {
$el.first().addClass('tt-cursor');
!silent && this.trigger('cursorMoved');
},
_removeCursor: function removeCursor() {
this._getCursor().removeClass('tt-cursor');
},
_moveCursor: function moveCursor(increment) {
var $suggestions, $oldCursor, newCursorIndex, $newCursor;
if (!this.isOpen) { return; }
$oldCursor = this._getCursor();
$suggestions = this._getSuggestions();
this._removeCursor();
// shifting before and after modulo to deal with -1 index
newCursorIndex = $suggestions.index($oldCursor) + increment;
newCursorIndex = (newCursorIndex + 1) % ($suggestions.length + 1) - 1;
if (newCursorIndex === -1) {
this.trigger('cursorRemoved');
return;
}
else if (newCursorIndex < -1) {
newCursorIndex = $suggestions.length - 1;
}
this._setCursor($newCursor = $suggestions.eq(newCursorIndex));
// in the case of scrollable overflow
// make sure the cursor is visible in the menu
this._ensureVisible($newCursor);
},
_ensureVisible: function ensureVisible($el) {
var elTop, elBottom, menuScrollTop, menuHeight;
elTop = $el.position().top;
elBottom = elTop + $el.outerHeight(true);
menuScrollTop = this.$menu.scrollTop();
menuHeight = this.$menu.height() +
parseInt(this.$menu.css('paddingTop'), 10) +
parseInt(this.$menu.css('paddingBottom'), 10);
if (elTop < 0) {
this.$menu.scrollTop(menuScrollTop + elTop);
}
else if (menuHeight < elBottom) {
this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight));
}
},
// ### public
close: function close() {
if (this.isOpen) {
this.isOpen = false;
this._removeCursor();
this._hide();
this.trigger('closed');
}
},
open: function open() {
if (!this.isOpen) {
this.isOpen = true;
!this.isEmpty && this._show();
this.trigger('opened');
}
},
setLanguageDirection: function setLanguageDirection(dir) {
this.$menu.css(dir === 'ltr' ? css.ltr : css.rtl);
},
moveCursorUp: function moveCursorUp() {
this._moveCursor(-1);
},
moveCursorDown: function moveCursorDown() {
this._moveCursor(+1);
},
getDatumForSuggestion: function getDatumForSuggestion($el) {
var datum = null;
if ($el.length) {
datum = {
raw: Dataset.extractDatum($el),
value: Dataset.extractValue($el),
datasetName: Dataset.extractDatasetName($el)
};
}
return datum;
},
getDatumForCursor: function getDatumForCursor() {
return this.getDatumForSuggestion(this._getCursor().first());
},
getDatumForTopSuggestion: function getDatumForTopSuggestion() {
return this.getDatumForSuggestion(this._getSuggestions().first());
},
update: function update(query) {
_.each(this.datasets, updateDataset);
function updateDataset(dataset) { dataset.update(query); }
},
empty: function empty() {
_.each(this.datasets, clearDataset);
this.isEmpty = true;
function clearDataset(dataset) { dataset.clear(); }
},
isVisible: function isVisible() {
return this.isOpen && !this.isEmpty;
},
destroy: function destroy() {
this.$menu.off('.tt');
this.$menu = null;
_.each(this.datasets, destroyDataset);
function destroyDataset(dataset) { dataset.destroy(); }
}
});
return Dropdown;
// helper functions
// ----------------
function initializeDataset(oDataset) {
return new Dataset(oDataset);
}
})();