blob: 95b4f8deb4ca973c85a138e60ac7af9bec21ee64 [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)
Nils Diewald2488d052015-04-09 21:46:02 +000017 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000018define([
19 'menu/item',
20 'menu/prefix',
Akronc7448732016-04-27 14:06:58 +020021 'menu/lengthField',
Akron9905e2a2016-05-10 16:06:44 +020022 'menu/slider',
Nils Diewald0e6992a2015-04-14 20:13:52 +000023 'util'
24], function (defaultItemClass,
Akronc7448732016-04-27 14:06:58 +020025 defaultPrefixClass,
Akron9905e2a2016-05-10 16:06:44 +020026 defaultLengthFieldClass,
27 sliderClass) {
Nils Diewaldfda29d92015-01-22 17:28:01 +000028
Nils Diewald0e6992a2015-04-14 20:13:52 +000029 // Default maximum number of menu items
30 var menuLimit = 8;
31
32 function _codeFromEvent (e) {
33 if (e.charCode && (e.keyCode == 0))
34 return e.charCode
35 return e.keyCode;
Nils Diewald59c02fc2015-03-07 01:29:09 +000036 };
37
Nils Diewald86dad5b2015-01-28 15:09:07 +000038
39 /**
40 * List of items for drop down menu (complete).
41 * Only a sublist of the menu is filtered (live).
42 * Only a sublist of the filtered menu is visible (shown).
43 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000044 return {
Nils Diewald86dad5b2015-01-28 15:09:07 +000045 /**
46 * Create new Menu based on the action prefix
47 * and a list of menu items.
48 *
49 * @this {Menu}
50 * @constructor
51 * @param {string} Context prefix
52 * @param {Array.<Array.<string>>} List of menu items
53 */
54 create : function (params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000055 return Object.create(this)._init(params);
Nils Diewald86dad5b2015-01-28 15:09:07 +000056 },
57
Akron5240b8c2016-05-20 09:17:41 +020058 // Initialize list
59 _init : function (itemClass, prefixClass, lengthFieldClass, params) {
60 var that = this;
61 this._itemClass = itemClass || defaultItemClass;
62
63 // Add prefix object
64 if (prefixClass !== undefined) {
65 this._prefix = prefixClass.create();
66 }
67 else {
68 this._prefix = defaultPrefixClass.create();
69 };
70 this._prefix._menu = this;
71
72 // Add lengthField object
73 if (lengthFieldClass !== undefined) {
74 this._lengthField = lengthFieldClass.create();
75 }
76 else {
77 this._lengthField = defaultLengthFieldClass.create();
78 };
79 this._lengthField._menu = this;
80
81 // Initialize slider
82 this._slider = sliderClass.create(this);
83
84 // Create the element
85 var e = document.createElement("ul");
86 e.style.opacity = 0;
87 e.style.outline = 0;
88 e.setAttribute('tabindex', 0);
89 e.classList.add('menu');
90 e.classList.add('roll');
91 e.appendChild(this._prefix.element());
92 e.appendChild(this._lengthField.element());
93 e.appendChild(this._slider.element());
94
95 // This has to be cleaned up later on
96 e["menu"] = this;
97
98 // Arrow keys
99 e.addEventListener(
100 'keydown',
101 function (ev) {
102 that._keydown(ev)
103 },
104 false
105 );
106
107 // Strings
108 e.addEventListener(
109 'keypress',
110 function (ev) {
111 that._keypress(ev)
112 },
113 false
114 );
115
116 // Mousewheel
117 e.addEventListener(
118 'wheel',
119 function (ev) {
120 that._mousewheel(ev)
121 },
122 false
123 );
124
125 this._element = e;
126 this.active = false;
127 // this.selected = undefined;
128 this._items = new Array();
129 var i = 0;
130
131 // Initialize item list based on parameters
132 for (i in params) {
133 var obj = this._itemClass.create(params[i]);
134
135 // This may become circular
136 obj["_menu"] = this;
137 this._lengthField.add(params[i]);
138 this._items.push(obj);
139 };
140
141 this._limit = menuLimit;
142 this._slider.length(this.liveLength());
143 this._slider.limit(this._limit);
144
145 this.position = 0; // position in the active list
146 this._active = -1; // active item in the item list
147 this._firstActive = false; // Show the first item active always?
148 this._reset();
149 return this;
150 },
151
152 // Initialize the item list
153 _initList : function () {
154
155 // Create a new list
156 if (this._list === undefined) {
157 this._list = [];
158 }
159 else if (this._list.length !== 0) {
160 this._boundary(false);
161 this._list.length = 0;
162 };
163
164 // Offset is initially zero
165 this._offset = 0;
166
167 // There is no prefix set
168 if (this.prefix().length <= 0) {
169
170 // add all items to the list and lowlight
171 for (var i = 0; i < this._items.length; i++) {
172 this._list.push(i);
173 this._items[i].lowlight();
174 };
175
176 return true;
177 };
178
179 /*
180 * There is a prefix set, so filter the list!
181 */
182 var pos;
183 var paddedPrefix = " " + this.prefix();
184
185 // Iterate over all items and choose preferred matching items
186 // i.e. the matching happens at the word start
187 for (pos = 0; pos < this._items.length; pos++) {
188 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
189 this._list.push(pos);
190 };
191
192 // The list is empty - so lower your expectations
193 // Iterate over all items and choose matching items
194 // i.e. the matching happens anywhere in the word
195 if (this._list.length == 0) {
196 for (pos = 0; pos < this._items.length; pos++) {
197 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
198 this._list.push(pos);
199 };
200 };
201
Akron6ed13992016-05-23 18:06:05 +0200202 this._slider.length(this._list.length);
203
Akron5240b8c2016-05-20 09:17:41 +0200204 // Filter was successful - yeah!
205 return this._list.length > 0 ? true : false;
206 },
207
208
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000209 /**
210 * Destroy this menu
211 * (in case you don't trust the
212 * mark and sweep GC)!
213 */
214 destroy : function () {
Akron47c086c2016-05-18 21:22:06 +0200215
Akron5240b8c2016-05-20 09:17:41 +0200216 // Remove circular reference to "this" in menu
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000217 if (this._element != undefined)
218 delete this._element["menu"];
219
Akron5240b8c2016-05-20 09:17:41 +0200220 // Remove circular reference to "this" in items
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000221 for (var i = 0; i < this._items.length; i++) {
222 delete this._items[i]["_menu"];
223 };
Akron5240b8c2016-05-20 09:17:41 +0200224
225 // Remove circular reference to "this" in prefix
Nils Diewald5c5a7472015-04-02 22:13:38 +0000226 delete this._prefix['_menu'];
Akron5240b8c2016-05-20 09:17:41 +0200227 delete this._lengthField['_menu'];
228 delete this._slider['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000229 },
230
Nils Diewald7148c6f2015-05-04 15:07:53 +0000231
232 /**
233 * Focus on this menu.
234 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000235 focus : function () {
236 this._element.focus();
237 },
238
Nils Diewald7148c6f2015-05-04 15:07:53 +0000239
Nils Diewald59c02fc2015-03-07 01:29:09 +0000240 // mouse wheel treatment
241 _mousewheel : function (e) {
242 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000243
244 delta = e.deltaY / 120;
245 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000246 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +0000247 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000248 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000249 e.halt();
250 },
251
Nils Diewald7148c6f2015-05-04 15:07:53 +0000252
Nils Diewald59c02fc2015-03-07 01:29:09 +0000253 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +0000254 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000255 var code = _codeFromEvent(e);
256
Nils Diewald59c02fc2015-03-07 01:29:09 +0000257 switch (code) {
258 case 27: // 'Esc'
259 e.halt();
260 this.hide();
261 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000262
Nils Diewald59c02fc2015-03-07 01:29:09 +0000263 case 38: // 'Up'
264 e.halt();
265 this.prev();
266 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000267 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +0000268 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000269 this.prev();
270 break;
271 case 40: // 'Down'
272 e.halt();
273 this.next();
274 break;
275 case 34: // 'Page down'
276 e.halt();
277 this.next();
278 break;
279 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000280 if (this._prefix.active())
281 break;
282
Akronf86eaea2016-05-13 18:02:27 +0200283 var item = this.liveItem(this.position);
Akron5ef4fa02015-06-02 16:25:14 +0200284
Nils Diewald5975d702015-03-09 17:45:42 +0000285 if (item["further"] !== undefined) {
286 item["further"].bind(item).apply();
Nils Diewald5975d702015-03-09 17:45:42 +0000287 };
Akron5ef4fa02015-06-02 16:25:14 +0200288
289 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000290 break;
291 case 13: // 'Enter'
292
293 // Click on prefix
294 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000295 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000296
297 // Click on item
298 else
Akronf86eaea2016-05-13 18:02:27 +0200299 this.liveItem(this.position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000300 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000301 break;
302 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000303 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000304 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000305 e.halt();
306 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000307 };
308 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000309
Nils Diewald47f366b2015-04-15 20:06:35 +0000310 // Add characters to prefix
311 _keypress : function (e) {
Akron5a1f5bb2016-05-23 22:00:39 +0200312 e.halt();
Nils Diewald47f366b2015-04-15 20:06:35 +0000313 var c = String.fromCharCode(_codeFromEvent(e)).toLowerCase();
Nils Diewald5975d702015-03-09 17:45:42 +0000314
Nils Diewald47f366b2015-04-15 20:06:35 +0000315 // Add prefix
316 this._prefix.add(c);
Akron5a1f5bb2016-05-23 22:00:39 +0200317 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000318 },
319
Akron47c086c2016-05-18 21:22:06 +0200320 /**
Akron5240b8c2016-05-20 09:17:41 +0200321 * Show a screen with a given offset
322 * in the viewport.
Akron47c086c2016-05-18 21:22:06 +0200323 */
324 screen : function (nr) {
325 if (this._offset === nr)
326 return;
Akron5a1f5bb2016-05-23 22:00:39 +0200327
328 // OPTIMIZE!!!
329
330 // TODO: This is just an optimization of screen
331 /*
332 if (this.position >= (this.limit() + this._offset) {
333 this._removeFirst();
334 this._offset++;
335 this._append(this._list[this.position]);
336 this._slider.offset(this._offset);
337 }
338 else if (this.position < this._offset) {
339
340 this._removeLast();
341 this._offset--;
342 this._prepend(this._list[this.position]);
343 // set the slider to the new offset
344 this._slider.offset(this._offset);
345 }
346 */
347
348
Akron5240b8c2016-05-20 09:17:41 +0200349 this.unshow();
350 this._offset = nr;
Akron47c086c2016-05-18 21:22:06 +0200351 this._showItems(nr);
352 },
353
Nils Diewald2fe12e12015-03-06 16:47:06 +0000354 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000355 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000356 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000357 element : function () {
358 return this._element;
359 },
360
Nils Diewald2fe12e12015-03-06 16:47:06 +0000361 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000362 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000363 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000364 itemClass : function () {
365 return this._itemClass;
366 },
367
368 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000369 * Get and set the numerical value
370 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000371 */
372 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000373 if (arguments.length === 1) {
Akron5240b8c2016-05-20 09:17:41 +0200374 if (this._limit !== limit) {
375 this._limit = limit;
376 this._slider.limit(limit);
377 };
Nils Diewald5975d702015-03-09 17:45:42 +0000378 return this;
379 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000380 return this._limit;
381 },
382
Nils Diewald7148c6f2015-05-04 15:07:53 +0000383
Nils Diewald86dad5b2015-01-28 15:09:07 +0000384 /**
385 * Upgrade this object to another object,
386 * while private data stays intact.
387 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000388 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000389 */
390 upgradeTo : function (props) {
391 for (var prop in props) {
392 this[prop] = props[prop];
393 };
394 return this;
395 },
396
Nils Diewald7148c6f2015-05-04 15:07:53 +0000397
Nils Diewald86dad5b2015-01-28 15:09:07 +0000398 /**
399 * Filter the list and make it visible
400 *
401 * @param {string} Prefix for filtering the list
402 */
Akron6ed13992016-05-23 18:06:05 +0200403 show : function (active) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000404
Akron5240b8c2016-05-20 09:17:41 +0200405 // show menu based on initial offset
406 this._unmark(); // Unmark everything that was marked before
407 this.unshow(); // Delete everything that is shown
Akron6ed13992016-05-23 18:06:05 +0200408
409 // Initialize the list
410 if (!this._initList()) {
411 // The prefix is not active
412 this._prefix.active(true);
413
414 // finally show the element
415 this._element.style.opacity = 1;
416
417 return true;
418 };
419
420 var offset = 0;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000421
422 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000423 // Todo: Or the last element chosen
Akron6ed13992016-05-23 18:06:05 +0200424 if (arguments.length === 1) {
425
426 // Normalize active value
427 if (active < 0)
428 active = 0;
429 else if (active > this.length())
430 active = this.length();
431
432 if (active > this._limit) {
433 offset = active;
434 if (offset > (this.length() - this._limit)) {
435 offset = this.length() - this._limit;
436 };
437 };
438
439 this.position = active;
440 this._active = active;
441 }
442
443 else if (this._firstActive) {
Akron47c086c2016-05-18 21:22:06 +0200444 this.position = 0;
445 this._active = 0;
446 }
Akroncb351d62016-05-19 23:10:33 +0200447
Akron47c086c2016-05-18 21:22:06 +0200448 else {
449 this.position = -1;
Akron6ed13992016-05-23 18:06:05 +0200450 };
451
452 this._offset = offset;
453 this._showItems(offset); // Show new item list
454
455 // Make chosen value active
456 if (this.position !== -1) {
457 this.shownItem(this.position).active(true);
458 };
Akron37513a62015-11-17 01:07:11 +0100459
Akron5240b8c2016-05-20 09:17:41 +0200460 // The prefix is not active
Nils Diewalde8518f82015-03-18 22:41:49 +0000461 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000462
Akron5240b8c2016-05-20 09:17:41 +0200463 // finally show the element
Nils Diewald2fe12e12015-03-06 16:47:06 +0000464 this._element.style.opacity = 1;
465
Akron5240b8c2016-05-20 09:17:41 +0200466 // Show the slider
Akron6ed13992016-05-23 18:06:05 +0200467 //this._slider.show();
Akron9905e2a2016-05-10 16:06:44 +0200468
Akron37513a62015-11-17 01:07:11 +0100469 // Iterate to the active item
470 if (this._active !== -1 && !this._prefix.isSet()) {
Akronf86eaea2016-05-13 18:02:27 +0200471 while (this._list[this.position] < this._active) {
Akron5240b8c2016-05-20 09:17:41 +0200472
473 // TODO. Improve this by moving using screen!
Akron37513a62015-11-17 01:07:11 +0100474 this.next();
475 };
476 };
477
Nils Diewald86dad5b2015-01-28 15:09:07 +0000478 // Add classes for rolling menus
479 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000480 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000481 },
482
Nils Diewald7148c6f2015-05-04 15:07:53 +0000483
484 /**
485 * Hide the menu and call the onHide callback.
486 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000487 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000488 this.active = false;
Akron5240b8c2016-05-20 09:17:41 +0200489 this._unmark();
490 this.unshow();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000491 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000492 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000493 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000494 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000495 },
496
Nils Diewald7148c6f2015-05-04 15:07:53 +0000497 /**
498 * Function released when the menu hides.
499 * This method is expected to be overridden.
500 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000501 onHide : function () {},
502
Nils Diewald7148c6f2015-05-04 15:07:53 +0000503
Nils Diewald86dad5b2015-01-28 15:09:07 +0000504 /**
505 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000506 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000507 */
Nils Diewald5975d702015-03-09 17:45:42 +0000508 prefix : function (pref) {
509 if (arguments.length === 1) {
510 this._prefix.value(pref);
511 return this;
512 };
513 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000514 },
515
Akronc7448732016-04-27 14:06:58 +0200516 /**
517 * Get the lengthField object.
518 */
519 lengthField : function () {
520 return this._lengthField;
521 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000522
Akron5240b8c2016-05-20 09:17:41 +0200523 /**
524 * Get the associated slider object.
525 */
526 slider : function () {
527 return this._slider;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000528 },
529
Akron5240b8c2016-05-20 09:17:41 +0200530
Nils Diewald86dad5b2015-01-28 15:09:07 +0000531 /**
532 * Delete all visible items from the menu element
533 */
Akron5240b8c2016-05-20 09:17:41 +0200534 unshow : function () {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000535 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000536
Nils Diewald2fe12e12015-03-06 16:47:06 +0000537 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000538 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200539 // Leave the prefix and lengthField
Akron9905e2a2016-05-10 16:06:44 +0200540 for (var i = children.length - 1; i >= 3; i--) {
Nils Diewald5975d702015-03-09 17:45:42 +0000541 this._element.removeChild(
542 children[i]
543 );
544 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000545 },
546
Nils Diewald2fe12e12015-03-06 16:47:06 +0000547 /**
548 * Get a specific item from the complete list
549 *
550 * @param {number} index of the list item
551 */
552 item : function (index) {
553 return this._items[index]
554 },
555
556
Nils Diewald86dad5b2015-01-28 15:09:07 +0000557 /**
558 * Get a specific item from the filtered list
559 *
560 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000561 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000562 */
563 liveItem : function (index) {
564 if (this._list === undefined)
565 if (!this._initList())
566 return;
567
568 return this._items[this._list[index]];
569 },
570
Nils Diewald86dad5b2015-01-28 15:09:07 +0000571
572 /**
Akron5240b8c2016-05-20 09:17:41 +0200573 * Get a specific item from the viewport list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000574 *
575 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000576 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000577 */
578 shownItem : function (index) {
579 if (index >= this.limit())
580 return;
581 return this.liveItem(this._offset + index);
582 },
583
584
Nils Diewald2fe12e12015-03-06 16:47:06 +0000585 /**
586 * Get the length of the full list
587 */
588 length : function () {
589 return this._items.length;
590 },
591
592
593 /**
Akron5240b8c2016-05-20 09:17:41 +0200594 * Length of the filtered item list.
595 */
596 liveLength : function () {
597 if (this._list === undefined)
598 this._initList();
599 return this._list.length;
600 },
601
602
603 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000604 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000605 */
606 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000607
Nils Diewald86dad5b2015-01-28 15:09:07 +0000608 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000609 var newItem;
610
Akron47c086c2016-05-18 21:22:06 +0200611 if (this.position !== -1) {
612 // Set new live item
613 if (!this._prefix.active()) {
614 var oldItem = this.liveItem(this.position);
615 oldItem.active(false);
616 };
Nils Diewalde8518f82015-03-18 22:41:49 +0000617 };
618
Akronf86eaea2016-05-13 18:02:27 +0200619 this.position++;
Akron47c086c2016-05-18 21:22:06 +0200620 this._active = this.position;
Nils Diewalde8518f82015-03-18 22:41:49 +0000621
Akronf86eaea2016-05-13 18:02:27 +0200622 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000623
Nils Diewald5975d702015-03-09 17:45:42 +0000624 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000625 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000626
627 // Activate prefix
628 var prefix = this._prefix;
629
630 // Mark prefix
631 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200632 this.position--;
Nils Diewald5975d702015-03-09 17:45:42 +0000633 prefix.active(true);
Akron47c086c2016-05-18 21:22:06 +0200634 this._active = -1;
Nils Diewald5975d702015-03-09 17:45:42 +0000635 return;
636 }
637 else {
638 this._offset = 0;
Akronf86eaea2016-05-13 18:02:27 +0200639 this.position = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000640 newItem = this.liveItem(0);
Akron47c086c2016-05-18 21:22:06 +0200641 this._active = 0;
Akron5240b8c2016-05-20 09:17:41 +0200642 this._unmark();
643 this.unshow();
Nils Diewald5975d702015-03-09 17:45:42 +0000644 this._showItems(0);
645 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000646 }
647
Akron5a1f5bb2016-05-23 22:00:39 +0200648 // The next element is after the viewport - roll down
Akronf86eaea2016-05-13 18:02:27 +0200649 else if (this.position >= (this.limit() + this._offset)) {
Akron5a1f5bb2016-05-23 22:00:39 +0200650
651 this.screen(this.position - this.limit() + 1);
652 }
653
654 // The next element is before the viewport - roll up
655 else if (this.position <= this._offset) {
656 this.screen(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000657 };
Nils Diewald5975d702015-03-09 17:45:42 +0000658
659 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000660 newItem.active(true);
661 },
662
Nils Diewalde8518f82015-03-18 22:41:49 +0000663 /*
Nils Diewald86dad5b2015-01-28 15:09:07 +0000664 * Make the previous item in the menu active
665 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000666 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000667
Nils Diewald2fe12e12015-03-06 16:47:06 +0000668 // No active element set
Akronf86eaea2016-05-13 18:02:27 +0200669 if (this.position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000670 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000671 // TODO: Choose last item
672 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000673
Nils Diewald5975d702015-03-09 17:45:42 +0000674 var newItem;
675
Nils Diewald86dad5b2015-01-28 15:09:07 +0000676 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000677 if (!this._prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200678 var oldItem = this.liveItem(this.position--);
Nils Diewald2d210752015-03-09 19:01:15 +0000679 oldItem.active(false);
680 };
681
Akronf86eaea2016-05-13 18:02:27 +0200682 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000683
684 // The previous element is undefined - roll to bottom
685 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000686
687 // Activate prefix
688 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000689 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000690
691 // Normalize offset
692 this._offset = this._offset < 0 ? 0 : this._offset;
693
Akronf86eaea2016-05-13 18:02:27 +0200694 this.position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000695
696 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200697 this.position++;
Nils Diewald5975d702015-03-09 17:45:42 +0000698 prefix.active(true);
699 return;
700 }
701 else {
Akronf86eaea2016-05-13 18:02:27 +0200702 newItem = this.liveItem(this.position);
Akron5240b8c2016-05-20 09:17:41 +0200703 this._unmark();
704 this.unshow();
Nils Diewald5975d702015-03-09 17:45:42 +0000705 this._showItems(this._offset);
706 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000707 }
708
Akron5a1f5bb2016-05-23 22:00:39 +0200709 // The previous element is before the view - roll up
Akronf86eaea2016-05-13 18:02:27 +0200710 else if (this.position < this._offset) {
Akron5a1f5bb2016-05-23 22:00:39 +0200711 this.screen(this.position);
712 }
713
714 // The previous element is after the view - roll down
715 else if (this.position >= (this.limit() + this._offset)) {
716 this.screen(this.position - this.limit() + 2);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000717 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000718
Nils Diewald5975d702015-03-09 17:45:42 +0000719 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000720 newItem.active(true);
721 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000722
723
Akron5240b8c2016-05-20 09:17:41 +0200724 // Unmark all items
725 _unmark : function () {
726 for (var i in this._list) {
727 var item = this._items[this._list[i]];
728 item.lowlight();
729 item.active(false);
730 };
731 },
732
733
734 // Reset chosen item and prefix
735 _reset : function () {
736 this._offset = 0;
737 this._pos = 0;
738 this._prefix.value('');
739 },
740
741
742 // Set boundary for viewport
743 _boundary : function (bool) {
744 this.item(this._list[0]).noMore(bool);
745 this.item(this._list[this._list.length - 1]).noMore(bool);
746 },
747
748
749 // Append Items that should be shown
750 _showItems : function (off) {
751
752 // Use list
753 var shown = 0;
754 var i;
755
756 for (i in this._list) {
757
758 // Don't show - it's before offset
759 shown++;
760 if (shown <= off)
761 continue;
762
763 var itemNr = this._list[i];
764 var item = this.item(itemNr);
765 this._append(itemNr);
766
767 // this._offset))
768 if (shown >= (this.limit() + off))
769 break;
770 };
771
772 // set the slider to the new offset
773 this._slider.offset(this._offset);
774 },
775
776
777 // Append item to the shown list based on index
778 _append : function (i) {
779 var item = this.item(i);
780
781 // Highlight based on prefix
782 if (this.prefix().length > 0)
783 item.highlight(this.prefix());
784
785 // Append element
786 this.element().appendChild(item.element());
787 },
788
789
790 // Prepend item to the shown list based on index
791 _prepend : function (i) {
792 var item = this.item(i);
793
794 // Highlight based on prefix
795 if (this.prefix().length > 0)
796 item.highlight(this.prefix());
797
798 var e = this.element();
799 // Append element after lengthField/prefix/slider
800 e.insertBefore(
801 item.element(),
802 e.children[3]
803 );
Nils Diewald5975d702015-03-09 17:45:42 +0000804 },
805
806
Nils Diewald2fe12e12015-03-06 16:47:06 +0000807 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000808 _removeFirst : function () {
809 this.item(this._list[this._offset]).lowlight();
Akron9905e2a2016-05-10 16:06:44 +0200810 // leave lengthField/prefix/slider
811 this._element.removeChild(this._element.children[3]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000812 },
813
Nils Diewald2fe12e12015-03-06 16:47:06 +0000814
815 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000816 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000817 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000818 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000819 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000820 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000821});
Akron5240b8c2016-05-20 09:17:41 +0200822
823
824 /*
825 * Page down to the first item on the next page
826 */
827 /*
828 nextPage : function () {
829
830 // Prefix is active
831 if (this._prefix.active()) {
832 this._prefix.active(false);
833 }
834
835 // Last item is chosen
836 else if (this.position >= this.limit() + this._offset) {
837
838 this.position = this.limit() + this._offset - 1;
839 newItem = this.liveItem(this.position);
840 var oldItem = this.liveItem(this.position--);
841 oldItem.active(false);
842 }
843
844 // Last item of page is chosen
845 else if (0) {
846
847 // Jump to last item
848 else {
849 var oldItem = this.liveItem(this.position);
850 oldItem.active(false);
851
852 this.position = this.limit() + this._offset - 1;
853 newItem = this.liveItem(this.position);
854 };
855
856 newItem.active(true);
857 },
858 */