blob: cc75926338616627e46d428e3faece3012754c93 [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 Diewald20f7ace2015-05-07 12:51:34 +00007 * TODO: First item shouldn't be automatically highlighted!
Nils Diewald0e6992a2015-04-14 20:13:52 +00008 * TODO: space is not a valid prefix!
Nils Diewald7148c6f2015-05-04 15:07:53 +00009 * TODO: Prefix should be case sensitive!
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
Nils Diewald6e43ffd2015-03-25 18:55:39 +000051 /**
52 * Destroy this menu
53 * (in case you don't trust the
54 * mark and sweep GC)!
55 */
56 destroy : function () {
Akron47c086c2016-05-18 21:22:06 +020057 this._prefix._menu = undefined;
58 this._lengthField._menu = undefined;
59 this._slider._menu = undefined;
60
Nils Diewald6e43ffd2015-03-25 18:55:39 +000061 if (this._element != undefined)
62 delete this._element["menu"];
63
64 for (var i = 0; i < this._items.length; i++) {
65 delete this._items[i]["_menu"];
66 };
Nils Diewald5c5a7472015-04-02 22:13:38 +000067 delete this._prefix['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +000068 },
69
Nils Diewald7148c6f2015-05-04 15:07:53 +000070
71 /**
72 * Focus on this menu.
73 */
Nils Diewald2fe12e12015-03-06 16:47:06 +000074 focus : function () {
75 this._element.focus();
76 },
77
Nils Diewald7148c6f2015-05-04 15:07:53 +000078
Nils Diewald59c02fc2015-03-07 01:29:09 +000079 // mouse wheel treatment
80 _mousewheel : function (e) {
81 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +000082
83 delta = e.deltaY / 120;
84 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000085 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +000086 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000087 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +000088 e.halt();
89 },
90
Nils Diewald7148c6f2015-05-04 15:07:53 +000091
Nils Diewald59c02fc2015-03-07 01:29:09 +000092 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +000093 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +000094 var code = _codeFromEvent(e);
95
Nils Diewald59c02fc2015-03-07 01:29:09 +000096 switch (code) {
97 case 27: // 'Esc'
98 e.halt();
99 this.hide();
100 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000101
Nils Diewald59c02fc2015-03-07 01:29:09 +0000102 case 38: // 'Up'
103 e.halt();
104 this.prev();
105 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000106 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +0000107 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000108 this.prev();
109 break;
110 case 40: // 'Down'
111 e.halt();
112 this.next();
113 break;
114 case 34: // 'Page down'
115 e.halt();
116 this.next();
117 break;
118 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000119 if (this._prefix.active())
120 break;
121
Akronf86eaea2016-05-13 18:02:27 +0200122 var item = this.liveItem(this.position);
Akron5ef4fa02015-06-02 16:25:14 +0200123
Nils Diewald5975d702015-03-09 17:45:42 +0000124 if (item["further"] !== undefined) {
125 item["further"].bind(item).apply();
Nils Diewald5975d702015-03-09 17:45:42 +0000126 };
Akron5ef4fa02015-06-02 16:25:14 +0200127
128 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000129 break;
130 case 13: // 'Enter'
131
132 // Click on prefix
133 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000134 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000135
136 // Click on item
137 else
Akronf86eaea2016-05-13 18:02:27 +0200138 this.liveItem(this.position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000139 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000140 break;
141 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000142 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000143 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000144 e.halt();
145 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000146 };
147 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000148
Nils Diewald47f366b2015-04-15 20:06:35 +0000149 // Add characters to prefix
150 _keypress : function (e) {
151 var c = String.fromCharCode(_codeFromEvent(e)).toLowerCase();
Nils Diewald5975d702015-03-09 17:45:42 +0000152
Nils Diewald47f366b2015-04-15 20:06:35 +0000153 // Add prefix
154 this._prefix.add(c);
155
156 if (!this.show()) {
157 this.prefix('').show();
158 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000159 };
160 },
161
Akron47c086c2016-05-18 21:22:06 +0200162 /**
163 * Show screen X
164 */
165 screen : function (nr) {
166 if (this._offset === nr)
167 return;
168
Akroncb351d62016-05-19 23:10:33 +0200169 this.delete();
Akron47c086c2016-05-18 21:22:06 +0200170 this._showItems(nr);
171 },
172
Nils Diewald2fe12e12015-03-06 16:47:06 +0000173 // Initialize list
Akron1ff3ac22016-04-28 16:30:45 +0200174 _init : function (itemClass, prefixClass, lengthFieldClass, params) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000175 var that = this;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000176 this._itemClass = itemClass || defaultItemClass;
Nils Diewald5975d702015-03-09 17:45:42 +0000177
Akronc7448732016-04-27 14:06:58 +0200178 // Add prefix object
179 if (prefixClass !== undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000180 this._prefix = prefixClass.create();
Akronc7448732016-04-27 14:06:58 +0200181 }
182 else {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000183 this._prefix = defaultPrefixClass.create();
Akronc7448732016-04-27 14:06:58 +0200184 };
Nils Diewald5c5a7472015-04-02 22:13:38 +0000185 this._prefix._menu = this;
186
Akronc7448732016-04-27 14:06:58 +0200187 // Add lengthField object
Akron1ff3ac22016-04-28 16:30:45 +0200188 if (lengthFieldClass !== undefined) {
189 this._lengthField = lengthFieldClass.create();
190 }
191 else {
192 this._lengthField = defaultLengthFieldClass.create();
193 };
Akronc7448732016-04-27 14:06:58 +0200194 this._lengthField._menu = this;
195
Akron9905e2a2016-05-10 16:06:44 +0200196 // Initialize the slider
Akron47c086c2016-05-18 21:22:06 +0200197 this._slider = sliderClass.create(this);
Akronc7448732016-04-27 14:06:58 +0200198
Nils Diewald5975d702015-03-09 17:45:42 +0000199 var e = document.createElement("ul");
Nils Diewald59c02fc2015-03-07 01:29:09 +0000200 e.style.opacity = 0;
201 e.style.outline = 0;
202 e.setAttribute('tabindex', 0);
Nils Diewald5c5a7472015-04-02 22:13:38 +0000203 e.classList.add('menu');
Nils Diewald58141332015-04-07 16:18:45 +0000204 e.classList.add('roll');
Nils Diewald5975d702015-03-09 17:45:42 +0000205 e.appendChild(this._prefix.element());
Akronc7448732016-04-27 14:06:58 +0200206 e.appendChild(this._lengthField.element());
Akron9905e2a2016-05-10 16:06:44 +0200207 e.appendChild(this._slider.element());
Nils Diewald86dad5b2015-01-28 15:09:07 +0000208
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000209 // This has to be cleaned up later on
210 e["menu"] = this;
211
Nils Diewald59c02fc2015-03-07 01:29:09 +0000212 // Arrow keys
213 e.addEventListener(
Nils Diewald47f366b2015-04-15 20:06:35 +0000214 'keydown',
215 function (ev) {
216 that._keydown(ev)
217 },
218 false
219 );
220
221 // Strings
222 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000223 'keypress',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000224 function (ev) {
Nils Diewald5975d702015-03-09 17:45:42 +0000225 that._keypress(ev)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000226 },
227 false
228 );
229
230 // Mousewheel
231 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000232 'wheel',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000233 function (ev) {
234 that._mousewheel(ev)
Nils Diewald2fe12e12015-03-06 16:47:06 +0000235 },
236 false
237 );
238
Nils Diewald5975d702015-03-09 17:45:42 +0000239 this._element = e;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000240 this.active = false;
Akron37513a62015-11-17 01:07:11 +0100241 // this.selected = undefined;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000242 this._items = new Array();
Akron9905e2a2016-05-10 16:06:44 +0200243 var i = 0;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000244
245 // Initialize item list based on parameters
Nils Diewald86dad5b2015-01-28 15:09:07 +0000246 for (i in params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000247 var obj = this._itemClass.create(params[i]);
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000248
249 // This may become circular
250 obj["_menu"] = this;
Akron1ff3ac22016-04-28 16:30:45 +0200251 this._lengthField.add(params[i]);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000252 this._items.push(obj);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000253 };
Akronc7448732016-04-27 14:06:58 +0200254
Nils Diewald0e6992a2015-04-14 20:13:52 +0000255 this._limit = menuLimit;
Akronf86eaea2016-05-13 18:02:27 +0200256 this._slider.length(this.liveLength());
Akron9905e2a2016-05-10 16:06:44 +0200257 this._slider.limit(this._limit);
258
Akronf86eaea2016-05-13 18:02:27 +0200259 this.position = 0; // position in the active list
260 this._active = -1; // active item in the item list
Nils Diewald20f7ace2015-05-07 12:51:34 +0000261 this._firstActive = false; // Show the first item active always?
Nils Diewald86dad5b2015-01-28 15:09:07 +0000262 this._reset();
263 return this;
264 },
265
Nils Diewald2fe12e12015-03-06 16:47:06 +0000266 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000267 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000268 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000269 element : function () {
270 return this._element;
271 },
272
Akronf86eaea2016-05-13 18:02:27 +0200273 slider : function () {
274 return this._slider;
275 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000276
Nils Diewald2fe12e12015-03-06 16:47:06 +0000277 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000278 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000279 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000280 itemClass : function () {
281 return this._itemClass;
282 },
283
284 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000285 * Get and set the numerical value
286 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000287 */
288 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000289 if (arguments.length === 1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000290 this._limit = limit;
Akron9905e2a2016-05-10 16:06:44 +0200291 this._slider.limit(limit);
Nils Diewald5975d702015-03-09 17:45:42 +0000292 return this;
293 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000294 return this._limit;
295 },
296
Nils Diewald7148c6f2015-05-04 15:07:53 +0000297
Nils Diewald86dad5b2015-01-28 15:09:07 +0000298 /**
299 * Upgrade this object to another object,
300 * while private data stays intact.
301 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000302 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000303 */
304 upgradeTo : function (props) {
305 for (var prop in props) {
306 this[prop] = props[prop];
307 };
308 return this;
309 },
310
Nils Diewald7148c6f2015-05-04 15:07:53 +0000311
Nils Diewald2fe12e12015-03-06 16:47:06 +0000312 // Reset chosen item and prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000313 _reset : function () {
314 this._offset = 0;
315 this._pos = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000316 this._prefix.value('');
Nils Diewald86dad5b2015-01-28 15:09:07 +0000317 },
318
Nils Diewald7148c6f2015-05-04 15:07:53 +0000319
Nils Diewald86dad5b2015-01-28 15:09:07 +0000320 /**
321 * Filter the list and make it visible
322 *
323 * @param {string} Prefix for filtering the list
324 */
Nils Diewald5975d702015-03-09 17:45:42 +0000325 show : function () {
Nils Diewalde8518f82015-03-18 22:41:49 +0000326
Nils Diewald86dad5b2015-01-28 15:09:07 +0000327 // Initialize the list
328 if (!this._initList())
Nils Diewald59c02fc2015-03-07 01:29:09 +0000329 return false;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000330
Nils Diewald2fe12e12015-03-06 16:47:06 +0000331 // show based on initial offset
Akroncb351d62016-05-19 23:10:33 +0200332 this.unmark();
333 this.delete();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000334 this._showItems(0);
335
336 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000337 // Todo: Or the last element chosen
Akron47c086c2016-05-18 21:22:06 +0200338 if (this._firstActive) {
Nils Diewald20f7ace2015-05-07 12:51:34 +0000339 this.liveItem(0).active(true);
Akron47c086c2016-05-18 21:22:06 +0200340 this.position = 0;
341 this._active = 0;
342 }
Akroncb351d62016-05-19 23:10:33 +0200343
Akron47c086c2016-05-18 21:22:06 +0200344 else {
345 this.position = -1;
346 }
Akron37513a62015-11-17 01:07:11 +0100347
Nils Diewalde8518f82015-03-18 22:41:49 +0000348 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000349
Nils Diewald2fe12e12015-03-06 16:47:06 +0000350 this._element.style.opacity = 1;
351
Akron9905e2a2016-05-10 16:06:44 +0200352 this._slider.show();
353
Akron37513a62015-11-17 01:07:11 +0100354 // Iterate to the active item
355 if (this._active !== -1 && !this._prefix.isSet()) {
Akronf86eaea2016-05-13 18:02:27 +0200356 while (this._list[this.position] < this._active) {
Akron37513a62015-11-17 01:07:11 +0100357 this.next();
358 };
359 };
360
Nils Diewald86dad5b2015-01-28 15:09:07 +0000361 // Add classes for rolling menus
362 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000363 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000364 },
365
Nils Diewald7148c6f2015-05-04 15:07:53 +0000366
367 /**
368 * Hide the menu and call the onHide callback.
369 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000370 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000371 this.active = false;
Akroncb351d62016-05-19 23:10:33 +0200372 this.unmark();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000373 this.delete();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000374 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000375 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000376 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000377 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000378 },
379
Nils Diewald7148c6f2015-05-04 15:07:53 +0000380 /**
381 * Function released when the menu hides.
382 * This method is expected to be overridden.
383 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000384 onHide : function () {},
385
Nils Diewald2fe12e12015-03-06 16:47:06 +0000386 // Initialize the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000387 _initList : function () {
388
Nils Diewald2fe12e12015-03-06 16:47:06 +0000389 // Create a new list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000390 if (this._list === undefined) {
391 this._list = [];
392 }
393 else if (this._list.length != 0) {
394 this._boundary(false);
395 this._list.length = 0;
396 };
397
398 // Offset is initially zero
399 this._offset = 0;
400
Nils Diewald2fe12e12015-03-06 16:47:06 +0000401 // There is no prefix set
Nils Diewald86dad5b2015-01-28 15:09:07 +0000402 if (this.prefix().length <= 0) {
Nils Diewald5975d702015-03-09 17:45:42 +0000403 var i = 0;
404 for (; i < this._items.length; i++)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000405 this._list.push(i);
Nils Diewald5975d702015-03-09 17:45:42 +0000406 while (this._items[++i] !== undefined) {
407 this._items[i].lowlight();
Nils Diewald5975d702015-03-09 17:45:42 +0000408 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000409 return true;
410 };
411
Nils Diewald2fe12e12015-03-06 16:47:06 +0000412 // There is a prefix set, so filter the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000413 var pos;
414 var paddedPrefix = " " + this.prefix();
415
Nils Diewald2fe12e12015-03-06 16:47:06 +0000416 // Iterate over all items and choose preferred matching items
417 // i.e. the matching happens at the word start
Nils Diewald86dad5b2015-01-28 15:09:07 +0000418 for (pos = 0; pos < this._items.length; pos++) {
419 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
420 this._list.push(pos);
421 };
422
Nils Diewald2fe12e12015-03-06 16:47:06 +0000423 // The list is empty - so lower your expectations
424 // Iterate over all items and choose matching items
425 // i.e. the matching happens anywhere in the word
Nils Diewald86dad5b2015-01-28 15:09:07 +0000426 if (this._list.length == 0) {
427 for (pos = 0; pos < this._items.length; pos++) {
428 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
429 this._list.push(pos);
430 };
431 };
432
Nils Diewald2fe12e12015-03-06 16:47:06 +0000433 // Filter was successful - yeah!
Nils Diewald86dad5b2015-01-28 15:09:07 +0000434 return this._list.length > 0 ? true : false;
435 },
436
437 // Set boundary for viewport
438 _boundary : function (bool) {
439 this.item(this._list[0]).noMore(bool);
440 this.item(this._list[this._list.length - 1]).noMore(bool);
441 },
442
Nils Diewald7148c6f2015-05-04 15:07:53 +0000443
Nils Diewald86dad5b2015-01-28 15:09:07 +0000444 /**
445 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000446 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000447 */
Nils Diewald5975d702015-03-09 17:45:42 +0000448 prefix : function (pref) {
449 if (arguments.length === 1) {
450 this._prefix.value(pref);
451 return this;
452 };
453 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000454 },
455
Akronc7448732016-04-27 14:06:58 +0200456 /**
457 * Get the lengthField object.
458 */
459 lengthField : function () {
460 return this._lengthField;
461 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000462
Nils Diewald2fe12e12015-03-06 16:47:06 +0000463 // Append Items that should be shown
Akron47c086c2016-05-18 21:22:06 +0200464 _showItems : function (off) {
465
Nils Diewald86dad5b2015-01-28 15:09:07 +0000466 // Use list
467 var shown = 0;
468 var i;
469 for (i in this._list) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000470 // Don't show - it's before offset
Akron47c086c2016-05-18 21:22:06 +0200471 if (shown++ < off)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000472 continue;
473
Akron47c086c2016-05-18 21:22:06 +0200474 var itemNr = this._list[i];
475 var item = this.item(itemNr);
476 this._append(itemNr);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000477
Akroncb351d62016-05-19 23:10:33 +0200478 if (i === this.position)
479 item.active(true);
Akron47c086c2016-05-18 21:22:06 +0200480
481 // this._offset))
482 if (shown >= (this.limit() + off))
Nils Diewald86dad5b2015-01-28 15:09:07 +0000483 break;
484 };
485 },
486
487 /**
488 * Delete all visible items from the menu element
489 */
490 delete : function () {
491 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000492
Nils Diewald5975d702015-03-09 17:45:42 +0000493 /*
Nils Diewald2fe12e12015-03-06 16:47:06 +0000494 // Iterate over all visible items
Nils Diewald86dad5b2015-01-28 15:09:07 +0000495 for (var i = 0; i <= this.limit(); i++) {
496
Nils Diewald5975d702015-03-09 17:45:42 +0000497 // there is a visible element
498 // unhighlight!
Nils Diewald59c02fc2015-03-07 01:29:09 +0000499 if (child = this.shownItem(i)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000500 child.lowlight();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000501 child.active(false);
502 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000503 };
Nils Diewald5975d702015-03-09 17:45:42 +0000504 */
505
Nils Diewald2fe12e12015-03-06 16:47:06 +0000506 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000507 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200508 // Leave the prefix and lengthField
Akron9905e2a2016-05-10 16:06:44 +0200509 for (var i = children.length - 1; i >= 3; i--) {
Nils Diewald5975d702015-03-09 17:45:42 +0000510 this._element.removeChild(
511 children[i]
512 );
513 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000514 },
515
Akroncb351d62016-05-19 23:10:33 +0200516 // Unmark all items
517 unmark : function () {
518 for (var i in this._list) {
519 var item = this._items[this._list[i]];
520 item.lowlight();
521 item.active(false);
522 };
523 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000524
525 // Append item to the shown list based on index
526 _append : function (i) {
527 var item = this.item(i);
528
529 // Highlight based on prefix
530 if (this.prefix().length > 0)
531 item.highlight(this.prefix());
532
533 // Append element
534 this.element().appendChild(item.element());
Akronf86eaea2016-05-13 18:02:27 +0200535
536 this._slider.offset(this._offset);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000537 },
538
539
Nils Diewald2fe12e12015-03-06 16:47:06 +0000540 // Prepend item to the shown list based on index
541 _prepend : function (i) {
542 var item = this.item(i);
543
544 // Highlight based on prefix
545 if (this.prefix().length > 0)
546 item.highlight(this.prefix());
547
548 var e = this.element();
Akron9905e2a2016-05-10 16:06:44 +0200549 // Append element after lengthField/prefix/slider
Nils Diewald2fe12e12015-03-06 16:47:06 +0000550 e.insertBefore(
551 item.element(),
Akron9905e2a2016-05-10 16:06:44 +0200552 e.children[3]
Nils Diewald2fe12e12015-03-06 16:47:06 +0000553 );
Akronf86eaea2016-05-13 18:02:27 +0200554
555 this._slider.offset(this._offset);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000556 },
557
558
559 /**
560 * Get a specific item from the complete list
561 *
562 * @param {number} index of the list item
563 */
564 item : function (index) {
565 return this._items[index]
566 },
567
568
Nils Diewald86dad5b2015-01-28 15:09:07 +0000569 /**
570 * Get a specific item from the filtered list
571 *
572 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000573 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000574 */
575 liveItem : function (index) {
576 if (this._list === undefined)
577 if (!this._initList())
578 return;
579
580 return this._items[this._list[index]];
581 },
582
Nils Diewald86dad5b2015-01-28 15:09:07 +0000583
584 /**
585 * Get a specific item from the visible list
586 *
587 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000588 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000589 */
590 shownItem : function (index) {
591 if (index >= this.limit())
592 return;
593 return this.liveItem(this._offset + index);
594 },
595
596
Nils Diewald2fe12e12015-03-06 16:47:06 +0000597 /**
598 * Get the length of the full list
599 */
600 length : function () {
601 return this._items.length;
602 },
603
604
605 /**
606 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000607 */
608 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000609
Nils Diewald86dad5b2015-01-28 15:09:07 +0000610 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000611 var newItem;
612
Akron47c086c2016-05-18 21:22:06 +0200613 if (this.position !== -1) {
614 // Set new live item
615 if (!this._prefix.active()) {
616 var oldItem = this.liveItem(this.position);
617 oldItem.active(false);
618 };
Nils Diewalde8518f82015-03-18 22:41:49 +0000619 };
620
Akronf86eaea2016-05-13 18:02:27 +0200621 this.position++;
Akron47c086c2016-05-18 21:22:06 +0200622 this._active = this.position;
Nils Diewalde8518f82015-03-18 22:41:49 +0000623
Akronf86eaea2016-05-13 18:02:27 +0200624 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000625
Nils Diewald5975d702015-03-09 17:45:42 +0000626 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000627 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000628
629 // Activate prefix
630 var prefix = this._prefix;
631
632 // Mark prefix
633 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200634 this.position--;
Nils Diewald5975d702015-03-09 17:45:42 +0000635 prefix.active(true);
Akron47c086c2016-05-18 21:22:06 +0200636 this._active = -1;
Nils Diewald5975d702015-03-09 17:45:42 +0000637 return;
638 }
639 else {
640 this._offset = 0;
Akronf86eaea2016-05-13 18:02:27 +0200641 this.position = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000642 newItem = this.liveItem(0);
Akron47c086c2016-05-18 21:22:06 +0200643 this._active = 0;
Akroncb351d62016-05-19 23:10:33 +0200644 this.unmark();
645 this.delete();
Nils Diewald5975d702015-03-09 17:45:42 +0000646 this._showItems(0);
647 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000648 }
649
650 // The next element is outside the view - roll down
Akronf86eaea2016-05-13 18:02:27 +0200651 else if (this.position >= (this.limit() + this._offset)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000652 this._removeFirst();
653 this._offset++;
Akronf86eaea2016-05-13 18:02:27 +0200654 this._append(this._list[this.position]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000655 };
Nils Diewald5975d702015-03-09 17:45:42 +0000656
657 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000658 newItem.active(true);
659 },
660
Nils Diewalde8518f82015-03-18 22:41:49 +0000661 /*
662 * Page down to the first item on the next page
663 */
664 /*
665 nextPage : function () {
666
667 // Prefix is active
668 if (this._prefix.active()) {
669 this._prefix.active(false);
670 }
671
672 // Last item is chosen
Akronf86eaea2016-05-13 18:02:27 +0200673 else if (this.position >= this.limit() + this._offset) {
Nils Diewalde8518f82015-03-18 22:41:49 +0000674
Akronf86eaea2016-05-13 18:02:27 +0200675 this.position = this.limit() + this._offset - 1;
676 newItem = this.liveItem(this.position);
677 var oldItem = this.liveItem(this.position--);
Nils Diewalde8518f82015-03-18 22:41:49 +0000678 oldItem.active(false);
679 }
680
681 // Last item of page is chosen
682 else if (0) {
683
684 // Jump to last item
685 else {
Akronf86eaea2016-05-13 18:02:27 +0200686 var oldItem = this.liveItem(this.position);
Nils Diewalde8518f82015-03-18 22:41:49 +0000687 oldItem.active(false);
688
Akronf86eaea2016-05-13 18:02:27 +0200689 this.position = this.limit() + this._offset - 1;
690 newItem = this.liveItem(this.position);
Nils Diewalde8518f82015-03-18 22:41:49 +0000691 };
692
693 newItem.active(true);
694 },
695 */
696
Nils Diewald86dad5b2015-01-28 15:09:07 +0000697
698 /*
699 * Make the previous item in the menu active
700 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000701 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000702
Nils Diewald2fe12e12015-03-06 16:47:06 +0000703 // No active element set
Akronf86eaea2016-05-13 18:02:27 +0200704 if (this.position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000705 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000706 // TODO: Choose last item
707 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000708
Nils Diewald5975d702015-03-09 17:45:42 +0000709 var newItem;
710
Nils Diewald86dad5b2015-01-28 15:09:07 +0000711 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000712 if (!this._prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200713 var oldItem = this.liveItem(this.position--);
Nils Diewald2d210752015-03-09 19:01:15 +0000714 oldItem.active(false);
715 };
716
Akronf86eaea2016-05-13 18:02:27 +0200717 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000718
719 // The previous element is undefined - roll to bottom
720 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000721
722 // Activate prefix
723 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000724 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000725
726 // Normalize offset
727 this._offset = this._offset < 0 ? 0 : this._offset;
728
Akronf86eaea2016-05-13 18:02:27 +0200729 this.position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000730
731 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200732 this.position++;
Nils Diewald5975d702015-03-09 17:45:42 +0000733 prefix.active(true);
734 return;
735 }
736 else {
Akronf86eaea2016-05-13 18:02:27 +0200737 newItem = this.liveItem(this.position);
Akroncb351d62016-05-19 23:10:33 +0200738 this.unmark();
739 this.delete();
Nils Diewald5975d702015-03-09 17:45:42 +0000740 this._showItems(this._offset);
741 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000742 }
743
744 // The previous element is outside the view - roll up
Akronf86eaea2016-05-13 18:02:27 +0200745 else if (this.position < this._offset) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000746 this._removeLast();
747 this._offset--;
Akronf86eaea2016-05-13 18:02:27 +0200748 this._prepend(this._list[this.position]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000749 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000750
Nils Diewald5975d702015-03-09 17:45:42 +0000751 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000752 newItem.active(true);
753 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000754
755
Nils Diewald7148c6f2015-05-04 15:07:53 +0000756 /**
757 * Length of the filtered item list.
758 */
Nils Diewald5975d702015-03-09 17:45:42 +0000759 liveLength : function () {
760 if (this._list === undefined)
761 this._initList();
762 return this._list.length;
763 },
764
765
Nils Diewald2fe12e12015-03-06 16:47:06 +0000766 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000767 _removeFirst : function () {
768 this.item(this._list[this._offset]).lowlight();
Akron9905e2a2016-05-10 16:06:44 +0200769 // leave lengthField/prefix/slider
770 this._element.removeChild(this._element.children[3]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000771 },
772
Nils Diewald2fe12e12015-03-06 16:47:06 +0000773
774 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000775 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000776 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000777 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000778 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000779 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000780});