blob: 2cd99d661c37fae2869a26f03e288b0c96f480b4 [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.
Nils Diewald2488d052015-04-09 21:46:02 +000010 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000011define([
12 'menu/item',
13 'menu/prefix',
Akronc7448732016-04-27 14:06:58 +020014 'menu/lengthField',
Akron9905e2a2016-05-10 16:06:44 +020015 'menu/slider',
Nils Diewald0e6992a2015-04-14 20:13:52 +000016 'util'
17], function (defaultItemClass,
Akronc7448732016-04-27 14:06:58 +020018 defaultPrefixClass,
Akron9905e2a2016-05-10 16:06:44 +020019 defaultLengthFieldClass,
20 sliderClass) {
Nils Diewaldfda29d92015-01-22 17:28:01 +000021
Nils Diewald0e6992a2015-04-14 20:13:52 +000022 // Default maximum number of menu items
23 var menuLimit = 8;
24
25 function _codeFromEvent (e) {
26 if (e.charCode && (e.keyCode == 0))
27 return e.charCode
28 return e.keyCode;
Nils Diewald59c02fc2015-03-07 01:29:09 +000029 };
30
Nils Diewald86dad5b2015-01-28 15:09:07 +000031
32 /**
33 * List of items for drop down menu (complete).
34 * Only a sublist of the menu is filtered (live).
35 * Only a sublist of the filtered menu is visible (shown).
36 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000037 return {
Nils Diewald86dad5b2015-01-28 15:09:07 +000038 /**
39 * Create new Menu based on the action prefix
40 * and a list of menu items.
41 *
42 * @this {Menu}
43 * @constructor
44 * @param {string} Context prefix
45 * @param {Array.<Array.<string>>} List of menu items
46 */
47 create : function (params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000048 return Object.create(this)._init(params);
Nils Diewald86dad5b2015-01-28 15:09:07 +000049 },
50
Akron5240b8c2016-05-20 09:17:41 +020051 // Initialize list
52 _init : function (itemClass, prefixClass, lengthFieldClass, params) {
Akrona92fd8d2016-05-24 21:13:41 +020053
Akron5240b8c2016-05-20 09:17:41 +020054 this._itemClass = itemClass || defaultItemClass;
55
56 // Add prefix object
57 if (prefixClass !== undefined) {
58 this._prefix = prefixClass.create();
59 }
60 else {
61 this._prefix = defaultPrefixClass.create();
62 };
63 this._prefix._menu = this;
64
65 // Add lengthField object
66 if (lengthFieldClass !== undefined) {
67 this._lengthField = lengthFieldClass.create();
68 }
69 else {
70 this._lengthField = defaultLengthFieldClass.create();
71 };
72 this._lengthField._menu = this;
73
74 // Initialize slider
75 this._slider = sliderClass.create(this);
76
77 // Create the element
Akron9c4d1ae2016-05-25 21:43:22 +020078 var el = document.createElement("ul");
79 with (el) {
80 style.opacity = 0;
81 style.outline = 0;
82 setAttribute('tabindex', 0);
83 classList.add('menu', 'roll');
84 appendChild(this._prefix.element());
85 appendChild(this._lengthField.element());
86 appendChild(this._slider.element());
87 };
Akron5240b8c2016-05-20 09:17:41 +020088
89 // This has to be cleaned up later on
Akron9c4d1ae2016-05-25 21:43:22 +020090 el["menu"] = this;
Akron5240b8c2016-05-20 09:17:41 +020091
92 // Arrow keys
Akron9c4d1ae2016-05-25 21:43:22 +020093 el.addEventListener(
Akron5240b8c2016-05-20 09:17:41 +020094 'keydown',
Akrona92fd8d2016-05-24 21:13:41 +020095 this._keydown.bind(this),
Akron5240b8c2016-05-20 09:17:41 +020096 false
97 );
98
99 // Strings
Akron9c4d1ae2016-05-25 21:43:22 +0200100 el.addEventListener(
Akron5240b8c2016-05-20 09:17:41 +0200101 'keypress',
Akrona92fd8d2016-05-24 21:13:41 +0200102 this._keypress.bind(this),
Akron5240b8c2016-05-20 09:17:41 +0200103 false
104 );
105
106 // Mousewheel
Akron9c4d1ae2016-05-25 21:43:22 +0200107 el.addEventListener(
Akron5240b8c2016-05-20 09:17:41 +0200108 'wheel',
Akrona92fd8d2016-05-24 21:13:41 +0200109 this._mousewheel.bind(this),
Akron5240b8c2016-05-20 09:17:41 +0200110 false
111 );
Akron9c4d1ae2016-05-25 21:43:22 +0200112 this._element = el;
Akron5240b8c2016-05-20 09:17:41 +0200113
Akron5240b8c2016-05-20 09:17:41 +0200114 this._items = new Array();
Akron5240b8c2016-05-20 09:17:41 +0200115
Akron9c4d1ae2016-05-25 21:43:22 +0200116 var i = 0;
Akron5240b8c2016-05-20 09:17:41 +0200117 // Initialize item list based on parameters
118 for (i in params) {
119 var obj = this._itemClass.create(params[i]);
120
121 // This may become circular
122 obj["_menu"] = this;
123 this._lengthField.add(params[i]);
124 this._items.push(obj);
125 };
126
Akron9c4d1ae2016-05-25 21:43:22 +0200127 this._limit = menuLimit;
128 this._slider.length(this.liveLength())
129 .limit(this._limit)
130 .reInit();
Akron5240b8c2016-05-20 09:17:41 +0200131
Akron5240b8c2016-05-20 09:17:41 +0200132 this._firstActive = false; // Show the first item active always?
Akron9c4d1ae2016-05-25 21:43:22 +0200133 this.offset = 0;
134 this.position = 0;
Akron5240b8c2016-05-20 09:17:41 +0200135 return this;
136 },
137
138 // Initialize the item list
139 _initList : function () {
140
141 // Create a new list
142 if (this._list === undefined) {
143 this._list = [];
144 }
145 else if (this._list.length !== 0) {
146 this._boundary(false);
147 this._list.length = 0;
148 };
149
150 // Offset is initially zero
Akron9c4d1ae2016-05-25 21:43:22 +0200151 this.offset = 0;
Akron5240b8c2016-05-20 09:17:41 +0200152
153 // There is no prefix set
154 if (this.prefix().length <= 0) {
155
156 // add all items to the list and lowlight
Akron97752a72016-05-25 14:43:07 +0200157 var i = 0;
158 for (; i < this._items.length; i++) {
Akron5240b8c2016-05-20 09:17:41 +0200159 this._list.push(i);
160 this._items[i].lowlight();
161 };
162
Akron9c4d1ae2016-05-25 21:43:22 +0200163 this._slider.length(i).reInit();;
Akron97752a72016-05-25 14:43:07 +0200164
Akron5240b8c2016-05-20 09:17:41 +0200165 return true;
166 };
167
168 /*
169 * There is a prefix set, so filter the list!
170 */
171 var pos;
Akron6ffad5d2016-05-24 17:16:58 +0200172 var prefix = " " + this.prefix().toLowerCase();
Akron5240b8c2016-05-20 09:17:41 +0200173
174 // Iterate over all items and choose preferred matching items
175 // i.e. the matching happens at the word start
176 for (pos = 0; pos < this._items.length; pos++) {
Akron6ffad5d2016-05-24 17:16:58 +0200177 if ((this.item(pos).lcField().indexOf(prefix)) >= 0)
Akron5240b8c2016-05-20 09:17:41 +0200178 this._list.push(pos);
179 };
180
181 // The list is empty - so lower your expectations
182 // Iterate over all items and choose matching items
183 // i.e. the matching happens anywhere in the word
Akron6ffad5d2016-05-24 17:16:58 +0200184 prefix = prefix.substring(1);
Akron5240b8c2016-05-20 09:17:41 +0200185 if (this._list.length == 0) {
186 for (pos = 0; pos < this._items.length; pos++) {
Akron6ffad5d2016-05-24 17:16:58 +0200187 if ((this.item(pos).lcField().indexOf(prefix)) >= 0)
Akron5240b8c2016-05-20 09:17:41 +0200188 this._list.push(pos);
189 };
190 };
191
Akron9c4d1ae2016-05-25 21:43:22 +0200192 this._slider.length(this._list.length).reInit();
Akron6ed13992016-05-23 18:06:05 +0200193
Akron5240b8c2016-05-20 09:17:41 +0200194 // Filter was successful - yeah!
195 return this._list.length > 0 ? true : false;
196 },
197
198
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000199 /**
200 * Destroy this menu
201 * (in case you don't trust the
202 * mark and sweep GC)!
203 */
204 destroy : function () {
Akron47c086c2016-05-18 21:22:06 +0200205
Akron5240b8c2016-05-20 09:17:41 +0200206 // Remove circular reference to "this" in menu
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000207 if (this._element != undefined)
208 delete this._element["menu"];
209
Akron5240b8c2016-05-20 09:17:41 +0200210 // Remove circular reference to "this" in items
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000211 for (var i = 0; i < this._items.length; i++) {
212 delete this._items[i]["_menu"];
213 };
Akron5240b8c2016-05-20 09:17:41 +0200214
215 // Remove circular reference to "this" in prefix
Nils Diewald5c5a7472015-04-02 22:13:38 +0000216 delete this._prefix['_menu'];
Akron5240b8c2016-05-20 09:17:41 +0200217 delete this._lengthField['_menu'];
218 delete this._slider['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000219 },
220
Nils Diewald7148c6f2015-05-04 15:07:53 +0000221
222 /**
223 * Focus on this menu.
224 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000225 focus : function () {
226 this._element.focus();
227 },
228
Nils Diewald7148c6f2015-05-04 15:07:53 +0000229
Nils Diewald59c02fc2015-03-07 01:29:09 +0000230 // mouse wheel treatment
231 _mousewheel : function (e) {
232 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000233
234 delta = e.deltaY / 120;
235 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000236 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +0000237 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000238 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000239 e.halt();
240 },
241
Nils Diewald7148c6f2015-05-04 15:07:53 +0000242
Nils Diewald59c02fc2015-03-07 01:29:09 +0000243 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +0000244 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000245 var code = _codeFromEvent(e);
246
Nils Diewald59c02fc2015-03-07 01:29:09 +0000247 switch (code) {
248 case 27: // 'Esc'
249 e.halt();
250 this.hide();
251 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000252
Nils Diewald59c02fc2015-03-07 01:29:09 +0000253 case 38: // 'Up'
254 e.halt();
255 this.prev();
256 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000257 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +0000258 e.halt();
Akron3c2730f2016-05-24 15:08:29 +0200259 this.pageUp();
Nils Diewald5975d702015-03-09 17:45:42 +0000260 break;
261 case 40: // 'Down'
262 e.halt();
263 this.next();
264 break;
265 case 34: // 'Page down'
266 e.halt();
Akron3c2730f2016-05-24 15:08:29 +0200267 this.pageDown();
Nils Diewald5975d702015-03-09 17:45:42 +0000268 break;
269 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000270 if (this._prefix.active())
271 break;
272
Akronf86eaea2016-05-13 18:02:27 +0200273 var item = this.liveItem(this.position);
Akron5ef4fa02015-06-02 16:25:14 +0200274
Nils Diewald5975d702015-03-09 17:45:42 +0000275 if (item["further"] !== undefined) {
276 item["further"].bind(item).apply();
Nils Diewald5975d702015-03-09 17:45:42 +0000277 };
Akron5ef4fa02015-06-02 16:25:14 +0200278
279 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000280 break;
281 case 13: // 'Enter'
282
283 // Click on prefix
284 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000285 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000286
287 // Click on item
288 else
Akronf86eaea2016-05-13 18:02:27 +0200289 this.liveItem(this.position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000290 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000291 break;
292 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000293 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000294 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000295 e.halt();
296 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000297 };
298 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000299
Nils Diewald47f366b2015-04-15 20:06:35 +0000300 // Add characters to prefix
301 _keypress : function (e) {
Akron9c2f9382016-05-25 16:36:04 +0200302 if (e.charCode !== 0) {
303 e.halt();
304 var c = String.fromCharCode(_codeFromEvent(e));
Nils Diewald5975d702015-03-09 17:45:42 +0000305
Akron9c2f9382016-05-25 16:36:04 +0200306 // Add prefix
307 this._prefix.add(c);
308 this.show();
309 };
Nils Diewald59c02fc2015-03-07 01:29:09 +0000310 },
311
Akron47c086c2016-05-18 21:22:06 +0200312 /**
Akron5240b8c2016-05-20 09:17:41 +0200313 * Show a screen with a given offset
314 * in the viewport.
Akron47c086c2016-05-18 21:22:06 +0200315 */
316 screen : function (nr) {
Akron3c2730f2016-05-24 15:08:29 +0200317 if (nr < 0) {
318 nr = 0
319 }
Akron6ac58442016-05-24 16:52:29 +0200320 else if (nr > (this.liveLength() - this.limit())) {
321 nr = (this.liveLength() - this.limit());
Akron3c2730f2016-05-24 15:08:29 +0200322 };
323
Akron9c4d1ae2016-05-25 21:43:22 +0200324 if (this.offset === nr)
Akron47c086c2016-05-18 21:22:06 +0200325 return;
Akron5a1f5bb2016-05-23 22:00:39 +0200326
Akron47c086c2016-05-18 21:22:06 +0200327 this._showItems(nr);
328 },
329
Nils Diewald2fe12e12015-03-06 16:47:06 +0000330 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000331 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000332 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000333 element : function () {
334 return this._element;
335 },
336
Nils Diewald2fe12e12015-03-06 16:47:06 +0000337 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000338 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000339 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000340 itemClass : function () {
341 return this._itemClass;
342 },
343
344 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000345 * Get and set the numerical value
346 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000347 */
348 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000349 if (arguments.length === 1) {
Akron5240b8c2016-05-20 09:17:41 +0200350 if (this._limit !== limit) {
351 this._limit = limit;
Akron9c4d1ae2016-05-25 21:43:22 +0200352 this._slider.limit(limit).reInit();
Akron5240b8c2016-05-20 09:17:41 +0200353 };
Nils Diewald5975d702015-03-09 17:45:42 +0000354 return this;
355 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000356 return this._limit;
357 },
358
Nils Diewald7148c6f2015-05-04 15:07:53 +0000359
Nils Diewald86dad5b2015-01-28 15:09:07 +0000360 /**
361 * Upgrade this object to another object,
362 * while private data stays intact.
363 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000364 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000365 */
366 upgradeTo : function (props) {
367 for (var prop in props) {
368 this[prop] = props[prop];
369 };
370 return this;
371 },
372
Nils Diewald7148c6f2015-05-04 15:07:53 +0000373
Nils Diewald86dad5b2015-01-28 15:09:07 +0000374 /**
Akron97752a72016-05-25 14:43:07 +0200375 * Filter the list and make it visible.
376 * This is always called once the prefix changes.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000377 *
378 * @param {string} Prefix for filtering the list
379 */
Akron6ed13992016-05-23 18:06:05 +0200380 show : function (active) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000381
Akron5240b8c2016-05-20 09:17:41 +0200382 // show menu based on initial offset
Akron6ac58442016-05-24 16:52:29 +0200383 this._unmark(); // Unmark everything that was marked before
Akrona92fd8d2016-05-24 21:13:41 +0200384 this.removeItems();
Akron6ed13992016-05-23 18:06:05 +0200385
386 // Initialize the list
387 if (!this._initList()) {
Akron6ac58442016-05-24 16:52:29 +0200388
Akron6ed13992016-05-23 18:06:05 +0200389 // The prefix is not active
390 this._prefix.active(true);
391
392 // finally show the element
393 this._element.style.opacity = 1;
394
395 return true;
396 };
397
398 var offset = 0;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000399
Akron9c2f9382016-05-25 16:36:04 +0200400
401 // 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
412 if (active > this._limit) {
413 offset = active;
Akron6ac58442016-05-24 16:52:29 +0200414 if (offset > (this.liveLength() - this._limit)) {
415 offset = this.liveLength() - this._limit;
Akron6ed13992016-05-23 18:06:05 +0200416 };
417 };
418
419 this.position = active;
Akron6ed13992016-05-23 18:06:05 +0200420 }
421
Akron9c2f9382016-05-25 16:36:04 +0200422 // Choose the first item
Akron6ed13992016-05-23 18:06:05 +0200423 else if (this._firstActive) {
Akron47c086c2016-05-18 21:22:06 +0200424 this.position = 0;
Akron47c086c2016-05-18 21:22:06 +0200425 }
Akroncb351d62016-05-19 23:10:33 +0200426
Akron9c2f9382016-05-25 16:36:04 +0200427 // Choose no item
Akron47c086c2016-05-18 21:22:06 +0200428 else {
429 this.position = -1;
Akron6ed13992016-05-23 18:06:05 +0200430 };
431
Akron9c4d1ae2016-05-25 21:43:22 +0200432 this.offset = offset;
Akron6ed13992016-05-23 18:06:05 +0200433 this._showItems(offset); // Show new item list
434
435 // Make chosen value active
436 if (this.position !== -1) {
Akron3c2730f2016-05-24 15:08:29 +0200437 this.liveItem(this.position).active(true);
Akron6ed13992016-05-23 18:06:05 +0200438 };
Akron37513a62015-11-17 01:07:11 +0100439
Akron5240b8c2016-05-20 09:17:41 +0200440 // The prefix is not active
Nils Diewalde8518f82015-03-18 22:41:49 +0000441 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000442
Akron5240b8c2016-05-20 09:17:41 +0200443 // finally show the element
Nils Diewald2fe12e12015-03-06 16:47:06 +0000444 this._element.style.opacity = 1;
445
Nils Diewald86dad5b2015-01-28 15:09:07 +0000446 // Add classes for rolling menus
447 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000448 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000449 },
450
Nils Diewald7148c6f2015-05-04 15:07:53 +0000451
452 /**
453 * Hide the menu and call the onHide callback.
454 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000455 hide : function () {
Akrona92fd8d2016-05-24 21:13:41 +0200456 this.removeItems();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000457 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000458 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000459 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000460 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000461 },
462
Nils Diewald7148c6f2015-05-04 15:07:53 +0000463 /**
464 * Function released when the menu hides.
465 * This method is expected to be overridden.
466 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000467 onHide : function () {},
468
Nils Diewald7148c6f2015-05-04 15:07:53 +0000469
Nils Diewald86dad5b2015-01-28 15:09:07 +0000470 /**
471 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000472 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000473 */
Nils Diewald5975d702015-03-09 17:45:42 +0000474 prefix : function (pref) {
475 if (arguments.length === 1) {
476 this._prefix.value(pref);
477 return this;
478 };
479 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000480 },
481
Akronc7448732016-04-27 14:06:58 +0200482 /**
483 * Get the lengthField object.
484 */
485 lengthField : function () {
486 return this._lengthField;
487 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000488
Akron5240b8c2016-05-20 09:17:41 +0200489 /**
490 * Get the associated slider object.
491 */
492 slider : function () {
493 return this._slider;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000494 },
495
Akron5240b8c2016-05-20 09:17:41 +0200496
Nils Diewald86dad5b2015-01-28 15:09:07 +0000497 /**
498 * Delete all visible items from the menu element
499 */
Akrona92fd8d2016-05-24 21:13:41 +0200500 removeItems : function () {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000501 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000502
Nils Diewald2fe12e12015-03-06 16:47:06 +0000503 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000504 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200505 // Leave the prefix and lengthField
Akron9905e2a2016-05-10 16:06:44 +0200506 for (var i = children.length - 1; i >= 3; i--) {
Nils Diewald5975d702015-03-09 17:45:42 +0000507 this._element.removeChild(
508 children[i]
509 );
510 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000511 },
512
Nils Diewald2fe12e12015-03-06 16:47:06 +0000513 /**
514 * Get a specific item from the complete list
515 *
516 * @param {number} index of the list item
517 */
518 item : function (index) {
519 return this._items[index]
520 },
521
522
Nils Diewald86dad5b2015-01-28 15:09:07 +0000523 /**
524 * Get a specific item from the filtered list
525 *
526 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000527 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000528 */
529 liveItem : function (index) {
530 if (this._list === undefined)
531 if (!this._initList())
532 return;
533
534 return this._items[this._list[index]];
535 },
536
Nils Diewald86dad5b2015-01-28 15:09:07 +0000537
538 /**
Akron5240b8c2016-05-20 09:17:41 +0200539 * Get a specific item from the viewport list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000540 *
541 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000542 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000543 */
544 shownItem : function (index) {
545 if (index >= this.limit())
546 return;
Akron9c4d1ae2016-05-25 21:43:22 +0200547 return this.liveItem(this.offset + index);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000548 },
549
550
Nils Diewald2fe12e12015-03-06 16:47:06 +0000551 /**
Akron9c4d1ae2016-05-25 21:43:22 +0200552 * Get the length of the full item list
Nils Diewald2fe12e12015-03-06 16:47:06 +0000553 */
554 length : function () {
555 return this._items.length;
556 },
557
558
559 /**
Akron5240b8c2016-05-20 09:17:41 +0200560 * Length of the filtered item list.
561 */
562 liveLength : function () {
563 if (this._list === undefined)
564 this._initList();
565 return this._list.length;
566 },
567
568
569 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000570 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000571 */
572 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000573
Akronb38afb22016-05-25 19:30:01 +0200574 // No list
575 if (this.liveLength() === 0)
576 return;
577
Akron9c4d1ae2016-05-25 21:43:22 +0200578 // Deactivate old item
579 if (this.position !== -1 && !this._prefix.active()) {
580 this.liveItem(this.position).active(false);
581 };
Nils Diewalde8518f82015-03-18 22:41:49 +0000582
Akron9c4d1ae2016-05-25 21:43:22 +0200583 // Get new active item
584 this.position++;
585 var newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000586
Nils Diewald5975d702015-03-09 17:45:42 +0000587 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000588 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000589
590 // Activate prefix
591 var prefix = this._prefix;
592
Akron9c4d1ae2016-05-25 21:43:22 +0200593 // Prefix is set and not active - choose!
Nils Diewald5975d702015-03-09 17:45:42 +0000594 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200595 this.position--;
Nils Diewald5975d702015-03-09 17:45:42 +0000596 prefix.active(true);
597 return;
598 }
Akron9c4d1ae2016-05-25 21:43:22 +0200599
600 // Choose first item
Nils Diewald5975d702015-03-09 17:45:42 +0000601 else {
Nils Diewald5975d702015-03-09 17:45:42 +0000602 newItem = this.liveItem(0);
Akron9c4d1ae2016-05-25 21:43:22 +0200603 // choose first item
604 this.position = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000605 this._showItems(0);
606 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000607 }
608
Akron5a1f5bb2016-05-23 22:00:39 +0200609 // The next element is after the viewport - roll down
Akron9c4d1ae2016-05-25 21:43:22 +0200610 else if (this.position >= (this.limit() + this.offset)) {
Akron5a1f5bb2016-05-23 22:00:39 +0200611 this.screen(this.position - this.limit() + 1);
612 }
613
614 // The next element is before the viewport - roll up
Akron9c4d1ae2016-05-25 21:43:22 +0200615 else if (this.position <= this.offset) {
Akron5a1f5bb2016-05-23 22:00:39 +0200616 this.screen(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000617 };
Nils Diewald5975d702015-03-09 17:45:42 +0000618
619 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000620 newItem.active(true);
621 },
622
Nils Diewalde8518f82015-03-18 22:41:49 +0000623 /*
Nils Diewald86dad5b2015-01-28 15:09:07 +0000624 * Make the previous item in the menu active
625 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000626 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000627
Akronb38afb22016-05-25 19:30:01 +0200628 // No list
629 if (this.liveLength() === 0)
630 return;
631
Akron9c4d1ae2016-05-25 21:43:22 +0200632 // Deactivate old item
Nils Diewald2d210752015-03-09 19:01:15 +0000633 if (!this._prefix.active()) {
Akron9c4d1ae2016-05-25 21:43:22 +0200634
635 // No active element set
636 if (this.position === -1) {
637 this.position = this.liveLength();
638 }
639
640 // No active element set
641 else {
642 this.liveItem(this.position--).active(false);
643 };
Nils Diewald2d210752015-03-09 19:01:15 +0000644 };
645
Akron9c4d1ae2016-05-25 21:43:22 +0200646 // Get new active item
647 var newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000648
649 // The previous element is undefined - roll to bottom
650 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000651
652 // Activate prefix
653 var prefix = this._prefix;
Akrona92fd8d2016-05-24 21:13:41 +0200654 var offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000655
656 // Normalize offset
Akrona92fd8d2016-05-24 21:13:41 +0200657 offset = offset < 0 ? 0 : offset;
Nils Diewald2d210752015-03-09 19:01:15 +0000658
Akron9c4d1ae2016-05-25 21:43:22 +0200659 // Choose the last item
Akronf86eaea2016-05-13 18:02:27 +0200660 this.position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000661
Akron9c4d1ae2016-05-25 21:43:22 +0200662 // Prefix is set and not active - choose!
Nils Diewald5975d702015-03-09 17:45:42 +0000663 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200664 this.position++;
Nils Diewald5975d702015-03-09 17:45:42 +0000665 prefix.active(true);
Akron9c4d1ae2016-05-25 21:43:22 +0200666 this.offset = offset;
Nils Diewald5975d702015-03-09 17:45:42 +0000667 return;
668 }
Akron9c4d1ae2016-05-25 21:43:22 +0200669
670 // Choose last item
Nils Diewald5975d702015-03-09 17:45:42 +0000671 else {
Akronf86eaea2016-05-13 18:02:27 +0200672 newItem = this.liveItem(this.position);
Akrona92fd8d2016-05-24 21:13:41 +0200673 this._showItems(offset);
Nils Diewald5975d702015-03-09 17:45:42 +0000674 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000675 }
676
Akron5a1f5bb2016-05-23 22:00:39 +0200677 // The previous element is before the view - roll up
Akron9c4d1ae2016-05-25 21:43:22 +0200678 else if (this.position < this.offset) {
Akron5a1f5bb2016-05-23 22:00:39 +0200679 this.screen(this.position);
680 }
681
682 // The previous element is after the view - roll down
Akron9c4d1ae2016-05-25 21:43:22 +0200683 else if (this.position >= (this.limit() + this.offset)) {
Akron5a1f5bb2016-05-23 22:00:39 +0200684 this.screen(this.position - this.limit() + 2);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000685 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000686
Nils Diewald5975d702015-03-09 17:45:42 +0000687 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000688 newItem.active(true);
689 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000690
Akron3c2730f2016-05-24 15:08:29 +0200691 /**
692 * Move the page up by limit!
693 */
694 pageUp : function () {
Akron9c4d1ae2016-05-25 21:43:22 +0200695 this.screen(this.offset - this.limit());
Akron3c2730f2016-05-24 15:08:29 +0200696 },
697
698
699 /**
700 * Move the page down by limit!
701 */
702 pageDown : function () {
Akron9c4d1ae2016-05-25 21:43:22 +0200703 this.screen(this.offset + this.limit());
Akron3c2730f2016-05-24 15:08:29 +0200704 },
705
Nils Diewald86dad5b2015-01-28 15:09:07 +0000706
Akron5240b8c2016-05-20 09:17:41 +0200707 // Unmark all items
708 _unmark : function () {
709 for (var i in this._list) {
710 var item = this._items[this._list[i]];
711 item.lowlight();
712 item.active(false);
713 };
714 },
715
Akron5240b8c2016-05-20 09:17:41 +0200716 // Set boundary for viewport
717 _boundary : function (bool) {
718 this.item(this._list[0]).noMore(bool);
719 this.item(this._list[this._list.length - 1]).noMore(bool);
720 },
721
722
723 // Append Items that should be shown
724 _showItems : function (off) {
725
Akrona92fd8d2016-05-24 21:13:41 +0200726 // optimization: scroll down one step
Akron9c4d1ae2016-05-25 21:43:22 +0200727 if (this.offset === (off - 1)) {
728 this.offset = off;
729
730 // Remove the HTML node from the first item
731 // leave lengthField/prefix/slider
732 this._element.removeChild(this._element.children[3]);
733 var pos = this.offset + this.limit() - 1;
Akrona92fd8d2016-05-24 21:13:41 +0200734 this._append(this._list[pos]);
735 }
Akron5240b8c2016-05-20 09:17:41 +0200736
Akrona92fd8d2016-05-24 21:13:41 +0200737 // optimization: scroll up one step
Akron9c4d1ae2016-05-25 21:43:22 +0200738 else if (this.offset === (off + 1)) {
739 this.offset = off;
740
741 // Remove the HTML node from the last item
742 this._element.removeChild(this._element.lastChild);
743
744 this._prepend(this._list[this.offset]);
Akrona92fd8d2016-05-24 21:13:41 +0200745 }
746 else {
Akron9c4d1ae2016-05-25 21:43:22 +0200747 this.offset = off;
Akron5240b8c2016-05-20 09:17:41 +0200748
Akrona92fd8d2016-05-24 21:13:41 +0200749 // Remove all items
750 this.removeItems();
Akron5240b8c2016-05-20 09:17:41 +0200751
Akrona92fd8d2016-05-24 21:13:41 +0200752 // Use list
753 var shown = 0;
754 var i;
Akron5240b8c2016-05-20 09:17:41 +0200755
Akrona92fd8d2016-05-24 21:13:41 +0200756 for (i in this._list) {
757
758 // Don't show - it's before offset
759 shown++;
760 if (shown <= off)
761 continue;
762
763 var itemNr = this._list[i];
764 var item = this.item(itemNr);
765 this._append(itemNr);
766
767 if (shown >= (this.limit() + off))
768 break;
769 };
Akron5240b8c2016-05-20 09:17:41 +0200770 };
771
772 // set the slider to the new offset
Akron9c4d1ae2016-05-25 21:43:22 +0200773 this._slider.offset(this.offset);
Akron5240b8c2016-05-20 09:17:41 +0200774 },
775
776
777 // Append item to the shown list based on index
778 _append : function (i) {
779 var item = this.item(i);
780
781 // Highlight based on prefix
Akrona92fd8d2016-05-24 21:13:41 +0200782 if (this.prefix().length > 0) {
Akron6ffad5d2016-05-24 17:16:58 +0200783 item.highlight(this.prefix().toLowerCase());
Akrona92fd8d2016-05-24 21:13:41 +0200784 };
785
Akron5240b8c2016-05-20 09:17:41 +0200786
787 // Append element
788 this.element().appendChild(item.element());
789 },
790
791
792 // Prepend item to the shown list based on index
793 _prepend : function (i) {
794 var item = this.item(i);
795
796 // Highlight based on prefix
Akrona92fd8d2016-05-24 21:13:41 +0200797 if (this.prefix().length > 0) {
Akron6ffad5d2016-05-24 17:16:58 +0200798 item.highlight(this.prefix().toLowerCase());
Akrona92fd8d2016-05-24 21:13:41 +0200799 };
Akron5240b8c2016-05-20 09:17:41 +0200800
801 var e = this.element();
Akron9c4d1ae2016-05-25 21:43:22 +0200802
Akron5240b8c2016-05-20 09:17:41 +0200803 // Append element after lengthField/prefix/slider
804 e.insertBefore(
805 item.element(),
806 e.children[3]
807 );
Nils Diewald2fe12e12015-03-06 16:47:06 +0000808 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000809 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000810});