| 15403 |
manish.sha |
1 |
/*
|
|
|
2 |
* typeahead.js
|
|
|
3 |
* https://github.com/twitter/typeahead.js
|
|
|
4 |
* Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT
|
|
|
5 |
*/
|
|
|
6 |
|
|
|
7 |
var Dropdown = (function() {
|
|
|
8 |
'use strict';
|
|
|
9 |
|
|
|
10 |
// constructor
|
|
|
11 |
// -----------
|
|
|
12 |
|
|
|
13 |
function Dropdown(o) {
|
|
|
14 |
var that = this, onSuggestionClick, onSuggestionMouseEnter,
|
|
|
15 |
onSuggestionMouseLeave;
|
|
|
16 |
|
|
|
17 |
o = o || {};
|
|
|
18 |
|
|
|
19 |
if (!o.menu) {
|
|
|
20 |
$.error('menu is required');
|
|
|
21 |
}
|
|
|
22 |
|
|
|
23 |
this.isOpen = false;
|
|
|
24 |
this.isEmpty = true;
|
|
|
25 |
|
|
|
26 |
this.datasets = _.map(o.datasets, initializeDataset);
|
|
|
27 |
|
|
|
28 |
// bound functions
|
|
|
29 |
onSuggestionClick = _.bind(this._onSuggestionClick, this);
|
|
|
30 |
onSuggestionMouseEnter = _.bind(this._onSuggestionMouseEnter, this);
|
|
|
31 |
onSuggestionMouseLeave = _.bind(this._onSuggestionMouseLeave, this);
|
|
|
32 |
|
|
|
33 |
this.$menu = $(o.menu)
|
|
|
34 |
.on('click.tt', '.tt-suggestion', onSuggestionClick)
|
|
|
35 |
.on('mouseenter.tt', '.tt-suggestion', onSuggestionMouseEnter)
|
|
|
36 |
.on('mouseleave.tt', '.tt-suggestion', onSuggestionMouseLeave);
|
|
|
37 |
|
|
|
38 |
_.each(this.datasets, function(dataset) {
|
|
|
39 |
that.$menu.append(dataset.getRoot());
|
|
|
40 |
dataset.onSync('rendered', that._onRendered, that);
|
|
|
41 |
});
|
|
|
42 |
}
|
|
|
43 |
|
|
|
44 |
// instance methods
|
|
|
45 |
// ----------------
|
|
|
46 |
|
|
|
47 |
_.mixin(Dropdown.prototype, EventEmitter, {
|
|
|
48 |
|
|
|
49 |
// ### private
|
|
|
50 |
|
|
|
51 |
_onSuggestionClick: function onSuggestionClick($e) {
|
|
|
52 |
this.trigger('suggestionClicked', $($e.currentTarget));
|
|
|
53 |
},
|
|
|
54 |
|
|
|
55 |
_onSuggestionMouseEnter: function onSuggestionMouseEnter($e) {
|
|
|
56 |
this._removeCursor();
|
|
|
57 |
this._setCursor($($e.currentTarget), true);
|
|
|
58 |
},
|
|
|
59 |
|
|
|
60 |
_onSuggestionMouseLeave: function onSuggestionMouseLeave() {
|
|
|
61 |
this._removeCursor();
|
|
|
62 |
},
|
|
|
63 |
|
|
|
64 |
_onRendered: function onRendered() {
|
|
|
65 |
this.isEmpty = _.every(this.datasets, isDatasetEmpty);
|
|
|
66 |
|
|
|
67 |
this.isEmpty ? this._hide() : (this.isOpen && this._show());
|
|
|
68 |
|
|
|
69 |
this.trigger('datasetRendered');
|
|
|
70 |
|
|
|
71 |
function isDatasetEmpty(dataset) { return dataset.isEmpty(); }
|
|
|
72 |
},
|
|
|
73 |
|
|
|
74 |
_hide: function() {
|
|
|
75 |
this.$menu.hide();
|
|
|
76 |
},
|
|
|
77 |
|
|
|
78 |
_show: function() {
|
|
|
79 |
// can't use jQuery#show because $menu is a span element we want
|
|
|
80 |
// display: block; not dislay: inline;
|
|
|
81 |
this.$menu.css('display', 'block');
|
|
|
82 |
},
|
|
|
83 |
|
|
|
84 |
_getSuggestions: function getSuggestions() {
|
|
|
85 |
return this.$menu.find('.tt-suggestion');
|
|
|
86 |
},
|
|
|
87 |
|
|
|
88 |
_getCursor: function getCursor() {
|
|
|
89 |
return this.$menu.find('.tt-cursor').first();
|
|
|
90 |
},
|
|
|
91 |
|
|
|
92 |
_setCursor: function setCursor($el, silent) {
|
|
|
93 |
$el.first().addClass('tt-cursor');
|
|
|
94 |
|
|
|
95 |
!silent && this.trigger('cursorMoved');
|
|
|
96 |
},
|
|
|
97 |
|
|
|
98 |
_removeCursor: function removeCursor() {
|
|
|
99 |
this._getCursor().removeClass('tt-cursor');
|
|
|
100 |
},
|
|
|
101 |
|
|
|
102 |
_moveCursor: function moveCursor(increment) {
|
|
|
103 |
var $suggestions, $oldCursor, newCursorIndex, $newCursor;
|
|
|
104 |
|
|
|
105 |
if (!this.isOpen) { return; }
|
|
|
106 |
|
|
|
107 |
$oldCursor = this._getCursor();
|
|
|
108 |
$suggestions = this._getSuggestions();
|
|
|
109 |
|
|
|
110 |
this._removeCursor();
|
|
|
111 |
|
|
|
112 |
// shifting before and after modulo to deal with -1 index
|
|
|
113 |
newCursorIndex = $suggestions.index($oldCursor) + increment;
|
|
|
114 |
newCursorIndex = (newCursorIndex + 1) % ($suggestions.length + 1) - 1;
|
|
|
115 |
|
|
|
116 |
if (newCursorIndex === -1) {
|
|
|
117 |
this.trigger('cursorRemoved');
|
|
|
118 |
|
|
|
119 |
return;
|
|
|
120 |
}
|
|
|
121 |
|
|
|
122 |
else if (newCursorIndex < -1) {
|
|
|
123 |
newCursorIndex = $suggestions.length - 1;
|
|
|
124 |
}
|
|
|
125 |
|
|
|
126 |
this._setCursor($newCursor = $suggestions.eq(newCursorIndex));
|
|
|
127 |
|
|
|
128 |
// in the case of scrollable overflow
|
|
|
129 |
// make sure the cursor is visible in the menu
|
|
|
130 |
this._ensureVisible($newCursor);
|
|
|
131 |
},
|
|
|
132 |
|
|
|
133 |
_ensureVisible: function ensureVisible($el) {
|
|
|
134 |
var elTop, elBottom, menuScrollTop, menuHeight;
|
|
|
135 |
|
|
|
136 |
elTop = $el.position().top;
|
|
|
137 |
elBottom = elTop + $el.outerHeight(true);
|
|
|
138 |
menuScrollTop = this.$menu.scrollTop();
|
|
|
139 |
menuHeight = this.$menu.height() +
|
|
|
140 |
parseInt(this.$menu.css('paddingTop'), 10) +
|
|
|
141 |
parseInt(this.$menu.css('paddingBottom'), 10);
|
|
|
142 |
|
|
|
143 |
if (elTop < 0) {
|
|
|
144 |
this.$menu.scrollTop(menuScrollTop + elTop);
|
|
|
145 |
}
|
|
|
146 |
|
|
|
147 |
else if (menuHeight < elBottom) {
|
|
|
148 |
this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight));
|
|
|
149 |
}
|
|
|
150 |
},
|
|
|
151 |
|
|
|
152 |
// ### public
|
|
|
153 |
|
|
|
154 |
close: function close() {
|
|
|
155 |
if (this.isOpen) {
|
|
|
156 |
this.isOpen = false;
|
|
|
157 |
|
|
|
158 |
this._removeCursor();
|
|
|
159 |
this._hide();
|
|
|
160 |
|
|
|
161 |
this.trigger('closed');
|
|
|
162 |
}
|
|
|
163 |
},
|
|
|
164 |
|
|
|
165 |
open: function open() {
|
|
|
166 |
if (!this.isOpen) {
|
|
|
167 |
this.isOpen = true;
|
|
|
168 |
|
|
|
169 |
!this.isEmpty && this._show();
|
|
|
170 |
|
|
|
171 |
this.trigger('opened');
|
|
|
172 |
}
|
|
|
173 |
},
|
|
|
174 |
|
|
|
175 |
setLanguageDirection: function setLanguageDirection(dir) {
|
|
|
176 |
this.$menu.css(dir === 'ltr' ? css.ltr : css.rtl);
|
|
|
177 |
},
|
|
|
178 |
|
|
|
179 |
moveCursorUp: function moveCursorUp() {
|
|
|
180 |
this._moveCursor(-1);
|
|
|
181 |
},
|
|
|
182 |
|
|
|
183 |
moveCursorDown: function moveCursorDown() {
|
|
|
184 |
this._moveCursor(+1);
|
|
|
185 |
},
|
|
|
186 |
|
|
|
187 |
getDatumForSuggestion: function getDatumForSuggestion($el) {
|
|
|
188 |
var datum = null;
|
|
|
189 |
|
|
|
190 |
if ($el.length) {
|
|
|
191 |
datum = {
|
|
|
192 |
raw: Dataset.extractDatum($el),
|
|
|
193 |
value: Dataset.extractValue($el),
|
|
|
194 |
datasetName: Dataset.extractDatasetName($el)
|
|
|
195 |
};
|
|
|
196 |
}
|
|
|
197 |
|
|
|
198 |
return datum;
|
|
|
199 |
},
|
|
|
200 |
|
|
|
201 |
getDatumForCursor: function getDatumForCursor() {
|
|
|
202 |
return this.getDatumForSuggestion(this._getCursor().first());
|
|
|
203 |
},
|
|
|
204 |
|
|
|
205 |
getDatumForTopSuggestion: function getDatumForTopSuggestion() {
|
|
|
206 |
return this.getDatumForSuggestion(this._getSuggestions().first());
|
|
|
207 |
},
|
|
|
208 |
|
|
|
209 |
update: function update(query) {
|
|
|
210 |
_.each(this.datasets, updateDataset);
|
|
|
211 |
|
|
|
212 |
function updateDataset(dataset) { dataset.update(query); }
|
|
|
213 |
},
|
|
|
214 |
|
|
|
215 |
empty: function empty() {
|
|
|
216 |
_.each(this.datasets, clearDataset);
|
|
|
217 |
this.isEmpty = true;
|
|
|
218 |
|
|
|
219 |
function clearDataset(dataset) { dataset.clear(); }
|
|
|
220 |
},
|
|
|
221 |
|
|
|
222 |
isVisible: function isVisible() {
|
|
|
223 |
return this.isOpen && !this.isEmpty;
|
|
|
224 |
},
|
|
|
225 |
|
|
|
226 |
destroy: function destroy() {
|
|
|
227 |
this.$menu.off('.tt');
|
|
|
228 |
|
|
|
229 |
this.$menu = null;
|
|
|
230 |
|
|
|
231 |
_.each(this.datasets, destroyDataset);
|
|
|
232 |
|
|
|
233 |
function destroyDataset(dataset) { dataset.destroy(); }
|
|
|
234 |
}
|
|
|
235 |
});
|
|
|
236 |
|
|
|
237 |
return Dropdown;
|
|
|
238 |
|
|
|
239 |
// helper functions
|
|
|
240 |
// ----------------
|
|
|
241 |
|
|
|
242 |
function initializeDataset(oDataset) {
|
|
|
243 |
return new Dataset(oDataset);
|
|
|
244 |
}
|
|
|
245 |
})();
|