blob: 484f47c73f97f1726ca26b47b89de5758ab10ddc [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?
12 * TODO: if the prefix is not found, it should be visible and active!
13 * TODO: Bug: The slider vanishes, if the prefix wasn't found in the demo
14 * (example: type "elt")
Nils Diewald2488d052015-04-09 21:46:02 +000015 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000016define([
17 'menu/item',
18 'menu/prefix',
Akronc7448732016-04-27 14:06:58 +020019 'menu/lengthField',
Akron9905e2a2016-05-10 16:06:44 +020020 'menu/slider',
Nils Diewald0e6992a2015-04-14 20:13:52 +000021 'util'
22], function (defaultItemClass,
Akronc7448732016-04-27 14:06:58 +020023 defaultPrefixClass,
Akron9905e2a2016-05-10 16:06:44 +020024 defaultLengthFieldClass,
25 sliderClass) {
Nils Diewaldfda29d92015-01-22 17:28:01 +000026
Nils Diewald0e6992a2015-04-14 20:13:52 +000027 // Default maximum number of menu items
28 var menuLimit = 8;
29
30 function _codeFromEvent (e) {
31 if (e.charCode && (e.keyCode == 0))
32 return e.charCode
33 return e.keyCode;
Nils Diewald59c02fc2015-03-07 01:29:09 +000034 };
35
Nils Diewald86dad5b2015-01-28 15:09:07 +000036
37 /**
38 * List of items for drop down menu (complete).
39 * Only a sublist of the menu is filtered (live).
40 * Only a sublist of the filtered menu is visible (shown).
41 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000042 return {
Nils Diewald86dad5b2015-01-28 15:09:07 +000043 /**
44 * Create new Menu based on the action prefix
45 * and a list of menu items.
46 *
47 * @this {Menu}
48 * @constructor
49 * @param {string} Context prefix
50 * @param {Array.<Array.<string>>} List of menu items
51 */
52 create : function (params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000053 return Object.create(this)._init(params);
Nils Diewald86dad5b2015-01-28 15:09:07 +000054 },
55
Akron5240b8c2016-05-20 09:17:41 +020056 // Initialize list
57 _init : function (itemClass, prefixClass, lengthFieldClass, params) {
58 var that = this;
59 this._itemClass = itemClass || defaultItemClass;
60
61 // Add prefix object
62 if (prefixClass !== undefined) {
63 this._prefix = prefixClass.create();
64 }
65 else {
66 this._prefix = defaultPrefixClass.create();
67 };
68 this._prefix._menu = this;
69
70 // Add lengthField object
71 if (lengthFieldClass !== undefined) {
72 this._lengthField = lengthFieldClass.create();
73 }
74 else {
75 this._lengthField = defaultLengthFieldClass.create();
76 };
77 this._lengthField._menu = this;
78
79 // Initialize slider
80 this._slider = sliderClass.create(this);
81
82 // Create the element
83 var e = document.createElement("ul");
84 e.style.opacity = 0;
85 e.style.outline = 0;
86 e.setAttribute('tabindex', 0);
87 e.classList.add('menu');
88 e.classList.add('roll');
89 e.appendChild(this._prefix.element());
90 e.appendChild(this._lengthField.element());
91 e.appendChild(this._slider.element());
92
93 // This has to be cleaned up later on
94 e["menu"] = this;
95
96 // Arrow keys
97 e.addEventListener(
98 'keydown',
99 function (ev) {
100 that._keydown(ev)
101 },
102 false
103 );
104
105 // Strings
106 e.addEventListener(
107 'keypress',
108 function (ev) {
109 that._keypress(ev)
110 },
111 false
112 );
113
114 // Mousewheel
115 e.addEventListener(
116 'wheel',
117 function (ev) {
118 that._mousewheel(ev)
119 },
120 false
121 );
122
123 this._element = e;
124 this.active = false;
125 // this.selected = undefined;
126 this._items = new Array();
127 var i = 0;
128
129 // Initialize item list based on parameters
130 for (i in params) {
131 var obj = this._itemClass.create(params[i]);
132
133 // This may become circular
134 obj["_menu"] = this;
135 this._lengthField.add(params[i]);
136 this._items.push(obj);
137 };
138
139 this._limit = menuLimit;
140 this._slider.length(this.liveLength());
141 this._slider.limit(this._limit);
142
143 this.position = 0; // position in the active list
144 this._active = -1; // active item in the item list
145 this._firstActive = false; // Show the first item active always?
146 this._reset();
147 return this;
148 },
149
150 // Initialize the item list
151 _initList : function () {
152
153 // Create a new list
154 if (this._list === undefined) {
155 this._list = [];
156 }
157 else if (this._list.length !== 0) {
158 this._boundary(false);
159 this._list.length = 0;
160 };
161
162 // Offset is initially zero
163 this._offset = 0;
164
165 // There is no prefix set
166 if (this.prefix().length <= 0) {
167
168 // add all items to the list and lowlight
169 for (var i = 0; i < this._items.length; i++) {
170 this._list.push(i);
171 this._items[i].lowlight();
172 };
173
174 return true;
175 };
176
177 /*
178 * There is a prefix set, so filter the list!
179 */
180 var pos;
181 var paddedPrefix = " " + this.prefix();
182
183 // Iterate over all items and choose preferred matching items
184 // i.e. the matching happens at the word start
185 for (pos = 0; pos < this._items.length; pos++) {
186 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
187 this._list.push(pos);
188 };
189
190 // The list is empty - so lower your expectations
191 // Iterate over all items and choose matching items
192 // i.e. the matching happens anywhere in the word
193 if (this._list.length == 0) {
194 for (pos = 0; pos < this._items.length; pos++) {
195 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
196 this._list.push(pos);
197 };
198 };
199
Akron6ed13992016-05-23 18:06:05 +0200200 this._slider.length(this._list.length);
201
Akron5240b8c2016-05-20 09:17:41 +0200202 // Filter was successful - yeah!
203 return this._list.length > 0 ? true : false;
204 },
205
206
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000207 /**
208 * Destroy this menu
209 * (in case you don't trust the
210 * mark and sweep GC)!
211 */
212 destroy : function () {
Akron47c086c2016-05-18 21:22:06 +0200213
Akron5240b8c2016-05-20 09:17:41 +0200214 // Remove circular reference to "this" in menu
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000215 if (this._element != undefined)
216 delete this._element["menu"];
217
Akron5240b8c2016-05-20 09:17:41 +0200218 // Remove circular reference to "this" in items
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000219 for (var i = 0; i < this._items.length; i++) {
220 delete this._items[i]["_menu"];
221 };
Akron5240b8c2016-05-20 09:17:41 +0200222
223 // Remove circular reference to "this" in prefix
Nils Diewald5c5a7472015-04-02 22:13:38 +0000224 delete this._prefix['_menu'];
Akron5240b8c2016-05-20 09:17:41 +0200225 delete this._lengthField['_menu'];
226 delete this._slider['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000227 },
228
Nils Diewald7148c6f2015-05-04 15:07:53 +0000229
230 /**
231 * Focus on this menu.
232 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000233 focus : function () {
234 this._element.focus();
235 },
236
Nils Diewald7148c6f2015-05-04 15:07:53 +0000237
Nils Diewald59c02fc2015-03-07 01:29:09 +0000238 // mouse wheel treatment
239 _mousewheel : function (e) {
240 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000241
242 delta = e.deltaY / 120;
243 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000244 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +0000245 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000246 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000247 e.halt();
248 },
249
Nils Diewald7148c6f2015-05-04 15:07:53 +0000250
Nils Diewald59c02fc2015-03-07 01:29:09 +0000251 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +0000252 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000253 var code = _codeFromEvent(e);
254
Nils Diewald59c02fc2015-03-07 01:29:09 +0000255 switch (code) {
256 case 27: // 'Esc'
257 e.halt();
258 this.hide();
259 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000260
Nils Diewald59c02fc2015-03-07 01:29:09 +0000261 case 38: // 'Up'
262 e.halt();
263 this.prev();
264 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000265 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +0000266 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000267 this.prev();
268 break;
269 case 40: // 'Down'
270 e.halt();
271 this.next();
272 break;
273 case 34: // 'Page down'
274 e.halt();
275 this.next();
276 break;
277 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000278 if (this._prefix.active())
279 break;
280
Akronf86eaea2016-05-13 18:02:27 +0200281 var item = this.liveItem(this.position);
Akron5ef4fa02015-06-02 16:25:14 +0200282
Nils Diewald5975d702015-03-09 17:45:42 +0000283 if (item["further"] !== undefined) {
284 item["further"].bind(item).apply();
Nils Diewald5975d702015-03-09 17:45:42 +0000285 };
Akron5ef4fa02015-06-02 16:25:14 +0200286
287 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000288 break;
289 case 13: // 'Enter'
290
291 // Click on prefix
292 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000293 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000294
295 // Click on item
296 else
Akronf86eaea2016-05-13 18:02:27 +0200297 this.liveItem(this.position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000298 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000299 break;
300 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000301 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000302 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000303 e.halt();
304 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000305 };
306 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000307
Nils Diewald47f366b2015-04-15 20:06:35 +0000308 // Add characters to prefix
309 _keypress : function (e) {
310 var c = String.fromCharCode(_codeFromEvent(e)).toLowerCase();
Nils Diewald5975d702015-03-09 17:45:42 +0000311
Nils Diewald47f366b2015-04-15 20:06:35 +0000312 // Add prefix
313 this._prefix.add(c);
314
315 if (!this.show()) {
316 this.prefix('').show();
317 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000318 };
319 },
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) {
326 if (this._offset === nr)
327 return;
Akron5240b8c2016-05-20 09:17:41 +0200328 this.unshow();
329 this._offset = nr;
Akron47c086c2016-05-18 21:22:06 +0200330 this._showItems(nr);
331 },
332
Nils Diewald2fe12e12015-03-06 16:47:06 +0000333 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000334 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000335 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000336 element : function () {
337 return this._element;
338 },
339
Nils Diewald2fe12e12015-03-06 16:47:06 +0000340 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000341 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000342 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000343 itemClass : function () {
344 return this._itemClass;
345 },
346
347 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000348 * Get and set the numerical value
349 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000350 */
351 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000352 if (arguments.length === 1) {
Akron5240b8c2016-05-20 09:17:41 +0200353 if (this._limit !== limit) {
354 this._limit = limit;
355 this._slider.limit(limit);
356 };
Nils Diewald5975d702015-03-09 17:45:42 +0000357 return this;
358 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000359 return this._limit;
360 },
361
Nils Diewald7148c6f2015-05-04 15:07:53 +0000362
Nils Diewald86dad5b2015-01-28 15:09:07 +0000363 /**
364 * Upgrade this object to another object,
365 * while private data stays intact.
366 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000367 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000368 */
369 upgradeTo : function (props) {
370 for (var prop in props) {
371 this[prop] = props[prop];
372 };
373 return this;
374 },
375
Nils Diewald7148c6f2015-05-04 15:07:53 +0000376
Nils Diewald86dad5b2015-01-28 15:09:07 +0000377 /**
378 * Filter the list and make it visible
379 *
380 * @param {string} Prefix for filtering the list
381 */
Akron6ed13992016-05-23 18:06:05 +0200382 show : function (active) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000383
Akron5240b8c2016-05-20 09:17:41 +0200384 // show menu based on initial offset
385 this._unmark(); // Unmark everything that was marked before
386 this.unshow(); // Delete everything that is shown
Akron6ed13992016-05-23 18:06:05 +0200387
388 // Initialize the list
389 if (!this._initList()) {
390 // The prefix is not active
391 this._prefix.active(true);
392
393 // finally show the element
394 this._element.style.opacity = 1;
395
396 return true;
397 };
398
399 var offset = 0;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000400
401 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000402 // Todo: Or the last element chosen
Akron6ed13992016-05-23 18:06:05 +0200403 if (arguments.length === 1) {
404
405 // Normalize active value
406 if (active < 0)
407 active = 0;
408 else if (active > this.length())
409 active = this.length();
410
411 if (active > this._limit) {
412 offset = active;
413 if (offset > (this.length() - this._limit)) {
414 offset = this.length() - this._limit;
415 };
416 };
417
418 this.position = active;
419 this._active = active;
420 }
421
422 else if (this._firstActive) {
Akron47c086c2016-05-18 21:22:06 +0200423 this.position = 0;
424 this._active = 0;
425 }
Akroncb351d62016-05-19 23:10:33 +0200426
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) {
436 this.shownItem(this.position).active(true);
437 };
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
Akron5240b8c2016-05-20 09:17:41 +0200445 // Show the slider
Akron6ed13992016-05-23 18:06:05 +0200446 //this._slider.show();
Akron9905e2a2016-05-10 16:06:44 +0200447
Akron37513a62015-11-17 01:07:11 +0100448 // Iterate to the active item
449 if (this._active !== -1 && !this._prefix.isSet()) {
Akronf86eaea2016-05-13 18:02:27 +0200450 while (this._list[this.position] < this._active) {
Akron5240b8c2016-05-20 09:17:41 +0200451
452 // TODO. Improve this by moving using screen!
Akron37513a62015-11-17 01:07:11 +0100453 this.next();
454 };
455 };
456
Nils Diewald86dad5b2015-01-28 15:09:07 +0000457 // Add classes for rolling menus
458 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000459 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000460 },
461
Nils Diewald7148c6f2015-05-04 15:07:53 +0000462
463 /**
464 * Hide the menu and call the onHide callback.
465 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000466 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000467 this.active = false;
Akron5240b8c2016-05-20 09:17:41 +0200468 this._unmark();
469 this.unshow();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000470 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000471 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000472 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000473 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000474 },
475
Nils Diewald7148c6f2015-05-04 15:07:53 +0000476 /**
477 * Function released when the menu hides.
478 * This method is expected to be overridden.
479 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000480 onHide : function () {},
481
Nils Diewald7148c6f2015-05-04 15:07:53 +0000482
Nils Diewald86dad5b2015-01-28 15:09:07 +0000483 /**
484 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000485 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000486 */
Nils Diewald5975d702015-03-09 17:45:42 +0000487 prefix : function (pref) {
488 if (arguments.length === 1) {
489 this._prefix.value(pref);
490 return this;
491 };
492 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000493 },
494
Akronc7448732016-04-27 14:06:58 +0200495 /**
496 * Get the lengthField object.
497 */
498 lengthField : function () {
499 return this._lengthField;
500 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000501
Akron5240b8c2016-05-20 09:17:41 +0200502 /**
503 * Get the associated slider object.
504 */
505 slider : function () {
506 return this._slider;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000507 },
508
Akron5240b8c2016-05-20 09:17:41 +0200509
Nils Diewald86dad5b2015-01-28 15:09:07 +0000510 /**
511 * Delete all visible items from the menu element
512 */
Akron5240b8c2016-05-20 09:17:41 +0200513 unshow : function () {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000514 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000515
Nils Diewald2fe12e12015-03-06 16:47:06 +0000516 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000517 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200518 // Leave the prefix and lengthField
Akron9905e2a2016-05-10 16:06:44 +0200519 for (var i = children.length - 1; i >= 3; i--) {
Nils Diewald5975d702015-03-09 17:45:42 +0000520 this._element.removeChild(
521 children[i]
522 );
523 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000524 },
525
Nils Diewald2fe12e12015-03-06 16:47:06 +0000526 /**
527 * Get a specific item from the complete list
528 *
529 * @param {number} index of the list item
530 */
531 item : function (index) {
532 return this._items[index]
533 },
534
535
Nils Diewald86dad5b2015-01-28 15:09:07 +0000536 /**
537 * Get a specific item from the filtered list
538 *
539 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000540 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000541 */
542 liveItem : function (index) {
543 if (this._list === undefined)
544 if (!this._initList())
545 return;
546
547 return this._items[this._list[index]];
548 },
549
Nils Diewald86dad5b2015-01-28 15:09:07 +0000550
551 /**
Akron5240b8c2016-05-20 09:17:41 +0200552 * Get a specific item from the viewport list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000553 *
554 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000555 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000556 */
557 shownItem : function (index) {
558 if (index >= this.limit())
559 return;
560 return this.liveItem(this._offset + index);
561 },
562
563
Nils Diewald2fe12e12015-03-06 16:47:06 +0000564 /**
565 * Get the length of the full list
566 */
567 length : function () {
568 return this._items.length;
569 },
570
571
572 /**
Akron5240b8c2016-05-20 09:17:41 +0200573 * Length of the filtered item list.
574 */
575 liveLength : function () {
576 if (this._list === undefined)
577 this._initList();
578 return this._list.length;
579 },
580
581
582 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000583 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000584 */
585 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000586
Nils Diewald86dad5b2015-01-28 15:09:07 +0000587 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000588 var newItem;
589
Akron47c086c2016-05-18 21:22:06 +0200590 if (this.position !== -1) {
591 // Set new live item
592 if (!this._prefix.active()) {
593 var oldItem = this.liveItem(this.position);
594 oldItem.active(false);
595 };
Nils Diewalde8518f82015-03-18 22:41:49 +0000596 };
597
Akronf86eaea2016-05-13 18:02:27 +0200598 this.position++;
Akron47c086c2016-05-18 21:22:06 +0200599 this._active = this.position;
Nils Diewalde8518f82015-03-18 22:41:49 +0000600
Akronf86eaea2016-05-13 18:02:27 +0200601 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000602
Nils Diewald5975d702015-03-09 17:45:42 +0000603 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000604 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000605
606 // Activate prefix
607 var prefix = this._prefix;
608
609 // Mark prefix
610 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200611 this.position--;
Nils Diewald5975d702015-03-09 17:45:42 +0000612 prefix.active(true);
Akron47c086c2016-05-18 21:22:06 +0200613 this._active = -1;
Nils Diewald5975d702015-03-09 17:45:42 +0000614 return;
615 }
616 else {
617 this._offset = 0;
Akronf86eaea2016-05-13 18:02:27 +0200618 this.position = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000619 newItem = this.liveItem(0);
Akron47c086c2016-05-18 21:22:06 +0200620 this._active = 0;
Akron5240b8c2016-05-20 09:17:41 +0200621 this._unmark();
622 this.unshow();
Nils Diewald5975d702015-03-09 17:45:42 +0000623 this._showItems(0);
624 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000625 }
626
627 // The next element is outside the view - roll down
Akronf86eaea2016-05-13 18:02:27 +0200628 else if (this.position >= (this.limit() + this._offset)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000629 this._removeFirst();
630 this._offset++;
Akronf86eaea2016-05-13 18:02:27 +0200631 this._append(this._list[this.position]);
Akron5240b8c2016-05-20 09:17:41 +0200632 this._slider.offset(this._offset);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000633 };
Nils Diewald5975d702015-03-09 17:45:42 +0000634
635 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000636 newItem.active(true);
637 },
638
Nils Diewalde8518f82015-03-18 22:41:49 +0000639 /*
Nils Diewald86dad5b2015-01-28 15:09:07 +0000640 * Make the previous item in the menu active
641 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000642 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000643
Nils Diewald2fe12e12015-03-06 16:47:06 +0000644 // No active element set
Akronf86eaea2016-05-13 18:02:27 +0200645 if (this.position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000646 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000647 // TODO: Choose last item
648 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000649
Nils Diewald5975d702015-03-09 17:45:42 +0000650 var newItem;
651
Nils Diewald86dad5b2015-01-28 15:09:07 +0000652 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000653 if (!this._prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200654 var oldItem = this.liveItem(this.position--);
Nils Diewald2d210752015-03-09 19:01:15 +0000655 oldItem.active(false);
656 };
657
Akronf86eaea2016-05-13 18:02:27 +0200658 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000659
660 // The previous element is undefined - roll to bottom
661 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000662
663 // Activate prefix
664 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000665 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000666
667 // Normalize offset
668 this._offset = this._offset < 0 ? 0 : this._offset;
669
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);
675 return;
676 }
677 else {
Akronf86eaea2016-05-13 18:02:27 +0200678 newItem = this.liveItem(this.position);
Akron5240b8c2016-05-20 09:17:41 +0200679 this._unmark();
680 this.unshow();
Nils Diewald5975d702015-03-09 17:45:42 +0000681 this._showItems(this._offset);
682 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000683 }
684
685 // The previous element is outside the view - roll up
Akronf86eaea2016-05-13 18:02:27 +0200686 else if (this.position < this._offset) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000687 this._removeLast();
688 this._offset--;
Akronf86eaea2016-05-13 18:02:27 +0200689 this._prepend(this._list[this.position]);
Akron5240b8c2016-05-20 09:17:41 +0200690 // set the slider to the new offset
691 this._slider.offset(this._offset);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000692 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000693
Nils Diewald5975d702015-03-09 17:45:42 +0000694 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000695 newItem.active(true);
696 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000697
698
Akron5240b8c2016-05-20 09:17:41 +0200699 // Unmark all items
700 _unmark : function () {
701 for (var i in this._list) {
702 var item = this._items[this._list[i]];
703 item.lowlight();
704 item.active(false);
705 };
706 },
707
708
709 // Reset chosen item and prefix
710 _reset : function () {
711 this._offset = 0;
712 this._pos = 0;
713 this._prefix.value('');
714 },
715
716
717 // Set boundary for viewport
718 _boundary : function (bool) {
719 this.item(this._list[0]).noMore(bool);
720 this.item(this._list[this._list.length - 1]).noMore(bool);
721 },
722
723
724 // Append Items that should be shown
725 _showItems : function (off) {
726
727 // Use list
728 var shown = 0;
729 var i;
730
731 for (i in this._list) {
732
733 // Don't show - it's before offset
734 shown++;
735 if (shown <= off)
736 continue;
737
738 var itemNr = this._list[i];
739 var item = this.item(itemNr);
740 this._append(itemNr);
741
742 // this._offset))
743 if (shown >= (this.limit() + off))
744 break;
745 };
746
747 // set the slider to the new offset
748 this._slider.offset(this._offset);
749 },
750
751
752 // Append item to the shown list based on index
753 _append : function (i) {
754 var item = this.item(i);
755
756 // Highlight based on prefix
757 if (this.prefix().length > 0)
758 item.highlight(this.prefix());
759
760 // Append element
761 this.element().appendChild(item.element());
762 },
763
764
765 // Prepend item to the shown list based on index
766 _prepend : function (i) {
767 var item = this.item(i);
768
769 // Highlight based on prefix
770 if (this.prefix().length > 0)
771 item.highlight(this.prefix());
772
773 var e = this.element();
774 // Append element after lengthField/prefix/slider
775 e.insertBefore(
776 item.element(),
777 e.children[3]
778 );
Nils Diewald5975d702015-03-09 17:45:42 +0000779 },
780
781
Nils Diewald2fe12e12015-03-06 16:47:06 +0000782 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000783 _removeFirst : function () {
784 this.item(this._list[this._offset]).lowlight();
Akron9905e2a2016-05-10 16:06:44 +0200785 // leave lengthField/prefix/slider
786 this._element.removeChild(this._element.children[3]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000787 },
788
Nils Diewald2fe12e12015-03-06 16:47:06 +0000789
790 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000791 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000792 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000793 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000794 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000795 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000796});
Akron5240b8c2016-05-20 09:17:41 +0200797
798
799 /*
800 * Page down to the first item on the next page
801 */
802 /*
803 nextPage : function () {
804
805 // Prefix is active
806 if (this._prefix.active()) {
807 this._prefix.active(false);
808 }
809
810 // Last item is chosen
811 else if (this.position >= this.limit() + this._offset) {
812
813 this.position = this.limit() + this._offset - 1;
814 newItem = this.liveItem(this.position);
815 var oldItem = this.liveItem(this.position--);
816 oldItem.active(false);
817 }
818
819 // Last item of page is chosen
820 else if (0) {
821
822 // Jump to last item
823 else {
824 var oldItem = this.liveItem(this.position);
825 oldItem.active(false);
826
827 this.position = this.limit() + this._offset - 1;
828 newItem = this.liveItem(this.position);
829 };
830
831 newItem.active(true);
832 },
833 */