blob: b2c541e0ae5af8067d2da3322a05a4881d9f303c [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!
Akron3c2730f2016-05-24 15:08:29 +02008 * TODO: Show the slider briefly on move (whenever screen is called).
Akron97752a72016-05-25 14:43:07 +02009 * TODO: Optimize scrolling to active item.
Nils Diewald2488d052015-04-09 21:46:02 +000010 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000011define([
12 'menu/item',
13 'menu/prefix',
Akronc7448732016-04-27 14:06:58 +020014 'menu/lengthField',
Akron9905e2a2016-05-10 16:06:44 +020015 'menu/slider',
Nils Diewald0e6992a2015-04-14 20:13:52 +000016 'util'
17], function (defaultItemClass,
Akronc7448732016-04-27 14:06:58 +020018 defaultPrefixClass,
Akron9905e2a2016-05-10 16:06:44 +020019 defaultLengthFieldClass,
20 sliderClass) {
Nils Diewaldfda29d92015-01-22 17:28:01 +000021
Nils Diewald0e6992a2015-04-14 20:13:52 +000022 // Default maximum number of menu items
23 var menuLimit = 8;
24
25 function _codeFromEvent (e) {
26 if (e.charCode && (e.keyCode == 0))
27 return e.charCode
28 return e.keyCode;
Nils Diewald59c02fc2015-03-07 01:29:09 +000029 };
30
Nils Diewald86dad5b2015-01-28 15:09:07 +000031
32 /**
33 * List of items for drop down menu (complete).
34 * Only a sublist of the menu is filtered (live).
35 * Only a sublist of the filtered menu is visible (shown).
36 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000037 return {
Nils Diewald86dad5b2015-01-28 15:09:07 +000038 /**
39 * Create new Menu based on the action prefix
40 * and a list of menu items.
41 *
42 * @this {Menu}
43 * @constructor
44 * @param {string} Context prefix
45 * @param {Array.<Array.<string>>} List of menu items
46 */
47 create : function (params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000048 return Object.create(this)._init(params);
Nils Diewald86dad5b2015-01-28 15:09:07 +000049 },
50
Akron5240b8c2016-05-20 09:17:41 +020051 // Initialize list
52 _init : function (itemClass, prefixClass, lengthFieldClass, params) {
Akrona92fd8d2016-05-24 21:13:41 +020053
Akron5240b8c2016-05-20 09:17:41 +020054 this._itemClass = itemClass || defaultItemClass;
55
56 // Add prefix object
57 if (prefixClass !== undefined) {
58 this._prefix = prefixClass.create();
59 }
60 else {
61 this._prefix = defaultPrefixClass.create();
62 };
63 this._prefix._menu = this;
64
65 // Add lengthField object
66 if (lengthFieldClass !== undefined) {
67 this._lengthField = lengthFieldClass.create();
68 }
69 else {
70 this._lengthField = defaultLengthFieldClass.create();
71 };
72 this._lengthField._menu = this;
73
74 // Initialize slider
75 this._slider = sliderClass.create(this);
76
77 // Create the element
78 var e = document.createElement("ul");
79 e.style.opacity = 0;
80 e.style.outline = 0;
81 e.setAttribute('tabindex', 0);
82 e.classList.add('menu');
83 e.classList.add('roll');
84 e.appendChild(this._prefix.element());
85 e.appendChild(this._lengthField.element());
86 e.appendChild(this._slider.element());
87
88 // This has to be cleaned up later on
89 e["menu"] = this;
90
91 // Arrow keys
92 e.addEventListener(
93 'keydown',
Akrona92fd8d2016-05-24 21:13:41 +020094 this._keydown.bind(this),
Akron5240b8c2016-05-20 09:17:41 +020095 false
96 );
97
98 // Strings
99 e.addEventListener(
100 'keypress',
Akrona92fd8d2016-05-24 21:13:41 +0200101 this._keypress.bind(this),
Akron5240b8c2016-05-20 09:17:41 +0200102 false
103 );
104
105 // Mousewheel
106 e.addEventListener(
107 'wheel',
Akrona92fd8d2016-05-24 21:13:41 +0200108 this._mousewheel.bind(this),
Akron5240b8c2016-05-20 09:17:41 +0200109 false
110 );
111
112 this._element = e;
113 this.active = false;
114 // this.selected = undefined;
115 this._items = new Array();
116 var i = 0;
117
118 // Initialize item list based on parameters
119 for (i in params) {
120 var obj = this._itemClass.create(params[i]);
121
122 // This may become circular
123 obj["_menu"] = this;
124 this._lengthField.add(params[i]);
125 this._items.push(obj);
126 };
127
128 this._limit = menuLimit;
129 this._slider.length(this.liveLength());
130 this._slider.limit(this._limit);
131
Akron5240b8c2016-05-20 09:17:41 +0200132 this._firstActive = false; // Show the first item active always?
133 this._reset();
134 return this;
135 },
136
137 // Initialize the item list
138 _initList : function () {
139
140 // Create a new list
141 if (this._list === undefined) {
142 this._list = [];
143 }
144 else if (this._list.length !== 0) {
145 this._boundary(false);
146 this._list.length = 0;
147 };
148
149 // Offset is initially zero
150 this._offset = 0;
151
152 // There is no prefix set
153 if (this.prefix().length <= 0) {
154
155 // add all items to the list and lowlight
Akron97752a72016-05-25 14:43:07 +0200156 var i = 0;
157 for (; i < this._items.length; i++) {
Akron5240b8c2016-05-20 09:17:41 +0200158 this._list.push(i);
159 this._items[i].lowlight();
160 };
161
Akron97752a72016-05-25 14:43:07 +0200162 this._slider.length(i);
163
Akron5240b8c2016-05-20 09:17:41 +0200164 return true;
165 };
166
167 /*
168 * There is a prefix set, so filter the list!
169 */
170 var pos;
Akron6ffad5d2016-05-24 17:16:58 +0200171 var prefix = " " + this.prefix().toLowerCase();
Akron5240b8c2016-05-20 09:17:41 +0200172
173 // Iterate over all items and choose preferred matching items
174 // i.e. the matching happens at the word start
175 for (pos = 0; pos < this._items.length; pos++) {
Akron6ffad5d2016-05-24 17:16:58 +0200176 if ((this.item(pos).lcField().indexOf(prefix)) >= 0)
Akron5240b8c2016-05-20 09:17:41 +0200177 this._list.push(pos);
178 };
179
180 // The list is empty - so lower your expectations
181 // Iterate over all items and choose matching items
182 // i.e. the matching happens anywhere in the word
Akron6ffad5d2016-05-24 17:16:58 +0200183 prefix = prefix.substring(1);
Akron5240b8c2016-05-20 09:17:41 +0200184 if (this._list.length == 0) {
185 for (pos = 0; pos < this._items.length; pos++) {
Akron6ffad5d2016-05-24 17:16:58 +0200186 if ((this.item(pos).lcField().indexOf(prefix)) >= 0)
Akron5240b8c2016-05-20 09:17:41 +0200187 this._list.push(pos);
188 };
189 };
190
Akron6ed13992016-05-23 18:06:05 +0200191 this._slider.length(this._list.length);
192
Akron5240b8c2016-05-20 09:17:41 +0200193 // Filter was successful - yeah!
194 return this._list.length > 0 ? true : false;
195 },
196
197
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000198 /**
199 * Destroy this menu
200 * (in case you don't trust the
201 * mark and sweep GC)!
202 */
203 destroy : function () {
Akron47c086c2016-05-18 21:22:06 +0200204
Akron5240b8c2016-05-20 09:17:41 +0200205 // Remove circular reference to "this" in menu
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000206 if (this._element != undefined)
207 delete this._element["menu"];
208
Akron5240b8c2016-05-20 09:17:41 +0200209 // Remove circular reference to "this" in items
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000210 for (var i = 0; i < this._items.length; i++) {
211 delete this._items[i]["_menu"];
212 };
Akron5240b8c2016-05-20 09:17:41 +0200213
214 // Remove circular reference to "this" in prefix
Nils Diewald5c5a7472015-04-02 22:13:38 +0000215 delete this._prefix['_menu'];
Akron5240b8c2016-05-20 09:17:41 +0200216 delete this._lengthField['_menu'];
217 delete this._slider['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000218 },
219
Nils Diewald7148c6f2015-05-04 15:07:53 +0000220
221 /**
222 * Focus on this menu.
223 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000224 focus : function () {
225 this._element.focus();
226 },
227
Nils Diewald7148c6f2015-05-04 15:07:53 +0000228
Nils Diewald59c02fc2015-03-07 01:29:09 +0000229 // mouse wheel treatment
230 _mousewheel : function (e) {
231 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000232
233 delta = e.deltaY / 120;
234 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000235 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +0000236 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000237 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000238 e.halt();
239 },
240
Nils Diewald7148c6f2015-05-04 15:07:53 +0000241
Nils Diewald59c02fc2015-03-07 01:29:09 +0000242 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +0000243 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000244 var code = _codeFromEvent(e);
245
Nils Diewald59c02fc2015-03-07 01:29:09 +0000246 switch (code) {
247 case 27: // 'Esc'
248 e.halt();
249 this.hide();
250 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000251
Nils Diewald59c02fc2015-03-07 01:29:09 +0000252 case 38: // 'Up'
253 e.halt();
254 this.prev();
255 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000256 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +0000257 e.halt();
Akron3c2730f2016-05-24 15:08:29 +0200258 this.pageUp();
Nils Diewald5975d702015-03-09 17:45:42 +0000259 break;
260 case 40: // 'Down'
261 e.halt();
262 this.next();
263 break;
264 case 34: // 'Page down'
265 e.halt();
Akron3c2730f2016-05-24 15:08:29 +0200266 this.pageDown();
Nils Diewald5975d702015-03-09 17:45:42 +0000267 break;
268 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000269 if (this._prefix.active())
270 break;
271
Akronf86eaea2016-05-13 18:02:27 +0200272 var item = this.liveItem(this.position);
Akron5ef4fa02015-06-02 16:25:14 +0200273
Nils Diewald5975d702015-03-09 17:45:42 +0000274 if (item["further"] !== undefined) {
275 item["further"].bind(item).apply();
Nils Diewald5975d702015-03-09 17:45:42 +0000276 };
Akron5ef4fa02015-06-02 16:25:14 +0200277
278 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000279 break;
280 case 13: // 'Enter'
281
282 // Click on prefix
283 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000284 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000285
286 // Click on item
287 else
Akronf86eaea2016-05-13 18:02:27 +0200288 this.liveItem(this.position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000289 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000290 break;
291 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000292 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000293 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000294 e.halt();
295 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000296 };
297 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000298
Nils Diewald47f366b2015-04-15 20:06:35 +0000299 // Add characters to prefix
300 _keypress : function (e) {
Akron9c2f9382016-05-25 16:36:04 +0200301 if (e.charCode !== 0) {
302 e.halt();
303 var c = String.fromCharCode(_codeFromEvent(e));
Nils Diewald5975d702015-03-09 17:45:42 +0000304
Akron9c2f9382016-05-25 16:36:04 +0200305 // Add prefix
306 this._prefix.add(c);
307 this.show();
308 };
Nils Diewald59c02fc2015-03-07 01:29:09 +0000309 },
310
Akron47c086c2016-05-18 21:22:06 +0200311 /**
Akron5240b8c2016-05-20 09:17:41 +0200312 * Show a screen with a given offset
313 * in the viewport.
Akron47c086c2016-05-18 21:22:06 +0200314 */
315 screen : function (nr) {
Akron3c2730f2016-05-24 15:08:29 +0200316 if (nr < 0) {
317 nr = 0
318 }
Akron6ac58442016-05-24 16:52:29 +0200319 else if (nr > (this.liveLength() - this.limit())) {
320 nr = (this.liveLength() - this.limit());
Akron3c2730f2016-05-24 15:08:29 +0200321 };
322
Akron47c086c2016-05-18 21:22:06 +0200323 if (this._offset === nr)
324 return;
Akron5a1f5bb2016-05-23 22:00:39 +0200325
Akron47c086c2016-05-18 21:22:06 +0200326 this._showItems(nr);
327 },
328
Nils Diewald2fe12e12015-03-06 16:47:06 +0000329 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000330 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000331 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000332 element : function () {
333 return this._element;
334 },
335
Nils Diewald2fe12e12015-03-06 16:47:06 +0000336 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000337 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000338 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000339 itemClass : function () {
340 return this._itemClass;
341 },
342
343 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000344 * Get and set the numerical value
345 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000346 */
347 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000348 if (arguments.length === 1) {
Akron5240b8c2016-05-20 09:17:41 +0200349 if (this._limit !== limit) {
350 this._limit = limit;
351 this._slider.limit(limit);
352 };
Nils Diewald5975d702015-03-09 17:45:42 +0000353 return this;
354 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000355 return this._limit;
356 },
357
Nils Diewald7148c6f2015-05-04 15:07:53 +0000358
Nils Diewald86dad5b2015-01-28 15:09:07 +0000359 /**
360 * Upgrade this object to another object,
361 * while private data stays intact.
362 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000363 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000364 */
365 upgradeTo : function (props) {
366 for (var prop in props) {
367 this[prop] = props[prop];
368 };
369 return this;
370 },
371
Nils Diewald7148c6f2015-05-04 15:07:53 +0000372
Nils Diewald86dad5b2015-01-28 15:09:07 +0000373 /**
Akron97752a72016-05-25 14:43:07 +0200374 * Filter the list and make it visible.
375 * This is always called once the prefix changes.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000376 *
377 * @param {string} Prefix for filtering the list
378 */
Akron6ed13992016-05-23 18:06:05 +0200379 show : function (active) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000380
Akron5240b8c2016-05-20 09:17:41 +0200381 // show menu based on initial offset
Akron6ac58442016-05-24 16:52:29 +0200382 this._unmark(); // Unmark everything that was marked before
Akrona92fd8d2016-05-24 21:13:41 +0200383 this.removeItems();
Akron6ed13992016-05-23 18:06:05 +0200384
385 // Initialize the list
386 if (!this._initList()) {
Akron6ac58442016-05-24 16:52:29 +0200387
Akron6ed13992016-05-23 18:06:05 +0200388 // The prefix is not active
389 this._prefix.active(true);
390
391 // finally show the element
392 this._element.style.opacity = 1;
393
394 return true;
395 };
396
397 var offset = 0;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000398
Akron9c2f9382016-05-25 16:36:04 +0200399
400 // Set a chosen value to active and move the viewport
Akron6ed13992016-05-23 18:06:05 +0200401 if (arguments.length === 1) {
402
403 // Normalize active value
Akron3c2730f2016-05-24 15:08:29 +0200404 if (active < 0) {
Akron6ed13992016-05-23 18:06:05 +0200405 active = 0;
Akron3c2730f2016-05-24 15:08:29 +0200406 }
Akron6ac58442016-05-24 16:52:29 +0200407 else if (active > this.liveLength()) {
408 active = this.liveLength() - 1;
Akron3c2730f2016-05-24 15:08:29 +0200409 };
Akron6ed13992016-05-23 18:06:05 +0200410
411 if (active > this._limit) {
412 offset = active;
Akron6ac58442016-05-24 16:52:29 +0200413 if (offset > (this.liveLength() - this._limit)) {
414 offset = this.liveLength() - this._limit;
Akron6ed13992016-05-23 18:06:05 +0200415 };
416 };
417
418 this.position = active;
Akron6ed13992016-05-23 18:06:05 +0200419 }
420
Akron9c2f9382016-05-25 16:36:04 +0200421 // Choose the first item
Akron6ed13992016-05-23 18:06:05 +0200422 else if (this._firstActive) {
Akron47c086c2016-05-18 21:22:06 +0200423 this.position = 0;
Akron47c086c2016-05-18 21:22:06 +0200424 }
Akroncb351d62016-05-19 23:10:33 +0200425
Akron9c2f9382016-05-25 16:36:04 +0200426 // Choose no item
Akron47c086c2016-05-18 21:22:06 +0200427 else {
428 this.position = -1;
Akron6ed13992016-05-23 18:06:05 +0200429 };
430
431 this._offset = offset;
432 this._showItems(offset); // Show new item list
433
434 // Make chosen value active
435 if (this.position !== -1) {
Akron3c2730f2016-05-24 15:08:29 +0200436 this.liveItem(this.position).active(true);
Akron6ed13992016-05-23 18:06:05 +0200437 };
Akron37513a62015-11-17 01:07:11 +0100438
Akron5240b8c2016-05-20 09:17:41 +0200439 // The prefix is not active
Nils Diewalde8518f82015-03-18 22:41:49 +0000440 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000441
Akron5240b8c2016-05-20 09:17:41 +0200442 // finally show the element
Nils Diewald2fe12e12015-03-06 16:47:06 +0000443 this._element.style.opacity = 1;
444
Nils Diewald86dad5b2015-01-28 15:09:07 +0000445 // Add classes for rolling menus
446 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000447 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000448 },
449
Nils Diewald7148c6f2015-05-04 15:07:53 +0000450
451 /**
452 * Hide the menu and call the onHide callback.
453 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000454 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000455 this.active = false;
Akron5240b8c2016-05-20 09:17:41 +0200456 this._unmark();
Akrona92fd8d2016-05-24 21:13:41 +0200457 this.removeItems();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000458 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000459 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000460 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000461 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000462 },
463
Nils Diewald7148c6f2015-05-04 15:07:53 +0000464 /**
465 * Function released when the menu hides.
466 * This method is expected to be overridden.
467 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000468 onHide : function () {},
469
Nils Diewald7148c6f2015-05-04 15:07:53 +0000470
Nils Diewald86dad5b2015-01-28 15:09:07 +0000471 /**
472 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000473 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000474 */
Nils Diewald5975d702015-03-09 17:45:42 +0000475 prefix : function (pref) {
476 if (arguments.length === 1) {
477 this._prefix.value(pref);
478 return this;
479 };
480 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000481 },
482
Akronc7448732016-04-27 14:06:58 +0200483 /**
484 * Get the lengthField object.
485 */
486 lengthField : function () {
487 return this._lengthField;
488 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000489
Akron5240b8c2016-05-20 09:17:41 +0200490 /**
491 * Get the associated slider object.
492 */
493 slider : function () {
494 return this._slider;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000495 },
496
Akron5240b8c2016-05-20 09:17:41 +0200497
Nils Diewald86dad5b2015-01-28 15:09:07 +0000498 /**
499 * Delete all visible items from the menu element
500 */
Akrona92fd8d2016-05-24 21:13:41 +0200501 removeItems : function () {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000502 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000503
Nils Diewald2fe12e12015-03-06 16:47:06 +0000504 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000505 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200506 // Leave the prefix and lengthField
Akron9905e2a2016-05-10 16:06:44 +0200507 for (var i = children.length - 1; i >= 3; i--) {
Nils Diewald5975d702015-03-09 17:45:42 +0000508 this._element.removeChild(
509 children[i]
510 );
511 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000512 },
513
Nils Diewald2fe12e12015-03-06 16:47:06 +0000514 /**
515 * Get a specific item from the complete list
516 *
517 * @param {number} index of the list item
518 */
519 item : function (index) {
520 return this._items[index]
521 },
522
523
Nils Diewald86dad5b2015-01-28 15:09:07 +0000524 /**
525 * Get a specific item from the filtered list
526 *
527 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000528 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000529 */
530 liveItem : function (index) {
531 if (this._list === undefined)
532 if (!this._initList())
533 return;
534
535 return this._items[this._list[index]];
536 },
537
Nils Diewald86dad5b2015-01-28 15:09:07 +0000538
539 /**
Akron5240b8c2016-05-20 09:17:41 +0200540 * Get a specific item from the viewport list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000541 *
542 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000543 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000544 */
545 shownItem : function (index) {
546 if (index >= this.limit())
547 return;
548 return this.liveItem(this._offset + index);
549 },
550
551
Nils Diewald2fe12e12015-03-06 16:47:06 +0000552 /**
553 * Get the length of the full list
554 */
555 length : function () {
556 return this._items.length;
557 },
558
559
560 /**
Akron5240b8c2016-05-20 09:17:41 +0200561 * Length of the filtered item list.
562 */
563 liveLength : function () {
564 if (this._list === undefined)
565 this._initList();
566 return this._list.length;
567 },
568
569
570 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000571 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000572 */
573 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000574
Nils Diewald86dad5b2015-01-28 15:09:07 +0000575 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000576 var newItem;
577
Akron47c086c2016-05-18 21:22:06 +0200578 if (this.position !== -1) {
579 // Set new live item
580 if (!this._prefix.active()) {
581 var oldItem = this.liveItem(this.position);
582 oldItem.active(false);
583 };
Nils Diewalde8518f82015-03-18 22:41:49 +0000584 };
585
Akronb38afb22016-05-25 19:30:01 +0200586 // No list
587 if (this.liveLength() === 0)
588 return;
589
Akronf86eaea2016-05-13 18:02:27 +0200590 this.position++;
Nils Diewalde8518f82015-03-18 22:41:49 +0000591
Akronf86eaea2016-05-13 18:02:27 +0200592 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000593
Nils Diewald5975d702015-03-09 17:45:42 +0000594 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000595 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000596
597 // Activate prefix
598 var prefix = this._prefix;
599
600 // Mark prefix
601 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200602 this.position--;
Nils Diewald5975d702015-03-09 17:45:42 +0000603 prefix.active(true);
604 return;
605 }
606 else {
Akronf86eaea2016-05-13 18:02:27 +0200607 this.position = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000608 newItem = this.liveItem(0);
609 this._showItems(0);
610 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000611 }
612
Akron5a1f5bb2016-05-23 22:00:39 +0200613 // The next element is after the viewport - roll down
Akronf86eaea2016-05-13 18:02:27 +0200614 else if (this.position >= (this.limit() + this._offset)) {
Akron5a1f5bb2016-05-23 22:00:39 +0200615 this.screen(this.position - this.limit() + 1);
616 }
617
618 // The next element is before the viewport - roll up
619 else if (this.position <= this._offset) {
620 this.screen(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000621 };
Nils Diewald5975d702015-03-09 17:45:42 +0000622
623 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000624 newItem.active(true);
625 },
626
Nils Diewalde8518f82015-03-18 22:41:49 +0000627 /*
Nils Diewald86dad5b2015-01-28 15:09:07 +0000628 * Make the previous item in the menu active
629 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000630 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000631
Nils Diewald2fe12e12015-03-06 16:47:06 +0000632 // No active element set
Akronf86eaea2016-05-13 18:02:27 +0200633 if (this.position === -1) {
Nils Diewalde8518f82015-03-18 22:41:49 +0000634 // TODO: Choose last item
Akronb38afb22016-05-25 19:30:01 +0200635 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000636 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000637
Akronb38afb22016-05-25 19:30:01 +0200638 // No list
639 if (this.liveLength() === 0)
640 return;
641
Nils Diewald5975d702015-03-09 17:45:42 +0000642 var newItem;
643
Nils Diewald86dad5b2015-01-28 15:09:07 +0000644 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000645 if (!this._prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200646 var oldItem = this.liveItem(this.position--);
Nils Diewald2d210752015-03-09 19:01:15 +0000647 oldItem.active(false);
648 };
649
Akronf86eaea2016-05-13 18:02:27 +0200650 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000651
652 // The previous element is undefined - roll to bottom
653 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000654
655 // Activate prefix
656 var prefix = this._prefix;
Akrona92fd8d2016-05-24 21:13:41 +0200657 var offset = this.liveLength() - this.limit();
658 // this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000659
660 // Normalize offset
Akrona92fd8d2016-05-24 21:13:41 +0200661 // this._offset = this._offset < 0 ? 0 : this._offset;
662 offset = offset < 0 ? 0 : offset;
Nils Diewald2d210752015-03-09 19:01:15 +0000663
Akronf86eaea2016-05-13 18:02:27 +0200664 this.position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000665
666 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200667 this.position++;
Nils Diewald5975d702015-03-09 17:45:42 +0000668 prefix.active(true);
Akrona92fd8d2016-05-24 21:13:41 +0200669 this._offset = offset;
Nils Diewald5975d702015-03-09 17:45:42 +0000670 return;
671 }
672 else {
Akronf86eaea2016-05-13 18:02:27 +0200673 newItem = this.liveItem(this.position);
Akron5240b8c2016-05-20 09:17:41 +0200674 this._unmark();
Akrona92fd8d2016-05-24 21:13:41 +0200675 this._showItems(offset);
Nils Diewald5975d702015-03-09 17:45:42 +0000676 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000677 }
678
Akron5a1f5bb2016-05-23 22:00:39 +0200679 // The previous element is before the view - roll up
Akronf86eaea2016-05-13 18:02:27 +0200680 else if (this.position < this._offset) {
Akron5a1f5bb2016-05-23 22:00:39 +0200681 this.screen(this.position);
682 }
683
684 // The previous element is after the view - roll down
685 else if (this.position >= (this.limit() + this._offset)) {
686 this.screen(this.position - this.limit() + 2);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000687 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000688
Nils Diewald5975d702015-03-09 17:45:42 +0000689 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000690 newItem.active(true);
691 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000692
Akron3c2730f2016-05-24 15:08:29 +0200693 /**
694 * Move the page up by limit!
695 */
696 pageUp : function () {
697 this.screen(this._offset - this.limit());
698 },
699
700
701 /**
702 * Move the page down by limit!
703 */
704 pageDown : function () {
705 this.screen(this._offset + this.limit());
706 },
707
Nils Diewald86dad5b2015-01-28 15:09:07 +0000708
Akron5240b8c2016-05-20 09:17:41 +0200709 // Unmark all items
710 _unmark : function () {
711 for (var i in this._list) {
712 var item = this._items[this._list[i]];
713 item.lowlight();
714 item.active(false);
715 };
716 },
717
718
719 // Reset chosen item and prefix
720 _reset : function () {
721 this._offset = 0;
Akron97752a72016-05-25 14:43:07 +0200722 this.position = 0;
Akron6ffad5d2016-05-24 17:16:58 +0200723 this._prefix.clear();
Akron5240b8c2016-05-20 09:17:41 +0200724 },
725
726
727 // Set boundary for viewport
728 _boundary : function (bool) {
729 this.item(this._list[0]).noMore(bool);
730 this.item(this._list[this._list.length - 1]).noMore(bool);
731 },
732
733
734 // Append Items that should be shown
735 _showItems : function (off) {
736
Akrona92fd8d2016-05-24 21:13:41 +0200737 // optimization: scroll down one step
738 if (this._offset === (off - 1)) {
739 this._offset = off;
740 this._removeFirst();
741 var pos = this._offset + this.limit() - 1;
742 this._append(this._list[pos]);
743 }
Akron5240b8c2016-05-20 09:17:41 +0200744
Akrona92fd8d2016-05-24 21:13:41 +0200745 // optimization: scroll up one step
746 else if (this._offset === (off + 1)) {
747 this._offset = off;
748 this._removeLast();
749 this._prepend(this._list[this._offset]);
750 }
751 else {
752 this._offset = off;
Akron5240b8c2016-05-20 09:17:41 +0200753
Akrona92fd8d2016-05-24 21:13:41 +0200754 // Remove all items
755 this.removeItems();
Akron5240b8c2016-05-20 09:17:41 +0200756
Akrona92fd8d2016-05-24 21:13:41 +0200757 // Use list
758 var shown = 0;
759 var i;
Akron5240b8c2016-05-20 09:17:41 +0200760
Akrona92fd8d2016-05-24 21:13:41 +0200761 for (i in this._list) {
762
763 // Don't show - it's before offset
764 shown++;
765 if (shown <= off)
766 continue;
767
768 var itemNr = this._list[i];
769 var item = this.item(itemNr);
770 this._append(itemNr);
771
772 if (shown >= (this.limit() + off))
773 break;
774 };
Akron5240b8c2016-05-20 09:17:41 +0200775 };
776
777 // set the slider to the new offset
778 this._slider.offset(this._offset);
779 },
780
781
782 // Append item to the shown list based on index
783 _append : function (i) {
784 var item = this.item(i);
785
786 // Highlight based on prefix
Akrona92fd8d2016-05-24 21:13:41 +0200787 if (this.prefix().length > 0) {
Akron6ffad5d2016-05-24 17:16:58 +0200788 item.highlight(this.prefix().toLowerCase());
Akrona92fd8d2016-05-24 21:13:41 +0200789 };
790
Akron5240b8c2016-05-20 09:17:41 +0200791
792 // Append element
793 this.element().appendChild(item.element());
794 },
795
796
797 // Prepend item to the shown list based on index
798 _prepend : function (i) {
799 var item = this.item(i);
800
801 // Highlight based on prefix
Akrona92fd8d2016-05-24 21:13:41 +0200802 if (this.prefix().length > 0) {
Akron6ffad5d2016-05-24 17:16:58 +0200803 item.highlight(this.prefix().toLowerCase());
Akrona92fd8d2016-05-24 21:13:41 +0200804 };
Akron5240b8c2016-05-20 09:17:41 +0200805
806 var e = this.element();
807 // Append element after lengthField/prefix/slider
808 e.insertBefore(
809 item.element(),
810 e.children[3]
811 );
Nils Diewald5975d702015-03-09 17:45:42 +0000812 },
813
814
Nils Diewald2fe12e12015-03-06 16:47:06 +0000815 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000816 _removeFirst : function () {
Akrona92fd8d2016-05-24 21:13:41 +0200817 // this.item(this._list[this._offset]).lowlight();
Akron9905e2a2016-05-10 16:06:44 +0200818 // leave lengthField/prefix/slider
819 this._element.removeChild(this._element.children[3]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000820 },
821
Nils Diewald2fe12e12015-03-06 16:47:06 +0000822
823 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000824 _removeLast : function () {
Akrona92fd8d2016-05-24 21:13:41 +0200825 // this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000826 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000827 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000828 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000829});