blob: e8028ea89936f45302ccdb248826a34fe2d0f547 [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!
Akron3c2730f2016-05-24 15:08:29 +02008 * TODO: Show the slider briefly on move (whenever screen is called).
Akron9c4d1ae2016-05-25 21:43:22 +02009 * TODO: Ignore alt+ and strg+ key strokes.
Akron0b92f692016-05-25 22:37:13 +020010 * TODO: Should scroll to a chosen value after prefixing, if the chosen value is live
Akron308db382016-05-30 22:34:07 +020011 * Add a "title" to a menu that is not scrollable.
Nils Diewald2488d052015-04-09 21:46:02 +000012 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000013define([
14 'menu/item',
15 'menu/prefix',
Akronc7448732016-04-27 14:06:58 +020016 'menu/lengthField',
Akron9905e2a2016-05-10 16:06:44 +020017 'menu/slider',
Nils Diewald0e6992a2015-04-14 20:13:52 +000018 'util'
19], function (defaultItemClass,
Akronc7448732016-04-27 14:06:58 +020020 defaultPrefixClass,
Akron9905e2a2016-05-10 16:06:44 +020021 defaultLengthFieldClass,
22 sliderClass) {
Nils Diewaldfda29d92015-01-22 17:28:01 +000023
Nils Diewald0e6992a2015-04-14 20:13:52 +000024 // Default maximum number of menu items
25 var menuLimit = 8;
26
27 function _codeFromEvent (e) {
28 if (e.charCode && (e.keyCode == 0))
29 return e.charCode
30 return e.keyCode;
Nils Diewald59c02fc2015-03-07 01:29:09 +000031 };
32
Nils Diewald86dad5b2015-01-28 15:09:07 +000033
34 /**
35 * List of items for drop down menu (complete).
36 * Only a sublist of the menu is filtered (live).
37 * Only a sublist of the filtered menu is visible (shown).
38 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000039 return {
Nils Diewald86dad5b2015-01-28 15:09:07 +000040 /**
41 * Create new Menu based on the action prefix
42 * and a list of menu items.
43 *
44 * @this {Menu}
45 * @constructor
46 * @param {string} Context prefix
47 * @param {Array.<Array.<string>>} List of menu items
48 */
49 create : function (params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000050 return Object.create(this)._init(params);
Nils Diewald86dad5b2015-01-28 15:09:07 +000051 },
52
Akron5240b8c2016-05-20 09:17:41 +020053 // Initialize list
54 _init : function (itemClass, prefixClass, lengthFieldClass, params) {
Akrona92fd8d2016-05-24 21:13:41 +020055
Akron5240b8c2016-05-20 09:17:41 +020056 this._itemClass = itemClass || defaultItemClass;
57
58 // Add prefix object
59 if (prefixClass !== undefined) {
60 this._prefix = prefixClass.create();
61 }
62 else {
63 this._prefix = defaultPrefixClass.create();
64 };
65 this._prefix._menu = this;
66
67 // Add lengthField object
68 if (lengthFieldClass !== undefined) {
69 this._lengthField = lengthFieldClass.create();
70 }
71 else {
72 this._lengthField = defaultLengthFieldClass.create();
73 };
74 this._lengthField._menu = this;
75
76 // Initialize slider
77 this._slider = sliderClass.create(this);
78
79 // Create the element
Akron9c4d1ae2016-05-25 21:43:22 +020080 var el = document.createElement("ul");
81 with (el) {
Akron9c4d1ae2016-05-25 21:43:22 +020082 style.outline = 0;
83 setAttribute('tabindex', 0);
84 classList.add('menu', 'roll');
85 appendChild(this._prefix.element());
86 appendChild(this._lengthField.element());
87 appendChild(this._slider.element());
88 };
Akron5240b8c2016-05-20 09:17:41 +020089
90 // This has to be cleaned up later on
Akron9c4d1ae2016-05-25 21:43:22 +020091 el["menu"] = this;
Akron5240b8c2016-05-20 09:17:41 +020092
93 // Arrow keys
Akron9c4d1ae2016-05-25 21:43:22 +020094 el.addEventListener(
Akron5240b8c2016-05-20 09:17:41 +020095 'keydown',
Akrona92fd8d2016-05-24 21:13:41 +020096 this._keydown.bind(this),
Akron5240b8c2016-05-20 09:17:41 +020097 false
98 );
99
100 // Strings
Akron9c4d1ae2016-05-25 21:43:22 +0200101 el.addEventListener(
Akron5240b8c2016-05-20 09:17:41 +0200102 'keypress',
Akrona92fd8d2016-05-24 21:13:41 +0200103 this._keypress.bind(this),
Akron5240b8c2016-05-20 09:17:41 +0200104 false
105 );
106
107 // Mousewheel
Akron9c4d1ae2016-05-25 21:43:22 +0200108 el.addEventListener(
Akron5240b8c2016-05-20 09:17:41 +0200109 'wheel',
Akrona92fd8d2016-05-24 21:13:41 +0200110 this._mousewheel.bind(this),
Akron5240b8c2016-05-20 09:17:41 +0200111 false
112 );
Akron9c4d1ae2016-05-25 21:43:22 +0200113 this._element = el;
Akron5240b8c2016-05-20 09:17:41 +0200114
Akron5240b8c2016-05-20 09:17:41 +0200115 this._items = new Array();
Akron5240b8c2016-05-20 09:17:41 +0200116
Akron9c4d1ae2016-05-25 21:43:22 +0200117 var i = 0;
Akron5240b8c2016-05-20 09:17:41 +0200118 // Initialize item list based on parameters
119 for (i in params) {
120 var obj = this._itemClass.create(params[i]);
121
122 // This may become circular
123 obj["_menu"] = this;
124 this._lengthField.add(params[i]);
125 this._items.push(obj);
126 };
127
Akron9c4d1ae2016-05-25 21:43:22 +0200128 this._limit = menuLimit;
129 this._slider.length(this.liveLength())
130 .limit(this._limit)
131 .reInit();
Akron5240b8c2016-05-20 09:17:41 +0200132
Akron5240b8c2016-05-20 09:17:41 +0200133 this._firstActive = false; // Show the first item active always?
Akron9c4d1ae2016-05-25 21:43:22 +0200134 this.offset = 0;
135 this.position = 0;
Akron5240b8c2016-05-20 09:17:41 +0200136 return this;
137 },
138
139 // Initialize the item list
140 _initList : function () {
141
142 // Create a new list
143 if (this._list === undefined) {
144 this._list = [];
145 }
146 else if (this._list.length !== 0) {
147 this._boundary(false);
148 this._list.length = 0;
149 };
150
151 // Offset is initially zero
Akron9c4d1ae2016-05-25 21:43:22 +0200152 this.offset = 0;
Akron5240b8c2016-05-20 09:17:41 +0200153
154 // There is no prefix set
155 if (this.prefix().length <= 0) {
156
157 // add all items to the list and lowlight
Akron97752a72016-05-25 14:43:07 +0200158 var i = 0;
159 for (; i < this._items.length; i++) {
Akron5240b8c2016-05-20 09:17:41 +0200160 this._list.push(i);
161 this._items[i].lowlight();
162 };
163
Akron9c4d1ae2016-05-25 21:43:22 +0200164 this._slider.length(i).reInit();;
Akron97752a72016-05-25 14:43:07 +0200165
Akron5240b8c2016-05-20 09:17:41 +0200166 return true;
167 };
168
169 /*
170 * There is a prefix set, so filter the list!
171 */
172 var pos;
Akron6ffad5d2016-05-24 17:16:58 +0200173 var prefix = " " + this.prefix().toLowerCase();
Akron5240b8c2016-05-20 09:17:41 +0200174
175 // Iterate over all items and choose preferred matching items
176 // i.e. the matching happens at the word start
177 for (pos = 0; pos < this._items.length; pos++) {
Akron6ffad5d2016-05-24 17:16:58 +0200178 if ((this.item(pos).lcField().indexOf(prefix)) >= 0)
Akron5240b8c2016-05-20 09:17:41 +0200179 this._list.push(pos);
180 };
181
182 // The list is empty - so lower your expectations
183 // Iterate over all items and choose matching items
184 // i.e. the matching happens anywhere in the word
Akron6ffad5d2016-05-24 17:16:58 +0200185 prefix = prefix.substring(1);
Akron5240b8c2016-05-20 09:17:41 +0200186 if (this._list.length == 0) {
187 for (pos = 0; pos < this._items.length; pos++) {
Akron6ffad5d2016-05-24 17:16:58 +0200188 if ((this.item(pos).lcField().indexOf(prefix)) >= 0)
Akron5240b8c2016-05-20 09:17:41 +0200189 this._list.push(pos);
190 };
191 };
192
Akron9c4d1ae2016-05-25 21:43:22 +0200193 this._slider.length(this._list.length).reInit();
Akron6ed13992016-05-23 18:06:05 +0200194
Akron5240b8c2016-05-20 09:17:41 +0200195 // Filter was successful - yeah!
196 return this._list.length > 0 ? true : false;
197 },
198
199
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000200 /**
201 * Destroy this menu
202 * (in case you don't trust the
203 * mark and sweep GC)!
204 */
205 destroy : function () {
Akron47c086c2016-05-18 21:22:06 +0200206
Akron5240b8c2016-05-20 09:17:41 +0200207 // Remove circular reference to "this" in menu
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000208 if (this._element != undefined)
209 delete this._element["menu"];
210
Akron5240b8c2016-05-20 09:17:41 +0200211 // Remove circular reference to "this" in items
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000212 for (var i = 0; i < this._items.length; i++) {
213 delete this._items[i]["_menu"];
214 };
Akron5240b8c2016-05-20 09:17:41 +0200215
216 // Remove circular reference to "this" in prefix
Nils Diewald5c5a7472015-04-02 22:13:38 +0000217 delete this._prefix['_menu'];
Akron5240b8c2016-05-20 09:17:41 +0200218 delete this._lengthField['_menu'];
219 delete this._slider['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000220 },
221
Nils Diewald7148c6f2015-05-04 15:07:53 +0000222
223 /**
224 * Focus on this menu.
225 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000226 focus : function () {
227 this._element.focus();
228 },
229
Nils Diewald7148c6f2015-05-04 15:07:53 +0000230
Nils Diewald59c02fc2015-03-07 01:29:09 +0000231 // mouse wheel treatment
232 _mousewheel : function (e) {
233 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000234
235 delta = e.deltaY / 120;
236 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000237 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +0000238 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000239 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000240 e.halt();
241 },
242
Nils Diewald7148c6f2015-05-04 15:07:53 +0000243
Nils Diewald59c02fc2015-03-07 01:29:09 +0000244 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +0000245 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000246 var code = _codeFromEvent(e);
247
Nils Diewald59c02fc2015-03-07 01:29:09 +0000248 switch (code) {
249 case 27: // 'Esc'
250 e.halt();
251 this.hide();
252 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000253
Nils Diewald59c02fc2015-03-07 01:29:09 +0000254 case 38: // 'Up'
255 e.halt();
256 this.prev();
257 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000258 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +0000259 e.halt();
Akron3c2730f2016-05-24 15:08:29 +0200260 this.pageUp();
Nils Diewald5975d702015-03-09 17:45:42 +0000261 break;
262 case 40: // 'Down'
263 e.halt();
264 this.next();
265 break;
266 case 34: // 'Page down'
267 e.halt();
Akron3c2730f2016-05-24 15:08:29 +0200268 this.pageDown();
Nils Diewald5975d702015-03-09 17:45:42 +0000269 break;
270 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000271 if (this._prefix.active())
272 break;
273
Akronf86eaea2016-05-13 18:02:27 +0200274 var item = this.liveItem(this.position);
Akron5ef4fa02015-06-02 16:25:14 +0200275
Nils Diewald5975d702015-03-09 17:45:42 +0000276 if (item["further"] !== undefined) {
277 item["further"].bind(item).apply();
Nils Diewald5975d702015-03-09 17:45:42 +0000278 };
Akron5ef4fa02015-06-02 16:25:14 +0200279
280 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000281 break;
282 case 13: // 'Enter'
283
284 // Click on prefix
285 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000286 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000287
288 // Click on item
289 else
Akronf86eaea2016-05-13 18:02:27 +0200290 this.liveItem(this.position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000291 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000292 break;
293 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000294 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000295 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000296 e.halt();
297 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000298 };
299 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000300
Nils Diewald47f366b2015-04-15 20:06:35 +0000301 // Add characters to prefix
302 _keypress : function (e) {
Akron9c2f9382016-05-25 16:36:04 +0200303 if (e.charCode !== 0) {
304 e.halt();
305 var c = String.fromCharCode(_codeFromEvent(e));
Nils Diewald5975d702015-03-09 17:45:42 +0000306
Akron9c2f9382016-05-25 16:36:04 +0200307 // Add prefix
308 this._prefix.add(c);
309 this.show();
310 };
Nils Diewald59c02fc2015-03-07 01:29:09 +0000311 },
312
Akron47c086c2016-05-18 21:22:06 +0200313 /**
Akron5240b8c2016-05-20 09:17:41 +0200314 * Show a screen with a given offset
315 * in the viewport.
Akron47c086c2016-05-18 21:22:06 +0200316 */
317 screen : function (nr) {
Akron3c2730f2016-05-24 15:08:29 +0200318 if (nr < 0) {
319 nr = 0
320 }
Akron6ac58442016-05-24 16:52:29 +0200321 else if (nr > (this.liveLength() - this.limit())) {
322 nr = (this.liveLength() - this.limit());
Akron3c2730f2016-05-24 15:08:29 +0200323 };
324
Akron9c4d1ae2016-05-25 21:43:22 +0200325 if (this.offset === nr)
Akron47c086c2016-05-18 21:22:06 +0200326 return;
Akron5a1f5bb2016-05-23 22:00:39 +0200327
Akron47c086c2016-05-18 21:22:06 +0200328 this._showItems(nr);
329 },
330
Nils Diewald2fe12e12015-03-06 16:47:06 +0000331 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000332 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000333 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000334 element : function () {
335 return this._element;
336 },
337
Nils Diewald2fe12e12015-03-06 16:47:06 +0000338 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000339 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000340 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000341 itemClass : function () {
342 return this._itemClass;
343 },
344
345 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000346 * Get and set the numerical value
347 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000348 */
349 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000350 if (arguments.length === 1) {
Akron5240b8c2016-05-20 09:17:41 +0200351 if (this._limit !== limit) {
352 this._limit = limit;
Akron9c4d1ae2016-05-25 21:43:22 +0200353 this._slider.limit(limit).reInit();
Akron5240b8c2016-05-20 09:17:41 +0200354 };
Nils Diewald5975d702015-03-09 17:45:42 +0000355 return this;
356 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000357 return this._limit;
358 },
359
Nils Diewald7148c6f2015-05-04 15:07:53 +0000360
Nils Diewald86dad5b2015-01-28 15:09:07 +0000361 /**
362 * Upgrade this object to another object,
363 * while private data stays intact.
364 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000365 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000366 */
367 upgradeTo : function (props) {
368 for (var prop in props) {
369 this[prop] = props[prop];
370 };
371 return this;
372 },
373
Nils Diewald7148c6f2015-05-04 15:07:53 +0000374
Nils Diewald86dad5b2015-01-28 15:09:07 +0000375 /**
Akron97752a72016-05-25 14:43:07 +0200376 * Filter the list and make it visible.
377 * This is always called once the prefix changes.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000378 *
379 * @param {string} Prefix for filtering the list
380 */
Akron6ed13992016-05-23 18:06:05 +0200381 show : function (active) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000382
Akron5240b8c2016-05-20 09:17:41 +0200383 // show menu based on initial offset
Akron6ac58442016-05-24 16:52:29 +0200384 this._unmark(); // Unmark everything that was marked before
Akrona92fd8d2016-05-24 21:13:41 +0200385 this.removeItems();
Akron6ed13992016-05-23 18:06:05 +0200386
387 // Initialize the list
388 if (!this._initList()) {
Akron6ac58442016-05-24 16:52:29 +0200389
Akron6ed13992016-05-23 18:06:05 +0200390 // The prefix is not active
391 this._prefix.active(true);
392
393 // finally show the element
Akron6bb71582016-06-10 20:41:08 +0200394 this._element.classList.add('visible');
Akron6ed13992016-05-23 18:06:05 +0200395
396 return true;
397 };
398
399 var offset = 0;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000400
Akron9c2f9382016-05-25 16:36:04 +0200401 // Set a chosen value to active and move the viewport
Akron6ed13992016-05-23 18:06:05 +0200402 if (arguments.length === 1) {
403
404 // Normalize active value
Akron3c2730f2016-05-24 15:08:29 +0200405 if (active < 0) {
Akron6ed13992016-05-23 18:06:05 +0200406 active = 0;
Akron3c2730f2016-05-24 15:08:29 +0200407 }
Akron6ac58442016-05-24 16:52:29 +0200408 else if (active > this.liveLength()) {
409 active = this.liveLength() - 1;
Akron3c2730f2016-05-24 15:08:29 +0200410 };
Akron6ed13992016-05-23 18:06:05 +0200411
Akron0b92f692016-05-25 22:37:13 +0200412 // Item is outside the first viewport
413 if (active >= this._limit) {
Akron6ed13992016-05-23 18:06:05 +0200414 offset = active;
Akron6ac58442016-05-24 16:52:29 +0200415 if (offset > (this.liveLength() - this._limit)) {
416 offset = this.liveLength() - this._limit;
Akron6ed13992016-05-23 18:06:05 +0200417 };
418 };
419
420 this.position = active;
Akron6ed13992016-05-23 18:06:05 +0200421 }
422
Akron9c2f9382016-05-25 16:36:04 +0200423 // Choose the first item
Akron6ed13992016-05-23 18:06:05 +0200424 else if (this._firstActive) {
Akron47c086c2016-05-18 21:22:06 +0200425 this.position = 0;
Akron47c086c2016-05-18 21:22:06 +0200426 }
Akroncb351d62016-05-19 23:10:33 +0200427
Akron9c2f9382016-05-25 16:36:04 +0200428 // Choose no item
Akron47c086c2016-05-18 21:22:06 +0200429 else {
430 this.position = -1;
Akron6ed13992016-05-23 18:06:05 +0200431 };
432
Akron9c4d1ae2016-05-25 21:43:22 +0200433 this.offset = offset;
Akron6ed13992016-05-23 18:06:05 +0200434 this._showItems(offset); // Show new item list
435
436 // Make chosen value active
437 if (this.position !== -1) {
Akron3c2730f2016-05-24 15:08:29 +0200438 this.liveItem(this.position).active(true);
Akron6ed13992016-05-23 18:06:05 +0200439 };
Akron37513a62015-11-17 01:07:11 +0100440
Akron5240b8c2016-05-20 09:17:41 +0200441 // The prefix is not active
Nils Diewalde8518f82015-03-18 22:41:49 +0000442 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000443
Akron5240b8c2016-05-20 09:17:41 +0200444 // finally show the element
Akron6bb71582016-06-10 20:41:08 +0200445 this._element.classList.add('visible');
Nils Diewald2fe12e12015-03-06 16:47:06 +0000446
Nils Diewald86dad5b2015-01-28 15:09:07 +0000447 // Add classes for rolling menus
448 this._boundary(true);
Akron6bb71582016-06-10 20:41:08 +0200449
Nils Diewald59c02fc2015-03-07 01:29:09 +0000450 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000451 },
452
Nils Diewald7148c6f2015-05-04 15:07:53 +0000453
454 /**
455 * Hide the menu and call the onHide callback.
456 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000457 hide : function () {
Akrona92fd8d2016-05-24 21:13:41 +0200458 this.removeItems();
Nils Diewald7148c6f2015-05-04 15:07:53 +0000459 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000460 this.onHide();
Akron6bb71582016-06-10 20:41:08 +0200461 this._element.classList.remove('visible');
462
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000463 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000464 },
465
Nils Diewald7148c6f2015-05-04 15:07:53 +0000466 /**
467 * Function released when the menu hides.
468 * This method is expected to be overridden.
469 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000470 onHide : function () {},
471
Nils Diewald7148c6f2015-05-04 15:07:53 +0000472
Nils Diewald86dad5b2015-01-28 15:09:07 +0000473 /**
474 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000475 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000476 */
Nils Diewald5975d702015-03-09 17:45:42 +0000477 prefix : function (pref) {
478 if (arguments.length === 1) {
479 this._prefix.value(pref);
480 return this;
481 };
482 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000483 },
484
Akronc7448732016-04-27 14:06:58 +0200485 /**
486 * Get the lengthField object.
487 */
488 lengthField : function () {
489 return this._lengthField;
490 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000491
Akron5240b8c2016-05-20 09:17:41 +0200492 /**
493 * Get the associated slider object.
494 */
495 slider : function () {
496 return this._slider;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000497 },
498
Akron5240b8c2016-05-20 09:17:41 +0200499
Nils Diewald86dad5b2015-01-28 15:09:07 +0000500 /**
501 * Delete all visible items from the menu element
502 */
Akrona92fd8d2016-05-24 21:13:41 +0200503 removeItems : function () {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000504 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000505
Nils Diewald2fe12e12015-03-06 16:47:06 +0000506 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000507 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200508 // Leave the prefix and lengthField
Akron9905e2a2016-05-10 16:06:44 +0200509 for (var i = children.length - 1; i >= 3; i--) {
Nils Diewald5975d702015-03-09 17:45:42 +0000510 this._element.removeChild(
511 children[i]
512 );
513 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000514 },
515
Nils Diewald2fe12e12015-03-06 16:47:06 +0000516 /**
517 * Get a specific item from the complete list
518 *
519 * @param {number} index of the list item
520 */
521 item : function (index) {
522 return this._items[index]
523 },
524
525
Nils Diewald86dad5b2015-01-28 15:09:07 +0000526 /**
527 * Get a specific item from the filtered list
528 *
529 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000530 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000531 */
532 liveItem : function (index) {
533 if (this._list === undefined)
534 if (!this._initList())
535 return;
536
537 return this._items[this._list[index]];
538 },
539
Nils Diewald86dad5b2015-01-28 15:09:07 +0000540
541 /**
Akron5240b8c2016-05-20 09:17:41 +0200542 * Get a specific item from the viewport list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000543 *
544 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000545 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000546 */
547 shownItem : function (index) {
548 if (index >= this.limit())
549 return;
Akron9c4d1ae2016-05-25 21:43:22 +0200550 return this.liveItem(this.offset + index);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000551 },
552
553
Nils Diewald2fe12e12015-03-06 16:47:06 +0000554 /**
Akron9c4d1ae2016-05-25 21:43:22 +0200555 * Get the length of the full item list
Nils Diewald2fe12e12015-03-06 16:47:06 +0000556 */
557 length : function () {
558 return this._items.length;
559 },
560
561
562 /**
Akron5240b8c2016-05-20 09:17:41 +0200563 * Length of the filtered item list.
564 */
565 liveLength : function () {
566 if (this._list === undefined)
567 this._initList();
568 return this._list.length;
569 },
570
571
572 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000573 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000574 */
575 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000576
Akronb38afb22016-05-25 19:30:01 +0200577 // No list
578 if (this.liveLength() === 0)
579 return;
580
Akron9c4d1ae2016-05-25 21:43:22 +0200581 // Deactivate old item
582 if (this.position !== -1 && !this._prefix.active()) {
583 this.liveItem(this.position).active(false);
584 };
Nils Diewalde8518f82015-03-18 22:41:49 +0000585
Akron9c4d1ae2016-05-25 21:43:22 +0200586 // Get new active item
587 this.position++;
588 var newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000589
Nils Diewald5975d702015-03-09 17:45:42 +0000590 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000591 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000592
593 // Activate prefix
594 var prefix = this._prefix;
595
Akron9c4d1ae2016-05-25 21:43:22 +0200596 // Prefix is set and not active - choose!
Nils Diewald5975d702015-03-09 17:45:42 +0000597 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200598 this.position--;
Nils Diewald5975d702015-03-09 17:45:42 +0000599 prefix.active(true);
600 return;
601 }
Akron9c4d1ae2016-05-25 21:43:22 +0200602
603 // Choose first item
Nils Diewald5975d702015-03-09 17:45:42 +0000604 else {
Nils Diewald5975d702015-03-09 17:45:42 +0000605 newItem = this.liveItem(0);
Akron9c4d1ae2016-05-25 21:43:22 +0200606 // choose first item
607 this.position = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000608 this._showItems(0);
609 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000610 }
611
Akron5a1f5bb2016-05-23 22:00:39 +0200612 // The next element is after the viewport - roll down
Akron9c4d1ae2016-05-25 21:43:22 +0200613 else if (this.position >= (this.limit() + this.offset)) {
Akron5a1f5bb2016-05-23 22:00:39 +0200614 this.screen(this.position - this.limit() + 1);
615 }
616
617 // The next element is before the viewport - roll up
Akron9c4d1ae2016-05-25 21:43:22 +0200618 else if (this.position <= this.offset) {
Akron5a1f5bb2016-05-23 22:00:39 +0200619 this.screen(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000620 };
Nils Diewald5975d702015-03-09 17:45:42 +0000621
622 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000623 newItem.active(true);
624 },
625
Nils Diewalde8518f82015-03-18 22:41:49 +0000626 /*
Nils Diewald86dad5b2015-01-28 15:09:07 +0000627 * Make the previous item in the menu active
628 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000629 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000630
Akronb38afb22016-05-25 19:30:01 +0200631 // No list
632 if (this.liveLength() === 0)
633 return;
634
Akron9c4d1ae2016-05-25 21:43:22 +0200635 // Deactivate old item
Nils Diewald2d210752015-03-09 19:01:15 +0000636 if (!this._prefix.active()) {
Akron9c4d1ae2016-05-25 21:43:22 +0200637
638 // No active element set
639 if (this.position === -1) {
640 this.position = this.liveLength();
641 }
642
643 // No active element set
644 else {
645 this.liveItem(this.position--).active(false);
646 };
Nils Diewald2d210752015-03-09 19:01:15 +0000647 };
648
Akron9c4d1ae2016-05-25 21:43:22 +0200649 // Get new active item
650 var newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000651
652 // The previous element is undefined - roll to bottom
653 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000654
655 // Activate prefix
656 var prefix = this._prefix;
Akrona92fd8d2016-05-24 21:13:41 +0200657 var offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000658
659 // Normalize offset
Akrona92fd8d2016-05-24 21:13:41 +0200660 offset = offset < 0 ? 0 : offset;
Nils Diewald2d210752015-03-09 19:01:15 +0000661
Akron9c4d1ae2016-05-25 21:43:22 +0200662 // Choose the last item
Akronf86eaea2016-05-13 18:02:27 +0200663 this.position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000664
Akron9c4d1ae2016-05-25 21:43:22 +0200665 // Prefix is set and not active - choose!
Nils Diewald5975d702015-03-09 17:45:42 +0000666 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200667 this.position++;
Nils Diewald5975d702015-03-09 17:45:42 +0000668 prefix.active(true);
Akron9c4d1ae2016-05-25 21:43:22 +0200669 this.offset = offset;
Nils Diewald5975d702015-03-09 17:45:42 +0000670 return;
671 }
Akron9c4d1ae2016-05-25 21:43:22 +0200672
673 // Choose last item
Nils Diewald5975d702015-03-09 17:45:42 +0000674 else {
Akronf86eaea2016-05-13 18:02:27 +0200675 newItem = this.liveItem(this.position);
Akrona92fd8d2016-05-24 21:13:41 +0200676 this._showItems(offset);
Nils Diewald5975d702015-03-09 17:45:42 +0000677 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000678 }
679
Akron5a1f5bb2016-05-23 22:00:39 +0200680 // The previous element is before the view - roll up
Akron9c4d1ae2016-05-25 21:43:22 +0200681 else if (this.position < this.offset) {
Akron5a1f5bb2016-05-23 22:00:39 +0200682 this.screen(this.position);
683 }
684
685 // The previous element is after the view - roll down
Akron9c4d1ae2016-05-25 21:43:22 +0200686 else if (this.position >= (this.limit() + this.offset)) {
Akron5a1f5bb2016-05-23 22:00:39 +0200687 this.screen(this.position - this.limit() + 2);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000688 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000689
Nils Diewald5975d702015-03-09 17:45:42 +0000690 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000691 newItem.active(true);
692 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000693
Akron3c2730f2016-05-24 15:08:29 +0200694 /**
695 * Move the page up by limit!
696 */
697 pageUp : function () {
Akron9c4d1ae2016-05-25 21:43:22 +0200698 this.screen(this.offset - this.limit());
Akron3c2730f2016-05-24 15:08:29 +0200699 },
700
701
702 /**
703 * Move the page down by limit!
704 */
705 pageDown : function () {
Akron9c4d1ae2016-05-25 21:43:22 +0200706 this.screen(this.offset + this.limit());
Akron3c2730f2016-05-24 15:08:29 +0200707 },
708
Nils Diewald86dad5b2015-01-28 15:09:07 +0000709
Akron5240b8c2016-05-20 09:17:41 +0200710 // Unmark all items
711 _unmark : function () {
712 for (var i in this._list) {
713 var item = this._items[this._list[i]];
714 item.lowlight();
715 item.active(false);
716 };
717 },
718
Akron5240b8c2016-05-20 09:17:41 +0200719 // Set boundary for viewport
720 _boundary : function (bool) {
721 this.item(this._list[0]).noMore(bool);
722 this.item(this._list[this._list.length - 1]).noMore(bool);
723 },
724
725
726 // Append Items that should be shown
727 _showItems : function (off) {
728
Akrona92fd8d2016-05-24 21:13:41 +0200729 // optimization: scroll down one step
Akron9c4d1ae2016-05-25 21:43:22 +0200730 if (this.offset === (off - 1)) {
731 this.offset = off;
732
733 // Remove the HTML node from the first item
734 // leave lengthField/prefix/slider
735 this._element.removeChild(this._element.children[3]);
736 var pos = this.offset + this.limit() - 1;
Akrona92fd8d2016-05-24 21:13:41 +0200737 this._append(this._list[pos]);
738 }
Akron5240b8c2016-05-20 09:17:41 +0200739
Akrona92fd8d2016-05-24 21:13:41 +0200740 // optimization: scroll up one step
Akron9c4d1ae2016-05-25 21:43:22 +0200741 else if (this.offset === (off + 1)) {
742 this.offset = off;
743
744 // Remove the HTML node from the last item
745 this._element.removeChild(this._element.lastChild);
746
747 this._prepend(this._list[this.offset]);
Akrona92fd8d2016-05-24 21:13:41 +0200748 }
749 else {
Akron9c4d1ae2016-05-25 21:43:22 +0200750 this.offset = off;
Akron5240b8c2016-05-20 09:17:41 +0200751
Akrona92fd8d2016-05-24 21:13:41 +0200752 // Remove all items
753 this.removeItems();
Akron5240b8c2016-05-20 09:17:41 +0200754
Akrona92fd8d2016-05-24 21:13:41 +0200755 // Use list
756 var shown = 0;
757 var i;
Akron5240b8c2016-05-20 09:17:41 +0200758
Akrona92fd8d2016-05-24 21:13:41 +0200759 for (i in this._list) {
760
761 // Don't show - it's before offset
762 shown++;
763 if (shown <= off)
764 continue;
765
766 var itemNr = this._list[i];
767 var item = this.item(itemNr);
768 this._append(itemNr);
769
770 if (shown >= (this.limit() + off))
771 break;
772 };
Akron5240b8c2016-05-20 09:17:41 +0200773 };
774
775 // set the slider to the new offset
Akron9c4d1ae2016-05-25 21:43:22 +0200776 this._slider.offset(this.offset);
Akron5240b8c2016-05-20 09:17:41 +0200777 },
778
779
780 // Append item to the shown list based on index
781 _append : function (i) {
782 var item = this.item(i);
783
784 // Highlight based on prefix
Akrona92fd8d2016-05-24 21:13:41 +0200785 if (this.prefix().length > 0) {
Akron6ffad5d2016-05-24 17:16:58 +0200786 item.highlight(this.prefix().toLowerCase());
Akrona92fd8d2016-05-24 21:13:41 +0200787 };
788
Akron5240b8c2016-05-20 09:17:41 +0200789
790 // Append element
791 this.element().appendChild(item.element());
792 },
793
794
795 // Prepend item to the shown list based on index
796 _prepend : function (i) {
797 var item = this.item(i);
798
799 // Highlight based on prefix
Akrona92fd8d2016-05-24 21:13:41 +0200800 if (this.prefix().length > 0) {
Akron6ffad5d2016-05-24 17:16:58 +0200801 item.highlight(this.prefix().toLowerCase());
Akrona92fd8d2016-05-24 21:13:41 +0200802 };
Akron5240b8c2016-05-20 09:17:41 +0200803
804 var e = this.element();
Akron9c4d1ae2016-05-25 21:43:22 +0200805
Akron5240b8c2016-05-20 09:17:41 +0200806 // Append element after lengthField/prefix/slider
807 e.insertBefore(
808 item.element(),
809 e.children[3]
810 );
Nils Diewald2fe12e12015-03-06 16:47:06 +0000811 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000812 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000813});