UNPKG

alpaca

Version:

Alpaca provides the easiest and fastest way to generate interactive forms for the web and mobile devices. It runs simply as HTML5 or more elaborately using Bootstrap, jQuery Mobile or jQuery UI. Alpaca uses Handlebars to process JSON schema and provide

246 lines (177 loc) 6.15 kB
/* * 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); } })();