blob: abe2a15a7875b92e3bdbea82b4f3a815dd943353 [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!
Akron5240b8c2016-05-20 09:17:41 +02008 * TODO: What is _pos and what is position?
Akron6ed13992016-05-23 18:06:05 +02009 * TODO: What is the difference between position
10 * and _active?
Akron5a1f5bb2016-05-23 22:00:39 +020011 * TODO: Ignore keys with function key combinations (other than shift)
Akron3c2730f2016-05-24 15:08:29 +020012 * TODO: Show the slider briefly on move (whenever screen is called).
Nils Diewald2488d052015-04-09 21:46:02 +000013 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000014define([
15 'menu/item',
16 'menu/prefix',
Akronc7448732016-04-27 14:06:58 +020017 'menu/lengthField',
Akron9905e2a2016-05-10 16:06:44 +020018 'menu/slider',
Nils Diewald0e6992a2015-04-14 20:13:52 +000019 'util'
20], function (defaultItemClass,
Akronc7448732016-04-27 14:06:58 +020021 defaultPrefixClass,
Akron9905e2a2016-05-10 16:06:44 +020022 defaultLengthFieldClass,
23 sliderClass) {
Nils Diewaldfda29d92015-01-22 17:28:01 +000024
Nils Diewald0e6992a2015-04-14 20:13:52 +000025 // Default maximum number of menu items
26 var menuLimit = 8;
27
28 function _codeFromEvent (e) {
29 if (e.charCode && (e.keyCode == 0))
30 return e.charCode
31 return e.keyCode;
Nils Diewald59c02fc2015-03-07 01:29:09 +000032 };
33
Nils Diewald86dad5b2015-01-28 15:09:07 +000034
35 /**
36 * List of items for drop down menu (complete).
37 * Only a sublist of the menu is filtered (live).
38 * Only a sublist of the filtered menu is visible (shown).
39 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000040 return {
Nils Diewald86dad5b2015-01-28 15:09:07 +000041 /**
42 * Create new Menu based on the action prefix
43 * and a list of menu items.
44 *
45 * @this {Menu}
46 * @constructor
47 * @param {string} Context prefix
48 * @param {Array.<Array.<string>>} List of menu items
49 */
50 create : function (params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000051 return Object.create(this)._init(params);
Nils Diewald86dad5b2015-01-28 15:09:07 +000052 },
53
Akron5240b8c2016-05-20 09:17:41 +020054 // Initialize list
55 _init : function (itemClass, prefixClass, lengthFieldClass, params) {
Akrona92fd8d2016-05-24 21:13:41 +020056
Akron5240b8c2016-05-20 09:17:41 +020057 this._itemClass = itemClass || defaultItemClass;
58
59 // Add prefix object
60 if (prefixClass !== undefined) {
61 this._prefix = prefixClass.create();
62 }
63 else {
64 this._prefix = defaultPrefixClass.create();
65 };
66 this._prefix._menu = this;
67
68 // Add lengthField object
69 if (lengthFieldClass !== undefined) {
70 this._lengthField = lengthFieldClass.create();
71 }
72 else {
73 this._lengthField = defaultLengthFieldClass.create();
74 };
75 this._lengthField._menu = this;
76
77 // Initialize slider
78 this._slider = sliderClass.create(this);
79
80 // Create the element
81 var e = document.createElement("ul");
82 e.style.opacity = 0;
83 e.style.outline = 0;
84 e.setAttribute('tabindex', 0);
85 e.classList.add('menu');
86 e.classList.add('roll');
87 e.appendChild(this._prefix.element());
88 e.appendChild(this._lengthField.element());
89 e.appendChild(this._slider.element());
90
91 // This has to be cleaned up later on
92 e["menu"] = this;
93
94 // Arrow keys
95 e.addEventListener(
96 'keydown',
Akrona92fd8d2016-05-24 21:13:41 +020097 this._keydown.bind(this),
Akron5240b8c2016-05-20 09:17:41 +020098 false
99 );
100
101 // Strings
102 e.addEventListener(
103 'keypress',
Akrona92fd8d2016-05-24 21:13:41 +0200104 this._keypress.bind(this),
Akron5240b8c2016-05-20 09:17:41 +0200105 false
106 );
107
108 // Mousewheel
109 e.addEventListener(
110 'wheel',
Akrona92fd8d2016-05-24 21:13:41 +0200111 this._mousewheel.bind(this),
Akron5240b8c2016-05-20 09:17:41 +0200112 false
113 );
114
115 this._element = e;
116 this.active = false;
117 // this.selected = undefined;
118 this._items = new Array();
119 var i = 0;
120
121 // Initialize item list based on parameters
122 for (i in params) {
123 var obj = this._itemClass.create(params[i]);
124
125 // This may become circular
126 obj["_menu"] = this;
127 this._lengthField.add(params[i]);
128 this._items.push(obj);
129 };
130
131 this._limit = menuLimit;
132 this._slider.length(this.liveLength());
133 this._slider.limit(this._limit);
134
135 this.position = 0; // position in the active list
136 this._active = -1; // active item in the item list
137 this._firstActive = false; // Show the first item active always?
138 this._reset();
139 return this;
140 },
141
142 // Initialize the item list
143 _initList : function () {
144
145 // Create a new list
146 if (this._list === undefined) {
147 this._list = [];
148 }
149 else if (this._list.length !== 0) {
150 this._boundary(false);
151 this._list.length = 0;
152 };
153
154 // Offset is initially zero
155 this._offset = 0;
156
157 // There is no prefix set
158 if (this.prefix().length <= 0) {
159
160 // add all items to the list and lowlight
161 for (var i = 0; i < this._items.length; i++) {
162 this._list.push(i);
163 this._items[i].lowlight();
164 };
165
166 return true;
167 };
168
169 /*
170 * There is a prefix set, so filter the list!
171 */
172 var pos;
Akron6ffad5d2016-05-24 17:16:58 +0200173 var prefix = " " + this.prefix().toLowerCase();
Akron5240b8c2016-05-20 09:17:41 +0200174
175 // Iterate over all items and choose preferred matching items
176 // i.e. the matching happens at the word start
177 for (pos = 0; pos < this._items.length; pos++) {
Akron6ffad5d2016-05-24 17:16:58 +0200178 if ((this.item(pos).lcField().indexOf(prefix)) >= 0)
Akron5240b8c2016-05-20 09:17:41 +0200179 this._list.push(pos);
180 };
181
182 // The list is empty - so lower your expectations
183 // Iterate over all items and choose matching items
184 // i.e. the matching happens anywhere in the word
Akron6ffad5d2016-05-24 17:16:58 +0200185 prefix = prefix.substring(1);
Akron5240b8c2016-05-20 09:17:41 +0200186 if (this._list.length == 0) {
187 for (pos = 0; pos < this._items.length; pos++) {
Akron6ffad5d2016-05-24 17:16:58 +0200188 if ((this.item(pos).lcField().indexOf(prefix)) >= 0)
Akron5240b8c2016-05-20 09:17:41 +0200189 this._list.push(pos);
190 };
191 };
192
Akron6ed13992016-05-23 18:06:05 +0200193 this._slider.length(this._list.length);
194
Akron5240b8c2016-05-20 09:17:41 +0200195 // Filter was successful - yeah!
196 return this._list.length > 0 ? true : false;
197 },
198
199
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000200 /**
201 * Destroy this menu
202 * (in case you don't trust the
203 * mark and sweep GC)!
204 */
205 destroy : function () {
Akron47c086c2016-05-18 21:22:06 +0200206
Akron5240b8c2016-05-20 09:17:41 +0200207 // Remove circular reference to "this" in menu
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000208 if (this._element != undefined)
209 delete this._element["menu"];
210
Akron5240b8c2016-05-20 09:17:41 +0200211 // Remove circular reference to "this" in items
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000212 for (var i = 0; i < this._items.length; i++) {
213 delete this._items[i]["_menu"];
214 };
Akron5240b8c2016-05-20 09:17:41 +0200215
216 // Remove circular reference to "this" in prefix
Nils Diewald5c5a7472015-04-02 22:13:38 +0000217 delete this._prefix['_menu'];
Akron5240b8c2016-05-20 09:17:41 +0200218 delete this._lengthField['_menu'];
219 delete this._slider['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000220 },
221
Nils Diewald7148c6f2015-05-04 15:07:53 +0000222
223 /**
224 * Focus on this menu.
225 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000226 focus : function () {
227 this._element.focus();
228 },
229
Nils Diewald7148c6f2015-05-04 15:07:53 +0000230
Nils Diewald59c02fc2015-03-07 01:29:09 +0000231 // mouse wheel treatment
232 _mousewheel : function (e) {
233 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000234
235 delta = e.deltaY / 120;
236 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000237 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +0000238 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000239 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000240 e.halt();
241 },
242
Nils Diewald7148c6f2015-05-04 15:07:53 +0000243
Nils Diewald59c02fc2015-03-07 01:29:09 +0000244 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +0000245 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000246 var code = _codeFromEvent(e);
247
Nils Diewald59c02fc2015-03-07 01:29:09 +0000248 switch (code) {
249 case 27: // 'Esc'
250 e.halt();
251 this.hide();
252 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000253
Nils Diewald59c02fc2015-03-07 01:29:09 +0000254 case 38: // 'Up'
255 e.halt();
256 this.prev();
257 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000258 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +0000259 e.halt();
Akron3c2730f2016-05-24 15:08:29 +0200260 this.pageUp();
Nils Diewald5975d702015-03-09 17:45:42 +0000261 break;
262 case 40: // 'Down'
263 e.halt();
264 this.next();
265 break;
266 case 34: // 'Page down'
267 e.halt();
Akron3c2730f2016-05-24 15:08:29 +0200268 this.pageDown();
Nils Diewald5975d702015-03-09 17:45:42 +0000269 break;
270 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000271 if (this._prefix.active())
272 break;
273
Akronf86eaea2016-05-13 18:02:27 +0200274 var item = this.liveItem(this.position);
Akron5ef4fa02015-06-02 16:25:14 +0200275
Nils Diewald5975d702015-03-09 17:45:42 +0000276 if (item["further"] !== undefined) {
277 item["further"].bind(item).apply();
Nils Diewald5975d702015-03-09 17:45:42 +0000278 };
Akron5ef4fa02015-06-02 16:25:14 +0200279
280 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000281 break;
282 case 13: // 'Enter'
283
284 // Click on prefix
285 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000286 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000287
288 // Click on item
289 else
Akronf86eaea2016-05-13 18:02:27 +0200290 this.liveItem(this.position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000291 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000292 break;
293 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000294 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000295 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000296 e.halt();
297 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000298 };
299 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000300
Nils Diewald47f366b2015-04-15 20:06:35 +0000301 // Add characters to prefix
302 _keypress : function (e) {
Akron5a1f5bb2016-05-23 22:00:39 +0200303 e.halt();
Akrona92fd8d2016-05-24 21:13:41 +0200304 var c = String.fromCharCode(_codeFromEvent(e));
Nils Diewald5975d702015-03-09 17:45:42 +0000305
Nils Diewald47f366b2015-04-15 20:06:35 +0000306 // Add prefix
307 this._prefix.add(c);
Akron5a1f5bb2016-05-23 22:00:39 +0200308 this.show();
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 /**
374 * Filter the list and make it visible
375 *
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;
418 this._active = active;
419 }
420
421 else if (this._firstActive) {
Akron47c086c2016-05-18 21:22:06 +0200422 this.position = 0;
423 this._active = 0;
424 }
Akroncb351d62016-05-19 23:10:33 +0200425
Akron47c086c2016-05-18 21:22:06 +0200426 else {
427 this.position = -1;
Akron6ed13992016-05-23 18:06:05 +0200428 };
429
430 this._offset = offset;
431 this._showItems(offset); // Show new item list
432
433 // Make chosen value active
434 if (this.position !== -1) {
Akron3c2730f2016-05-24 15:08:29 +0200435 this.liveItem(this.position).active(true);
Akron6ed13992016-05-23 18:06:05 +0200436 };
Akron37513a62015-11-17 01:07:11 +0100437
Akron5240b8c2016-05-20 09:17:41 +0200438 // The prefix is not active
Nils Diewalde8518f82015-03-18 22:41:49 +0000439 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000440
Akron5240b8c2016-05-20 09:17:41 +0200441 // finally show the element
Nils Diewald2fe12e12015-03-06 16:47:06 +0000442 this._element.style.opacity = 1;
443
Akron5240b8c2016-05-20 09:17:41 +0200444 // Show the slider
Akron6ed13992016-05-23 18:06:05 +0200445 //this._slider.show();
Akron9905e2a2016-05-10 16:06:44 +0200446
Akron37513a62015-11-17 01:07:11 +0100447 // Iterate to the active item
448 if (this._active !== -1 && !this._prefix.isSet()) {
Akronf86eaea2016-05-13 18:02:27 +0200449 while (this._list[this.position] < this._active) {
Akron5240b8c2016-05-20 09:17:41 +0200450
451 // TODO. Improve this by moving using screen!
Akron37513a62015-11-17 01:07:11 +0100452 this.next();
453 };
454 };
455
Nils Diewald86dad5b2015-01-28 15:09:07 +0000456 // Add classes for rolling menus
457 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000458 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000459 },
460
Nils Diewald7148c6f2015-05-04 15:07:53 +0000461
462 /**
463 * Hide the menu and call the onHide callback.
464 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000465 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000466 this.active = false;
Akron5240b8c2016-05-20 09:17:41 +0200467 this._unmark();
Akrona92fd8d2016-05-24 21:13:41 +0200468 this.removeItems();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000469 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000470 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000471 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000472 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000473 },
474
Nils Diewald7148c6f2015-05-04 15:07:53 +0000475 /**
476 * Function released when the menu hides.
477 * This method is expected to be overridden.
478 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000479 onHide : function () {},
480
Nils Diewald7148c6f2015-05-04 15:07:53 +0000481
Nils Diewald86dad5b2015-01-28 15:09:07 +0000482 /**
483 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000484 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000485 */
Nils Diewald5975d702015-03-09 17:45:42 +0000486 prefix : function (pref) {
487 if (arguments.length === 1) {
488 this._prefix.value(pref);
489 return this;
490 };
491 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000492 },
493
Akronc7448732016-04-27 14:06:58 +0200494 /**
495 * Get the lengthField object.
496 */
497 lengthField : function () {
498 return this._lengthField;
499 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000500
Akron5240b8c2016-05-20 09:17:41 +0200501 /**
502 * Get the associated slider object.
503 */
504 slider : function () {
505 return this._slider;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000506 },
507
Akron5240b8c2016-05-20 09:17:41 +0200508
Nils Diewald86dad5b2015-01-28 15:09:07 +0000509 /**
510 * Delete all visible items from the menu element
511 */
Akrona92fd8d2016-05-24 21:13:41 +0200512 removeItems : function () {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000513 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000514
Nils Diewald2fe12e12015-03-06 16:47:06 +0000515 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000516 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200517 // Leave the prefix and lengthField
Akron9905e2a2016-05-10 16:06:44 +0200518 for (var i = children.length - 1; i >= 3; i--) {
Nils Diewald5975d702015-03-09 17:45:42 +0000519 this._element.removeChild(
520 children[i]
521 );
522 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000523 },
524
Nils Diewald2fe12e12015-03-06 16:47:06 +0000525 /**
526 * Get a specific item from the complete list
527 *
528 * @param {number} index of the list item
529 */
530 item : function (index) {
531 return this._items[index]
532 },
533
534
Nils Diewald86dad5b2015-01-28 15:09:07 +0000535 /**
536 * Get a specific item from the filtered list
537 *
538 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000539 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000540 */
541 liveItem : function (index) {
542 if (this._list === undefined)
543 if (!this._initList())
544 return;
545
546 return this._items[this._list[index]];
547 },
548
Nils Diewald86dad5b2015-01-28 15:09:07 +0000549
550 /**
Akron5240b8c2016-05-20 09:17:41 +0200551 * Get a specific item from the viewport list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000552 *
553 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000554 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000555 */
556 shownItem : function (index) {
557 if (index >= this.limit())
558 return;
559 return this.liveItem(this._offset + index);
560 },
561
562
Nils Diewald2fe12e12015-03-06 16:47:06 +0000563 /**
564 * Get the length of the full list
565 */
566 length : function () {
567 return this._items.length;
568 },
569
570
571 /**
Akron5240b8c2016-05-20 09:17:41 +0200572 * Length of the filtered item list.
573 */
574 liveLength : function () {
575 if (this._list === undefined)
576 this._initList();
577 return this._list.length;
578 },
579
580
581 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000582 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000583 */
584 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000585
Nils Diewald86dad5b2015-01-28 15:09:07 +0000586 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000587 var newItem;
588
Akron47c086c2016-05-18 21:22:06 +0200589 if (this.position !== -1) {
590 // Set new live item
591 if (!this._prefix.active()) {
592 var oldItem = this.liveItem(this.position);
593 oldItem.active(false);
594 };
Nils Diewalde8518f82015-03-18 22:41:49 +0000595 };
596
Akronf86eaea2016-05-13 18:02:27 +0200597 this.position++;
Akron47c086c2016-05-18 21:22:06 +0200598 this._active = 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);
Akron47c086c2016-05-18 21:22:06 +0200612 this._active = -1;
Nils Diewald5975d702015-03-09 17:45:42 +0000613 return;
614 }
615 else {
Akronf86eaea2016-05-13 18:02:27 +0200616 this.position = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000617 newItem = this.liveItem(0);
Akron47c086c2016-05-18 21:22:06 +0200618 this._active = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000619 this._showItems(0);
620 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000621 }
622
Akron5a1f5bb2016-05-23 22:00:39 +0200623 // The next element is after the viewport - roll down
Akronf86eaea2016-05-13 18:02:27 +0200624 else if (this.position >= (this.limit() + this._offset)) {
Akron5a1f5bb2016-05-23 22:00:39 +0200625 this.screen(this.position - this.limit() + 1);
626 }
627
628 // The next element is before the viewport - roll up
629 else if (this.position <= this._offset) {
630 this.screen(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000631 };
Nils Diewald5975d702015-03-09 17:45:42 +0000632
633 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000634 newItem.active(true);
635 },
636
Nils Diewalde8518f82015-03-18 22:41:49 +0000637 /*
Nils Diewald86dad5b2015-01-28 15:09:07 +0000638 * Make the previous item in the menu active
639 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000640 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000641
Nils Diewald2fe12e12015-03-06 16:47:06 +0000642 // No active element set
Akronf86eaea2016-05-13 18:02:27 +0200643 if (this.position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000644 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000645 // TODO: Choose last item
646 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000647
Nils Diewald5975d702015-03-09 17:45:42 +0000648 var newItem;
649
Nils Diewald86dad5b2015-01-28 15:09:07 +0000650 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000651 if (!this._prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200652 var oldItem = this.liveItem(this.position--);
Nils Diewald2d210752015-03-09 19:01:15 +0000653 oldItem.active(false);
654 };
655
Akronf86eaea2016-05-13 18:02:27 +0200656 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000657
658 // The previous element is undefined - roll to bottom
659 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000660
661 // Activate prefix
662 var prefix = this._prefix;
Akrona92fd8d2016-05-24 21:13:41 +0200663 var offset = this.liveLength() - this.limit();
664 // this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000665
666 // Normalize offset
Akrona92fd8d2016-05-24 21:13:41 +0200667 // this._offset = this._offset < 0 ? 0 : this._offset;
668 offset = offset < 0 ? 0 : offset;
Nils Diewald2d210752015-03-09 19:01:15 +0000669
Akronf86eaea2016-05-13 18:02:27 +0200670 this.position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000671
672 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200673 this.position++;
Nils Diewald5975d702015-03-09 17:45:42 +0000674 prefix.active(true);
Akrona92fd8d2016-05-24 21:13:41 +0200675 this._offset = offset;
Nils Diewald5975d702015-03-09 17:45:42 +0000676 return;
677 }
678 else {
Akronf86eaea2016-05-13 18:02:27 +0200679 newItem = this.liveItem(this.position);
Akron5240b8c2016-05-20 09:17:41 +0200680 this._unmark();
Akrona92fd8d2016-05-24 21:13:41 +0200681 this._showItems(offset);
Nils Diewald5975d702015-03-09 17:45:42 +0000682 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000683 }
684
Akron5a1f5bb2016-05-23 22:00:39 +0200685 // The previous element is before the view - roll up
Akronf86eaea2016-05-13 18:02:27 +0200686 else if (this.position < this._offset) {
Akron5a1f5bb2016-05-23 22:00:39 +0200687 this.screen(this.position);
688 }
689
690 // The previous element is after the view - roll down
691 else if (this.position >= (this.limit() + this._offset)) {
692 this.screen(this.position - this.limit() + 2);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000693 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000694
Nils Diewald5975d702015-03-09 17:45:42 +0000695 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000696 newItem.active(true);
697 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000698
Akron3c2730f2016-05-24 15:08:29 +0200699 /**
700 * Move the page up by limit!
701 */
702 pageUp : function () {
703 this.screen(this._offset - this.limit());
704 },
705
706
707 /**
708 * Move the page down by limit!
709 */
710 pageDown : function () {
711 this.screen(this._offset + this.limit());
712 },
713
Nils Diewald86dad5b2015-01-28 15:09:07 +0000714
Akron5240b8c2016-05-20 09:17:41 +0200715 // Unmark all items
716 _unmark : function () {
717 for (var i in this._list) {
718 var item = this._items[this._list[i]];
719 item.lowlight();
720 item.active(false);
721 };
722 },
723
724
725 // Reset chosen item and prefix
726 _reset : function () {
727 this._offset = 0;
728 this._pos = 0;
Akron6ffad5d2016-05-24 17:16:58 +0200729 this._prefix.clear();
Akron5240b8c2016-05-20 09:17:41 +0200730 },
731
732
733 // Set boundary for viewport
734 _boundary : function (bool) {
735 this.item(this._list[0]).noMore(bool);
736 this.item(this._list[this._list.length - 1]).noMore(bool);
737 },
738
739
740 // Append Items that should be shown
741 _showItems : function (off) {
742
Akrona92fd8d2016-05-24 21:13:41 +0200743 // optimization: scroll down one step
744 if (this._offset === (off - 1)) {
745 this._offset = off;
746 this._removeFirst();
747 var pos = this._offset + this.limit() - 1;
748 this._append(this._list[pos]);
749 }
Akron5240b8c2016-05-20 09:17:41 +0200750
Akrona92fd8d2016-05-24 21:13:41 +0200751 // optimization: scroll up one step
752 else if (this._offset === (off + 1)) {
753 this._offset = off;
754 this._removeLast();
755 this._prepend(this._list[this._offset]);
756 }
757 else {
758 this._offset = off;
Akron5240b8c2016-05-20 09:17:41 +0200759
Akrona92fd8d2016-05-24 21:13:41 +0200760 // Remove all items
761 this.removeItems();
Akron5240b8c2016-05-20 09:17:41 +0200762
Akrona92fd8d2016-05-24 21:13:41 +0200763 // Use list
764 var shown = 0;
765 var i;
Akron5240b8c2016-05-20 09:17:41 +0200766
Akrona92fd8d2016-05-24 21:13:41 +0200767 for (i in this._list) {
768
769 // Don't show - it's before offset
770 shown++;
771 if (shown <= off)
772 continue;
773
774 var itemNr = this._list[i];
775 var item = this.item(itemNr);
776 this._append(itemNr);
777
778 if (shown >= (this.limit() + off))
779 break;
780 };
Akron5240b8c2016-05-20 09:17:41 +0200781 };
782
783 // set the slider to the new offset
784 this._slider.offset(this._offset);
785 },
786
787
788 // Append item to the shown list based on index
789 _append : function (i) {
790 var item = this.item(i);
791
792 // Highlight based on prefix
Akrona92fd8d2016-05-24 21:13:41 +0200793 if (this.prefix().length > 0) {
Akron6ffad5d2016-05-24 17:16:58 +0200794 item.highlight(this.prefix().toLowerCase());
Akrona92fd8d2016-05-24 21:13:41 +0200795 };
796
Akron5240b8c2016-05-20 09:17:41 +0200797
798 // Append element
799 this.element().appendChild(item.element());
800 },
801
802
803 // Prepend item to the shown list based on index
804 _prepend : function (i) {
805 var item = this.item(i);
806
807 // Highlight based on prefix
Akrona92fd8d2016-05-24 21:13:41 +0200808 if (this.prefix().length > 0) {
Akron6ffad5d2016-05-24 17:16:58 +0200809 item.highlight(this.prefix().toLowerCase());
Akrona92fd8d2016-05-24 21:13:41 +0200810 };
Akron5240b8c2016-05-20 09:17:41 +0200811
812 var e = this.element();
813 // Append element after lengthField/prefix/slider
814 e.insertBefore(
815 item.element(),
816 e.children[3]
817 );
Nils Diewald5975d702015-03-09 17:45:42 +0000818 },
819
820
Nils Diewald2fe12e12015-03-06 16:47:06 +0000821 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000822 _removeFirst : function () {
Akrona92fd8d2016-05-24 21:13:41 +0200823 // this.item(this._list[this._offset]).lowlight();
Akron9905e2a2016-05-10 16:06:44 +0200824 // leave lengthField/prefix/slider
825 this._element.removeChild(this._element.children[3]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000826 },
827
Nils Diewald2fe12e12015-03-06 16:47:06 +0000828
829 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000830 _removeLast : function () {
Akrona92fd8d2016-05-24 21:13:41 +0200831 // this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000832 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000833 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000834 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000835});