blob: 9ed6e7c6ffc97a2de477d3094d90a26c0eb5c531 [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 *
Akron7524be12016-06-01 17:31:33 +020044 *
45 * Accepts an associative array containg the elements
46 * itemClass, prefixClass, lengthFieldClass
47 *
Nils Diewald86dad5b2015-01-28 15:09:07 +000048 * @this {Menu}
49 * @constructor
50 * @param {string} Context prefix
51 * @param {Array.<Array.<string>>} List of menu items
52 */
Akron7524be12016-06-01 17:31:33 +020053 create : function (list, params) {
54 return Object.create(this)._init(list, params);
Nils Diewald86dad5b2015-01-28 15:09:07 +000055 },
56
Akron5240b8c2016-05-20 09:17:41 +020057 // Initialize list
Akron7524be12016-06-01 17:31:33 +020058 _init : function (list, params) {
Akrona92fd8d2016-05-24 21:13:41 +020059
Akron7524be12016-06-01 17:31:33 +020060 if (params === undefined)
61 params = {};
62
63 this._itemClass = params["itemClass"] || defaultItemClass;
Akron5240b8c2016-05-20 09:17:41 +020064
65 // Add prefix object
Akron7524be12016-06-01 17:31:33 +020066 if (params["prefixClass"] !== undefined) {
67 this._prefix = params["prefixClass"].create();
Akron5240b8c2016-05-20 09:17:41 +020068 }
69 else {
70 this._prefix = defaultPrefixClass.create();
71 };
72 this._prefix._menu = this;
73
74 // Add lengthField object
Akron7524be12016-06-01 17:31:33 +020075 if (params["lengthFieldClass"] !== undefined) {
76 this._lengthField = params["lengthFieldClass"].create();
Akron5240b8c2016-05-20 09:17:41 +020077 }
78 else {
79 this._lengthField = defaultLengthFieldClass.create();
80 };
81 this._lengthField._menu = this;
82
83 // Initialize slider
84 this._slider = sliderClass.create(this);
85
86 // Create the element
Akron9c4d1ae2016-05-25 21:43:22 +020087 var el = document.createElement("ul");
88 with (el) {
Akron9c4d1ae2016-05-25 21:43:22 +020089 style.outline = 0;
90 setAttribute('tabindex', 0);
91 classList.add('menu', 'roll');
92 appendChild(this._prefix.element());
93 appendChild(this._lengthField.element());
94 appendChild(this._slider.element());
95 };
Akron5240b8c2016-05-20 09:17:41 +020096
97 // This has to be cleaned up later on
Akron9c4d1ae2016-05-25 21:43:22 +020098 el["menu"] = this;
Akron5240b8c2016-05-20 09:17:41 +020099
100 // Arrow keys
Akron9c4d1ae2016-05-25 21:43:22 +0200101 el.addEventListener(
Akron5240b8c2016-05-20 09:17:41 +0200102 'keydown',
Akrona92fd8d2016-05-24 21:13:41 +0200103 this._keydown.bind(this),
Akron5240b8c2016-05-20 09:17:41 +0200104 false
105 );
106
107 // Strings
Akron9c4d1ae2016-05-25 21:43:22 +0200108 el.addEventListener(
Akron5240b8c2016-05-20 09:17:41 +0200109 'keypress',
Akrona92fd8d2016-05-24 21:13:41 +0200110 this._keypress.bind(this),
Akron5240b8c2016-05-20 09:17:41 +0200111 false
112 );
113
114 // Mousewheel
Akron9c4d1ae2016-05-25 21:43:22 +0200115 el.addEventListener(
Akron5240b8c2016-05-20 09:17:41 +0200116 'wheel',
Akrona92fd8d2016-05-24 21:13:41 +0200117 this._mousewheel.bind(this),
Akron5240b8c2016-05-20 09:17:41 +0200118 false
119 );
Akron9c4d1ae2016-05-25 21:43:22 +0200120 this._element = el;
Akron5240b8c2016-05-20 09:17:41 +0200121
Akron5240b8c2016-05-20 09:17:41 +0200122 this._items = new Array();
Akron5240b8c2016-05-20 09:17:41 +0200123
Akron9c4d1ae2016-05-25 21:43:22 +0200124 var i = 0;
Akron5240b8c2016-05-20 09:17:41 +0200125 // Initialize item list based on parameters
Akron7524be12016-06-01 17:31:33 +0200126 for (i in list) {
127 var obj = this._itemClass.create(list[i]);
Akron5240b8c2016-05-20 09:17:41 +0200128
129 // This may become circular
130 obj["_menu"] = this;
Akron7524be12016-06-01 17:31:33 +0200131 this._lengthField.add(list[i]);
Akron5240b8c2016-05-20 09:17:41 +0200132 this._items.push(obj);
133 };
134
Akron9c4d1ae2016-05-25 21:43:22 +0200135 this._limit = menuLimit;
136 this._slider.length(this.liveLength())
137 .limit(this._limit)
138 .reInit();
Akron5240b8c2016-05-20 09:17:41 +0200139
Akron5240b8c2016-05-20 09:17:41 +0200140 this._firstActive = false; // Show the first item active always?
Akron9c4d1ae2016-05-25 21:43:22 +0200141 this.offset = 0;
142 this.position = 0;
Akron5240b8c2016-05-20 09:17:41 +0200143 return this;
144 },
145
146 // Initialize the item list
147 _initList : function () {
148
149 // Create a new list
150 if (this._list === undefined) {
151 this._list = [];
152 }
153 else if (this._list.length !== 0) {
154 this._boundary(false);
155 this._list.length = 0;
156 };
157
158 // Offset is initially zero
Akron9c4d1ae2016-05-25 21:43:22 +0200159 this.offset = 0;
Akron5240b8c2016-05-20 09:17:41 +0200160
161 // There is no prefix set
162 if (this.prefix().length <= 0) {
163
164 // add all items to the list and lowlight
Akron97752a72016-05-25 14:43:07 +0200165 var i = 0;
166 for (; i < this._items.length; i++) {
Akron5240b8c2016-05-20 09:17:41 +0200167 this._list.push(i);
168 this._items[i].lowlight();
169 };
170
Akron9c4d1ae2016-05-25 21:43:22 +0200171 this._slider.length(i).reInit();;
Akron97752a72016-05-25 14:43:07 +0200172
Akron5240b8c2016-05-20 09:17:41 +0200173 return true;
174 };
175
176 /*
177 * There is a prefix set, so filter the list!
178 */
179 var pos;
Akron6ffad5d2016-05-24 17:16:58 +0200180 var prefix = " " + this.prefix().toLowerCase();
Akron5240b8c2016-05-20 09:17:41 +0200181
182 // Iterate over all items and choose preferred matching items
183 // i.e. the matching happens at the word start
184 for (pos = 0; pos < this._items.length; pos++) {
Akron6ffad5d2016-05-24 17:16:58 +0200185 if ((this.item(pos).lcField().indexOf(prefix)) >= 0)
Akron5240b8c2016-05-20 09:17:41 +0200186 this._list.push(pos);
187 };
188
189 // The list is empty - so lower your expectations
190 // Iterate over all items and choose matching items
191 // i.e. the matching happens anywhere in the word
Akron6ffad5d2016-05-24 17:16:58 +0200192 prefix = prefix.substring(1);
Akron5240b8c2016-05-20 09:17:41 +0200193 if (this._list.length == 0) {
194 for (pos = 0; pos < this._items.length; pos++) {
Akron6ffad5d2016-05-24 17:16:58 +0200195 if ((this.item(pos).lcField().indexOf(prefix)) >= 0)
Akron5240b8c2016-05-20 09:17:41 +0200196 this._list.push(pos);
197 };
198 };
199
Akron9c4d1ae2016-05-25 21:43:22 +0200200 this._slider.length(this._list.length).reInit();
Akron6ed13992016-05-23 18:06:05 +0200201
Akron5240b8c2016-05-20 09:17:41 +0200202 // Filter was successful - yeah!
203 return this._list.length > 0 ? true : false;
204 },
205
206
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000207 /**
208 * Destroy this menu
209 * (in case you don't trust the
210 * mark and sweep GC)!
211 */
212 destroy : function () {
Akron47c086c2016-05-18 21:22:06 +0200213
Akron5240b8c2016-05-20 09:17:41 +0200214 // Remove circular reference to "this" in menu
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000215 if (this._element != undefined)
216 delete this._element["menu"];
217
Akron5240b8c2016-05-20 09:17:41 +0200218 // Remove circular reference to "this" in items
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000219 for (var i = 0; i < this._items.length; i++) {
220 delete this._items[i]["_menu"];
221 };
Akron5240b8c2016-05-20 09:17:41 +0200222
223 // Remove circular reference to "this" in prefix
Nils Diewald5c5a7472015-04-02 22:13:38 +0000224 delete this._prefix['_menu'];
Akron5240b8c2016-05-20 09:17:41 +0200225 delete this._lengthField['_menu'];
226 delete this._slider['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000227 },
228
Nils Diewald7148c6f2015-05-04 15:07:53 +0000229
230 /**
231 * Focus on this menu.
232 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000233 focus : function () {
234 this._element.focus();
235 },
236
Nils Diewald7148c6f2015-05-04 15:07:53 +0000237
Nils Diewald59c02fc2015-03-07 01:29:09 +0000238 // mouse wheel treatment
239 _mousewheel : function (e) {
240 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000241
242 delta = e.deltaY / 120;
243 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000244 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +0000245 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000246 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000247 e.halt();
248 },
249
Nils Diewald7148c6f2015-05-04 15:07:53 +0000250
Nils Diewald59c02fc2015-03-07 01:29:09 +0000251 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +0000252 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000253 var code = _codeFromEvent(e);
254
Nils Diewald59c02fc2015-03-07 01:29:09 +0000255 switch (code) {
256 case 27: // 'Esc'
257 e.halt();
258 this.hide();
259 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000260
Nils Diewald59c02fc2015-03-07 01:29:09 +0000261 case 38: // 'Up'
262 e.halt();
263 this.prev();
264 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000265 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +0000266 e.halt();
Akron3c2730f2016-05-24 15:08:29 +0200267 this.pageUp();
Nils Diewald5975d702015-03-09 17:45:42 +0000268 break;
269 case 40: // 'Down'
270 e.halt();
271 this.next();
272 break;
273 case 34: // 'Page down'
274 e.halt();
Akron3c2730f2016-05-24 15:08:29 +0200275 this.pageDown();
Nils Diewald5975d702015-03-09 17:45:42 +0000276 break;
277 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000278 if (this._prefix.active())
279 break;
280
Akronf86eaea2016-05-13 18:02:27 +0200281 var item = this.liveItem(this.position);
Akron5ef4fa02015-06-02 16:25:14 +0200282
Nils Diewald5975d702015-03-09 17:45:42 +0000283 if (item["further"] !== undefined) {
284 item["further"].bind(item).apply();
Nils Diewald5975d702015-03-09 17:45:42 +0000285 };
Akron5ef4fa02015-06-02 16:25:14 +0200286
287 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000288 break;
289 case 13: // 'Enter'
290
291 // Click on prefix
292 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000293 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000294
295 // Click on item
296 else
Akronf86eaea2016-05-13 18:02:27 +0200297 this.liveItem(this.position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000298 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000299 break;
300 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000301 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000302 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000303 e.halt();
304 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000305 };
306 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000307
Nils Diewald47f366b2015-04-15 20:06:35 +0000308 // Add characters to prefix
309 _keypress : function (e) {
Akron9c2f9382016-05-25 16:36:04 +0200310 if (e.charCode !== 0) {
311 e.halt();
312 var c = String.fromCharCode(_codeFromEvent(e));
Nils Diewald5975d702015-03-09 17:45:42 +0000313
Akron9c2f9382016-05-25 16:36:04 +0200314 // Add prefix
315 this._prefix.add(c);
316 this.show();
317 };
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) {
Akron3c2730f2016-05-24 15:08:29 +0200325 if (nr < 0) {
326 nr = 0
327 }
Akron6ac58442016-05-24 16:52:29 +0200328 else if (nr > (this.liveLength() - this.limit())) {
329 nr = (this.liveLength() - this.limit());
Akron3c2730f2016-05-24 15:08:29 +0200330 };
331
Akron9c4d1ae2016-05-25 21:43:22 +0200332 if (this.offset === nr)
Akron47c086c2016-05-18 21:22:06 +0200333 return;
Akron5a1f5bb2016-05-23 22:00:39 +0200334
Akron47c086c2016-05-18 21:22:06 +0200335 this._showItems(nr);
336 },
337
Nils Diewald2fe12e12015-03-06 16:47:06 +0000338 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000339 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000340 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000341 element : function () {
342 return this._element;
343 },
344
Nils Diewald2fe12e12015-03-06 16:47:06 +0000345 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000346 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000347 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000348 itemClass : function () {
349 return this._itemClass;
350 },
351
352 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000353 * Get and set the numerical value
354 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000355 */
356 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000357 if (arguments.length === 1) {
Akron5240b8c2016-05-20 09:17:41 +0200358 if (this._limit !== limit) {
359 this._limit = limit;
Akron9c4d1ae2016-05-25 21:43:22 +0200360 this._slider.limit(limit).reInit();
Akron5240b8c2016-05-20 09:17:41 +0200361 };
Nils Diewald5975d702015-03-09 17:45:42 +0000362 return this;
363 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000364 return this._limit;
365 },
366
Nils Diewald7148c6f2015-05-04 15:07:53 +0000367
Nils Diewald86dad5b2015-01-28 15:09:07 +0000368 /**
369 * Upgrade this object to another object,
370 * while private data stays intact.
371 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000372 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000373 */
374 upgradeTo : function (props) {
375 for (var prop in props) {
376 this[prop] = props[prop];
377 };
378 return this;
379 },
380
Nils Diewald7148c6f2015-05-04 15:07:53 +0000381
Nils Diewald86dad5b2015-01-28 15:09:07 +0000382 /**
Akron97752a72016-05-25 14:43:07 +0200383 * Filter the list and make it visible.
384 * This is always called once the prefix changes.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000385 *
386 * @param {string} Prefix for filtering the list
387 */
Akron6ed13992016-05-23 18:06:05 +0200388 show : function (active) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000389
Akron5240b8c2016-05-20 09:17:41 +0200390 // show menu based on initial offset
Akron6ac58442016-05-24 16:52:29 +0200391 this._unmark(); // Unmark everything that was marked before
Akrona92fd8d2016-05-24 21:13:41 +0200392 this.removeItems();
Akron6ed13992016-05-23 18:06:05 +0200393
394 // Initialize the list
395 if (!this._initList()) {
Akron6ac58442016-05-24 16:52:29 +0200396
Akron6ed13992016-05-23 18:06:05 +0200397 // The prefix is not active
398 this._prefix.active(true);
399
400 // finally show the element
Akron6bb71582016-06-10 20:41:08 +0200401 this._element.classList.add('visible');
Akron6ed13992016-05-23 18:06:05 +0200402
403 return true;
404 };
405
406 var offset = 0;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000407
Akron9c2f9382016-05-25 16:36:04 +0200408 // Set a chosen value to active and move the viewport
Akron6ed13992016-05-23 18:06:05 +0200409 if (arguments.length === 1) {
410
411 // Normalize active value
Akron3c2730f2016-05-24 15:08:29 +0200412 if (active < 0) {
Akron6ed13992016-05-23 18:06:05 +0200413 active = 0;
Akron3c2730f2016-05-24 15:08:29 +0200414 }
Akron7524be12016-06-01 17:31:33 +0200415 else if (active >= this.liveLength()) {
Akron6ac58442016-05-24 16:52:29 +0200416 active = this.liveLength() - 1;
Akron3c2730f2016-05-24 15:08:29 +0200417 };
Akron6ed13992016-05-23 18:06:05 +0200418
Akron0b92f692016-05-25 22:37:13 +0200419 // Item is outside the first viewport
420 if (active >= this._limit) {
Akron6ed13992016-05-23 18:06:05 +0200421 offset = active;
Akron6ac58442016-05-24 16:52:29 +0200422 if (offset > (this.liveLength() - this._limit)) {
423 offset = this.liveLength() - this._limit;
Akron6ed13992016-05-23 18:06:05 +0200424 };
425 };
426
427 this.position = active;
Akron6ed13992016-05-23 18:06:05 +0200428 }
429
Akron9c2f9382016-05-25 16:36:04 +0200430 // Choose the first item
Akron6ed13992016-05-23 18:06:05 +0200431 else if (this._firstActive) {
Akron47c086c2016-05-18 21:22:06 +0200432 this.position = 0;
Akron47c086c2016-05-18 21:22:06 +0200433 }
Akroncb351d62016-05-19 23:10:33 +0200434
Akron9c2f9382016-05-25 16:36:04 +0200435 // Choose no item
Akron47c086c2016-05-18 21:22:06 +0200436 else {
437 this.position = -1;
Akron6ed13992016-05-23 18:06:05 +0200438 };
439
Akron9c4d1ae2016-05-25 21:43:22 +0200440 this.offset = offset;
Akron6ed13992016-05-23 18:06:05 +0200441 this._showItems(offset); // Show new item list
442
443 // Make chosen value active
444 if (this.position !== -1) {
Akron3c2730f2016-05-24 15:08:29 +0200445 this.liveItem(this.position).active(true);
Akron6ed13992016-05-23 18:06:05 +0200446 };
Akron37513a62015-11-17 01:07:11 +0100447
Akron5240b8c2016-05-20 09:17:41 +0200448 // The prefix is not active
Nils Diewalde8518f82015-03-18 22:41:49 +0000449 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000450
Akron5240b8c2016-05-20 09:17:41 +0200451 // finally show the element
Akron6bb71582016-06-10 20:41:08 +0200452 this._element.classList.add('visible');
Nils Diewald2fe12e12015-03-06 16:47:06 +0000453
Nils Diewald86dad5b2015-01-28 15:09:07 +0000454 // Add classes for rolling menus
455 this._boundary(true);
Akron6bb71582016-06-10 20:41:08 +0200456
Nils Diewald59c02fc2015-03-07 01:29:09 +0000457 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000458 },
459
Nils Diewald7148c6f2015-05-04 15:07:53 +0000460
461 /**
462 * Hide the menu and call the onHide callback.
463 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000464 hide : function () {
Akrona92fd8d2016-05-24 21:13:41 +0200465 this.removeItems();
Nils Diewald7148c6f2015-05-04 15:07:53 +0000466 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000467 this.onHide();
Akron6bb71582016-06-10 20:41:08 +0200468 this._element.classList.remove('visible');
469
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000470 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000471 },
472
Nils Diewald7148c6f2015-05-04 15:07:53 +0000473 /**
474 * Function released when the menu hides.
475 * This method is expected to be overridden.
476 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000477 onHide : function () {},
478
Nils Diewald7148c6f2015-05-04 15:07:53 +0000479
Nils Diewald86dad5b2015-01-28 15:09:07 +0000480 /**
481 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000482 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000483 */
Nils Diewald5975d702015-03-09 17:45:42 +0000484 prefix : function (pref) {
485 if (arguments.length === 1) {
486 this._prefix.value(pref);
487 return this;
488 };
489 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000490 },
491
Akronc7448732016-04-27 14:06:58 +0200492 /**
493 * Get the lengthField object.
494 */
495 lengthField : function () {
496 return this._lengthField;
497 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000498
Akron5240b8c2016-05-20 09:17:41 +0200499 /**
500 * Get the associated slider object.
501 */
502 slider : function () {
503 return this._slider;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000504 },
505
Akron5240b8c2016-05-20 09:17:41 +0200506
Nils Diewald86dad5b2015-01-28 15:09:07 +0000507 /**
508 * Delete all visible items from the menu element
509 */
Akrona92fd8d2016-05-24 21:13:41 +0200510 removeItems : function () {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000511 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000512
Nils Diewald2fe12e12015-03-06 16:47:06 +0000513 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000514 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200515 // Leave the prefix and lengthField
Akron9905e2a2016-05-10 16:06:44 +0200516 for (var i = children.length - 1; i >= 3; i--) {
Nils Diewald5975d702015-03-09 17:45:42 +0000517 this._element.removeChild(
518 children[i]
519 );
520 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000521 },
522
Nils Diewald2fe12e12015-03-06 16:47:06 +0000523 /**
524 * Get a specific item from the complete list
525 *
526 * @param {number} index of the list item
527 */
528 item : function (index) {
529 return this._items[index]
530 },
531
532
Nils Diewald86dad5b2015-01-28 15:09:07 +0000533 /**
534 * Get a specific item from the filtered list
535 *
536 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000537 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000538 */
539 liveItem : function (index) {
540 if (this._list === undefined)
541 if (!this._initList())
542 return;
543
544 return this._items[this._list[index]];
545 },
546
Nils Diewald86dad5b2015-01-28 15:09:07 +0000547
548 /**
Akron5240b8c2016-05-20 09:17:41 +0200549 * Get a specific item from the viewport list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000550 *
551 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000552 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000553 */
554 shownItem : function (index) {
555 if (index >= this.limit())
556 return;
Akron9c4d1ae2016-05-25 21:43:22 +0200557 return this.liveItem(this.offset + index);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000558 },
559
560
Nils Diewald2fe12e12015-03-06 16:47:06 +0000561 /**
Akron9c4d1ae2016-05-25 21:43:22 +0200562 * Get the length of the full item list
Nils Diewald2fe12e12015-03-06 16:47:06 +0000563 */
564 length : function () {
565 return this._items.length;
566 },
567
568
569 /**
Akron5240b8c2016-05-20 09:17:41 +0200570 * Length of the filtered item list.
571 */
572 liveLength : function () {
573 if (this._list === undefined)
574 this._initList();
575 return this._list.length;
576 },
577
578
579 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000580 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000581 */
582 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000583
Akronb38afb22016-05-25 19:30:01 +0200584 // No list
585 if (this.liveLength() === 0)
586 return;
587
Akron9c4d1ae2016-05-25 21:43:22 +0200588 // Deactivate old item
589 if (this.position !== -1 && !this._prefix.active()) {
590 this.liveItem(this.position).active(false);
591 };
Nils Diewalde8518f82015-03-18 22:41:49 +0000592
Akron9c4d1ae2016-05-25 21:43:22 +0200593 // Get new active item
594 this.position++;
595 var newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000596
Nils Diewald5975d702015-03-09 17:45:42 +0000597 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000598 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000599
600 // Activate prefix
601 var prefix = this._prefix;
602
Akron9c4d1ae2016-05-25 21:43:22 +0200603 // Prefix is set and not active - choose!
Nils Diewald5975d702015-03-09 17:45:42 +0000604 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200605 this.position--;
Nils Diewald5975d702015-03-09 17:45:42 +0000606 prefix.active(true);
607 return;
608 }
Akron9c4d1ae2016-05-25 21:43:22 +0200609
610 // Choose first item
Nils Diewald5975d702015-03-09 17:45:42 +0000611 else {
Nils Diewald5975d702015-03-09 17:45:42 +0000612 newItem = this.liveItem(0);
Akron9c4d1ae2016-05-25 21:43:22 +0200613 // choose first item
614 this.position = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000615 this._showItems(0);
616 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000617 }
618
Akron5a1f5bb2016-05-23 22:00:39 +0200619 // The next element is after the viewport - roll down
Akron9c4d1ae2016-05-25 21:43:22 +0200620 else if (this.position >= (this.limit() + this.offset)) {
Akron5a1f5bb2016-05-23 22:00:39 +0200621 this.screen(this.position - this.limit() + 1);
622 }
623
624 // The next element is before the viewport - roll up
Akron9c4d1ae2016-05-25 21:43:22 +0200625 else if (this.position <= this.offset) {
Akron5a1f5bb2016-05-23 22:00:39 +0200626 this.screen(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000627 };
Nils Diewald5975d702015-03-09 17:45:42 +0000628
629 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000630 newItem.active(true);
631 },
632
Nils Diewalde8518f82015-03-18 22:41:49 +0000633 /*
Nils Diewald86dad5b2015-01-28 15:09:07 +0000634 * Make the previous item in the menu active
635 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000636 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000637
Akronb38afb22016-05-25 19:30:01 +0200638 // No list
639 if (this.liveLength() === 0)
640 return;
641
Akron9c4d1ae2016-05-25 21:43:22 +0200642 // Deactivate old item
Nils Diewald2d210752015-03-09 19:01:15 +0000643 if (!this._prefix.active()) {
Akron9c4d1ae2016-05-25 21:43:22 +0200644
645 // No active element set
646 if (this.position === -1) {
647 this.position = this.liveLength();
648 }
649
650 // No active element set
651 else {
652 this.liveItem(this.position--).active(false);
653 };
Nils Diewald2d210752015-03-09 19:01:15 +0000654 };
655
Akron9c4d1ae2016-05-25 21:43:22 +0200656 // Get new active item
657 var newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000658
659 // The previous element is undefined - roll to bottom
660 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000661
662 // Activate prefix
663 var prefix = this._prefix;
Akrona92fd8d2016-05-24 21:13:41 +0200664 var offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000665
666 // Normalize offset
Akrona92fd8d2016-05-24 21:13:41 +0200667 offset = offset < 0 ? 0 : offset;
Nils Diewald2d210752015-03-09 19:01:15 +0000668
Akron9c4d1ae2016-05-25 21:43:22 +0200669 // Choose the last item
Akronf86eaea2016-05-13 18:02:27 +0200670 this.position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000671
Akron9c4d1ae2016-05-25 21:43:22 +0200672 // Prefix is set and not active - choose!
Nils Diewald5975d702015-03-09 17:45:42 +0000673 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200674 this.position++;
Nils Diewald5975d702015-03-09 17:45:42 +0000675 prefix.active(true);
Akron9c4d1ae2016-05-25 21:43:22 +0200676 this.offset = offset;
Nils Diewald5975d702015-03-09 17:45:42 +0000677 return;
678 }
Akron9c4d1ae2016-05-25 21:43:22 +0200679
680 // Choose last item
Nils Diewald5975d702015-03-09 17:45:42 +0000681 else {
Akronf86eaea2016-05-13 18:02:27 +0200682 newItem = this.liveItem(this.position);
Akrona92fd8d2016-05-24 21:13:41 +0200683 this._showItems(offset);
Nils Diewald5975d702015-03-09 17:45:42 +0000684 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000685 }
686
Akron5a1f5bb2016-05-23 22:00:39 +0200687 // The previous element is before the view - roll up
Akron9c4d1ae2016-05-25 21:43:22 +0200688 else if (this.position < this.offset) {
Akron5a1f5bb2016-05-23 22:00:39 +0200689 this.screen(this.position);
690 }
691
692 // The previous element is after the view - roll down
Akron9c4d1ae2016-05-25 21:43:22 +0200693 else if (this.position >= (this.limit() + this.offset)) {
Akron5a1f5bb2016-05-23 22:00:39 +0200694 this.screen(this.position - this.limit() + 2);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000695 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000696
Nils Diewald5975d702015-03-09 17:45:42 +0000697 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000698 newItem.active(true);
699 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000700
Akron3c2730f2016-05-24 15:08:29 +0200701 /**
702 * Move the page up by limit!
703 */
704 pageUp : function () {
Akron9c4d1ae2016-05-25 21:43:22 +0200705 this.screen(this.offset - this.limit());
Akron3c2730f2016-05-24 15:08:29 +0200706 },
707
708
709 /**
710 * Move the page down by limit!
711 */
712 pageDown : function () {
Akron9c4d1ae2016-05-25 21:43:22 +0200713 this.screen(this.offset + this.limit());
Akron3c2730f2016-05-24 15:08:29 +0200714 },
715
Nils Diewald86dad5b2015-01-28 15:09:07 +0000716
Akron5240b8c2016-05-20 09:17:41 +0200717 // Unmark all items
718 _unmark : function () {
719 for (var i in this._list) {
720 var item = this._items[this._list[i]];
721 item.lowlight();
722 item.active(false);
723 };
724 },
725
Akron5240b8c2016-05-20 09:17:41 +0200726 // Set boundary for viewport
727 _boundary : function (bool) {
728 this.item(this._list[0]).noMore(bool);
729 this.item(this._list[this._list.length - 1]).noMore(bool);
730 },
731
732
733 // Append Items that should be shown
734 _showItems : function (off) {
735
Akrona92fd8d2016-05-24 21:13:41 +0200736 // optimization: scroll down one step
Akron9c4d1ae2016-05-25 21:43:22 +0200737 if (this.offset === (off - 1)) {
738 this.offset = off;
739
740 // Remove the HTML node from the first item
741 // leave lengthField/prefix/slider
742 this._element.removeChild(this._element.children[3]);
743 var pos = this.offset + this.limit() - 1;
Akrona92fd8d2016-05-24 21:13:41 +0200744 this._append(this._list[pos]);
745 }
Akron5240b8c2016-05-20 09:17:41 +0200746
Akrona92fd8d2016-05-24 21:13:41 +0200747 // optimization: scroll up one step
Akron9c4d1ae2016-05-25 21:43:22 +0200748 else if (this.offset === (off + 1)) {
749 this.offset = off;
750
751 // Remove the HTML node from the last item
752 this._element.removeChild(this._element.lastChild);
753
754 this._prepend(this._list[this.offset]);
Akrona92fd8d2016-05-24 21:13:41 +0200755 }
756 else {
Akron9c4d1ae2016-05-25 21:43:22 +0200757 this.offset = off;
Akron5240b8c2016-05-20 09:17:41 +0200758
Akrona92fd8d2016-05-24 21:13:41 +0200759 // Remove all items
760 this.removeItems();
Akron5240b8c2016-05-20 09:17:41 +0200761
Akrona92fd8d2016-05-24 21:13:41 +0200762 // Use list
763 var shown = 0;
764 var i;
Akron5240b8c2016-05-20 09:17:41 +0200765
Akrona92fd8d2016-05-24 21:13:41 +0200766 for (i in this._list) {
767
768 // Don't show - it's before offset
769 shown++;
770 if (shown <= off)
771 continue;
772
773 var itemNr = this._list[i];
774 var item = this.item(itemNr);
775 this._append(itemNr);
776
777 if (shown >= (this.limit() + off))
778 break;
779 };
Akron5240b8c2016-05-20 09:17:41 +0200780 };
781
782 // set the slider to the new offset
Akron9c4d1ae2016-05-25 21:43:22 +0200783 this._slider.offset(this.offset);
Akron5240b8c2016-05-20 09:17:41 +0200784 },
785
786
787 // Append item to the shown list based on index
788 _append : function (i) {
789 var item = this.item(i);
790
791 // Highlight based on prefix
Akrona92fd8d2016-05-24 21:13:41 +0200792 if (this.prefix().length > 0) {
Akron6ffad5d2016-05-24 17:16:58 +0200793 item.highlight(this.prefix().toLowerCase());
Akrona92fd8d2016-05-24 21:13:41 +0200794 };
795
Akron5240b8c2016-05-20 09:17:41 +0200796
797 // Append element
798 this.element().appendChild(item.element());
799 },
800
801
802 // Prepend item to the shown list based on index
803 _prepend : function (i) {
804 var item = this.item(i);
805
806 // Highlight based on prefix
Akrona92fd8d2016-05-24 21:13:41 +0200807 if (this.prefix().length > 0) {
Akron6ffad5d2016-05-24 17:16:58 +0200808 item.highlight(this.prefix().toLowerCase());
Akrona92fd8d2016-05-24 21:13:41 +0200809 };
Akron5240b8c2016-05-20 09:17:41 +0200810
811 var e = this.element();
Akron9c4d1ae2016-05-25 21:43:22 +0200812
Akron5240b8c2016-05-20 09:17:41 +0200813 // Append element after lengthField/prefix/slider
814 e.insertBefore(
815 item.element(),
816 e.children[3]
817 );
Nils Diewald2fe12e12015-03-06 16:47:06 +0000818 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000819 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000820});