blob: d7ec89feeb484a98a282f5098eb4dce96b992806 [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/*
Akron3c2730f2016-05-24 15:08:29 +02007 * TODO: Show the slider briefly on move (whenever screen is called).
Akron9c4d1ae2016-05-25 21:43:22 +02008 * TODO: Ignore alt+ and strg+ key strokes.
Akron0b92f692016-05-25 22:37:13 +02009 * TODO: Should scroll to a chosen value after prefixing, if the chosen value is live
Akron201b72a2016-06-03 01:46:19 +020010 * TODO: Add a "title" to a menu that is not scrollable.
11 * TODO: Make the menu responsive by showing less items on smaller screens
12 * or anytime items would be outside the screen.
Akron02360e42016-06-07 13:41:12 +020013 * TODO: Add a .match() method to items for scrolling and probably for prefixing.
14 * TODO: Add static header (for title, sortation fields, but also for menu points like "fragments" and "history".
Akrone91da782017-12-15 17:17:50 +010015 * TODO: Support space separated list of prefixes so "co no" will highlight "common noun"
Nils Diewald2488d052015-04-09 21:46:02 +000016 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000017define([
18 'menu/item',
19 'menu/prefix',
Akronc7448732016-04-27 14:06:58 +020020 'menu/lengthField',
Akron9905e2a2016-05-10 16:06:44 +020021 'menu/slider',
Nils Diewald0e6992a2015-04-14 20:13:52 +000022 'util'
23], function (defaultItemClass,
Akrone4961b12017-05-10 21:04:46 +020024 defaultPrefixClass,
25 defaultLengthFieldClass,
26 sliderClass) {
Nils Diewaldfda29d92015-01-22 17:28:01 +000027
Nils Diewald0e6992a2015-04-14 20:13:52 +000028 // Default maximum number of menu items
29 var menuLimit = 8;
30
31 function _codeFromEvent (e) {
32 if (e.charCode && (e.keyCode == 0))
33 return e.charCode
34 return e.keyCode;
Nils Diewald59c02fc2015-03-07 01:29:09 +000035 };
36
Nils Diewald86dad5b2015-01-28 15:09:07 +000037
38 /**
39 * List of items for drop down menu (complete).
40 * Only a sublist of the menu is filtered (live).
41 * Only a sublist of the filtered menu is visible (shown).
42 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000043 return {
Nils Diewald86dad5b2015-01-28 15:09:07 +000044 /**
45 * Create new Menu based on the action prefix
46 * and a list of menu items.
47 *
Akron7524be12016-06-01 17:31:33 +020048 *
49 * Accepts an associative array containg the elements
50 * itemClass, prefixClass, lengthFieldClass
51 *
Nils Diewald86dad5b2015-01-28 15:09:07 +000052 * @this {Menu}
53 * @constructor
54 * @param {string} Context prefix
55 * @param {Array.<Array.<string>>} List of menu items
56 */
Akron7524be12016-06-01 17:31:33 +020057 create : function (list, params) {
58 return Object.create(this)._init(list, params);
Nils Diewald86dad5b2015-01-28 15:09:07 +000059 },
60
Akron5240b8c2016-05-20 09:17:41 +020061 // Initialize list
Akron7524be12016-06-01 17:31:33 +020062 _init : function (list, params) {
Akrona92fd8d2016-05-24 21:13:41 +020063
Akron7524be12016-06-01 17:31:33 +020064 if (params === undefined)
Akrone4961b12017-05-10 21:04:46 +020065 params = {};
Akron7524be12016-06-01 17:31:33 +020066
67 this._itemClass = params["itemClass"] || defaultItemClass;
Akron5240b8c2016-05-20 09:17:41 +020068
69 // Add prefix object
Akron7524be12016-06-01 17:31:33 +020070 if (params["prefixClass"] !== undefined) {
Akrone4961b12017-05-10 21:04:46 +020071 this._prefix = params["prefixClass"].create();
Akron5240b8c2016-05-20 09:17:41 +020072 }
73 else {
Akrone4961b12017-05-10 21:04:46 +020074 this._prefix = defaultPrefixClass.create();
Akron5240b8c2016-05-20 09:17:41 +020075 };
76 this._prefix._menu = this;
77
78 // Add lengthField object
Akron7524be12016-06-01 17:31:33 +020079 if (params["lengthFieldClass"] !== undefined) {
Akrone4961b12017-05-10 21:04:46 +020080 this._lengthField = params["lengthFieldClass"].create();
Akron5240b8c2016-05-20 09:17:41 +020081 }
82 else {
Akrone4961b12017-05-10 21:04:46 +020083 this._lengthField = defaultLengthFieldClass.create();
Akron5240b8c2016-05-20 09:17:41 +020084 };
85 this._lengthField._menu = this;
86
87 // Initialize slider
88 this._slider = sliderClass.create(this);
89
90 // Create the element
Akron9c4d1ae2016-05-25 21:43:22 +020091 var el = document.createElement("ul");
92 with (el) {
Akrone4961b12017-05-10 21:04:46 +020093 style.outline = 0;
94 setAttribute('tabindex', 0);
95 classList.add('menu', 'roll');
96 appendChild(this._prefix.element());
97 appendChild(this._lengthField.element());
98 appendChild(this._slider.element());
Akron9c4d1ae2016-05-25 21:43:22 +020099 };
Akron5240b8c2016-05-20 09:17:41 +0200100
101 // This has to be cleaned up later on
Akron9c4d1ae2016-05-25 21:43:22 +0200102 el["menu"] = this;
Akron5240b8c2016-05-20 09:17:41 +0200103
104 // Arrow keys
Akron9c4d1ae2016-05-25 21:43:22 +0200105 el.addEventListener(
Akrone4961b12017-05-10 21:04:46 +0200106 'keydown',
107 this._keydown.bind(this),
108 false
Akron5240b8c2016-05-20 09:17:41 +0200109 );
110
111 // Strings
Akron9c4d1ae2016-05-25 21:43:22 +0200112 el.addEventListener(
Akrone4961b12017-05-10 21:04:46 +0200113 'keypress',
114 this._keypress.bind(this),
115 false
Akron5240b8c2016-05-20 09:17:41 +0200116 );
117
118 // Mousewheel
Akron9c4d1ae2016-05-25 21:43:22 +0200119 el.addEventListener(
Akrone4961b12017-05-10 21:04:46 +0200120 'wheel',
121 this._mousewheel.bind(this),
122 false
Akron5240b8c2016-05-20 09:17:41 +0200123 );
Akron9c4d1ae2016-05-25 21:43:22 +0200124 this._element = el;
Akrone4961b12017-05-10 21:04:46 +0200125
Akron5240b8c2016-05-20 09:17:41 +0200126 this._items = new Array();
Akron5240b8c2016-05-20 09:17:41 +0200127
Akron9c4d1ae2016-05-25 21:43:22 +0200128 var i = 0;
Akron5240b8c2016-05-20 09:17:41 +0200129 // Initialize item list based on parameters
Akron7524be12016-06-01 17:31:33 +0200130 for (i in list) {
Akrone4961b12017-05-10 21:04:46 +0200131 var obj = this._itemClass.create(list[i]);
Akron5240b8c2016-05-20 09:17:41 +0200132
Akrone4961b12017-05-10 21:04:46 +0200133 // This may become circular
134 obj["_menu"] = this;
135 this._lengthField.add(list[i]);
136 this._items.push(obj);
Akron5240b8c2016-05-20 09:17:41 +0200137 };
138
Akron9c4d1ae2016-05-25 21:43:22 +0200139 this._limit = menuLimit;
140 this._slider.length(this.liveLength())
Akrone4961b12017-05-10 21:04:46 +0200141 .limit(this._limit)
142 .reInit();
143
Akron5240b8c2016-05-20 09:17:41 +0200144 this._firstActive = false; // Show the first item active always?
Akron9c4d1ae2016-05-25 21:43:22 +0200145 this.offset = 0;
146 this.position = 0;
Akron5240b8c2016-05-20 09:17:41 +0200147 return this;
148 },
149
150 // Initialize the item list
151 _initList : function () {
152
153 // Create a new list
154 if (this._list === undefined) {
Akrone4961b12017-05-10 21:04:46 +0200155 this._list = [];
Akron5240b8c2016-05-20 09:17:41 +0200156 }
157 else if (this._list.length !== 0) {
Akrone4961b12017-05-10 21:04:46 +0200158 this._boundary(false);
159 this._list.length = 0;
Akron5240b8c2016-05-20 09:17:41 +0200160 };
161
162 // Offset is initially zero
Akron9c4d1ae2016-05-25 21:43:22 +0200163 this.offset = 0;
Akron5240b8c2016-05-20 09:17:41 +0200164
165 // There is no prefix set
166 if (this.prefix().length <= 0) {
167
Akrone4961b12017-05-10 21:04:46 +0200168 // add all items to the list and lowlight
169 var i = 0;
170 for (; i < this._items.length; i++) {
171 this._list.push(i);
172 this._items[i].lowlight();
173 };
Akron5240b8c2016-05-20 09:17:41 +0200174
Akrone4961b12017-05-10 21:04:46 +0200175 this._slider.length(i).reInit();;
Akron97752a72016-05-25 14:43:07 +0200176
Akrone4961b12017-05-10 21:04:46 +0200177 return true;
Akron5240b8c2016-05-20 09:17:41 +0200178 };
179
180 /*
181 * There is a prefix set, so filter the list!
182 */
183 var pos;
Akronacffc652017-12-18 21:21:25 +0100184 var prefixList = this.prefix().toLowerCase().split(" ");
185
186 var items = [];
187 var maxPoints = 1; // minimum 1
Akron5240b8c2016-05-20 09:17:41 +0200188
189 // Iterate over all items and choose preferred matching items
190 // i.e. the matching happens at the word start
191 for (pos = 0; pos < this._items.length; pos++) {
Akronacffc652017-12-18 21:21:25 +0100192
193 var points = 0;
194
195 for (pref = 0; pref < prefixList.length; pref++) {
196 var prefix = " " + prefixList[pref];
197
198 // Check if it matches at the beginning
199 if ((this.item(pos).lcField().indexOf(prefix)) >= 0) {
200 points += 5;
201 }
202
203 // Check if it matches anywhere
204 else if ((this.item(pos).lcField().indexOf(prefix.substring(1))) >= 0) {
205 points += 1;
206 };
207 };
208
209 if (points > maxPoints) {
210 this._list = [pos];
211 maxPoints = points;
212 }
213 else if (points == maxPoints) {
Akrone4961b12017-05-10 21:04:46 +0200214 this._list.push(pos);
Akronacffc652017-12-18 21:21:25 +0100215 }
Akron5240b8c2016-05-20 09:17:41 +0200216 };
217
218 // The list is empty - so lower your expectations
219 // Iterate over all items and choose matching items
220 // i.e. the matching happens anywhere in the word
Akronacffc652017-12-18 21:21:25 +0100221 /*
Akron6ffad5d2016-05-24 17:16:58 +0200222 prefix = prefix.substring(1);
Akron5240b8c2016-05-20 09:17:41 +0200223 if (this._list.length == 0) {
Akrone4961b12017-05-10 21:04:46 +0200224 for (pos = 0; pos < this._items.length; pos++) {
225 if ((this.item(pos).lcField().indexOf(prefix)) >= 0)
226 this._list.push(pos);
227 };
Akron5240b8c2016-05-20 09:17:41 +0200228 };
Akronacffc652017-12-18 21:21:25 +0100229 */
Akron5240b8c2016-05-20 09:17:41 +0200230
Akron9c4d1ae2016-05-25 21:43:22 +0200231 this._slider.length(this._list.length).reInit();
Akron6ed13992016-05-23 18:06:05 +0200232
Akron5240b8c2016-05-20 09:17:41 +0200233 // Filter was successful - yeah!
234 return this._list.length > 0 ? true : false;
235 },
236
237
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000238 /**
239 * Destroy this menu
240 * (in case you don't trust the
241 * mark and sweep GC)!
242 */
243 destroy : function () {
Akron47c086c2016-05-18 21:22:06 +0200244
Akron5240b8c2016-05-20 09:17:41 +0200245 // Remove circular reference to "this" in menu
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000246 if (this._element != undefined)
Akrone4961b12017-05-10 21:04:46 +0200247 delete this._element["menu"];
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000248
Akron5240b8c2016-05-20 09:17:41 +0200249 // Remove circular reference to "this" in items
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000250 for (var i = 0; i < this._items.length; i++) {
Akrone4961b12017-05-10 21:04:46 +0200251 delete this._items[i]["_menu"];
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000252 };
Akron5240b8c2016-05-20 09:17:41 +0200253
254 // Remove circular reference to "this" in prefix
Nils Diewald5c5a7472015-04-02 22:13:38 +0000255 delete this._prefix['_menu'];
Akron5240b8c2016-05-20 09:17:41 +0200256 delete this._lengthField['_menu'];
257 delete this._slider['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000258 },
259
Nils Diewald7148c6f2015-05-04 15:07:53 +0000260
261 /**
262 * Focus on this menu.
263 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000264 focus : function () {
265 this._element.focus();
266 },
267
Nils Diewald7148c6f2015-05-04 15:07:53 +0000268
Nils Diewald59c02fc2015-03-07 01:29:09 +0000269 // mouse wheel treatment
270 _mousewheel : function (e) {
271 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000272
273 delta = e.deltaY / 120;
274 if (delta > 0)
Akrone4961b12017-05-10 21:04:46 +0200275 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +0000276 else if (delta < 0)
Akrone4961b12017-05-10 21:04:46 +0200277 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000278 e.halt();
279 },
280
Nils Diewald7148c6f2015-05-04 15:07:53 +0000281
Nils Diewald59c02fc2015-03-07 01:29:09 +0000282 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +0000283 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000284 var code = _codeFromEvent(e);
285
Nils Diewald59c02fc2015-03-07 01:29:09 +0000286 switch (code) {
287 case 27: // 'Esc'
Akrone4961b12017-05-10 21:04:46 +0200288 e.halt();
289 this.hide();
290 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000291
Nils Diewald59c02fc2015-03-07 01:29:09 +0000292 case 38: // 'Up'
Akrone4961b12017-05-10 21:04:46 +0200293 e.halt();
294 this.prev();
295 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000296 case 33: // 'Page up'
Akrone4961b12017-05-10 21:04:46 +0200297 e.halt();
298 this.pageUp();
299 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000300 case 40: // 'Down'
Akrone4961b12017-05-10 21:04:46 +0200301 e.halt();
302 this.next();
303 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000304 case 34: // 'Page down'
Akrone4961b12017-05-10 21:04:46 +0200305 e.halt();
306 this.pageDown();
307 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000308 case 39: // 'Right'
Akrone4961b12017-05-10 21:04:46 +0200309 if (this._prefix.active())
310 break;
Nils Diewalde8518f82015-03-18 22:41:49 +0000311
Akrone4961b12017-05-10 21:04:46 +0200312 var item = this.liveItem(this.position);
313
314 if (item["further"] !== undefined) {
315 item["further"].bind(item).apply();
316 };
317
318 e.halt();
319 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000320 case 13: // 'Enter'
321
Akrone4961b12017-05-10 21:04:46 +0200322 // Click on prefix
323 if (this._prefix.active())
324 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000325
Akrone4961b12017-05-10 21:04:46 +0200326 // Click on item
327 else
328 this.liveItem(this.position).onclick(e);
329 e.halt();
330 break;
Nils Diewald59c02fc2015-03-07 01:29:09 +0000331 case 8: // 'Backspace'
Akrone4961b12017-05-10 21:04:46 +0200332 this._prefix.chop();
333 this.show();
334 e.halt();
335 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000336 };
337 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000338
Nils Diewald47f366b2015-04-15 20:06:35 +0000339 // Add characters to prefix
340 _keypress : function (e) {
Akron9c2f9382016-05-25 16:36:04 +0200341 if (e.charCode !== 0) {
Akrone4961b12017-05-10 21:04:46 +0200342 e.halt();
343 var c = String.fromCharCode(_codeFromEvent(e));
344
345 // Add prefix
346 this._prefix.add(c);
347 this.show();
Akron9c2f9382016-05-25 16:36:04 +0200348 };
Nils Diewald59c02fc2015-03-07 01:29:09 +0000349 },
350
Akron47c086c2016-05-18 21:22:06 +0200351 /**
Akron5240b8c2016-05-20 09:17:41 +0200352 * Show a screen with a given offset
353 * in the viewport.
Akron47c086c2016-05-18 21:22:06 +0200354 */
355 screen : function (nr) {
Akron3c2730f2016-05-24 15:08:29 +0200356 if (nr < 0) {
Akrone4961b12017-05-10 21:04:46 +0200357 nr = 0
Akron3c2730f2016-05-24 15:08:29 +0200358 }
Akron6ac58442016-05-24 16:52:29 +0200359 else if (nr > (this.liveLength() - this.limit())) {
Akrone4961b12017-05-10 21:04:46 +0200360 nr = (this.liveLength() - this.limit());
Akron3c2730f2016-05-24 15:08:29 +0200361 };
362
Akron9c4d1ae2016-05-25 21:43:22 +0200363 if (this.offset === nr)
Akrone4961b12017-05-10 21:04:46 +0200364 return;
Akron5a1f5bb2016-05-23 22:00:39 +0200365
Akron47c086c2016-05-18 21:22:06 +0200366 this._showItems(nr);
367 },
368
Nils Diewald2fe12e12015-03-06 16:47:06 +0000369 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000370 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000371 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000372 element : function () {
373 return this._element;
374 },
375
Nils Diewald2fe12e12015-03-06 16:47:06 +0000376 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000377 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000378 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000379 itemClass : function () {
380 return this._itemClass;
381 },
382
383 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000384 * Get and set the numerical value
385 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000386 */
387 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000388 if (arguments.length === 1) {
Akrone4961b12017-05-10 21:04:46 +0200389 if (this._limit !== limit) {
390 this._limit = limit;
391 this._slider.limit(limit).reInit();
392 };
393 return this;
Nils Diewald5975d702015-03-09 17:45:42 +0000394 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000395 return this._limit;
396 },
397
Nils Diewald7148c6f2015-05-04 15:07:53 +0000398
Nils Diewald86dad5b2015-01-28 15:09:07 +0000399 /**
400 * Upgrade this object to another object,
401 * while private data stays intact.
402 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000403 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000404 */
405 upgradeTo : function (props) {
406 for (var prop in props) {
Akrone4961b12017-05-10 21:04:46 +0200407 this[prop] = props[prop];
Nils Diewald86dad5b2015-01-28 15:09:07 +0000408 };
409 return this;
410 },
411
Nils Diewald7148c6f2015-05-04 15:07:53 +0000412
Nils Diewald86dad5b2015-01-28 15:09:07 +0000413 /**
Akron97752a72016-05-25 14:43:07 +0200414 * Filter the list and make it visible.
415 * This is always called once the prefix changes.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000416 *
417 * @param {string} Prefix for filtering the list
418 */
Akron6ed13992016-05-23 18:06:05 +0200419 show : function (active) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000420
Akron5240b8c2016-05-20 09:17:41 +0200421 // show menu based on initial offset
Akron6ac58442016-05-24 16:52:29 +0200422 this._unmark(); // Unmark everything that was marked before
Akrona92fd8d2016-05-24 21:13:41 +0200423 this.removeItems();
Akron6ed13992016-05-23 18:06:05 +0200424
425 // Initialize the list
426 if (!this._initList()) {
Akron6ac58442016-05-24 16:52:29 +0200427
Akrone4961b12017-05-10 21:04:46 +0200428 // The prefix is not active
429 this._prefix.active(true);
Akron6ed13992016-05-23 18:06:05 +0200430
Akrone4961b12017-05-10 21:04:46 +0200431 // finally show the element
432 this._element.classList.add('visible');
433
434 return true;
Akron6ed13992016-05-23 18:06:05 +0200435 };
436
437 var offset = 0;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000438
Akron9c2f9382016-05-25 16:36:04 +0200439 // Set a chosen value to active and move the viewport
Akron6ed13992016-05-23 18:06:05 +0200440 if (arguments.length === 1) {
441
Akrone4961b12017-05-10 21:04:46 +0200442 // Normalize active value
443 if (active < 0) {
444 active = 0;
445 }
446 else if (active >= this.liveLength()) {
447 active = this.liveLength() - 1;
448 };
Akron6ed13992016-05-23 18:06:05 +0200449
Akrone4961b12017-05-10 21:04:46 +0200450 // Item is outside the first viewport
451 if (active >= this._limit) {
452 offset = active;
453 if (offset > (this.liveLength() - this._limit)) {
454 offset = this.liveLength() - this._limit;
455 };
456 };
457
458 this.position = active;
Akron6ed13992016-05-23 18:06:05 +0200459 }
460
Akron9c2f9382016-05-25 16:36:04 +0200461 // Choose the first item
Akron6ed13992016-05-23 18:06:05 +0200462 else if (this._firstActive) {
Akrone4961b12017-05-10 21:04:46 +0200463 this.position = 0;
Akron47c086c2016-05-18 21:22:06 +0200464 }
Akroncb351d62016-05-19 23:10:33 +0200465
Akron9c2f9382016-05-25 16:36:04 +0200466 // Choose no item
Akron47c086c2016-05-18 21:22:06 +0200467 else {
Akrone4961b12017-05-10 21:04:46 +0200468 this.position = -1;
Akron6ed13992016-05-23 18:06:05 +0200469 };
470
Akron9c4d1ae2016-05-25 21:43:22 +0200471 this.offset = offset;
Akron6ed13992016-05-23 18:06:05 +0200472 this._showItems(offset); // Show new item list
473
474 // Make chosen value active
475 if (this.position !== -1) {
Akrone4961b12017-05-10 21:04:46 +0200476 this.liveItem(this.position).active(true);
Akron6ed13992016-05-23 18:06:05 +0200477 };
Akron37513a62015-11-17 01:07:11 +0100478
Akron5240b8c2016-05-20 09:17:41 +0200479 // The prefix is not active
Nils Diewalde8518f82015-03-18 22:41:49 +0000480 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000481
Akron5240b8c2016-05-20 09:17:41 +0200482 // finally show the element
Akron6bb71582016-06-10 20:41:08 +0200483 this._element.classList.add('visible');
Nils Diewald2fe12e12015-03-06 16:47:06 +0000484
Nils Diewald86dad5b2015-01-28 15:09:07 +0000485 // Add classes for rolling menus
486 this._boundary(true);
Akron6bb71582016-06-10 20:41:08 +0200487
Nils Diewald59c02fc2015-03-07 01:29:09 +0000488 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000489 },
490
Nils Diewald7148c6f2015-05-04 15:07:53 +0000491
492 /**
493 * Hide the menu and call the onHide callback.
494 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000495 hide : function () {
Akrona92fd8d2016-05-24 21:13:41 +0200496 this.removeItems();
Nils Diewald7148c6f2015-05-04 15:07:53 +0000497 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000498 this.onHide();
Akron6bb71582016-06-10 20:41:08 +0200499 this._element.classList.remove('visible');
500
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000501 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000502 },
503
Nils Diewald7148c6f2015-05-04 15:07:53 +0000504 /**
505 * Function released when the menu hides.
506 * This method is expected to be overridden.
507 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000508 onHide : function () {},
509
Nils Diewald7148c6f2015-05-04 15:07:53 +0000510
Nils Diewald86dad5b2015-01-28 15:09:07 +0000511 /**
512 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000513 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000514 */
Nils Diewald5975d702015-03-09 17:45:42 +0000515 prefix : function (pref) {
516 if (arguments.length === 1) {
Akrone4961b12017-05-10 21:04:46 +0200517 this._prefix.value(pref);
518 return this;
Nils Diewald5975d702015-03-09 17:45:42 +0000519 };
520 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000521 },
522
Akronc7448732016-04-27 14:06:58 +0200523 /**
524 * Get the lengthField object.
525 */
526 lengthField : function () {
527 return this._lengthField;
528 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000529
Akron5240b8c2016-05-20 09:17:41 +0200530 /**
531 * Get the associated slider object.
532 */
533 slider : function () {
534 return this._slider;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000535 },
536
Akron5240b8c2016-05-20 09:17:41 +0200537
Nils Diewald86dad5b2015-01-28 15:09:07 +0000538 /**
539 * Delete all visible items from the menu element
540 */
Akrona92fd8d2016-05-24 21:13:41 +0200541 removeItems : function () {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000542 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000543
Nils Diewald2fe12e12015-03-06 16:47:06 +0000544 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000545 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200546 // Leave the prefix and lengthField
Akron9905e2a2016-05-10 16:06:44 +0200547 for (var i = children.length - 1; i >= 3; i--) {
Akrone4961b12017-05-10 21:04:46 +0200548 this._element.removeChild(
549 children[i]
550 );
Nils Diewald5975d702015-03-09 17:45:42 +0000551 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000552 },
553
Nils Diewald2fe12e12015-03-06 16:47:06 +0000554 /**
555 * Get a specific item from the complete list
556 *
557 * @param {number} index of the list item
558 */
559 item : function (index) {
560 return this._items[index]
561 },
562
563
Nils Diewald86dad5b2015-01-28 15:09:07 +0000564 /**
565 * Get a specific item from the filtered list
566 *
567 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000568 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000569 */
570 liveItem : function (index) {
571 if (this._list === undefined)
Akrone4961b12017-05-10 21:04:46 +0200572 if (!this._initList())
573 return;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000574
575 return this._items[this._list[index]];
576 },
577
Nils Diewald86dad5b2015-01-28 15:09:07 +0000578
579 /**
Akron5240b8c2016-05-20 09:17:41 +0200580 * Get a specific item from the viewport list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000581 *
582 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000583 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000584 */
585 shownItem : function (index) {
586 if (index >= this.limit())
Akrone4961b12017-05-10 21:04:46 +0200587 return;
Akron9c4d1ae2016-05-25 21:43:22 +0200588 return this.liveItem(this.offset + index);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000589 },
590
591
Nils Diewald2fe12e12015-03-06 16:47:06 +0000592 /**
Akron9c4d1ae2016-05-25 21:43:22 +0200593 * Get the length of the full item list
Nils Diewald2fe12e12015-03-06 16:47:06 +0000594 */
595 length : function () {
596 return this._items.length;
597 },
598
599
600 /**
Akron5240b8c2016-05-20 09:17:41 +0200601 * Length of the filtered item list.
602 */
603 liveLength : function () {
604 if (this._list === undefined)
Akrone4961b12017-05-10 21:04:46 +0200605 this._initList();
Akron5240b8c2016-05-20 09:17:41 +0200606 return this._list.length;
607 },
608
609
610 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000611 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000612 */
613 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000614
Akronb38afb22016-05-25 19:30:01 +0200615 // No list
616 if (this.liveLength() === 0)
Akrone4961b12017-05-10 21:04:46 +0200617 return;
Akronb38afb22016-05-25 19:30:01 +0200618
Akron9c4d1ae2016-05-25 21:43:22 +0200619 // Deactivate old item
620 if (this.position !== -1 && !this._prefix.active()) {
Akrone4961b12017-05-10 21:04:46 +0200621 this.liveItem(this.position).active(false);
Akron9c4d1ae2016-05-25 21:43:22 +0200622 };
Nils Diewalde8518f82015-03-18 22:41:49 +0000623
Akron9c4d1ae2016-05-25 21:43:22 +0200624 // Get new active item
625 this.position++;
626 var newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000627
Nils Diewald5975d702015-03-09 17:45:42 +0000628 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000629 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000630
Akrone4961b12017-05-10 21:04:46 +0200631 // Activate prefix
632 var prefix = this._prefix;
Nils Diewald5975d702015-03-09 17:45:42 +0000633
Akrone4961b12017-05-10 21:04:46 +0200634 // Prefix is set and not active - choose!
635 if (prefix.isSet() && !prefix.active()) {
636 this.position--;
637 prefix.active(true);
638 return;
639 }
Akron9c4d1ae2016-05-25 21:43:22 +0200640
Akrone4961b12017-05-10 21:04:46 +0200641 // Choose first item
642 else {
643 newItem = this.liveItem(0);
644 // choose first item
645 this.position = 0;
646 this._showItems(0);
647 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000648 }
649
Akron5a1f5bb2016-05-23 22:00:39 +0200650 // The next element is after the viewport - roll down
Akron9c4d1ae2016-05-25 21:43:22 +0200651 else if (this.position >= (this.limit() + this.offset)) {
Akrone4961b12017-05-10 21:04:46 +0200652 this.screen(this.position - this.limit() + 1);
Akron5a1f5bb2016-05-23 22:00:39 +0200653 }
654
655 // The next element is before the viewport - roll up
Akron9c4d1ae2016-05-25 21:43:22 +0200656 else if (this.position <= this.offset) {
Akrone4961b12017-05-10 21:04:46 +0200657 this.screen(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000658 };
Nils Diewald5975d702015-03-09 17:45:42 +0000659
660 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000661 newItem.active(true);
662 },
663
Nils Diewalde8518f82015-03-18 22:41:49 +0000664 /*
Nils Diewald86dad5b2015-01-28 15:09:07 +0000665 * Make the previous item in the menu active
666 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000667 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000668
Akronb38afb22016-05-25 19:30:01 +0200669 // No list
670 if (this.liveLength() === 0)
Akrone4961b12017-05-10 21:04:46 +0200671 return;
Akronb38afb22016-05-25 19:30:01 +0200672
Akron9c4d1ae2016-05-25 21:43:22 +0200673 // Deactivate old item
Nils Diewald2d210752015-03-09 19:01:15 +0000674 if (!this._prefix.active()) {
Akron9c4d1ae2016-05-25 21:43:22 +0200675
Akrone4961b12017-05-10 21:04:46 +0200676 // No active element set
677 if (this.position === -1) {
678 this.position = this.liveLength();
679 }
Akron9c4d1ae2016-05-25 21:43:22 +0200680
Akrone4961b12017-05-10 21:04:46 +0200681 // No active element set
682 else {
683 this.liveItem(this.position--).active(false);
684 };
Nils Diewald2d210752015-03-09 19:01:15 +0000685 };
686
Akron9c4d1ae2016-05-25 21:43:22 +0200687 // Get new active item
688 var newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000689
690 // The previous element is undefined - roll to bottom
691 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000692
Akrone4961b12017-05-10 21:04:46 +0200693 // Activate prefix
694 var prefix = this._prefix;
695 var offset = this.liveLength() - this.limit();
696
697 // Normalize offset
698 offset = offset < 0 ? 0 : offset;
Nils Diewald2d210752015-03-09 19:01:15 +0000699
Akrone4961b12017-05-10 21:04:46 +0200700 // Choose the last item
701 this.position = this.liveLength() - 1;
702
703 // Prefix is set and not active - choose!
704 if (prefix.isSet() && !prefix.active()) {
705 this.position++;
706 prefix.active(true);
707 this.offset = offset;
708 return;
709 }
Nils Diewald2d210752015-03-09 19:01:15 +0000710
Akrone4961b12017-05-10 21:04:46 +0200711 // Choose last item
712 else {
713 newItem = this.liveItem(this.position);
714 this._showItems(offset);
715 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000716 }
717
Akron5a1f5bb2016-05-23 22:00:39 +0200718 // The previous element is before the view - roll up
Akron9c4d1ae2016-05-25 21:43:22 +0200719 else if (this.position < this.offset) {
Akrone4961b12017-05-10 21:04:46 +0200720 this.screen(this.position);
Akron5a1f5bb2016-05-23 22:00:39 +0200721 }
722
723 // The previous element is after the view - roll down
Akron9c4d1ae2016-05-25 21:43:22 +0200724 else if (this.position >= (this.limit() + this.offset)) {
Akrone4961b12017-05-10 21:04:46 +0200725 this.screen(this.position - this.limit() + 2);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000726 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000727
Nils Diewald5975d702015-03-09 17:45:42 +0000728 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000729 newItem.active(true);
730 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000731
Akron3c2730f2016-05-24 15:08:29 +0200732 /**
733 * Move the page up by limit!
734 */
735 pageUp : function () {
Akron9c4d1ae2016-05-25 21:43:22 +0200736 this.screen(this.offset - this.limit());
Akron3c2730f2016-05-24 15:08:29 +0200737 },
738
739
740 /**
741 * Move the page down by limit!
742 */
743 pageDown : function () {
Akron9c4d1ae2016-05-25 21:43:22 +0200744 this.screen(this.offset + this.limit());
Akron3c2730f2016-05-24 15:08:29 +0200745 },
746
Nils Diewald86dad5b2015-01-28 15:09:07 +0000747
Akron5240b8c2016-05-20 09:17:41 +0200748 // Unmark all items
749 _unmark : function () {
750 for (var i in this._list) {
Akrone4961b12017-05-10 21:04:46 +0200751 var item = this._items[this._list[i]];
752 item.lowlight();
753 item.active(false);
Akron5240b8c2016-05-20 09:17:41 +0200754 };
755 },
756
Akron5240b8c2016-05-20 09:17:41 +0200757 // Set boundary for viewport
758 _boundary : function (bool) {
759 this.item(this._list[0]).noMore(bool);
760 this.item(this._list[this._list.length - 1]).noMore(bool);
761 },
762
763
764 // Append Items that should be shown
765 _showItems : function (off) {
766
Akrona92fd8d2016-05-24 21:13:41 +0200767 // optimization: scroll down one step
Akron9c4d1ae2016-05-25 21:43:22 +0200768 if (this.offset === (off - 1)) {
Akrone4961b12017-05-10 21:04:46 +0200769 this.offset = off;
Akron9c4d1ae2016-05-25 21:43:22 +0200770
Akrone4961b12017-05-10 21:04:46 +0200771 // Remove the HTML node from the first item
772 // leave lengthField/prefix/slider
773 this._element.removeChild(this._element.children[3]);
774 var pos = this.offset + this.limit() - 1;
775 this._append(this._list[pos]);
Akrona92fd8d2016-05-24 21:13:41 +0200776 }
Akron5240b8c2016-05-20 09:17:41 +0200777
Akrona92fd8d2016-05-24 21:13:41 +0200778 // optimization: scroll up one step
Akron9c4d1ae2016-05-25 21:43:22 +0200779 else if (this.offset === (off + 1)) {
Akrone4961b12017-05-10 21:04:46 +0200780 this.offset = off;
Akron9c4d1ae2016-05-25 21:43:22 +0200781
Akrone4961b12017-05-10 21:04:46 +0200782 // Remove the HTML node from the last item
783 this._element.removeChild(this._element.lastChild);
Akron9c4d1ae2016-05-25 21:43:22 +0200784
Akrone4961b12017-05-10 21:04:46 +0200785 this._prepend(this._list[this.offset]);
Akrona92fd8d2016-05-24 21:13:41 +0200786 }
787 else {
Akrone4961b12017-05-10 21:04:46 +0200788 this.offset = off;
Akron5240b8c2016-05-20 09:17:41 +0200789
Akrone4961b12017-05-10 21:04:46 +0200790 // Remove all items
791 this.removeItems();
Akron5240b8c2016-05-20 09:17:41 +0200792
Akrone4961b12017-05-10 21:04:46 +0200793 // Use list
794 var shown = 0;
795 var i;
Akron5240b8c2016-05-20 09:17:41 +0200796
Akrone4961b12017-05-10 21:04:46 +0200797 for (i in this._list) {
Akrona92fd8d2016-05-24 21:13:41 +0200798
Akrone4961b12017-05-10 21:04:46 +0200799 // Don't show - it's before offset
800 shown++;
801 if (shown <= off)
802 continue;
Akrona92fd8d2016-05-24 21:13:41 +0200803
Akrone4961b12017-05-10 21:04:46 +0200804 var itemNr = this._list[i];
805 var item = this.item(itemNr);
806 this._append(itemNr);
807
808 if (shown >= (this.limit() + off))
809 break;
810 };
Akron5240b8c2016-05-20 09:17:41 +0200811 };
812
813 // set the slider to the new offset
Akron9c4d1ae2016-05-25 21:43:22 +0200814 this._slider.offset(this.offset);
Akron5240b8c2016-05-20 09:17:41 +0200815 },
816
817
818 // Append item to the shown list based on index
819 _append : function (i) {
820 var item = this.item(i);
821
822 // Highlight based on prefix
Akrona92fd8d2016-05-24 21:13:41 +0200823 if (this.prefix().length > 0) {
Akrone4961b12017-05-10 21:04:46 +0200824 item.highlight(this.prefix().toLowerCase());
Akrona92fd8d2016-05-24 21:13:41 +0200825 };
826
Akron5240b8c2016-05-20 09:17:41 +0200827
828 // Append element
829 this.element().appendChild(item.element());
830 },
831
832
833 // Prepend item to the shown list based on index
834 _prepend : function (i) {
835 var item = this.item(i);
836
837 // Highlight based on prefix
Akrona92fd8d2016-05-24 21:13:41 +0200838 if (this.prefix().length > 0) {
Akrone4961b12017-05-10 21:04:46 +0200839 item.highlight(this.prefix().toLowerCase());
Akrona92fd8d2016-05-24 21:13:41 +0200840 };
Akron5240b8c2016-05-20 09:17:41 +0200841
842 var e = this.element();
Akron9c4d1ae2016-05-25 21:43:22 +0200843
Akron5240b8c2016-05-20 09:17:41 +0200844 // Append element after lengthField/prefix/slider
845 e.insertBefore(
Akrone4961b12017-05-10 21:04:46 +0200846 item.element(),
847 e.children[3]
Akron5240b8c2016-05-20 09:17:41 +0200848 );
Nils Diewald2fe12e12015-03-06 16:47:06 +0000849 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000850 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000851});