blob: 2d9aacb7da66fe4a15c05562da879dfea90fc209 [file] [log] [blame]
Nils Diewald2fe12e12015-03-06 16:47:06 +00001/**
Nils Diewald7148c6f2015-05-04 15:07:53 +00002 * Scrollable drop-down menus with view filter.
Nils Diewald2fe12e12015-03-06 16:47:06 +00003 *
4 * @author Nils Diewald
5 */
Nils Diewald2488d052015-04-09 21:46:02 +00006/*
Nils Diewald20f7ace2015-05-07 12:51:34 +00007 * TODO: First item shouldn't be automatically highlighted!
Nils Diewald0e6992a2015-04-14 20:13:52 +00008 * TODO: space is not a valid prefix!
Nils Diewald7148c6f2015-05-04 15:07:53 +00009 * TODO: Prefix should be case sensitive!
Nils Diewald2488d052015-04-09 21:46:02 +000010 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000011define([
12 'menu/item',
13 'menu/prefix',
14 'util'
15], function (defaultItemClass,
16 defaultPrefixClass) {
Nils Diewaldfda29d92015-01-22 17:28:01 +000017
Nils Diewald0e6992a2015-04-14 20:13:52 +000018 // Default maximum number of menu items
19 var menuLimit = 8;
20
21 function _codeFromEvent (e) {
22 if (e.charCode && (e.keyCode == 0))
23 return e.charCode
24 return e.keyCode;
Nils Diewald59c02fc2015-03-07 01:29:09 +000025 };
26
Nils Diewald86dad5b2015-01-28 15:09:07 +000027
28 /**
29 * List of items for drop down menu (complete).
30 * Only a sublist of the menu is filtered (live).
31 * Only a sublist of the filtered menu is visible (shown).
32 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000033 return {
Nils Diewald86dad5b2015-01-28 15:09:07 +000034 /**
35 * Create new Menu based on the action prefix
36 * and a list of menu items.
37 *
38 * @this {Menu}
39 * @constructor
40 * @param {string} Context prefix
41 * @param {Array.<Array.<string>>} List of menu items
42 */
43 create : function (params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000044 return Object.create(this)._init(params);
Nils Diewald86dad5b2015-01-28 15:09:07 +000045 },
46
Nils Diewald6e43ffd2015-03-25 18:55:39 +000047 /**
48 * Destroy this menu
49 * (in case you don't trust the
50 * mark and sweep GC)!
51 */
52 destroy : function () {
53 if (this._element != undefined)
54 delete this._element["menu"];
55
56 for (var i = 0; i < this._items.length; i++) {
57 delete this._items[i]["_menu"];
58 };
Nils Diewald5c5a7472015-04-02 22:13:38 +000059 delete this._prefix['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +000060 },
61
Nils Diewald7148c6f2015-05-04 15:07:53 +000062
63 /**
64 * Focus on this menu.
65 */
Nils Diewald2fe12e12015-03-06 16:47:06 +000066 focus : function () {
67 this._element.focus();
68 },
69
Nils Diewald7148c6f2015-05-04 15:07:53 +000070
Nils Diewald59c02fc2015-03-07 01:29:09 +000071 // mouse wheel treatment
72 _mousewheel : function (e) {
73 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +000074
75 delta = e.deltaY / 120;
76 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000077 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +000078 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000079 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +000080 e.halt();
81 },
82
Nils Diewald7148c6f2015-05-04 15:07:53 +000083
Nils Diewald59c02fc2015-03-07 01:29:09 +000084 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +000085 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +000086 var code = _codeFromEvent(e);
87
Nils Diewald59c02fc2015-03-07 01:29:09 +000088 switch (code) {
89 case 27: // 'Esc'
90 e.halt();
91 this.hide();
92 break;
Nils Diewald5975d702015-03-09 17:45:42 +000093
Nils Diewald59c02fc2015-03-07 01:29:09 +000094 case 38: // 'Up'
95 e.halt();
96 this.prev();
97 break;
Nils Diewald5975d702015-03-09 17:45:42 +000098 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +000099 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000100 this.prev();
101 break;
102 case 40: // 'Down'
103 e.halt();
104 this.next();
105 break;
106 case 34: // 'Page down'
107 e.halt();
108 this.next();
109 break;
110 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000111 if (this._prefix.active())
112 break;
113
Nils Diewald5975d702015-03-09 17:45:42 +0000114 var item = this.liveItem(this._position);
Akron5ef4fa02015-06-02 16:25:14 +0200115
Nils Diewald5975d702015-03-09 17:45:42 +0000116 if (item["further"] !== undefined) {
117 item["further"].bind(item).apply();
Nils Diewald5975d702015-03-09 17:45:42 +0000118 };
Akron5ef4fa02015-06-02 16:25:14 +0200119
120 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000121 break;
122 case 13: // 'Enter'
123
124 // Click on prefix
125 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000126 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000127
128 // Click on item
129 else
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000130 this.liveItem(this._position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000131 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000132 break;
133 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000134 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000135 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000136 e.halt();
137 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000138 };
139 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000140
Nils Diewald47f366b2015-04-15 20:06:35 +0000141 // Add characters to prefix
142 _keypress : function (e) {
143 var c = String.fromCharCode(_codeFromEvent(e)).toLowerCase();
Nils Diewald5975d702015-03-09 17:45:42 +0000144
Nils Diewald47f366b2015-04-15 20:06:35 +0000145 // Add prefix
146 this._prefix.add(c);
147
148 if (!this.show()) {
149 this.prefix('').show();
150 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000151 };
152 },
153
Nils Diewald2fe12e12015-03-06 16:47:06 +0000154 // Initialize list
Nils Diewald5975d702015-03-09 17:45:42 +0000155 _init : function (itemClass, prefixClass, params) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000156 var that = this;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000157 this._itemClass = itemClass || defaultItemClass;
Nils Diewald5975d702015-03-09 17:45:42 +0000158
159 if (prefixClass !== undefined)
160 this._prefix = prefixClass.create();
161 else
Nils Diewald0e6992a2015-04-14 20:13:52 +0000162 this._prefix = defaultPrefixClass.create();
Nils Diewald5975d702015-03-09 17:45:42 +0000163
Nils Diewald5c5a7472015-04-02 22:13:38 +0000164 this._prefix._menu = this;
165
Nils Diewald5975d702015-03-09 17:45:42 +0000166 var e = document.createElement("ul");
Nils Diewald59c02fc2015-03-07 01:29:09 +0000167 e.style.opacity = 0;
168 e.style.outline = 0;
169 e.setAttribute('tabindex', 0);
Nils Diewald5c5a7472015-04-02 22:13:38 +0000170 e.classList.add('menu');
Nils Diewald58141332015-04-07 16:18:45 +0000171 e.classList.add('roll');
Nils Diewald5975d702015-03-09 17:45:42 +0000172 e.appendChild(this._prefix.element());
Nils Diewald86dad5b2015-01-28 15:09:07 +0000173
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000174 // This has to be cleaned up later on
175 e["menu"] = this;
176
Nils Diewald59c02fc2015-03-07 01:29:09 +0000177 // Arrow keys
178 e.addEventListener(
Nils Diewald47f366b2015-04-15 20:06:35 +0000179 'keydown',
180 function (ev) {
181 that._keydown(ev)
182 },
183 false
184 );
185
186 // Strings
187 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000188 'keypress',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000189 function (ev) {
Nils Diewald5975d702015-03-09 17:45:42 +0000190 that._keypress(ev)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000191 },
192 false
193 );
194
195 // Mousewheel
196 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000197 'wheel',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000198 function (ev) {
199 that._mousewheel(ev)
Nils Diewald2fe12e12015-03-06 16:47:06 +0000200 },
201 false
202 );
203
Nils Diewald5975d702015-03-09 17:45:42 +0000204 this._element = e;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000205 this.active = false;
Akron37513a62015-11-17 01:07:11 +0100206 // this.selected = undefined;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000207 this._items = new Array();
208 var i;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000209
210 // Initialize item list based on parameters
Nils Diewald86dad5b2015-01-28 15:09:07 +0000211 for (i in params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000212 var obj = this._itemClass.create(params[i]);
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000213
214 // This may become circular
215 obj["_menu"] = this;
216
Nils Diewald2fe12e12015-03-06 16:47:06 +0000217 this._items.push(obj);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000218 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000219 this._limit = menuLimit;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000220 this._position = 0; // position in the active list
221 this._active = -1; // active item in the item list
Nils Diewald20f7ace2015-05-07 12:51:34 +0000222 this._firstActive = false; // Show the first item active always?
Nils Diewald86dad5b2015-01-28 15:09:07 +0000223 this._reset();
224 return this;
225 },
226
Nils Diewald2fe12e12015-03-06 16:47:06 +0000227 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000228 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000229 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000230 element : function () {
231 return this._element;
232 },
233
Nils Diewald7148c6f2015-05-04 15:07:53 +0000234
Nils Diewald2fe12e12015-03-06 16:47:06 +0000235 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000236 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000237 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000238 itemClass : function () {
239 return this._itemClass;
240 },
241
242 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000243 * Get and set the numerical value
244 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000245 */
246 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000247 if (arguments.length === 1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000248 this._limit = limit;
Nils Diewald5975d702015-03-09 17:45:42 +0000249 return this;
250 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000251 return this._limit;
252 },
253
Nils Diewald7148c6f2015-05-04 15:07:53 +0000254
Nils Diewald86dad5b2015-01-28 15:09:07 +0000255 /**
256 * Upgrade this object to another object,
257 * while private data stays intact.
258 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000259 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000260 */
261 upgradeTo : function (props) {
262 for (var prop in props) {
263 this[prop] = props[prop];
264 };
265 return this;
266 },
267
Nils Diewald7148c6f2015-05-04 15:07:53 +0000268
Nils Diewald2fe12e12015-03-06 16:47:06 +0000269 // Reset chosen item and prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000270 _reset : function () {
271 this._offset = 0;
272 this._pos = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000273 this._prefix.value('');
Nils Diewald86dad5b2015-01-28 15:09:07 +0000274 },
275
Nils Diewald7148c6f2015-05-04 15:07:53 +0000276
Nils Diewald86dad5b2015-01-28 15:09:07 +0000277 /**
278 * Filter the list and make it visible
279 *
280 * @param {string} Prefix for filtering the list
281 */
Nils Diewald5975d702015-03-09 17:45:42 +0000282 show : function () {
Nils Diewalde8518f82015-03-18 22:41:49 +0000283
Nils Diewald86dad5b2015-01-28 15:09:07 +0000284 // Initialize the list
285 if (!this._initList())
Nils Diewald59c02fc2015-03-07 01:29:09 +0000286 return false;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000287
Nils Diewald2fe12e12015-03-06 16:47:06 +0000288 // show based on initial offset
Nils Diewald86dad5b2015-01-28 15:09:07 +0000289 this._showItems(0);
290
291 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000292 // Todo: Or the last element chosen
Nils Diewald20f7ace2015-05-07 12:51:34 +0000293 if (this._firstActive)
294 this.liveItem(0).active(true);
295
Akron37513a62015-11-17 01:07:11 +0100296 this._position = 0;
297
Nils Diewalde8518f82015-03-18 22:41:49 +0000298 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000299
Nils Diewald2fe12e12015-03-06 16:47:06 +0000300 this._element.style.opacity = 1;
301
Akron37513a62015-11-17 01:07:11 +0100302 // Iterate to the active item
303 if (this._active !== -1 && !this._prefix.isSet()) {
304 while (this._list[this._position] < this._active) {
305 this.next();
306 };
307 };
308
Nils Diewald86dad5b2015-01-28 15:09:07 +0000309 // Add classes for rolling menus
310 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000311 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000312 },
313
Nils Diewald7148c6f2015-05-04 15:07:53 +0000314
315 /**
316 * Hide the menu and call the onHide callback.
317 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000318 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000319 this.active = false;
320 this.delete();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000321 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000322 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000323 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000324 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000325 },
326
Nils Diewald7148c6f2015-05-04 15:07:53 +0000327 /**
328 * Function released when the menu hides.
329 * This method is expected to be overridden.
330 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000331 onHide : function () {},
332
Nils Diewald2fe12e12015-03-06 16:47:06 +0000333 // Initialize the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000334 _initList : function () {
335
Nils Diewald2fe12e12015-03-06 16:47:06 +0000336 // Create a new list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000337 if (this._list === undefined) {
338 this._list = [];
339 }
340 else if (this._list.length != 0) {
341 this._boundary(false);
342 this._list.length = 0;
343 };
344
345 // Offset is initially zero
346 this._offset = 0;
347
Nils Diewald2fe12e12015-03-06 16:47:06 +0000348 // There is no prefix set
Nils Diewald86dad5b2015-01-28 15:09:07 +0000349 if (this.prefix().length <= 0) {
Nils Diewald5975d702015-03-09 17:45:42 +0000350 var i = 0;
351 for (; i < this._items.length; i++)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000352 this._list.push(i);
Nils Diewald5975d702015-03-09 17:45:42 +0000353 while (this._items[++i] !== undefined) {
354 this._items[i].lowlight();
Nils Diewald2488d052015-04-09 21:46:02 +0000355 // console.log(this._item);
Nils Diewald5975d702015-03-09 17:45:42 +0000356 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000357 return true;
358 };
359
Nils Diewald2fe12e12015-03-06 16:47:06 +0000360 // There is a prefix set, so filter the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000361 var pos;
362 var paddedPrefix = " " + this.prefix();
363
Nils Diewald2fe12e12015-03-06 16:47:06 +0000364 // Iterate over all items and choose preferred matching items
365 // i.e. the matching happens at the word start
Nils Diewald86dad5b2015-01-28 15:09:07 +0000366 for (pos = 0; pos < this._items.length; pos++) {
367 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
368 this._list.push(pos);
369 };
370
Nils Diewald2fe12e12015-03-06 16:47:06 +0000371 // The list is empty - so lower your expectations
372 // Iterate over all items and choose matching items
373 // i.e. the matching happens anywhere in the word
Nils Diewald86dad5b2015-01-28 15:09:07 +0000374 if (this._list.length == 0) {
375 for (pos = 0; pos < this._items.length; pos++) {
376 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
377 this._list.push(pos);
378 };
379 };
380
Nils Diewald2fe12e12015-03-06 16:47:06 +0000381 // Filter was successful - yeah!
Nils Diewald86dad5b2015-01-28 15:09:07 +0000382 return this._list.length > 0 ? true : false;
383 },
384
385 // Set boundary for viewport
386 _boundary : function (bool) {
387 this.item(this._list[0]).noMore(bool);
388 this.item(this._list[this._list.length - 1]).noMore(bool);
389 },
390
Nils Diewald7148c6f2015-05-04 15:07:53 +0000391
Nils Diewald86dad5b2015-01-28 15:09:07 +0000392 /**
393 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000394 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000395 */
Nils Diewald5975d702015-03-09 17:45:42 +0000396 prefix : function (pref) {
397 if (arguments.length === 1) {
398 this._prefix.value(pref);
399 return this;
400 };
401 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000402 },
403
Nils Diewald7148c6f2015-05-04 15:07:53 +0000404
Nils Diewald2fe12e12015-03-06 16:47:06 +0000405 // Append Items that should be shown
Nils Diewald86dad5b2015-01-28 15:09:07 +0000406 _showItems : function (offset) {
407 this.delete();
408
409 // Use list
410 var shown = 0;
411 var i;
412 for (i in this._list) {
413
414 // Don't show - it's before offset
415 if (shown++ < offset)
416 continue;
417
418 this._append(this._list[i]);
419
420 if (shown >= (this.limit() + this._offset))
421 break;
422 };
423 },
424
425 /**
426 * Delete all visible items from the menu element
427 */
428 delete : function () {
429 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000430
Nils Diewald5975d702015-03-09 17:45:42 +0000431 /*
Nils Diewald2fe12e12015-03-06 16:47:06 +0000432 // Iterate over all visible items
Nils Diewald86dad5b2015-01-28 15:09:07 +0000433 for (var i = 0; i <= this.limit(); i++) {
434
Nils Diewald5975d702015-03-09 17:45:42 +0000435 // there is a visible element
436 // unhighlight!
Nils Diewald59c02fc2015-03-07 01:29:09 +0000437 if (child = this.shownItem(i)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000438 child.lowlight();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000439 child.active(false);
440 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000441 };
Nils Diewald5975d702015-03-09 17:45:42 +0000442 */
443
444 for (var i in this._list) {
445 var item = this._items[this._list[i]];
446 item.lowlight();
447 item.active(false);
448 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000449
Nils Diewald2fe12e12015-03-06 16:47:06 +0000450 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000451 var children = this._element.childNodes;
452 for (var i = children.length - 1; i >= 1; i--) {
453 this._element.removeChild(
454 children[i]
455 );
456 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000457 },
458
459
460 // Append item to the shown list based on index
461 _append : function (i) {
462 var item = this.item(i);
463
464 // Highlight based on prefix
465 if (this.prefix().length > 0)
466 item.highlight(this.prefix());
467
468 // Append element
469 this.element().appendChild(item.element());
470 },
471
472
Nils Diewald2fe12e12015-03-06 16:47:06 +0000473 // Prepend item to the shown list based on index
474 _prepend : function (i) {
475 var item = this.item(i);
476
477 // Highlight based on prefix
478 if (this.prefix().length > 0)
479 item.highlight(this.prefix());
480
481 var e = this.element();
482 // Append element
483 e.insertBefore(
484 item.element(),
Nils Diewald5975d702015-03-09 17:45:42 +0000485 e.children[1]
Nils Diewald2fe12e12015-03-06 16:47:06 +0000486 );
487 },
488
489
490 /**
491 * Get a specific item from the complete list
492 *
493 * @param {number} index of the list item
494 */
495 item : function (index) {
496 return this._items[index]
497 },
498
499
Nils Diewald86dad5b2015-01-28 15:09:07 +0000500 /**
501 * Get a specific item from the filtered list
502 *
503 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000504 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000505 */
506 liveItem : function (index) {
507 if (this._list === undefined)
508 if (!this._initList())
509 return;
510
511 return this._items[this._list[index]];
512 },
513
Nils Diewald86dad5b2015-01-28 15:09:07 +0000514
515 /**
516 * Get a specific item from the visible list
517 *
518 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000519 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000520 */
521 shownItem : function (index) {
522 if (index >= this.limit())
523 return;
524 return this.liveItem(this._offset + index);
525 },
526
527
Nils Diewald2fe12e12015-03-06 16:47:06 +0000528 /**
529 * Get the length of the full list
530 */
531 length : function () {
532 return this._items.length;
533 },
534
535
536 /**
537 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000538 */
539 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000540
Nils Diewald86dad5b2015-01-28 15:09:07 +0000541 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000542 if (this._position === -1)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000543 return;
544
Nils Diewald5975d702015-03-09 17:45:42 +0000545 var newItem;
546
Nils Diewald86dad5b2015-01-28 15:09:07 +0000547 // Set new live item
Nils Diewalde8518f82015-03-18 22:41:49 +0000548 if (!this._prefix.active()) {
549 var oldItem = this.liveItem(this._position);
550 oldItem.active(false);
551 };
552
553 this._position++;
554
Nils Diewald5975d702015-03-09 17:45:42 +0000555 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000556
Nils Diewald5975d702015-03-09 17:45:42 +0000557 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000558 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000559
560 // Activate prefix
561 var prefix = this._prefix;
562
563 // Mark prefix
564 if (prefix.isSet() && !prefix.active()) {
565 this._position--;
566 prefix.active(true);
567 return;
568 }
569 else {
570 this._offset = 0;
571 this._position = 0;
572 newItem = this.liveItem(0);
573 this._showItems(0);
574 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000575 }
576
577 // The next element is outside the view - roll down
Nils Diewald2fe12e12015-03-06 16:47:06 +0000578 else if (this._position >= (this.limit() + this._offset)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000579 this._removeFirst();
580 this._offset++;
581 this._append(this._list[this._position]);
582 };
Nils Diewald5975d702015-03-09 17:45:42 +0000583
584 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000585 newItem.active(true);
586 },
587
Nils Diewalde8518f82015-03-18 22:41:49 +0000588 /*
589 * Page down to the first item on the next page
590 */
591 /*
592 nextPage : function () {
593
594 // Prefix is active
595 if (this._prefix.active()) {
596 this._prefix.active(false);
597 }
598
599 // Last item is chosen
600 else if (this._position >= this.limit() + this._offset) {
601
602 this._position = this.limit() + this._offset - 1;
603 newItem = this.liveItem(this._position);
604 var oldItem = this.liveItem(this._position--);
605 oldItem.active(false);
606 }
607
608 // Last item of page is chosen
609 else if (0) {
610
611 // Jump to last item
612 else {
613 var oldItem = this.liveItem(this._position);
614 oldItem.active(false);
615
616 this._position = this.limit() + this._offset - 1;
617 newItem = this.liveItem(this._position);
618 };
619
620 newItem.active(true);
621 },
622 */
623
Nils Diewald86dad5b2015-01-28 15:09:07 +0000624
625 /*
626 * Make the previous item in the menu active
627 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000628 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000629
Nils Diewald2fe12e12015-03-06 16:47:06 +0000630 // No active element set
Nils Diewalde8518f82015-03-18 22:41:49 +0000631 if (this._position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000632 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000633 // TODO: Choose last item
634 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000635
Nils Diewald5975d702015-03-09 17:45:42 +0000636 var newItem;
637
Nils Diewald86dad5b2015-01-28 15:09:07 +0000638 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000639 if (!this._prefix.active()) {
640 var oldItem = this.liveItem(this._position--);
641 oldItem.active(false);
642 };
643
Nils Diewald5975d702015-03-09 17:45:42 +0000644 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000645
646 // The previous element is undefined - roll to bottom
647 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000648
649 // Activate prefix
650 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000651 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000652
653 // Normalize offset
654 this._offset = this._offset < 0 ? 0 : this._offset;
655
Nils Diewald2fe12e12015-03-06 16:47:06 +0000656 this._position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000657
658 if (prefix.isSet() && !prefix.active()) {
659 this._position++;
660 prefix.active(true);
661 return;
662 }
663 else {
664 newItem = this.liveItem(this._position);
665 this._showItems(this._offset);
666 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000667 }
668
669 // The previous element is outside the view - roll up
670 else if (this._position < this._offset) {
671 this._removeLast();
672 this._offset--;
673 this._prepend(this._list[this._position]);
674 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000675
Nils Diewald5975d702015-03-09 17:45:42 +0000676 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000677 newItem.active(true);
678 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000679
680
Nils Diewald7148c6f2015-05-04 15:07:53 +0000681 /**
682 * Length of the filtered item list.
683 */
Nils Diewald5975d702015-03-09 17:45:42 +0000684 liveLength : function () {
685 if (this._list === undefined)
686 this._initList();
687 return this._list.length;
688 },
689
690
Nils Diewald2fe12e12015-03-06 16:47:06 +0000691 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000692 _removeFirst : function () {
693 this.item(this._list[this._offset]).lowlight();
Nils Diewald5975d702015-03-09 17:45:42 +0000694 this._element.removeChild(this._element.children[1]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000695 },
696
Nils Diewald2fe12e12015-03-06 16:47:06 +0000697
698 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000699 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000700 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000701 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000702 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000703 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000704});