blob: b6008339a80c181f7e662ac3458ab17dce8845c4 [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 Diewald0e6992a2015-04-14 20:13:52 +00007 * TODO: space is not a valid prefix!
Akron5a1f5bb2016-05-23 22:00:39 +02008 * TODO: Ignore keys with function key combinations (other than shift)
Akron3c2730f2016-05-24 15:08:29 +02009 * TODO: Show the slider briefly on move (whenever screen is called).
Akron97752a72016-05-25 14:43:07 +020010 * TODO: Optimize scrolling to active item.
Nils Diewald2488d052015-04-09 21:46:02 +000011 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000012define([
13 'menu/item',
14 'menu/prefix',
Akronc7448732016-04-27 14:06:58 +020015 'menu/lengthField',
Akron9905e2a2016-05-10 16:06:44 +020016 'menu/slider',
Nils Diewald0e6992a2015-04-14 20:13:52 +000017 'util'
18], function (defaultItemClass,
Akronc7448732016-04-27 14:06:58 +020019 defaultPrefixClass,
Akron9905e2a2016-05-10 16:06:44 +020020 defaultLengthFieldClass,
21 sliderClass) {
Nils Diewaldfda29d92015-01-22 17:28:01 +000022
Nils Diewald0e6992a2015-04-14 20:13:52 +000023 // Default maximum number of menu items
24 var menuLimit = 8;
25
26 function _codeFromEvent (e) {
27 if (e.charCode && (e.keyCode == 0))
28 return e.charCode
29 return e.keyCode;
Nils Diewald59c02fc2015-03-07 01:29:09 +000030 };
31
Nils Diewald86dad5b2015-01-28 15:09:07 +000032
33 /**
34 * List of items for drop down menu (complete).
35 * Only a sublist of the menu is filtered (live).
36 * Only a sublist of the filtered menu is visible (shown).
37 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000038 return {
Nils Diewald86dad5b2015-01-28 15:09:07 +000039 /**
40 * Create new Menu based on the action prefix
41 * and a list of menu items.
42 *
43 * @this {Menu}
44 * @constructor
45 * @param {string} Context prefix
46 * @param {Array.<Array.<string>>} List of menu items
47 */
48 create : function (params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000049 return Object.create(this)._init(params);
Nils Diewald86dad5b2015-01-28 15:09:07 +000050 },
51
Akron5240b8c2016-05-20 09:17:41 +020052 // Initialize list
53 _init : function (itemClass, prefixClass, lengthFieldClass, params) {
Akrona92fd8d2016-05-24 21:13:41 +020054
Akron5240b8c2016-05-20 09:17:41 +020055 this._itemClass = itemClass || defaultItemClass;
56
57 // Add prefix object
58 if (prefixClass !== undefined) {
59 this._prefix = prefixClass.create();
60 }
61 else {
62 this._prefix = defaultPrefixClass.create();
63 };
64 this._prefix._menu = this;
65
66 // Add lengthField object
67 if (lengthFieldClass !== undefined) {
68 this._lengthField = lengthFieldClass.create();
69 }
70 else {
71 this._lengthField = defaultLengthFieldClass.create();
72 };
73 this._lengthField._menu = this;
74
75 // Initialize slider
76 this._slider = sliderClass.create(this);
77
78 // Create the element
79 var e = document.createElement("ul");
80 e.style.opacity = 0;
81 e.style.outline = 0;
82 e.setAttribute('tabindex', 0);
83 e.classList.add('menu');
84 e.classList.add('roll');
85 e.appendChild(this._prefix.element());
86 e.appendChild(this._lengthField.element());
87 e.appendChild(this._slider.element());
88
89 // This has to be cleaned up later on
90 e["menu"] = this;
91
92 // Arrow keys
93 e.addEventListener(
94 'keydown',
Akrona92fd8d2016-05-24 21:13:41 +020095 this._keydown.bind(this),
Akron5240b8c2016-05-20 09:17:41 +020096 false
97 );
98
99 // Strings
100 e.addEventListener(
101 'keypress',
Akrona92fd8d2016-05-24 21:13:41 +0200102 this._keypress.bind(this),
Akron5240b8c2016-05-20 09:17:41 +0200103 false
104 );
105
106 // Mousewheel
107 e.addEventListener(
108 'wheel',
Akrona92fd8d2016-05-24 21:13:41 +0200109 this._mousewheel.bind(this),
Akron5240b8c2016-05-20 09:17:41 +0200110 false
111 );
112
113 this._element = e;
114 this.active = false;
115 // this.selected = undefined;
116 this._items = new Array();
117 var i = 0;
118
119 // Initialize item list based on parameters
120 for (i in params) {
121 var obj = this._itemClass.create(params[i]);
122
123 // This may become circular
124 obj["_menu"] = this;
125 this._lengthField.add(params[i]);
126 this._items.push(obj);
127 };
128
129 this._limit = menuLimit;
130 this._slider.length(this.liveLength());
131 this._slider.limit(this._limit);
132
Akron5240b8c2016-05-20 09:17:41 +0200133 this._firstActive = false; // Show the first item active always?
134 this._reset();
135 return this;
136 },
137
138 // Initialize the item list
139 _initList : function () {
140
141 // Create a new list
142 if (this._list === undefined) {
143 this._list = [];
144 }
145 else if (this._list.length !== 0) {
146 this._boundary(false);
147 this._list.length = 0;
148 };
149
150 // Offset is initially zero
151 this._offset = 0;
152
153 // There is no prefix set
154 if (this.prefix().length <= 0) {
155
156 // add all items to the list and lowlight
Akron97752a72016-05-25 14:43:07 +0200157 var i = 0;
158 for (; i < this._items.length; i++) {
Akron5240b8c2016-05-20 09:17:41 +0200159 this._list.push(i);
160 this._items[i].lowlight();
161 };
162
Akron97752a72016-05-25 14:43:07 +0200163 this._slider.length(i);
164
Akron5240b8c2016-05-20 09:17:41 +0200165 return true;
166 };
167
168 /*
169 * There is a prefix set, so filter the list!
170 */
171 var pos;
Akron6ffad5d2016-05-24 17:16:58 +0200172 var prefix = " " + this.prefix().toLowerCase();
Akron5240b8c2016-05-20 09:17:41 +0200173
174 // Iterate over all items and choose preferred matching items
175 // i.e. the matching happens at the word start
176 for (pos = 0; pos < this._items.length; pos++) {
Akron6ffad5d2016-05-24 17:16:58 +0200177 if ((this.item(pos).lcField().indexOf(prefix)) >= 0)
Akron5240b8c2016-05-20 09:17:41 +0200178 this._list.push(pos);
179 };
180
181 // The list is empty - so lower your expectations
182 // Iterate over all items and choose matching items
183 // i.e. the matching happens anywhere in the word
Akron6ffad5d2016-05-24 17:16:58 +0200184 prefix = prefix.substring(1);
Akron5240b8c2016-05-20 09:17:41 +0200185 if (this._list.length == 0) {
186 for (pos = 0; pos < this._items.length; pos++) {
Akron6ffad5d2016-05-24 17:16:58 +0200187 if ((this.item(pos).lcField().indexOf(prefix)) >= 0)
Akron5240b8c2016-05-20 09:17:41 +0200188 this._list.push(pos);
189 };
190 };
191
Akron6ed13992016-05-23 18:06:05 +0200192 this._slider.length(this._list.length);
193
Akron5240b8c2016-05-20 09:17:41 +0200194 // Filter was successful - yeah!
195 return this._list.length > 0 ? true : false;
196 },
197
198
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000199 /**
200 * Destroy this menu
201 * (in case you don't trust the
202 * mark and sweep GC)!
203 */
204 destroy : function () {
Akron47c086c2016-05-18 21:22:06 +0200205
Akron5240b8c2016-05-20 09:17:41 +0200206 // Remove circular reference to "this" in menu
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000207 if (this._element != undefined)
208 delete this._element["menu"];
209
Akron5240b8c2016-05-20 09:17:41 +0200210 // Remove circular reference to "this" in items
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000211 for (var i = 0; i < this._items.length; i++) {
212 delete this._items[i]["_menu"];
213 };
Akron5240b8c2016-05-20 09:17:41 +0200214
215 // Remove circular reference to "this" in prefix
Nils Diewald5c5a7472015-04-02 22:13:38 +0000216 delete this._prefix['_menu'];
Akron5240b8c2016-05-20 09:17:41 +0200217 delete this._lengthField['_menu'];
218 delete this._slider['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000219 },
220
Nils Diewald7148c6f2015-05-04 15:07:53 +0000221
222 /**
223 * Focus on this menu.
224 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000225 focus : function () {
226 this._element.focus();
227 },
228
Nils Diewald7148c6f2015-05-04 15:07:53 +0000229
Nils Diewald59c02fc2015-03-07 01:29:09 +0000230 // mouse wheel treatment
231 _mousewheel : function (e) {
232 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000233
234 delta = e.deltaY / 120;
235 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000236 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +0000237 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000238 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000239 e.halt();
240 },
241
Nils Diewald7148c6f2015-05-04 15:07:53 +0000242
Nils Diewald59c02fc2015-03-07 01:29:09 +0000243 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +0000244 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000245 var code = _codeFromEvent(e);
246
Nils Diewald59c02fc2015-03-07 01:29:09 +0000247 switch (code) {
248 case 27: // 'Esc'
249 e.halt();
250 this.hide();
251 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000252
Nils Diewald59c02fc2015-03-07 01:29:09 +0000253 case 38: // 'Up'
254 e.halt();
255 this.prev();
256 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000257 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +0000258 e.halt();
Akron3c2730f2016-05-24 15:08:29 +0200259 this.pageUp();
Nils Diewald5975d702015-03-09 17:45:42 +0000260 break;
261 case 40: // 'Down'
262 e.halt();
263 this.next();
264 break;
265 case 34: // 'Page down'
266 e.halt();
Akron3c2730f2016-05-24 15:08:29 +0200267 this.pageDown();
Nils Diewald5975d702015-03-09 17:45:42 +0000268 break;
269 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000270 if (this._prefix.active())
271 break;
272
Akronf86eaea2016-05-13 18:02:27 +0200273 var item = this.liveItem(this.position);
Akron5ef4fa02015-06-02 16:25:14 +0200274
Nils Diewald5975d702015-03-09 17:45:42 +0000275 if (item["further"] !== undefined) {
276 item["further"].bind(item).apply();
Nils Diewald5975d702015-03-09 17:45:42 +0000277 };
Akron5ef4fa02015-06-02 16:25:14 +0200278
279 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000280 break;
281 case 13: // 'Enter'
282
283 // Click on prefix
284 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000285 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000286
287 // Click on item
288 else
Akronf86eaea2016-05-13 18:02:27 +0200289 this.liveItem(this.position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000290 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000291 break;
292 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000293 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000294 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000295 e.halt();
296 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000297 };
298 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000299
Nils Diewald47f366b2015-04-15 20:06:35 +0000300 // Add characters to prefix
301 _keypress : function (e) {
Akron5a1f5bb2016-05-23 22:00:39 +0200302 e.halt();
Akrona92fd8d2016-05-24 21:13:41 +0200303 var c = String.fromCharCode(_codeFromEvent(e));
Nils Diewald5975d702015-03-09 17:45:42 +0000304
Nils Diewald47f366b2015-04-15 20:06:35 +0000305 // Add prefix
306 this._prefix.add(c);
Akron5a1f5bb2016-05-23 22:00:39 +0200307 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000308 },
309
Akron47c086c2016-05-18 21:22:06 +0200310 /**
Akron5240b8c2016-05-20 09:17:41 +0200311 * Show a screen with a given offset
312 * in the viewport.
Akron47c086c2016-05-18 21:22:06 +0200313 */
314 screen : function (nr) {
Akron3c2730f2016-05-24 15:08:29 +0200315 if (nr < 0) {
316 nr = 0
317 }
Akron6ac58442016-05-24 16:52:29 +0200318 else if (nr > (this.liveLength() - this.limit())) {
319 nr = (this.liveLength() - this.limit());
Akron3c2730f2016-05-24 15:08:29 +0200320 };
321
Akron47c086c2016-05-18 21:22:06 +0200322 if (this._offset === nr)
323 return;
Akron5a1f5bb2016-05-23 22:00:39 +0200324
Akron47c086c2016-05-18 21:22:06 +0200325 this._showItems(nr);
326 },
327
Nils Diewald2fe12e12015-03-06 16:47:06 +0000328 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000329 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000330 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000331 element : function () {
332 return this._element;
333 },
334
Nils Diewald2fe12e12015-03-06 16:47:06 +0000335 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000336 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000337 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000338 itemClass : function () {
339 return this._itemClass;
340 },
341
342 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000343 * Get and set the numerical value
344 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000345 */
346 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000347 if (arguments.length === 1) {
Akron5240b8c2016-05-20 09:17:41 +0200348 if (this._limit !== limit) {
349 this._limit = limit;
350 this._slider.limit(limit);
351 };
Nils Diewald5975d702015-03-09 17:45:42 +0000352 return this;
353 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000354 return this._limit;
355 },
356
Nils Diewald7148c6f2015-05-04 15:07:53 +0000357
Nils Diewald86dad5b2015-01-28 15:09:07 +0000358 /**
359 * Upgrade this object to another object,
360 * while private data stays intact.
361 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000362 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000363 */
364 upgradeTo : function (props) {
365 for (var prop in props) {
366 this[prop] = props[prop];
367 };
368 return this;
369 },
370
Nils Diewald7148c6f2015-05-04 15:07:53 +0000371
Nils Diewald86dad5b2015-01-28 15:09:07 +0000372 /**
Akron97752a72016-05-25 14:43:07 +0200373 * Filter the list and make it visible.
374 * This is always called once the prefix changes.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000375 *
376 * @param {string} Prefix for filtering the list
377 */
Akron6ed13992016-05-23 18:06:05 +0200378 show : function (active) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000379
Akron5240b8c2016-05-20 09:17:41 +0200380 // show menu based on initial offset
Akron6ac58442016-05-24 16:52:29 +0200381 this._unmark(); // Unmark everything that was marked before
Akrona92fd8d2016-05-24 21:13:41 +0200382 this.removeItems();
Akron6ed13992016-05-23 18:06:05 +0200383
384 // Initialize the list
385 if (!this._initList()) {
Akron6ac58442016-05-24 16:52:29 +0200386
Akron6ed13992016-05-23 18:06:05 +0200387 // The prefix is not active
388 this._prefix.active(true);
389
390 // finally show the element
391 this._element.style.opacity = 1;
392
393 return true;
394 };
395
396 var offset = 0;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000397
398 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000399 // Todo: Or the last element chosen
Akron6ed13992016-05-23 18:06:05 +0200400 if (arguments.length === 1) {
401
402 // Normalize active value
Akron3c2730f2016-05-24 15:08:29 +0200403 if (active < 0) {
Akron6ed13992016-05-23 18:06:05 +0200404 active = 0;
Akron3c2730f2016-05-24 15:08:29 +0200405 }
Akron6ac58442016-05-24 16:52:29 +0200406 else if (active > this.liveLength()) {
407 active = this.liveLength() - 1;
Akron3c2730f2016-05-24 15:08:29 +0200408 };
Akron6ed13992016-05-23 18:06:05 +0200409
410 if (active > this._limit) {
411 offset = active;
Akron6ac58442016-05-24 16:52:29 +0200412 if (offset > (this.liveLength() - this._limit)) {
413 offset = this.liveLength() - this._limit;
Akron6ed13992016-05-23 18:06:05 +0200414 };
415 };
416
417 this.position = active;
Akron6ed13992016-05-23 18:06:05 +0200418 }
419
420 else if (this._firstActive) {
Akron47c086c2016-05-18 21:22:06 +0200421 this.position = 0;
Akron47c086c2016-05-18 21:22:06 +0200422 }
Akroncb351d62016-05-19 23:10:33 +0200423
Akron47c086c2016-05-18 21:22:06 +0200424 else {
425 this.position = -1;
Akron6ed13992016-05-23 18:06:05 +0200426 };
427
428 this._offset = offset;
429 this._showItems(offset); // Show new item list
430
431 // Make chosen value active
432 if (this.position !== -1) {
Akron3c2730f2016-05-24 15:08:29 +0200433 this.liveItem(this.position).active(true);
Akron6ed13992016-05-23 18:06:05 +0200434 };
Akron37513a62015-11-17 01:07:11 +0100435
Akron5240b8c2016-05-20 09:17:41 +0200436 // The prefix is not active
Nils Diewalde8518f82015-03-18 22:41:49 +0000437 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000438
Akron5240b8c2016-05-20 09:17:41 +0200439 // finally show the element
Nils Diewald2fe12e12015-03-06 16:47:06 +0000440 this._element.style.opacity = 1;
441
Akron5240b8c2016-05-20 09:17:41 +0200442 // Show the slider
Akron6ed13992016-05-23 18:06:05 +0200443 //this._slider.show();
Akron9905e2a2016-05-10 16:06:44 +0200444
Akron37513a62015-11-17 01:07:11 +0100445 // Iterate to the active item
Akron97752a72016-05-25 14:43:07 +0200446 if (this.position !== -1 && !this._prefix.isSet()) {
447
448 // TODO: OPTIMIZE
449
450 while (this._list[this.position] < this.position) {
Akron5240b8c2016-05-20 09:17:41 +0200451
452 // TODO. Improve this by moving using screen!
Akron37513a62015-11-17 01:07:11 +0100453 this.next();
454 };
455 };
456
Nils Diewald86dad5b2015-01-28 15:09:07 +0000457 // Add classes for rolling menus
458 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000459 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000460 },
461
Nils Diewald7148c6f2015-05-04 15:07:53 +0000462
463 /**
464 * Hide the menu and call the onHide callback.
465 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000466 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000467 this.active = false;
Akron5240b8c2016-05-20 09:17:41 +0200468 this._unmark();
Akrona92fd8d2016-05-24 21:13:41 +0200469 this.removeItems();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000470 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000471 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000472 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000473 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000474 },
475
Nils Diewald7148c6f2015-05-04 15:07:53 +0000476 /**
477 * Function released when the menu hides.
478 * This method is expected to be overridden.
479 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000480 onHide : function () {},
481
Nils Diewald7148c6f2015-05-04 15:07:53 +0000482
Nils Diewald86dad5b2015-01-28 15:09:07 +0000483 /**
484 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000485 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000486 */
Nils Diewald5975d702015-03-09 17:45:42 +0000487 prefix : function (pref) {
488 if (arguments.length === 1) {
489 this._prefix.value(pref);
490 return this;
491 };
492 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000493 },
494
Akronc7448732016-04-27 14:06:58 +0200495 /**
496 * Get the lengthField object.
497 */
498 lengthField : function () {
499 return this._lengthField;
500 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000501
Akron5240b8c2016-05-20 09:17:41 +0200502 /**
503 * Get the associated slider object.
504 */
505 slider : function () {
506 return this._slider;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000507 },
508
Akron5240b8c2016-05-20 09:17:41 +0200509
Nils Diewald86dad5b2015-01-28 15:09:07 +0000510 /**
511 * Delete all visible items from the menu element
512 */
Akrona92fd8d2016-05-24 21:13:41 +0200513 removeItems : function () {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000514 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000515
Nils Diewald2fe12e12015-03-06 16:47:06 +0000516 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000517 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200518 // Leave the prefix and lengthField
Akron9905e2a2016-05-10 16:06:44 +0200519 for (var i = children.length - 1; i >= 3; i--) {
Nils Diewald5975d702015-03-09 17:45:42 +0000520 this._element.removeChild(
521 children[i]
522 );
523 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000524 },
525
Nils Diewald2fe12e12015-03-06 16:47:06 +0000526 /**
527 * Get a specific item from the complete list
528 *
529 * @param {number} index of the list item
530 */
531 item : function (index) {
532 return this._items[index]
533 },
534
535
Nils Diewald86dad5b2015-01-28 15:09:07 +0000536 /**
537 * Get a specific item from the filtered list
538 *
539 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000540 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000541 */
542 liveItem : function (index) {
543 if (this._list === undefined)
544 if (!this._initList())
545 return;
546
547 return this._items[this._list[index]];
548 },
549
Nils Diewald86dad5b2015-01-28 15:09:07 +0000550
551 /**
Akron5240b8c2016-05-20 09:17:41 +0200552 * Get a specific item from the viewport list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000553 *
554 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000555 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000556 */
557 shownItem : function (index) {
558 if (index >= this.limit())
559 return;
560 return this.liveItem(this._offset + index);
561 },
562
563
Nils Diewald2fe12e12015-03-06 16:47:06 +0000564 /**
565 * Get the length of the full list
566 */
567 length : function () {
568 return this._items.length;
569 },
570
571
572 /**
Akron5240b8c2016-05-20 09:17:41 +0200573 * Length of the filtered item list.
574 */
575 liveLength : function () {
576 if (this._list === undefined)
577 this._initList();
578 return this._list.length;
579 },
580
581
582 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000583 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000584 */
585 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000586
Nils Diewald86dad5b2015-01-28 15:09:07 +0000587 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000588 var newItem;
589
Akron47c086c2016-05-18 21:22:06 +0200590 if (this.position !== -1) {
591 // Set new live item
592 if (!this._prefix.active()) {
593 var oldItem = this.liveItem(this.position);
594 oldItem.active(false);
595 };
Nils Diewalde8518f82015-03-18 22:41:49 +0000596 };
597
Akronf86eaea2016-05-13 18:02:27 +0200598 this.position++;
Nils Diewalde8518f82015-03-18 22:41:49 +0000599
Akronf86eaea2016-05-13 18:02:27 +0200600 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000601
Nils Diewald5975d702015-03-09 17:45:42 +0000602 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000603 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000604
605 // Activate prefix
606 var prefix = this._prefix;
607
608 // Mark prefix
609 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200610 this.position--;
Nils Diewald5975d702015-03-09 17:45:42 +0000611 prefix.active(true);
612 return;
613 }
614 else {
Akronf86eaea2016-05-13 18:02:27 +0200615 this.position = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000616 newItem = this.liveItem(0);
617 this._showItems(0);
618 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000619 }
620
Akron5a1f5bb2016-05-23 22:00:39 +0200621 // The next element is after the viewport - roll down
Akronf86eaea2016-05-13 18:02:27 +0200622 else if (this.position >= (this.limit() + this._offset)) {
Akron5a1f5bb2016-05-23 22:00:39 +0200623 this.screen(this.position - this.limit() + 1);
624 }
625
626 // The next element is before the viewport - roll up
627 else if (this.position <= this._offset) {
628 this.screen(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000629 };
Nils Diewald5975d702015-03-09 17:45:42 +0000630
631 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000632 newItem.active(true);
633 },
634
Nils Diewalde8518f82015-03-18 22:41:49 +0000635 /*
Nils Diewald86dad5b2015-01-28 15:09:07 +0000636 * Make the previous item in the menu active
637 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000638 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000639
Nils Diewald2fe12e12015-03-06 16:47:06 +0000640 // No active element set
Akronf86eaea2016-05-13 18:02:27 +0200641 if (this.position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000642 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000643 // TODO: Choose last item
644 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000645
Nils Diewald5975d702015-03-09 17:45:42 +0000646 var newItem;
647
Nils Diewald86dad5b2015-01-28 15:09:07 +0000648 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000649 if (!this._prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200650 var oldItem = this.liveItem(this.position--);
Nils Diewald2d210752015-03-09 19:01:15 +0000651 oldItem.active(false);
652 };
653
Akronf86eaea2016-05-13 18:02:27 +0200654 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000655
656 // The previous element is undefined - roll to bottom
657 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000658
659 // Activate prefix
660 var prefix = this._prefix;
Akrona92fd8d2016-05-24 21:13:41 +0200661 var offset = this.liveLength() - this.limit();
662 // this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000663
664 // Normalize offset
Akrona92fd8d2016-05-24 21:13:41 +0200665 // this._offset = this._offset < 0 ? 0 : this._offset;
666 offset = offset < 0 ? 0 : offset;
Nils Diewald2d210752015-03-09 19:01:15 +0000667
Akronf86eaea2016-05-13 18:02:27 +0200668 this.position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000669
670 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200671 this.position++;
Nils Diewald5975d702015-03-09 17:45:42 +0000672 prefix.active(true);
Akrona92fd8d2016-05-24 21:13:41 +0200673 this._offset = offset;
Nils Diewald5975d702015-03-09 17:45:42 +0000674 return;
675 }
676 else {
Akronf86eaea2016-05-13 18:02:27 +0200677 newItem = this.liveItem(this.position);
Akron5240b8c2016-05-20 09:17:41 +0200678 this._unmark();
Akrona92fd8d2016-05-24 21:13:41 +0200679 this._showItems(offset);
Nils Diewald5975d702015-03-09 17:45:42 +0000680 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000681 }
682
Akron5a1f5bb2016-05-23 22:00:39 +0200683 // The previous element is before the view - roll up
Akronf86eaea2016-05-13 18:02:27 +0200684 else if (this.position < this._offset) {
Akron5a1f5bb2016-05-23 22:00:39 +0200685 this.screen(this.position);
686 }
687
688 // The previous element is after the view - roll down
689 else if (this.position >= (this.limit() + this._offset)) {
690 this.screen(this.position - this.limit() + 2);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000691 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000692
Nils Diewald5975d702015-03-09 17:45:42 +0000693 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000694 newItem.active(true);
695 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000696
Akron3c2730f2016-05-24 15:08:29 +0200697 /**
698 * Move the page up by limit!
699 */
700 pageUp : function () {
701 this.screen(this._offset - this.limit());
702 },
703
704
705 /**
706 * Move the page down by limit!
707 */
708 pageDown : function () {
709 this.screen(this._offset + this.limit());
710 },
711
Nils Diewald86dad5b2015-01-28 15:09:07 +0000712
Akron5240b8c2016-05-20 09:17:41 +0200713 // Unmark all items
714 _unmark : function () {
715 for (var i in this._list) {
716 var item = this._items[this._list[i]];
717 item.lowlight();
718 item.active(false);
719 };
720 },
721
722
723 // Reset chosen item and prefix
724 _reset : function () {
725 this._offset = 0;
Akron97752a72016-05-25 14:43:07 +0200726 this.position = 0;
Akron6ffad5d2016-05-24 17:16:58 +0200727 this._prefix.clear();
Akron5240b8c2016-05-20 09:17:41 +0200728 },
729
730
731 // Set boundary for viewport
732 _boundary : function (bool) {
733 this.item(this._list[0]).noMore(bool);
734 this.item(this._list[this._list.length - 1]).noMore(bool);
735 },
736
737
738 // Append Items that should be shown
739 _showItems : function (off) {
740
Akrona92fd8d2016-05-24 21:13:41 +0200741 // optimization: scroll down one step
742 if (this._offset === (off - 1)) {
743 this._offset = off;
744 this._removeFirst();
745 var pos = this._offset + this.limit() - 1;
746 this._append(this._list[pos]);
747 }
Akron5240b8c2016-05-20 09:17:41 +0200748
Akrona92fd8d2016-05-24 21:13:41 +0200749 // optimization: scroll up one step
750 else if (this._offset === (off + 1)) {
751 this._offset = off;
752 this._removeLast();
753 this._prepend(this._list[this._offset]);
754 }
755 else {
756 this._offset = off;
Akron5240b8c2016-05-20 09:17:41 +0200757
Akrona92fd8d2016-05-24 21:13:41 +0200758 // Remove all items
759 this.removeItems();
Akron5240b8c2016-05-20 09:17:41 +0200760
Akrona92fd8d2016-05-24 21:13:41 +0200761 // Use list
762 var shown = 0;
763 var i;
Akron5240b8c2016-05-20 09:17:41 +0200764
Akrona92fd8d2016-05-24 21:13:41 +0200765 for (i in this._list) {
766
767 // Don't show - it's before offset
768 shown++;
769 if (shown <= off)
770 continue;
771
772 var itemNr = this._list[i];
773 var item = this.item(itemNr);
774 this._append(itemNr);
775
776 if (shown >= (this.limit() + off))
777 break;
778 };
Akron5240b8c2016-05-20 09:17:41 +0200779 };
780
781 // set the slider to the new offset
782 this._slider.offset(this._offset);
783 },
784
785
786 // Append item to the shown list based on index
787 _append : function (i) {
788 var item = this.item(i);
789
790 // Highlight based on prefix
Akrona92fd8d2016-05-24 21:13:41 +0200791 if (this.prefix().length > 0) {
Akron6ffad5d2016-05-24 17:16:58 +0200792 item.highlight(this.prefix().toLowerCase());
Akrona92fd8d2016-05-24 21:13:41 +0200793 };
794
Akron5240b8c2016-05-20 09:17:41 +0200795
796 // Append element
797 this.element().appendChild(item.element());
798 },
799
800
801 // Prepend item to the shown list based on index
802 _prepend : function (i) {
803 var item = this.item(i);
804
805 // Highlight based on prefix
Akrona92fd8d2016-05-24 21:13:41 +0200806 if (this.prefix().length > 0) {
Akron6ffad5d2016-05-24 17:16:58 +0200807 item.highlight(this.prefix().toLowerCase());
Akrona92fd8d2016-05-24 21:13:41 +0200808 };
Akron5240b8c2016-05-20 09:17:41 +0200809
810 var e = this.element();
811 // Append element after lengthField/prefix/slider
812 e.insertBefore(
813 item.element(),
814 e.children[3]
815 );
Nils Diewald5975d702015-03-09 17:45:42 +0000816 },
817
818
Nils Diewald2fe12e12015-03-06 16:47:06 +0000819 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000820 _removeFirst : function () {
Akrona92fd8d2016-05-24 21:13:41 +0200821 // this.item(this._list[this._offset]).lowlight();
Akron9905e2a2016-05-10 16:06:44 +0200822 // leave lengthField/prefix/slider
823 this._element.removeChild(this._element.children[3]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000824 },
825
Nils Diewald2fe12e12015-03-06 16:47:06 +0000826
827 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000828 _removeLast : function () {
Akrona92fd8d2016-05-24 21:13:41 +0200829 // this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000830 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000831 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000832 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000833});