blob: 0b013b94ec5a656b338df4463533364d2312184e [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!
Nils Diewald7148c6f2015-05-04 15:07:53 +00008 * TODO: Prefix should be case sensitive!
Akron5240b8c2016-05-20 09:17:41 +02009 * TODO: What is _pos and what is position?
Akron6ed13992016-05-23 18:06:05 +020010 * TODO: What is the difference between position
11 * and _active?
Akron5a1f5bb2016-05-23 22:00:39 +020012 * TODO: next and prev should use an optimized version of _screen
13 * TODO: On next and prev the viewport should move
14 * to the active item.
15 * TODO: pageUp and pageDown should use _screen
16 * TODO: Ignore keys with function key combinations (other than shift)
Akron3c2730f2016-05-24 15:08:29 +020017 * TODO: Show the slider briefly on move (whenever screen is called).
Nils Diewald2488d052015-04-09 21:46:02 +000018 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000019define([
20 'menu/item',
21 'menu/prefix',
Akronc7448732016-04-27 14:06:58 +020022 'menu/lengthField',
Akron9905e2a2016-05-10 16:06:44 +020023 'menu/slider',
Nils Diewald0e6992a2015-04-14 20:13:52 +000024 'util'
25], function (defaultItemClass,
Akronc7448732016-04-27 14:06:58 +020026 defaultPrefixClass,
Akron9905e2a2016-05-10 16:06:44 +020027 defaultLengthFieldClass,
28 sliderClass) {
Nils Diewaldfda29d92015-01-22 17:28:01 +000029
Nils Diewald0e6992a2015-04-14 20:13:52 +000030 // Default maximum number of menu items
31 var menuLimit = 8;
32
33 function _codeFromEvent (e) {
34 if (e.charCode && (e.keyCode == 0))
35 return e.charCode
36 return e.keyCode;
Nils Diewald59c02fc2015-03-07 01:29:09 +000037 };
38
Nils Diewald86dad5b2015-01-28 15:09:07 +000039
40 /**
41 * List of items for drop down menu (complete).
42 * Only a sublist of the menu is filtered (live).
43 * Only a sublist of the filtered menu is visible (shown).
44 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000045 return {
Nils Diewald86dad5b2015-01-28 15:09:07 +000046 /**
47 * Create new Menu based on the action prefix
48 * and a list of menu items.
49 *
50 * @this {Menu}
51 * @constructor
52 * @param {string} Context prefix
53 * @param {Array.<Array.<string>>} List of menu items
54 */
55 create : function (params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000056 return Object.create(this)._init(params);
Nils Diewald86dad5b2015-01-28 15:09:07 +000057 },
58
Akron5240b8c2016-05-20 09:17:41 +020059 // Initialize list
60 _init : function (itemClass, prefixClass, lengthFieldClass, params) {
61 var that = this;
62 this._itemClass = itemClass || defaultItemClass;
63
64 // Add prefix object
65 if (prefixClass !== undefined) {
66 this._prefix = prefixClass.create();
67 }
68 else {
69 this._prefix = defaultPrefixClass.create();
70 };
71 this._prefix._menu = this;
72
73 // Add lengthField object
74 if (lengthFieldClass !== undefined) {
75 this._lengthField = lengthFieldClass.create();
76 }
77 else {
78 this._lengthField = defaultLengthFieldClass.create();
79 };
80 this._lengthField._menu = this;
81
82 // Initialize slider
83 this._slider = sliderClass.create(this);
84
85 // Create the element
86 var e = document.createElement("ul");
87 e.style.opacity = 0;
88 e.style.outline = 0;
89 e.setAttribute('tabindex', 0);
90 e.classList.add('menu');
91 e.classList.add('roll');
92 e.appendChild(this._prefix.element());
93 e.appendChild(this._lengthField.element());
94 e.appendChild(this._slider.element());
95
96 // This has to be cleaned up later on
97 e["menu"] = this;
98
99 // Arrow keys
100 e.addEventListener(
101 'keydown',
102 function (ev) {
103 that._keydown(ev)
104 },
105 false
106 );
107
108 // Strings
109 e.addEventListener(
110 'keypress',
111 function (ev) {
112 that._keypress(ev)
113 },
114 false
115 );
116
117 // Mousewheel
118 e.addEventListener(
119 'wheel',
120 function (ev) {
121 that._mousewheel(ev)
122 },
123 false
124 );
125
126 this._element = e;
127 this.active = false;
128 // this.selected = undefined;
129 this._items = new Array();
130 var i = 0;
131
132 // Initialize item list based on parameters
133 for (i in params) {
134 var obj = this._itemClass.create(params[i]);
135
136 // This may become circular
137 obj["_menu"] = this;
138 this._lengthField.add(params[i]);
139 this._items.push(obj);
140 };
141
142 this._limit = menuLimit;
143 this._slider.length(this.liveLength());
144 this._slider.limit(this._limit);
145
146 this.position = 0; // position in the active list
147 this._active = -1; // active item in the item list
148 this._firstActive = false; // Show the first item active always?
149 this._reset();
150 return this;
151 },
152
153 // Initialize the item list
154 _initList : function () {
155
156 // Create a new list
157 if (this._list === undefined) {
158 this._list = [];
159 }
160 else if (this._list.length !== 0) {
161 this._boundary(false);
162 this._list.length = 0;
163 };
164
165 // Offset is initially zero
166 this._offset = 0;
167
168 // There is no prefix set
169 if (this.prefix().length <= 0) {
170
171 // add all items to the list and lowlight
172 for (var i = 0; i < this._items.length; i++) {
173 this._list.push(i);
174 this._items[i].lowlight();
175 };
176
177 return true;
178 };
179
180 /*
181 * There is a prefix set, so filter the list!
182 */
183 var pos;
184 var paddedPrefix = " " + this.prefix();
185
186 // Iterate over all items and choose preferred matching items
187 // i.e. the matching happens at the word start
188 for (pos = 0; pos < this._items.length; pos++) {
189 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
190 this._list.push(pos);
191 };
192
193 // The list is empty - so lower your expectations
194 // Iterate over all items and choose matching items
195 // i.e. the matching happens anywhere in the word
196 if (this._list.length == 0) {
197 for (pos = 0; pos < this._items.length; pos++) {
198 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
199 this._list.push(pos);
200 };
201 };
202
Akron6ed13992016-05-23 18:06:05 +0200203 this._slider.length(this._list.length);
204
Akron5240b8c2016-05-20 09:17:41 +0200205 // Filter was successful - yeah!
206 return this._list.length > 0 ? true : false;
207 },
208
209
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000210 /**
211 * Destroy this menu
212 * (in case you don't trust the
213 * mark and sweep GC)!
214 */
215 destroy : function () {
Akron47c086c2016-05-18 21:22:06 +0200216
Akron5240b8c2016-05-20 09:17:41 +0200217 // Remove circular reference to "this" in menu
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000218 if (this._element != undefined)
219 delete this._element["menu"];
220
Akron5240b8c2016-05-20 09:17:41 +0200221 // Remove circular reference to "this" in items
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000222 for (var i = 0; i < this._items.length; i++) {
223 delete this._items[i]["_menu"];
224 };
Akron5240b8c2016-05-20 09:17:41 +0200225
226 // Remove circular reference to "this" in prefix
Nils Diewald5c5a7472015-04-02 22:13:38 +0000227 delete this._prefix['_menu'];
Akron5240b8c2016-05-20 09:17:41 +0200228 delete this._lengthField['_menu'];
229 delete this._slider['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000230 },
231
Nils Diewald7148c6f2015-05-04 15:07:53 +0000232
233 /**
234 * Focus on this menu.
235 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000236 focus : function () {
237 this._element.focus();
238 },
239
Nils Diewald7148c6f2015-05-04 15:07:53 +0000240
Nils Diewald59c02fc2015-03-07 01:29:09 +0000241 // mouse wheel treatment
242 _mousewheel : function (e) {
243 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000244
245 delta = e.deltaY / 120;
246 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000247 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +0000248 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000249 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000250 e.halt();
251 },
252
Nils Diewald7148c6f2015-05-04 15:07:53 +0000253
Nils Diewald59c02fc2015-03-07 01:29:09 +0000254 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +0000255 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000256 var code = _codeFromEvent(e);
257
Nils Diewald59c02fc2015-03-07 01:29:09 +0000258 switch (code) {
259 case 27: // 'Esc'
260 e.halt();
261 this.hide();
262 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000263
Nils Diewald59c02fc2015-03-07 01:29:09 +0000264 case 38: // 'Up'
265 e.halt();
266 this.prev();
267 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000268 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +0000269 e.halt();
Akron3c2730f2016-05-24 15:08:29 +0200270 this.pageUp();
Nils Diewald5975d702015-03-09 17:45:42 +0000271 break;
272 case 40: // 'Down'
273 e.halt();
274 this.next();
275 break;
276 case 34: // 'Page down'
277 e.halt();
Akron3c2730f2016-05-24 15:08:29 +0200278 this.pageDown();
Nils Diewald5975d702015-03-09 17:45:42 +0000279 break;
280 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000281 if (this._prefix.active())
282 break;
283
Akronf86eaea2016-05-13 18:02:27 +0200284 var item = this.liveItem(this.position);
Akron5ef4fa02015-06-02 16:25:14 +0200285
Nils Diewald5975d702015-03-09 17:45:42 +0000286 if (item["further"] !== undefined) {
287 item["further"].bind(item).apply();
Nils Diewald5975d702015-03-09 17:45:42 +0000288 };
Akron5ef4fa02015-06-02 16:25:14 +0200289
290 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000291 break;
292 case 13: // 'Enter'
293
294 // Click on prefix
295 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000296 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000297
298 // Click on item
299 else
Akronf86eaea2016-05-13 18:02:27 +0200300 this.liveItem(this.position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000301 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000302 break;
303 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000304 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000305 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000306 e.halt();
307 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000308 };
309 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000310
Nils Diewald47f366b2015-04-15 20:06:35 +0000311 // Add characters to prefix
312 _keypress : function (e) {
Akron5a1f5bb2016-05-23 22:00:39 +0200313 e.halt();
Nils Diewald47f366b2015-04-15 20:06:35 +0000314 var c = String.fromCharCode(_codeFromEvent(e)).toLowerCase();
Nils Diewald5975d702015-03-09 17:45:42 +0000315
Nils Diewald47f366b2015-04-15 20:06:35 +0000316 // Add prefix
317 this._prefix.add(c);
Akron5a1f5bb2016-05-23 22:00:39 +0200318 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000319 },
320
Akron47c086c2016-05-18 21:22:06 +0200321 /**
Akron5240b8c2016-05-20 09:17:41 +0200322 * Show a screen with a given offset
323 * in the viewport.
Akron47c086c2016-05-18 21:22:06 +0200324 */
325 screen : function (nr) {
Akron3c2730f2016-05-24 15:08:29 +0200326 if (nr < 0) {
327 nr = 0
328 }
Akron6ac58442016-05-24 16:52:29 +0200329 else if (nr > (this.liveLength() - this.limit())) {
330 nr = (this.liveLength() - this.limit());
Akron3c2730f2016-05-24 15:08:29 +0200331 };
332
Akron47c086c2016-05-18 21:22:06 +0200333 if (this._offset === nr)
334 return;
Akron5a1f5bb2016-05-23 22:00:39 +0200335
336 // OPTIMIZE!!!
337
338 // TODO: This is just an optimization of screen
339 /*
340 if (this.position >= (this.limit() + this._offset) {
341 this._removeFirst();
342 this._offset++;
343 this._append(this._list[this.position]);
344 this._slider.offset(this._offset);
345 }
346 else if (this.position < this._offset) {
347
348 this._removeLast();
349 this._offset--;
350 this._prepend(this._list[this.position]);
351 // set the slider to the new offset
352 this._slider.offset(this._offset);
353 }
354 */
355
356
Akron5240b8c2016-05-20 09:17:41 +0200357 this.unshow();
358 this._offset = nr;
Akron47c086c2016-05-18 21:22:06 +0200359 this._showItems(nr);
360 },
361
Nils Diewald2fe12e12015-03-06 16:47:06 +0000362 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000363 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000364 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000365 element : function () {
366 return this._element;
367 },
368
Nils Diewald2fe12e12015-03-06 16:47:06 +0000369 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000370 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000371 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000372 itemClass : function () {
373 return this._itemClass;
374 },
375
376 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000377 * Get and set the numerical value
378 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000379 */
380 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000381 if (arguments.length === 1) {
Akron5240b8c2016-05-20 09:17:41 +0200382 if (this._limit !== limit) {
383 this._limit = limit;
384 this._slider.limit(limit);
385 };
Nils Diewald5975d702015-03-09 17:45:42 +0000386 return this;
387 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000388 return this._limit;
389 },
390
Nils Diewald7148c6f2015-05-04 15:07:53 +0000391
Nils Diewald86dad5b2015-01-28 15:09:07 +0000392 /**
393 * Upgrade this object to another object,
394 * while private data stays intact.
395 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000396 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000397 */
398 upgradeTo : function (props) {
399 for (var prop in props) {
400 this[prop] = props[prop];
401 };
402 return this;
403 },
404
Nils Diewald7148c6f2015-05-04 15:07:53 +0000405
Nils Diewald86dad5b2015-01-28 15:09:07 +0000406 /**
407 * Filter the list and make it visible
408 *
409 * @param {string} Prefix for filtering the list
410 */
Akron6ed13992016-05-23 18:06:05 +0200411 show : function (active) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000412
Akron5240b8c2016-05-20 09:17:41 +0200413 // show menu based on initial offset
Akron6ac58442016-05-24 16:52:29 +0200414 this._unmark(); // Unmark everything that was marked before
Akron5240b8c2016-05-20 09:17:41 +0200415 this.unshow(); // Delete everything that is shown
Akron6ed13992016-05-23 18:06:05 +0200416
417 // Initialize the list
418 if (!this._initList()) {
Akron6ac58442016-05-24 16:52:29 +0200419
Akron6ed13992016-05-23 18:06:05 +0200420 // The prefix is not active
421 this._prefix.active(true);
422
423 // finally show the element
424 this._element.style.opacity = 1;
425
426 return true;
427 };
428
429 var offset = 0;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000430
431 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000432 // Todo: Or the last element chosen
Akron6ed13992016-05-23 18:06:05 +0200433 if (arguments.length === 1) {
434
435 // Normalize active value
Akron3c2730f2016-05-24 15:08:29 +0200436 if (active < 0) {
Akron6ed13992016-05-23 18:06:05 +0200437 active = 0;
Akron3c2730f2016-05-24 15:08:29 +0200438 }
Akron6ac58442016-05-24 16:52:29 +0200439 else if (active > this.liveLength()) {
440 active = this.liveLength() - 1;
Akron3c2730f2016-05-24 15:08:29 +0200441 };
Akron6ed13992016-05-23 18:06:05 +0200442
443 if (active > this._limit) {
444 offset = active;
Akron6ac58442016-05-24 16:52:29 +0200445 if (offset > (this.liveLength() - this._limit)) {
446 offset = this.liveLength() - this._limit;
Akron6ed13992016-05-23 18:06:05 +0200447 };
448 };
449
450 this.position = active;
451 this._active = active;
452 }
453
454 else if (this._firstActive) {
Akron47c086c2016-05-18 21:22:06 +0200455 this.position = 0;
456 this._active = 0;
457 }
Akroncb351d62016-05-19 23:10:33 +0200458
Akron47c086c2016-05-18 21:22:06 +0200459 else {
460 this.position = -1;
Akron6ed13992016-05-23 18:06:05 +0200461 };
462
463 this._offset = offset;
464 this._showItems(offset); // Show new item list
465
466 // Make chosen value active
467 if (this.position !== -1) {
Akron3c2730f2016-05-24 15:08:29 +0200468 this.liveItem(this.position).active(true);
Akron6ed13992016-05-23 18:06:05 +0200469 };
Akron37513a62015-11-17 01:07:11 +0100470
Akron5240b8c2016-05-20 09:17:41 +0200471 // The prefix is not active
Nils Diewalde8518f82015-03-18 22:41:49 +0000472 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000473
Akron5240b8c2016-05-20 09:17:41 +0200474 // finally show the element
Nils Diewald2fe12e12015-03-06 16:47:06 +0000475 this._element.style.opacity = 1;
476
Akron5240b8c2016-05-20 09:17:41 +0200477 // Show the slider
Akron6ed13992016-05-23 18:06:05 +0200478 //this._slider.show();
Akron9905e2a2016-05-10 16:06:44 +0200479
Akron37513a62015-11-17 01:07:11 +0100480 // Iterate to the active item
481 if (this._active !== -1 && !this._prefix.isSet()) {
Akronf86eaea2016-05-13 18:02:27 +0200482 while (this._list[this.position] < this._active) {
Akron5240b8c2016-05-20 09:17:41 +0200483
484 // TODO. Improve this by moving using screen!
Akron37513a62015-11-17 01:07:11 +0100485 this.next();
486 };
487 };
488
Nils Diewald86dad5b2015-01-28 15:09:07 +0000489 // Add classes for rolling menus
490 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000491 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000492 },
493
Nils Diewald7148c6f2015-05-04 15:07:53 +0000494
495 /**
496 * Hide the menu and call the onHide callback.
497 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000498 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000499 this.active = false;
Akron5240b8c2016-05-20 09:17:41 +0200500 this._unmark();
501 this.unshow();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000502 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000503 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000504 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000505 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000506 },
507
Nils Diewald7148c6f2015-05-04 15:07:53 +0000508 /**
509 * Function released when the menu hides.
510 * This method is expected to be overridden.
511 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000512 onHide : function () {},
513
Nils Diewald7148c6f2015-05-04 15:07:53 +0000514
Nils Diewald86dad5b2015-01-28 15:09:07 +0000515 /**
516 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000517 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000518 */
Nils Diewald5975d702015-03-09 17:45:42 +0000519 prefix : function (pref) {
520 if (arguments.length === 1) {
521 this._prefix.value(pref);
522 return this;
523 };
524 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000525 },
526
Akronc7448732016-04-27 14:06:58 +0200527 /**
528 * Get the lengthField object.
529 */
530 lengthField : function () {
531 return this._lengthField;
532 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000533
Akron5240b8c2016-05-20 09:17:41 +0200534 /**
535 * Get the associated slider object.
536 */
537 slider : function () {
538 return this._slider;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000539 },
540
Akron5240b8c2016-05-20 09:17:41 +0200541
Nils Diewald86dad5b2015-01-28 15:09:07 +0000542 /**
543 * Delete all visible items from the menu element
544 */
Akron5240b8c2016-05-20 09:17:41 +0200545 unshow : function () {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000546 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000547
Nils Diewald2fe12e12015-03-06 16:47:06 +0000548 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000549 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200550 // Leave the prefix and lengthField
Akron9905e2a2016-05-10 16:06:44 +0200551 for (var i = children.length - 1; i >= 3; i--) {
Nils Diewald5975d702015-03-09 17:45:42 +0000552 this._element.removeChild(
553 children[i]
554 );
555 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000556 },
557
Nils Diewald2fe12e12015-03-06 16:47:06 +0000558 /**
559 * Get a specific item from the complete list
560 *
561 * @param {number} index of the list item
562 */
563 item : function (index) {
564 return this._items[index]
565 },
566
567
Nils Diewald86dad5b2015-01-28 15:09:07 +0000568 /**
569 * Get a specific item from the filtered list
570 *
571 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000572 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000573 */
574 liveItem : function (index) {
575 if (this._list === undefined)
576 if (!this._initList())
577 return;
578
579 return this._items[this._list[index]];
580 },
581
Nils Diewald86dad5b2015-01-28 15:09:07 +0000582
583 /**
Akron5240b8c2016-05-20 09:17:41 +0200584 * Get a specific item from the viewport list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000585 *
586 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000587 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000588 */
589 shownItem : function (index) {
590 if (index >= this.limit())
591 return;
592 return this.liveItem(this._offset + index);
593 },
594
595
Nils Diewald2fe12e12015-03-06 16:47:06 +0000596 /**
597 * Get the length of the full list
598 */
599 length : function () {
600 return this._items.length;
601 },
602
603
604 /**
Akron5240b8c2016-05-20 09:17:41 +0200605 * Length of the filtered item list.
606 */
607 liveLength : function () {
608 if (this._list === undefined)
609 this._initList();
610 return this._list.length;
611 },
612
613
614 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000615 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000616 */
617 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000618
Nils Diewald86dad5b2015-01-28 15:09:07 +0000619 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000620 var newItem;
621
Akron47c086c2016-05-18 21:22:06 +0200622 if (this.position !== -1) {
623 // Set new live item
624 if (!this._prefix.active()) {
625 var oldItem = this.liveItem(this.position);
626 oldItem.active(false);
627 };
Nils Diewalde8518f82015-03-18 22:41:49 +0000628 };
629
Akronf86eaea2016-05-13 18:02:27 +0200630 this.position++;
Akron47c086c2016-05-18 21:22:06 +0200631 this._active = this.position;
Nils Diewalde8518f82015-03-18 22:41:49 +0000632
Akronf86eaea2016-05-13 18:02:27 +0200633 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000634
Nils Diewald5975d702015-03-09 17:45:42 +0000635 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000636 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000637
638 // Activate prefix
639 var prefix = this._prefix;
640
641 // Mark prefix
642 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200643 this.position--;
Nils Diewald5975d702015-03-09 17:45:42 +0000644 prefix.active(true);
Akron47c086c2016-05-18 21:22:06 +0200645 this._active = -1;
Nils Diewald5975d702015-03-09 17:45:42 +0000646 return;
647 }
648 else {
649 this._offset = 0;
Akronf86eaea2016-05-13 18:02:27 +0200650 this.position = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000651 newItem = this.liveItem(0);
Akron47c086c2016-05-18 21:22:06 +0200652 this._active = 0;
Akron5240b8c2016-05-20 09:17:41 +0200653 this._unmark();
654 this.unshow();
Nils Diewald5975d702015-03-09 17:45:42 +0000655 this._showItems(0);
656 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000657 }
658
Akron5a1f5bb2016-05-23 22:00:39 +0200659 // The next element is after the viewport - roll down
Akronf86eaea2016-05-13 18:02:27 +0200660 else if (this.position >= (this.limit() + this._offset)) {
Akron5a1f5bb2016-05-23 22:00:39 +0200661
662 this.screen(this.position - this.limit() + 1);
663 }
664
665 // The next element is before the viewport - roll up
666 else if (this.position <= this._offset) {
667 this.screen(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000668 };
Nils Diewald5975d702015-03-09 17:45:42 +0000669
670 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000671 newItem.active(true);
672 },
673
Nils Diewalde8518f82015-03-18 22:41:49 +0000674 /*
Nils Diewald86dad5b2015-01-28 15:09:07 +0000675 * Make the previous item in the menu active
676 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000677 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000678
Nils Diewald2fe12e12015-03-06 16:47:06 +0000679 // No active element set
Akronf86eaea2016-05-13 18:02:27 +0200680 if (this.position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000681 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000682 // TODO: Choose last item
683 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000684
Nils Diewald5975d702015-03-09 17:45:42 +0000685 var newItem;
686
Nils Diewald86dad5b2015-01-28 15:09:07 +0000687 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000688 if (!this._prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200689 var oldItem = this.liveItem(this.position--);
Nils Diewald2d210752015-03-09 19:01:15 +0000690 oldItem.active(false);
691 };
692
Akronf86eaea2016-05-13 18:02:27 +0200693 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000694
695 // The previous element is undefined - roll to bottom
696 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000697
698 // Activate prefix
699 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000700 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000701
702 // Normalize offset
703 this._offset = this._offset < 0 ? 0 : this._offset;
704
Akronf86eaea2016-05-13 18:02:27 +0200705 this.position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000706
707 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200708 this.position++;
Nils Diewald5975d702015-03-09 17:45:42 +0000709 prefix.active(true);
710 return;
711 }
712 else {
Akronf86eaea2016-05-13 18:02:27 +0200713 newItem = this.liveItem(this.position);
Akron5240b8c2016-05-20 09:17:41 +0200714 this._unmark();
715 this.unshow();
Nils Diewald5975d702015-03-09 17:45:42 +0000716 this._showItems(this._offset);
717 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000718 }
719
Akron5a1f5bb2016-05-23 22:00:39 +0200720 // The previous element is before the view - roll up
Akronf86eaea2016-05-13 18:02:27 +0200721 else if (this.position < this._offset) {
Akron5a1f5bb2016-05-23 22:00:39 +0200722 this.screen(this.position);
723 }
724
725 // The previous element is after the view - roll down
726 else if (this.position >= (this.limit() + this._offset)) {
727 this.screen(this.position - this.limit() + 2);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000728 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000729
Nils Diewald5975d702015-03-09 17:45:42 +0000730 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000731 newItem.active(true);
732 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000733
Akron3c2730f2016-05-24 15:08:29 +0200734 /**
735 * Move the page up by limit!
736 */
737 pageUp : function () {
738 this.screen(this._offset - this.limit());
739 },
740
741
742 /**
743 * Move the page down by limit!
744 */
745 pageDown : function () {
746 this.screen(this._offset + this.limit());
747 },
748
Nils Diewald86dad5b2015-01-28 15:09:07 +0000749
Akron5240b8c2016-05-20 09:17:41 +0200750 // Unmark all items
751 _unmark : function () {
752 for (var i in this._list) {
753 var item = this._items[this._list[i]];
754 item.lowlight();
755 item.active(false);
756 };
757 },
758
759
760 // Reset chosen item and prefix
761 _reset : function () {
762 this._offset = 0;
763 this._pos = 0;
764 this._prefix.value('');
765 },
766
767
768 // Set boundary for viewport
769 _boundary : function (bool) {
770 this.item(this._list[0]).noMore(bool);
771 this.item(this._list[this._list.length - 1]).noMore(bool);
772 },
773
774
775 // Append Items that should be shown
776 _showItems : function (off) {
777
778 // Use list
779 var shown = 0;
780 var i;
781
782 for (i in this._list) {
783
784 // Don't show - it's before offset
785 shown++;
786 if (shown <= off)
787 continue;
788
789 var itemNr = this._list[i];
790 var item = this.item(itemNr);
791 this._append(itemNr);
792
793 // this._offset))
794 if (shown >= (this.limit() + off))
795 break;
796 };
797
798 // set the slider to the new offset
799 this._slider.offset(this._offset);
800 },
801
802
803 // Append item to the shown list based on index
804 _append : function (i) {
805 var item = this.item(i);
806
807 // Highlight based on prefix
808 if (this.prefix().length > 0)
809 item.highlight(this.prefix());
810
811 // Append element
812 this.element().appendChild(item.element());
813 },
814
815
816 // Prepend item to the shown list based on index
817 _prepend : function (i) {
818 var item = this.item(i);
819
820 // Highlight based on prefix
821 if (this.prefix().length > 0)
822 item.highlight(this.prefix());
823
824 var e = this.element();
825 // Append element after lengthField/prefix/slider
826 e.insertBefore(
827 item.element(),
828 e.children[3]
829 );
Nils Diewald5975d702015-03-09 17:45:42 +0000830 },
831
832
Nils Diewald2fe12e12015-03-06 16:47:06 +0000833 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000834 _removeFirst : function () {
835 this.item(this._list[this._offset]).lowlight();
Akron9905e2a2016-05-10 16:06:44 +0200836 // leave lengthField/prefix/slider
837 this._element.removeChild(this._element.children[3]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000838 },
839
Nils Diewald2fe12e12015-03-06 16:47:06 +0000840
841 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000842 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000843 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000844 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000845 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000846 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000847});