blob: bd363e393e429db8544873cdf21b91f401d6e1be [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 () {
57 if (this._element != undefined)
58 delete this._element["menu"];
59
60 for (var i = 0; i < this._items.length; i++) {
61 delete this._items[i]["_menu"];
62 };
Nils Diewald5c5a7472015-04-02 22:13:38 +000063 delete this._prefix['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +000064 },
65
Nils Diewald7148c6f2015-05-04 15:07:53 +000066
67 /**
68 * Focus on this menu.
69 */
Nils Diewald2fe12e12015-03-06 16:47:06 +000070 focus : function () {
71 this._element.focus();
72 },
73
Nils Diewald7148c6f2015-05-04 15:07:53 +000074
Nils Diewald59c02fc2015-03-07 01:29:09 +000075 // mouse wheel treatment
76 _mousewheel : function (e) {
77 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +000078
79 delta = e.deltaY / 120;
80 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000081 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +000082 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000083 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +000084 e.halt();
85 },
86
Nils Diewald7148c6f2015-05-04 15:07:53 +000087
Nils Diewald59c02fc2015-03-07 01:29:09 +000088 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +000089 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +000090 var code = _codeFromEvent(e);
91
Nils Diewald59c02fc2015-03-07 01:29:09 +000092 switch (code) {
93 case 27: // 'Esc'
94 e.halt();
95 this.hide();
96 break;
Nils Diewald5975d702015-03-09 17:45:42 +000097
Nils Diewald59c02fc2015-03-07 01:29:09 +000098 case 38: // 'Up'
99 e.halt();
100 this.prev();
101 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000102 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +0000103 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000104 this.prev();
105 break;
106 case 40: // 'Down'
107 e.halt();
108 this.next();
109 break;
110 case 34: // 'Page down'
111 e.halt();
112 this.next();
113 break;
114 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000115 if (this._prefix.active())
116 break;
117
Akronf86eaea2016-05-13 18:02:27 +0200118 var item = this.liveItem(this.position);
Akron5ef4fa02015-06-02 16:25:14 +0200119
Nils Diewald5975d702015-03-09 17:45:42 +0000120 if (item["further"] !== undefined) {
121 item["further"].bind(item).apply();
Nils Diewald5975d702015-03-09 17:45:42 +0000122 };
Akron5ef4fa02015-06-02 16:25:14 +0200123
124 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000125 break;
126 case 13: // 'Enter'
127
128 // Click on prefix
129 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000130 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000131
132 // Click on item
133 else
Akronf86eaea2016-05-13 18:02:27 +0200134 this.liveItem(this.position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000135 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000136 break;
137 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000138 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000139 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000140 e.halt();
141 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000142 };
143 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000144
Nils Diewald47f366b2015-04-15 20:06:35 +0000145 // Add characters to prefix
146 _keypress : function (e) {
147 var c = String.fromCharCode(_codeFromEvent(e)).toLowerCase();
Nils Diewald5975d702015-03-09 17:45:42 +0000148
Nils Diewald47f366b2015-04-15 20:06:35 +0000149 // Add prefix
150 this._prefix.add(c);
151
152 if (!this.show()) {
153 this.prefix('').show();
154 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000155 };
156 },
157
Nils Diewald2fe12e12015-03-06 16:47:06 +0000158 // Initialize list
Akron1ff3ac22016-04-28 16:30:45 +0200159 _init : function (itemClass, prefixClass, lengthFieldClass, params) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000160 var that = this;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000161 this._itemClass = itemClass || defaultItemClass;
Nils Diewald5975d702015-03-09 17:45:42 +0000162
Akronc7448732016-04-27 14:06:58 +0200163 // Add prefix object
164 if (prefixClass !== undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000165 this._prefix = prefixClass.create();
Akronc7448732016-04-27 14:06:58 +0200166 }
167 else {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000168 this._prefix = defaultPrefixClass.create();
Akronc7448732016-04-27 14:06:58 +0200169 };
Nils Diewald5c5a7472015-04-02 22:13:38 +0000170 this._prefix._menu = this;
171
Akronc7448732016-04-27 14:06:58 +0200172 // Add lengthField object
Akron1ff3ac22016-04-28 16:30:45 +0200173 if (lengthFieldClass !== undefined) {
174 this._lengthField = lengthFieldClass.create();
175 }
176 else {
177 this._lengthField = defaultLengthFieldClass.create();
178 };
Akronc7448732016-04-27 14:06:58 +0200179 this._lengthField._menu = this;
180
Akron9905e2a2016-05-10 16:06:44 +0200181 // Initialize the slider
182 this._slider = sliderClass.create();
Akronc7448732016-04-27 14:06:58 +0200183
Nils Diewald5975d702015-03-09 17:45:42 +0000184 var e = document.createElement("ul");
Nils Diewald59c02fc2015-03-07 01:29:09 +0000185 e.style.opacity = 0;
186 e.style.outline = 0;
187 e.setAttribute('tabindex', 0);
Nils Diewald5c5a7472015-04-02 22:13:38 +0000188 e.classList.add('menu');
Nils Diewald58141332015-04-07 16:18:45 +0000189 e.classList.add('roll');
Nils Diewald5975d702015-03-09 17:45:42 +0000190 e.appendChild(this._prefix.element());
Akronc7448732016-04-27 14:06:58 +0200191 e.appendChild(this._lengthField.element());
Akron9905e2a2016-05-10 16:06:44 +0200192 e.appendChild(this._slider.element());
Nils Diewald86dad5b2015-01-28 15:09:07 +0000193
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000194 // This has to be cleaned up later on
195 e["menu"] = this;
196
Nils Diewald59c02fc2015-03-07 01:29:09 +0000197 // Arrow keys
198 e.addEventListener(
Nils Diewald47f366b2015-04-15 20:06:35 +0000199 'keydown',
200 function (ev) {
201 that._keydown(ev)
202 },
203 false
204 );
205
206 // Strings
207 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000208 'keypress',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000209 function (ev) {
Nils Diewald5975d702015-03-09 17:45:42 +0000210 that._keypress(ev)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000211 },
212 false
213 );
214
215 // Mousewheel
216 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000217 'wheel',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000218 function (ev) {
219 that._mousewheel(ev)
Nils Diewald2fe12e12015-03-06 16:47:06 +0000220 },
221 false
222 );
223
Nils Diewald5975d702015-03-09 17:45:42 +0000224 this._element = e;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000225 this.active = false;
Akron37513a62015-11-17 01:07:11 +0100226 // this.selected = undefined;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000227 this._items = new Array();
Akron9905e2a2016-05-10 16:06:44 +0200228 var i = 0;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000229
230 // Initialize item list based on parameters
Nils Diewald86dad5b2015-01-28 15:09:07 +0000231 for (i in params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000232 var obj = this._itemClass.create(params[i]);
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000233
234 // This may become circular
235 obj["_menu"] = this;
Akron1ff3ac22016-04-28 16:30:45 +0200236 this._lengthField.add(params[i]);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000237 this._items.push(obj);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000238 };
Akronc7448732016-04-27 14:06:58 +0200239
Nils Diewald0e6992a2015-04-14 20:13:52 +0000240 this._limit = menuLimit;
Akronf86eaea2016-05-13 18:02:27 +0200241 this._slider.length(this.liveLength());
Akron9905e2a2016-05-10 16:06:44 +0200242 this._slider.limit(this._limit);
243
Akronf86eaea2016-05-13 18:02:27 +0200244 this.position = 0; // position in the active list
245 this._active = -1; // active item in the item list
Nils Diewald20f7ace2015-05-07 12:51:34 +0000246 this._firstActive = false; // Show the first item active always?
Nils Diewald86dad5b2015-01-28 15:09:07 +0000247 this._reset();
248 return this;
249 },
250
Nils Diewald2fe12e12015-03-06 16:47:06 +0000251 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000252 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000253 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000254 element : function () {
255 return this._element;
256 },
257
Akronf86eaea2016-05-13 18:02:27 +0200258 slider : function () {
259 return this._slider;
260 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000261
Nils Diewald2fe12e12015-03-06 16:47:06 +0000262 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000263 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000264 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000265 itemClass : function () {
266 return this._itemClass;
267 },
268
269 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000270 * Get and set the numerical value
271 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000272 */
273 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000274 if (arguments.length === 1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000275 this._limit = limit;
Akron9905e2a2016-05-10 16:06:44 +0200276 this._slider.limit(limit);
Nils Diewald5975d702015-03-09 17:45:42 +0000277 return this;
278 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000279 return this._limit;
280 },
281
Nils Diewald7148c6f2015-05-04 15:07:53 +0000282
Nils Diewald86dad5b2015-01-28 15:09:07 +0000283 /**
284 * Upgrade this object to another object,
285 * while private data stays intact.
286 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000287 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000288 */
289 upgradeTo : function (props) {
290 for (var prop in props) {
291 this[prop] = props[prop];
292 };
293 return this;
294 },
295
Nils Diewald7148c6f2015-05-04 15:07:53 +0000296
Nils Diewald2fe12e12015-03-06 16:47:06 +0000297 // Reset chosen item and prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000298 _reset : function () {
299 this._offset = 0;
300 this._pos = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000301 this._prefix.value('');
Nils Diewald86dad5b2015-01-28 15:09:07 +0000302 },
303
Nils Diewald7148c6f2015-05-04 15:07:53 +0000304
Nils Diewald86dad5b2015-01-28 15:09:07 +0000305 /**
306 * Filter the list and make it visible
307 *
308 * @param {string} Prefix for filtering the list
309 */
Nils Diewald5975d702015-03-09 17:45:42 +0000310 show : function () {
Nils Diewalde8518f82015-03-18 22:41:49 +0000311
Nils Diewald86dad5b2015-01-28 15:09:07 +0000312 // Initialize the list
313 if (!this._initList())
Nils Diewald59c02fc2015-03-07 01:29:09 +0000314 return false;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000315
Nils Diewald2fe12e12015-03-06 16:47:06 +0000316 // show based on initial offset
Nils Diewald86dad5b2015-01-28 15:09:07 +0000317 this._showItems(0);
318
319 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000320 // Todo: Or the last element chosen
Nils Diewald20f7ace2015-05-07 12:51:34 +0000321 if (this._firstActive)
322 this.liveItem(0).active(true);
323
Akronf86eaea2016-05-13 18:02:27 +0200324 this.position = 0;
Akron37513a62015-11-17 01:07:11 +0100325
Nils Diewalde8518f82015-03-18 22:41:49 +0000326 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000327
Nils Diewald2fe12e12015-03-06 16:47:06 +0000328 this._element.style.opacity = 1;
329
Akron9905e2a2016-05-10 16:06:44 +0200330 this._slider.show();
331
Akron37513a62015-11-17 01:07:11 +0100332 // Iterate to the active item
333 if (this._active !== -1 && !this._prefix.isSet()) {
Akronf86eaea2016-05-13 18:02:27 +0200334 while (this._list[this.position] < this._active) {
Akron37513a62015-11-17 01:07:11 +0100335 this.next();
336 };
337 };
338
Nils Diewald86dad5b2015-01-28 15:09:07 +0000339 // Add classes for rolling menus
340 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000341 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000342 },
343
Nils Diewald7148c6f2015-05-04 15:07:53 +0000344
345 /**
346 * Hide the menu and call the onHide callback.
347 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000348 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000349 this.active = false;
350 this.delete();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000351 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000352 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000353 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000354 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000355 },
356
Nils Diewald7148c6f2015-05-04 15:07:53 +0000357 /**
358 * Function released when the menu hides.
359 * This method is expected to be overridden.
360 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000361 onHide : function () {},
362
Nils Diewald2fe12e12015-03-06 16:47:06 +0000363 // Initialize the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000364 _initList : function () {
365
Nils Diewald2fe12e12015-03-06 16:47:06 +0000366 // Create a new list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000367 if (this._list === undefined) {
368 this._list = [];
369 }
370 else if (this._list.length != 0) {
371 this._boundary(false);
372 this._list.length = 0;
373 };
374
375 // Offset is initially zero
376 this._offset = 0;
377
Nils Diewald2fe12e12015-03-06 16:47:06 +0000378 // There is no prefix set
Nils Diewald86dad5b2015-01-28 15:09:07 +0000379 if (this.prefix().length <= 0) {
Nils Diewald5975d702015-03-09 17:45:42 +0000380 var i = 0;
381 for (; i < this._items.length; i++)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000382 this._list.push(i);
Nils Diewald5975d702015-03-09 17:45:42 +0000383 while (this._items[++i] !== undefined) {
384 this._items[i].lowlight();
Nils Diewald5975d702015-03-09 17:45:42 +0000385 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000386 return true;
387 };
388
Nils Diewald2fe12e12015-03-06 16:47:06 +0000389 // There is a prefix set, so filter the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000390 var pos;
391 var paddedPrefix = " " + this.prefix();
392
Nils Diewald2fe12e12015-03-06 16:47:06 +0000393 // Iterate over all items and choose preferred matching items
394 // i.e. the matching happens at the word start
Nils Diewald86dad5b2015-01-28 15:09:07 +0000395 for (pos = 0; pos < this._items.length; pos++) {
396 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
397 this._list.push(pos);
398 };
399
Nils Diewald2fe12e12015-03-06 16:47:06 +0000400 // The list is empty - so lower your expectations
401 // Iterate over all items and choose matching items
402 // i.e. the matching happens anywhere in the word
Nils Diewald86dad5b2015-01-28 15:09:07 +0000403 if (this._list.length == 0) {
404 for (pos = 0; pos < this._items.length; pos++) {
405 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
406 this._list.push(pos);
407 };
408 };
409
Nils Diewald2fe12e12015-03-06 16:47:06 +0000410 // Filter was successful - yeah!
Nils Diewald86dad5b2015-01-28 15:09:07 +0000411 return this._list.length > 0 ? true : false;
412 },
413
414 // Set boundary for viewport
415 _boundary : function (bool) {
416 this.item(this._list[0]).noMore(bool);
417 this.item(this._list[this._list.length - 1]).noMore(bool);
418 },
419
Nils Diewald7148c6f2015-05-04 15:07:53 +0000420
Nils Diewald86dad5b2015-01-28 15:09:07 +0000421 /**
422 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000423 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000424 */
Nils Diewald5975d702015-03-09 17:45:42 +0000425 prefix : function (pref) {
426 if (arguments.length === 1) {
427 this._prefix.value(pref);
428 return this;
429 };
430 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000431 },
432
Akronc7448732016-04-27 14:06:58 +0200433 /**
434 * Get the lengthField object.
435 */
436 lengthField : function () {
437 return this._lengthField;
438 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000439
Nils Diewald2fe12e12015-03-06 16:47:06 +0000440 // Append Items that should be shown
Nils Diewald86dad5b2015-01-28 15:09:07 +0000441 _showItems : function (offset) {
442 this.delete();
443
444 // Use list
445 var shown = 0;
446 var i;
447 for (i in this._list) {
448
449 // Don't show - it's before offset
450 if (shown++ < offset)
451 continue;
452
453 this._append(this._list[i]);
454
455 if (shown >= (this.limit() + this._offset))
456 break;
457 };
458 },
459
460 /**
461 * Delete all visible items from the menu element
462 */
463 delete : function () {
464 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000465
Nils Diewald5975d702015-03-09 17:45:42 +0000466 /*
Nils Diewald2fe12e12015-03-06 16:47:06 +0000467 // Iterate over all visible items
Nils Diewald86dad5b2015-01-28 15:09:07 +0000468 for (var i = 0; i <= this.limit(); i++) {
469
Nils Diewald5975d702015-03-09 17:45:42 +0000470 // there is a visible element
471 // unhighlight!
Nils Diewald59c02fc2015-03-07 01:29:09 +0000472 if (child = this.shownItem(i)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000473 child.lowlight();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000474 child.active(false);
475 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000476 };
Nils Diewald5975d702015-03-09 17:45:42 +0000477 */
478
479 for (var i in this._list) {
480 var item = this._items[this._list[i]];
481 item.lowlight();
482 item.active(false);
483 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000484
Nils Diewald2fe12e12015-03-06 16:47:06 +0000485 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000486 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200487 // Leave the prefix and lengthField
Akron9905e2a2016-05-10 16:06:44 +0200488 for (var i = children.length - 1; i >= 3; i--) {
Nils Diewald5975d702015-03-09 17:45:42 +0000489 this._element.removeChild(
490 children[i]
491 );
492 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000493 },
494
495
496 // Append item to the shown list based on index
497 _append : function (i) {
498 var item = this.item(i);
499
500 // Highlight based on prefix
501 if (this.prefix().length > 0)
502 item.highlight(this.prefix());
503
504 // Append element
505 this.element().appendChild(item.element());
Akronf86eaea2016-05-13 18:02:27 +0200506
507 this._slider.offset(this._offset);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000508 },
509
510
Nils Diewald2fe12e12015-03-06 16:47:06 +0000511 // Prepend item to the shown list based on index
512 _prepend : function (i) {
513 var item = this.item(i);
514
515 // Highlight based on prefix
516 if (this.prefix().length > 0)
517 item.highlight(this.prefix());
518
519 var e = this.element();
Akron9905e2a2016-05-10 16:06:44 +0200520 // Append element after lengthField/prefix/slider
Nils Diewald2fe12e12015-03-06 16:47:06 +0000521 e.insertBefore(
522 item.element(),
Akron9905e2a2016-05-10 16:06:44 +0200523 e.children[3]
Nils Diewald2fe12e12015-03-06 16:47:06 +0000524 );
Akronf86eaea2016-05-13 18:02:27 +0200525
526 this._slider.offset(this._offset);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000527 },
528
529
530 /**
531 * Get a specific item from the complete list
532 *
533 * @param {number} index of the list item
534 */
535 item : function (index) {
536 return this._items[index]
537 },
538
539
Nils Diewald86dad5b2015-01-28 15:09:07 +0000540 /**
541 * Get a specific item from the filtered list
542 *
543 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000544 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000545 */
546 liveItem : function (index) {
547 if (this._list === undefined)
548 if (!this._initList())
549 return;
550
551 return this._items[this._list[index]];
552 },
553
Nils Diewald86dad5b2015-01-28 15:09:07 +0000554
555 /**
556 * Get a specific item from the visible list
557 *
558 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000559 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000560 */
561 shownItem : function (index) {
562 if (index >= this.limit())
563 return;
564 return this.liveItem(this._offset + index);
565 },
566
567
Nils Diewald2fe12e12015-03-06 16:47:06 +0000568 /**
569 * Get the length of the full list
570 */
571 length : function () {
572 return this._items.length;
573 },
574
575
576 /**
577 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000578 */
579 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000580
Nils Diewald86dad5b2015-01-28 15:09:07 +0000581 // No active element set
Akronf86eaea2016-05-13 18:02:27 +0200582 if (this.position === -1)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000583 return;
584
Nils Diewald5975d702015-03-09 17:45:42 +0000585 var newItem;
586
Nils Diewald86dad5b2015-01-28 15:09:07 +0000587 // Set new live item
Nils Diewalde8518f82015-03-18 22:41:49 +0000588 if (!this._prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200589 var oldItem = this.liveItem(this.position);
Nils Diewalde8518f82015-03-18 22:41:49 +0000590 oldItem.active(false);
591 };
592
Akronf86eaea2016-05-13 18:02:27 +0200593 this.position++;
Nils Diewalde8518f82015-03-18 22:41:49 +0000594
Akronf86eaea2016-05-13 18:02:27 +0200595 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
603 // Mark prefix
604 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 }
609 else {
610 this._offset = 0;
Akronf86eaea2016-05-13 18:02:27 +0200611 this.position = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000612 newItem = this.liveItem(0);
613 this._showItems(0);
614 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000615 }
616
617 // The next element is outside the view - roll down
Akronf86eaea2016-05-13 18:02:27 +0200618 else if (this.position >= (this.limit() + this._offset)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000619 this._removeFirst();
620 this._offset++;
Akronf86eaea2016-05-13 18:02:27 +0200621 this._append(this._list[this.position]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000622 };
Nils Diewald5975d702015-03-09 17:45:42 +0000623
624 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000625 newItem.active(true);
626 },
627
Nils Diewalde8518f82015-03-18 22:41:49 +0000628 /*
629 * Page down to the first item on the next page
630 */
631 /*
632 nextPage : function () {
633
634 // Prefix is active
635 if (this._prefix.active()) {
636 this._prefix.active(false);
637 }
638
639 // Last item is chosen
Akronf86eaea2016-05-13 18:02:27 +0200640 else if (this.position >= this.limit() + this._offset) {
Nils Diewalde8518f82015-03-18 22:41:49 +0000641
Akronf86eaea2016-05-13 18:02:27 +0200642 this.position = this.limit() + this._offset - 1;
643 newItem = this.liveItem(this.position);
644 var oldItem = this.liveItem(this.position--);
Nils Diewalde8518f82015-03-18 22:41:49 +0000645 oldItem.active(false);
646 }
647
648 // Last item of page is chosen
649 else if (0) {
650
651 // Jump to last item
652 else {
Akronf86eaea2016-05-13 18:02:27 +0200653 var oldItem = this.liveItem(this.position);
Nils Diewalde8518f82015-03-18 22:41:49 +0000654 oldItem.active(false);
655
Akronf86eaea2016-05-13 18:02:27 +0200656 this.position = this.limit() + this._offset - 1;
657 newItem = this.liveItem(this.position);
Nils Diewalde8518f82015-03-18 22:41:49 +0000658 };
659
660 newItem.active(true);
661 },
662 */
663
Nils Diewald86dad5b2015-01-28 15:09:07 +0000664
665 /*
666 * Make the previous item in the menu active
667 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000668 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000669
Nils Diewald2fe12e12015-03-06 16:47:06 +0000670 // No active element set
Akronf86eaea2016-05-13 18:02:27 +0200671 if (this.position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000672 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000673 // TODO: Choose last item
674 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000675
Nils Diewald5975d702015-03-09 17:45:42 +0000676 var newItem;
677
Nils Diewald86dad5b2015-01-28 15:09:07 +0000678 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000679 if (!this._prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200680 var oldItem = this.liveItem(this.position--);
Nils Diewald2d210752015-03-09 19:01:15 +0000681 oldItem.active(false);
682 };
683
Akronf86eaea2016-05-13 18:02:27 +0200684 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000685
686 // The previous element is undefined - roll to bottom
687 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000688
689 // Activate prefix
690 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000691 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000692
693 // Normalize offset
694 this._offset = this._offset < 0 ? 0 : this._offset;
695
Akronf86eaea2016-05-13 18:02:27 +0200696 this.position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000697
698 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200699 this.position++;
Nils Diewald5975d702015-03-09 17:45:42 +0000700 prefix.active(true);
701 return;
702 }
703 else {
Akronf86eaea2016-05-13 18:02:27 +0200704 newItem = this.liveItem(this.position);
Nils Diewald5975d702015-03-09 17:45:42 +0000705 this._showItems(this._offset);
706 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000707 }
708
709 // The previous element is outside the view - roll up
Akronf86eaea2016-05-13 18:02:27 +0200710 else if (this.position < this._offset) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000711 this._removeLast();
712 this._offset--;
Akronf86eaea2016-05-13 18:02:27 +0200713 this._prepend(this._list[this.position]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000714 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000715
Nils Diewald5975d702015-03-09 17:45:42 +0000716 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000717 newItem.active(true);
718 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000719
720
Nils Diewald7148c6f2015-05-04 15:07:53 +0000721 /**
722 * Length of the filtered item list.
723 */
Nils Diewald5975d702015-03-09 17:45:42 +0000724 liveLength : function () {
725 if (this._list === undefined)
726 this._initList();
727 return this._list.length;
728 },
729
730
Nils Diewald2fe12e12015-03-06 16:47:06 +0000731 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000732 _removeFirst : function () {
733 this.item(this._list[this._offset]).lowlight();
Akron9905e2a2016-05-10 16:06:44 +0200734 // leave lengthField/prefix/slider
735 this._element.removeChild(this._element.children[3]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000736 },
737
Nils Diewald2fe12e12015-03-06 16:47:06 +0000738
739 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000740 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000741 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000742 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000743 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000744 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000745});