blob: 602eb375cd65dc447ecbcb2c83dc8becce1c93d9 [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 }
329 else if (nr > (this.length() - this.limit())) {
330 nr = (this.length() - this.limit());
331 };
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
414 this._unmark(); // Unmark everything that was marked before
415 this.unshow(); // Delete everything that is shown
Akron6ed13992016-05-23 18:06:05 +0200416
417 // Initialize the list
418 if (!this._initList()) {
419 // The prefix is not active
420 this._prefix.active(true);
421
422 // finally show the element
423 this._element.style.opacity = 1;
424
425 return true;
426 };
427
428 var offset = 0;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000429
430 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000431 // Todo: Or the last element chosen
Akron6ed13992016-05-23 18:06:05 +0200432 if (arguments.length === 1) {
433
434 // Normalize active value
Akron3c2730f2016-05-24 15:08:29 +0200435 if (active < 0) {
Akron6ed13992016-05-23 18:06:05 +0200436 active = 0;
Akron3c2730f2016-05-24 15:08:29 +0200437 }
438 else if (active > this.length()) {
439 active = this.length() - 1;
440 };
Akron6ed13992016-05-23 18:06:05 +0200441
442 if (active > this._limit) {
443 offset = active;
444 if (offset > (this.length() - this._limit)) {
445 offset = this.length() - this._limit;
446 };
447 };
448
449 this.position = active;
450 this._active = active;
451 }
452
453 else if (this._firstActive) {
Akron47c086c2016-05-18 21:22:06 +0200454 this.position = 0;
455 this._active = 0;
456 }
Akroncb351d62016-05-19 23:10:33 +0200457
Akron47c086c2016-05-18 21:22:06 +0200458 else {
459 this.position = -1;
Akron6ed13992016-05-23 18:06:05 +0200460 };
461
462 this._offset = offset;
463 this._showItems(offset); // Show new item list
464
465 // Make chosen value active
466 if (this.position !== -1) {
Akron3c2730f2016-05-24 15:08:29 +0200467 this.liveItem(this.position).active(true);
Akron6ed13992016-05-23 18:06:05 +0200468 };
Akron37513a62015-11-17 01:07:11 +0100469
Akron5240b8c2016-05-20 09:17:41 +0200470 // The prefix is not active
Nils Diewalde8518f82015-03-18 22:41:49 +0000471 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000472
Akron5240b8c2016-05-20 09:17:41 +0200473 // finally show the element
Nils Diewald2fe12e12015-03-06 16:47:06 +0000474 this._element.style.opacity = 1;
475
Akron5240b8c2016-05-20 09:17:41 +0200476 // Show the slider
Akron6ed13992016-05-23 18:06:05 +0200477 //this._slider.show();
Akron9905e2a2016-05-10 16:06:44 +0200478
Akron37513a62015-11-17 01:07:11 +0100479 // Iterate to the active item
480 if (this._active !== -1 && !this._prefix.isSet()) {
Akronf86eaea2016-05-13 18:02:27 +0200481 while (this._list[this.position] < this._active) {
Akron5240b8c2016-05-20 09:17:41 +0200482
483 // TODO. Improve this by moving using screen!
Akron37513a62015-11-17 01:07:11 +0100484 this.next();
485 };
486 };
487
Nils Diewald86dad5b2015-01-28 15:09:07 +0000488 // Add classes for rolling menus
489 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000490 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000491 },
492
Nils Diewald7148c6f2015-05-04 15:07:53 +0000493
494 /**
495 * Hide the menu and call the onHide callback.
496 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000497 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000498 this.active = false;
Akron5240b8c2016-05-20 09:17:41 +0200499 this._unmark();
500 this.unshow();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000501 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000502 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000503 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000504 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000505 },
506
Nils Diewald7148c6f2015-05-04 15:07:53 +0000507 /**
508 * Function released when the menu hides.
509 * This method is expected to be overridden.
510 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000511 onHide : function () {},
512
Nils Diewald7148c6f2015-05-04 15:07:53 +0000513
Nils Diewald86dad5b2015-01-28 15:09:07 +0000514 /**
515 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000516 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000517 */
Nils Diewald5975d702015-03-09 17:45:42 +0000518 prefix : function (pref) {
519 if (arguments.length === 1) {
520 this._prefix.value(pref);
521 return this;
522 };
523 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000524 },
525
Akronc7448732016-04-27 14:06:58 +0200526 /**
527 * Get the lengthField object.
528 */
529 lengthField : function () {
530 return this._lengthField;
531 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000532
Akron5240b8c2016-05-20 09:17:41 +0200533 /**
534 * Get the associated slider object.
535 */
536 slider : function () {
537 return this._slider;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000538 },
539
Akron5240b8c2016-05-20 09:17:41 +0200540
Nils Diewald86dad5b2015-01-28 15:09:07 +0000541 /**
542 * Delete all visible items from the menu element
543 */
Akron5240b8c2016-05-20 09:17:41 +0200544 unshow : function () {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000545 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000546
Nils Diewald2fe12e12015-03-06 16:47:06 +0000547 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000548 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200549 // Leave the prefix and lengthField
Akron9905e2a2016-05-10 16:06:44 +0200550 for (var i = children.length - 1; i >= 3; i--) {
Nils Diewald5975d702015-03-09 17:45:42 +0000551 this._element.removeChild(
552 children[i]
553 );
554 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000555 },
556
Nils Diewald2fe12e12015-03-06 16:47:06 +0000557 /**
558 * Get a specific item from the complete list
559 *
560 * @param {number} index of the list item
561 */
562 item : function (index) {
563 return this._items[index]
564 },
565
566
Nils Diewald86dad5b2015-01-28 15:09:07 +0000567 /**
568 * Get a specific item from the filtered list
569 *
570 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000571 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000572 */
573 liveItem : function (index) {
574 if (this._list === undefined)
575 if (!this._initList())
576 return;
577
578 return this._items[this._list[index]];
579 },
580
Nils Diewald86dad5b2015-01-28 15:09:07 +0000581
582 /**
Akron5240b8c2016-05-20 09:17:41 +0200583 * Get a specific item from the viewport list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000584 *
585 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000586 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000587 */
588 shownItem : function (index) {
589 if (index >= this.limit())
590 return;
591 return this.liveItem(this._offset + index);
592 },
593
594
Nils Diewald2fe12e12015-03-06 16:47:06 +0000595 /**
596 * Get the length of the full list
597 */
598 length : function () {
599 return this._items.length;
600 },
601
602
603 /**
Akron5240b8c2016-05-20 09:17:41 +0200604 * Length of the filtered item list.
605 */
606 liveLength : function () {
607 if (this._list === undefined)
608 this._initList();
609 return this._list.length;
610 },
611
612
613 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000614 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000615 */
616 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000617
Nils Diewald86dad5b2015-01-28 15:09:07 +0000618 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000619 var newItem;
620
Akron47c086c2016-05-18 21:22:06 +0200621 if (this.position !== -1) {
622 // Set new live item
623 if (!this._prefix.active()) {
624 var oldItem = this.liveItem(this.position);
625 oldItem.active(false);
626 };
Nils Diewalde8518f82015-03-18 22:41:49 +0000627 };
628
Akronf86eaea2016-05-13 18:02:27 +0200629 this.position++;
Akron47c086c2016-05-18 21:22:06 +0200630 this._active = this.position;
Nils Diewalde8518f82015-03-18 22:41:49 +0000631
Akronf86eaea2016-05-13 18:02:27 +0200632 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000633
Nils Diewald5975d702015-03-09 17:45:42 +0000634 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000635 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000636
637 // Activate prefix
638 var prefix = this._prefix;
639
640 // Mark prefix
641 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200642 this.position--;
Nils Diewald5975d702015-03-09 17:45:42 +0000643 prefix.active(true);
Akron47c086c2016-05-18 21:22:06 +0200644 this._active = -1;
Nils Diewald5975d702015-03-09 17:45:42 +0000645 return;
646 }
647 else {
648 this._offset = 0;
Akronf86eaea2016-05-13 18:02:27 +0200649 this.position = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000650 newItem = this.liveItem(0);
Akron47c086c2016-05-18 21:22:06 +0200651 this._active = 0;
Akron5240b8c2016-05-20 09:17:41 +0200652 this._unmark();
653 this.unshow();
Nils Diewald5975d702015-03-09 17:45:42 +0000654 this._showItems(0);
655 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000656 }
657
Akron5a1f5bb2016-05-23 22:00:39 +0200658 // The next element is after the viewport - roll down
Akronf86eaea2016-05-13 18:02:27 +0200659 else if (this.position >= (this.limit() + this._offset)) {
Akron5a1f5bb2016-05-23 22:00:39 +0200660
661 this.screen(this.position - this.limit() + 1);
662 }
663
664 // The next element is before the viewport - roll up
665 else if (this.position <= this._offset) {
666 this.screen(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000667 };
Nils Diewald5975d702015-03-09 17:45:42 +0000668
669 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000670 newItem.active(true);
671 },
672
Nils Diewalde8518f82015-03-18 22:41:49 +0000673 /*
Nils Diewald86dad5b2015-01-28 15:09:07 +0000674 * Make the previous item in the menu active
675 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000676 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000677
Nils Diewald2fe12e12015-03-06 16:47:06 +0000678 // No active element set
Akronf86eaea2016-05-13 18:02:27 +0200679 if (this.position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000680 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000681 // TODO: Choose last item
682 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000683
Nils Diewald5975d702015-03-09 17:45:42 +0000684 var newItem;
685
Nils Diewald86dad5b2015-01-28 15:09:07 +0000686 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000687 if (!this._prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200688 var oldItem = this.liveItem(this.position--);
Nils Diewald2d210752015-03-09 19:01:15 +0000689 oldItem.active(false);
690 };
691
Akronf86eaea2016-05-13 18:02:27 +0200692 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000693
694 // The previous element is undefined - roll to bottom
695 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000696
697 // Activate prefix
698 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000699 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000700
701 // Normalize offset
702 this._offset = this._offset < 0 ? 0 : this._offset;
703
Akronf86eaea2016-05-13 18:02:27 +0200704 this.position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000705
706 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200707 this.position++;
Nils Diewald5975d702015-03-09 17:45:42 +0000708 prefix.active(true);
709 return;
710 }
711 else {
Akronf86eaea2016-05-13 18:02:27 +0200712 newItem = this.liveItem(this.position);
Akron5240b8c2016-05-20 09:17:41 +0200713 this._unmark();
714 this.unshow();
Nils Diewald5975d702015-03-09 17:45:42 +0000715 this._showItems(this._offset);
716 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000717 }
718
Akron5a1f5bb2016-05-23 22:00:39 +0200719 // The previous element is before the view - roll up
Akronf86eaea2016-05-13 18:02:27 +0200720 else if (this.position < this._offset) {
Akron5a1f5bb2016-05-23 22:00:39 +0200721 this.screen(this.position);
722 }
723
724 // The previous element is after the view - roll down
725 else if (this.position >= (this.limit() + this._offset)) {
726 this.screen(this.position - this.limit() + 2);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000727 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000728
Nils Diewald5975d702015-03-09 17:45:42 +0000729 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000730 newItem.active(true);
731 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000732
Akron3c2730f2016-05-24 15:08:29 +0200733 /**
734 * Move the page up by limit!
735 */
736 pageUp : function () {
737 this.screen(this._offset - this.limit());
738 },
739
740
741 /**
742 * Move the page down by limit!
743 */
744 pageDown : function () {
745 this.screen(this._offset + this.limit());
746 },
747
Nils Diewald86dad5b2015-01-28 15:09:07 +0000748
Akron5240b8c2016-05-20 09:17:41 +0200749 // Unmark all items
750 _unmark : function () {
751 for (var i in this._list) {
752 var item = this._items[this._list[i]];
753 item.lowlight();
754 item.active(false);
755 };
756 },
757
758
759 // Reset chosen item and prefix
760 _reset : function () {
761 this._offset = 0;
762 this._pos = 0;
763 this._prefix.value('');
764 },
765
766
767 // Set boundary for viewport
768 _boundary : function (bool) {
769 this.item(this._list[0]).noMore(bool);
770 this.item(this._list[this._list.length - 1]).noMore(bool);
771 },
772
773
774 // Append Items that should be shown
775 _showItems : function (off) {
776
777 // Use list
778 var shown = 0;
779 var i;
780
781 for (i in this._list) {
782
783 // Don't show - it's before offset
784 shown++;
785 if (shown <= off)
786 continue;
787
788 var itemNr = this._list[i];
789 var item = this.item(itemNr);
790 this._append(itemNr);
791
792 // this._offset))
793 if (shown >= (this.limit() + off))
794 break;
795 };
796
797 // set the slider to the new offset
798 this._slider.offset(this._offset);
799 },
800
801
802 // Append item to the shown list based on index
803 _append : function (i) {
804 var item = this.item(i);
805
806 // Highlight based on prefix
807 if (this.prefix().length > 0)
808 item.highlight(this.prefix());
809
810 // Append element
811 this.element().appendChild(item.element());
812 },
813
814
815 // Prepend item to the shown list based on index
816 _prepend : function (i) {
817 var item = this.item(i);
818
819 // Highlight based on prefix
820 if (this.prefix().length > 0)
821 item.highlight(this.prefix());
822
823 var e = this.element();
824 // Append element after lengthField/prefix/slider
825 e.insertBefore(
826 item.element(),
827 e.children[3]
828 );
Nils Diewald5975d702015-03-09 17:45:42 +0000829 },
830
831
Nils Diewald2fe12e12015-03-06 16:47:06 +0000832 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000833 _removeFirst : function () {
834 this.item(this._list[this._offset]).lowlight();
Akron9905e2a2016-05-10 16:06:44 +0200835 // leave lengthField/prefix/slider
836 this._element.removeChild(this._element.children[3]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000837 },
838
Nils Diewald2fe12e12015-03-06 16:47:06 +0000839
840 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000841 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000842 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000843 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000844 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000845 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000846});