blob: 420d0bccbe14eb4cc8e9e0fdc3cdfbbfc2c6812e [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',
Nils Diewald0e6992a2015-04-14 20:13:52 +000015 'util'
16], function (defaultItemClass,
Akronc7448732016-04-27 14:06:58 +020017 defaultPrefixClass,
Akron1ff3ac22016-04-28 16:30:45 +020018 defaultLengthFieldClass) {
Nils Diewaldfda29d92015-01-22 17:28:01 +000019
Nils Diewald0e6992a2015-04-14 20:13:52 +000020 // Default maximum number of menu items
21 var menuLimit = 8;
22
23 function _codeFromEvent (e) {
24 if (e.charCode && (e.keyCode == 0))
25 return e.charCode
26 return e.keyCode;
Nils Diewald59c02fc2015-03-07 01:29:09 +000027 };
28
Nils Diewald86dad5b2015-01-28 15:09:07 +000029
30 /**
31 * List of items for drop down menu (complete).
32 * Only a sublist of the menu is filtered (live).
33 * Only a sublist of the filtered menu is visible (shown).
34 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000035 return {
Nils Diewald86dad5b2015-01-28 15:09:07 +000036 /**
37 * Create new Menu based on the action prefix
38 * and a list of menu items.
39 *
40 * @this {Menu}
41 * @constructor
42 * @param {string} Context prefix
43 * @param {Array.<Array.<string>>} List of menu items
44 */
45 create : function (params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000046 return Object.create(this)._init(params);
Nils Diewald86dad5b2015-01-28 15:09:07 +000047 },
48
Nils Diewald6e43ffd2015-03-25 18:55:39 +000049 /**
50 * Destroy this menu
51 * (in case you don't trust the
52 * mark and sweep GC)!
53 */
54 destroy : function () {
55 if (this._element != undefined)
56 delete this._element["menu"];
57
58 for (var i = 0; i < this._items.length; i++) {
59 delete this._items[i]["_menu"];
60 };
Nils Diewald5c5a7472015-04-02 22:13:38 +000061 delete this._prefix['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +000062 },
63
Nils Diewald7148c6f2015-05-04 15:07:53 +000064
65 /**
66 * Focus on this menu.
67 */
Nils Diewald2fe12e12015-03-06 16:47:06 +000068 focus : function () {
69 this._element.focus();
70 },
71
Nils Diewald7148c6f2015-05-04 15:07:53 +000072
Nils Diewald59c02fc2015-03-07 01:29:09 +000073 // mouse wheel treatment
74 _mousewheel : function (e) {
75 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +000076
77 delta = e.deltaY / 120;
78 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000079 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +000080 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000081 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +000082 e.halt();
83 },
84
Nils Diewald7148c6f2015-05-04 15:07:53 +000085
Nils Diewald59c02fc2015-03-07 01:29:09 +000086 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +000087 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +000088 var code = _codeFromEvent(e);
89
Nils Diewald59c02fc2015-03-07 01:29:09 +000090 switch (code) {
91 case 27: // 'Esc'
92 e.halt();
93 this.hide();
94 break;
Nils Diewald5975d702015-03-09 17:45:42 +000095
Nils Diewald59c02fc2015-03-07 01:29:09 +000096 case 38: // 'Up'
97 e.halt();
98 this.prev();
99 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000100 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +0000101 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000102 this.prev();
103 break;
104 case 40: // 'Down'
105 e.halt();
106 this.next();
107 break;
108 case 34: // 'Page down'
109 e.halt();
110 this.next();
111 break;
112 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000113 if (this._prefix.active())
114 break;
115
Nils Diewald5975d702015-03-09 17:45:42 +0000116 var item = this.liveItem(this._position);
Akron5ef4fa02015-06-02 16:25:14 +0200117
Nils Diewald5975d702015-03-09 17:45:42 +0000118 if (item["further"] !== undefined) {
119 item["further"].bind(item).apply();
Nils Diewald5975d702015-03-09 17:45:42 +0000120 };
Akron5ef4fa02015-06-02 16:25:14 +0200121
122 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000123 break;
124 case 13: // 'Enter'
125
126 // Click on prefix
127 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000128 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000129
130 // Click on item
131 else
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000132 this.liveItem(this._position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000133 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000134 break;
135 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000136 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000137 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000138 e.halt();
139 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000140 };
141 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000142
Nils Diewald47f366b2015-04-15 20:06:35 +0000143 // Add characters to prefix
144 _keypress : function (e) {
145 var c = String.fromCharCode(_codeFromEvent(e)).toLowerCase();
Nils Diewald5975d702015-03-09 17:45:42 +0000146
Nils Diewald47f366b2015-04-15 20:06:35 +0000147 // Add prefix
148 this._prefix.add(c);
149
150 if (!this.show()) {
151 this.prefix('').show();
152 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000153 };
154 },
155
Nils Diewald2fe12e12015-03-06 16:47:06 +0000156 // Initialize list
Akron1ff3ac22016-04-28 16:30:45 +0200157 _init : function (itemClass, prefixClass, lengthFieldClass, params) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000158 var that = this;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000159 this._itemClass = itemClass || defaultItemClass;
Nils Diewald5975d702015-03-09 17:45:42 +0000160
Akronc7448732016-04-27 14:06:58 +0200161 // Add prefix object
162 if (prefixClass !== undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000163 this._prefix = prefixClass.create();
Akronc7448732016-04-27 14:06:58 +0200164 }
165 else {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000166 this._prefix = defaultPrefixClass.create();
Akronc7448732016-04-27 14:06:58 +0200167 };
Nils Diewald5c5a7472015-04-02 22:13:38 +0000168 this._prefix._menu = this;
169
Akronc7448732016-04-27 14:06:58 +0200170 // Add lengthField object
Akron1ff3ac22016-04-28 16:30:45 +0200171 if (lengthFieldClass !== undefined) {
172 this._lengthField = lengthFieldClass.create();
173 }
174 else {
175 this._lengthField = defaultLengthFieldClass.create();
176 };
Akronc7448732016-04-27 14:06:58 +0200177 this._lengthField._menu = this;
178
179
Nils Diewald5975d702015-03-09 17:45:42 +0000180 var e = document.createElement("ul");
Nils Diewald59c02fc2015-03-07 01:29:09 +0000181 e.style.opacity = 0;
182 e.style.outline = 0;
183 e.setAttribute('tabindex', 0);
Nils Diewald5c5a7472015-04-02 22:13:38 +0000184 e.classList.add('menu');
Nils Diewald58141332015-04-07 16:18:45 +0000185 e.classList.add('roll');
Nils Diewald5975d702015-03-09 17:45:42 +0000186 e.appendChild(this._prefix.element());
Akronc7448732016-04-27 14:06:58 +0200187 e.appendChild(this._lengthField.element());
Nils Diewald86dad5b2015-01-28 15:09:07 +0000188
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000189 // This has to be cleaned up later on
190 e["menu"] = this;
191
Nils Diewald59c02fc2015-03-07 01:29:09 +0000192 // Arrow keys
193 e.addEventListener(
Nils Diewald47f366b2015-04-15 20:06:35 +0000194 'keydown',
195 function (ev) {
196 that._keydown(ev)
197 },
198 false
199 );
200
201 // Strings
202 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000203 'keypress',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000204 function (ev) {
Nils Diewald5975d702015-03-09 17:45:42 +0000205 that._keypress(ev)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000206 },
207 false
208 );
209
210 // Mousewheel
211 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000212 'wheel',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000213 function (ev) {
214 that._mousewheel(ev)
Nils Diewald2fe12e12015-03-06 16:47:06 +0000215 },
216 false
217 );
218
Nils Diewald5975d702015-03-09 17:45:42 +0000219 this._element = e;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000220 this.active = false;
Akron37513a62015-11-17 01:07:11 +0100221 // this.selected = undefined;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000222 this._items = new Array();
223 var i;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000224
225 // Initialize item list based on parameters
Nils Diewald86dad5b2015-01-28 15:09:07 +0000226 for (i in params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000227 var obj = this._itemClass.create(params[i]);
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000228
229 // This may become circular
230 obj["_menu"] = this;
Akron1ff3ac22016-04-28 16:30:45 +0200231 this._lengthField.add(params[i]);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000232 this._items.push(obj);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000233 };
Akronc7448732016-04-27 14:06:58 +0200234
Nils Diewald0e6992a2015-04-14 20:13:52 +0000235 this._limit = menuLimit;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000236 this._position = 0; // position in the active list
237 this._active = -1; // active item in the item list
Nils Diewald20f7ace2015-05-07 12:51:34 +0000238 this._firstActive = false; // Show the first item active always?
Nils Diewald86dad5b2015-01-28 15:09:07 +0000239 this._reset();
240 return this;
241 },
242
Nils Diewald2fe12e12015-03-06 16:47:06 +0000243 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000244 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000245 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000246 element : function () {
247 return this._element;
248 },
249
Nils Diewald7148c6f2015-05-04 15:07:53 +0000250
Nils Diewald2fe12e12015-03-06 16:47:06 +0000251 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000252 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000253 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000254 itemClass : function () {
255 return this._itemClass;
256 },
257
258 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000259 * Get and set the numerical value
260 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000261 */
262 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000263 if (arguments.length === 1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000264 this._limit = limit;
Nils Diewald5975d702015-03-09 17:45:42 +0000265 return this;
266 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000267 return this._limit;
268 },
269
Nils Diewald7148c6f2015-05-04 15:07:53 +0000270
Nils Diewald86dad5b2015-01-28 15:09:07 +0000271 /**
272 * Upgrade this object to another object,
273 * while private data stays intact.
274 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000275 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000276 */
277 upgradeTo : function (props) {
278 for (var prop in props) {
279 this[prop] = props[prop];
280 };
281 return this;
282 },
283
Nils Diewald7148c6f2015-05-04 15:07:53 +0000284
Nils Diewald2fe12e12015-03-06 16:47:06 +0000285 // Reset chosen item and prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000286 _reset : function () {
287 this._offset = 0;
288 this._pos = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000289 this._prefix.value('');
Nils Diewald86dad5b2015-01-28 15:09:07 +0000290 },
291
Nils Diewald7148c6f2015-05-04 15:07:53 +0000292
Nils Diewald86dad5b2015-01-28 15:09:07 +0000293 /**
294 * Filter the list and make it visible
295 *
296 * @param {string} Prefix for filtering the list
297 */
Nils Diewald5975d702015-03-09 17:45:42 +0000298 show : function () {
Nils Diewalde8518f82015-03-18 22:41:49 +0000299
Nils Diewald86dad5b2015-01-28 15:09:07 +0000300 // Initialize the list
301 if (!this._initList())
Nils Diewald59c02fc2015-03-07 01:29:09 +0000302 return false;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000303
Nils Diewald2fe12e12015-03-06 16:47:06 +0000304 // show based on initial offset
Nils Diewald86dad5b2015-01-28 15:09:07 +0000305 this._showItems(0);
306
307 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000308 // Todo: Or the last element chosen
Nils Diewald20f7ace2015-05-07 12:51:34 +0000309 if (this._firstActive)
310 this.liveItem(0).active(true);
311
Akron37513a62015-11-17 01:07:11 +0100312 this._position = 0;
313
Nils Diewalde8518f82015-03-18 22:41:49 +0000314 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000315
Nils Diewald2fe12e12015-03-06 16:47:06 +0000316 this._element.style.opacity = 1;
317
Akron37513a62015-11-17 01:07:11 +0100318 // Iterate to the active item
319 if (this._active !== -1 && !this._prefix.isSet()) {
320 while (this._list[this._position] < this._active) {
321 this.next();
322 };
323 };
324
Nils Diewald86dad5b2015-01-28 15:09:07 +0000325 // Add classes for rolling menus
326 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000327 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000328 },
329
Nils Diewald7148c6f2015-05-04 15:07:53 +0000330
331 /**
332 * Hide the menu and call the onHide callback.
333 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000334 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000335 this.active = false;
336 this.delete();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000337 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000338 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000339 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000340 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000341 },
342
Nils Diewald7148c6f2015-05-04 15:07:53 +0000343 /**
344 * Function released when the menu hides.
345 * This method is expected to be overridden.
346 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000347 onHide : function () {},
348
Nils Diewald2fe12e12015-03-06 16:47:06 +0000349 // Initialize the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000350 _initList : function () {
351
Nils Diewald2fe12e12015-03-06 16:47:06 +0000352 // Create a new list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000353 if (this._list === undefined) {
354 this._list = [];
355 }
356 else if (this._list.length != 0) {
357 this._boundary(false);
358 this._list.length = 0;
359 };
360
361 // Offset is initially zero
362 this._offset = 0;
363
Nils Diewald2fe12e12015-03-06 16:47:06 +0000364 // There is no prefix set
Nils Diewald86dad5b2015-01-28 15:09:07 +0000365 if (this.prefix().length <= 0) {
Nils Diewald5975d702015-03-09 17:45:42 +0000366 var i = 0;
367 for (; i < this._items.length; i++)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000368 this._list.push(i);
Nils Diewald5975d702015-03-09 17:45:42 +0000369 while (this._items[++i] !== undefined) {
370 this._items[i].lowlight();
Nils Diewald5975d702015-03-09 17:45:42 +0000371 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000372 return true;
373 };
374
Nils Diewald2fe12e12015-03-06 16:47:06 +0000375 // There is a prefix set, so filter the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000376 var pos;
377 var paddedPrefix = " " + this.prefix();
378
Nils Diewald2fe12e12015-03-06 16:47:06 +0000379 // Iterate over all items and choose preferred matching items
380 // i.e. the matching happens at the word start
Nils Diewald86dad5b2015-01-28 15:09:07 +0000381 for (pos = 0; pos < this._items.length; pos++) {
382 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
383 this._list.push(pos);
384 };
385
Nils Diewald2fe12e12015-03-06 16:47:06 +0000386 // The list is empty - so lower your expectations
387 // Iterate over all items and choose matching items
388 // i.e. the matching happens anywhere in the word
Nils Diewald86dad5b2015-01-28 15:09:07 +0000389 if (this._list.length == 0) {
390 for (pos = 0; pos < this._items.length; pos++) {
391 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
392 this._list.push(pos);
393 };
394 };
395
Nils Diewald2fe12e12015-03-06 16:47:06 +0000396 // Filter was successful - yeah!
Nils Diewald86dad5b2015-01-28 15:09:07 +0000397 return this._list.length > 0 ? true : false;
398 },
399
400 // Set boundary for viewport
401 _boundary : function (bool) {
402 this.item(this._list[0]).noMore(bool);
403 this.item(this._list[this._list.length - 1]).noMore(bool);
404 },
405
Nils Diewald7148c6f2015-05-04 15:07:53 +0000406
Nils Diewald86dad5b2015-01-28 15:09:07 +0000407 /**
408 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000409 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000410 */
Nils Diewald5975d702015-03-09 17:45:42 +0000411 prefix : function (pref) {
412 if (arguments.length === 1) {
413 this._prefix.value(pref);
414 return this;
415 };
416 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000417 },
418
Akronc7448732016-04-27 14:06:58 +0200419 /**
420 * Get the lengthField object.
421 */
422 lengthField : function () {
423 return this._lengthField;
424 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000425
Nils Diewald2fe12e12015-03-06 16:47:06 +0000426 // Append Items that should be shown
Nils Diewald86dad5b2015-01-28 15:09:07 +0000427 _showItems : function (offset) {
428 this.delete();
429
430 // Use list
431 var shown = 0;
432 var i;
433 for (i in this._list) {
434
435 // Don't show - it's before offset
436 if (shown++ < offset)
437 continue;
438
439 this._append(this._list[i]);
440
441 if (shown >= (this.limit() + this._offset))
442 break;
443 };
444 },
445
446 /**
447 * Delete all visible items from the menu element
448 */
449 delete : function () {
450 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000451
Nils Diewald5975d702015-03-09 17:45:42 +0000452 /*
Nils Diewald2fe12e12015-03-06 16:47:06 +0000453 // Iterate over all visible items
Nils Diewald86dad5b2015-01-28 15:09:07 +0000454 for (var i = 0; i <= this.limit(); i++) {
455
Nils Diewald5975d702015-03-09 17:45:42 +0000456 // there is a visible element
457 // unhighlight!
Nils Diewald59c02fc2015-03-07 01:29:09 +0000458 if (child = this.shownItem(i)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000459 child.lowlight();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000460 child.active(false);
461 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000462 };
Nils Diewald5975d702015-03-09 17:45:42 +0000463 */
464
465 for (var i in this._list) {
466 var item = this._items[this._list[i]];
467 item.lowlight();
468 item.active(false);
469 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000470
Nils Diewald2fe12e12015-03-06 16:47:06 +0000471 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000472 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200473 // Leave the prefix and lengthField
474 for (var i = children.length - 1; i >= 2; i--) {
Nils Diewald5975d702015-03-09 17:45:42 +0000475 this._element.removeChild(
476 children[i]
477 );
478 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000479 },
480
481
482 // Append item to the shown list based on index
483 _append : function (i) {
484 var item = this.item(i);
485
486 // Highlight based on prefix
487 if (this.prefix().length > 0)
488 item.highlight(this.prefix());
489
490 // Append element
491 this.element().appendChild(item.element());
492 },
493
494
Nils Diewald2fe12e12015-03-06 16:47:06 +0000495 // Prepend item to the shown list based on index
496 _prepend : function (i) {
497 var item = this.item(i);
498
499 // Highlight based on prefix
500 if (this.prefix().length > 0)
501 item.highlight(this.prefix());
502
503 var e = this.element();
Akronc7448732016-04-27 14:06:58 +0200504 // Append element after lengthFiled/prefix
Nils Diewald2fe12e12015-03-06 16:47:06 +0000505 e.insertBefore(
506 item.element(),
Akronc7448732016-04-27 14:06:58 +0200507 e.children[2]
Nils Diewald2fe12e12015-03-06 16:47:06 +0000508 );
509 },
510
511
512 /**
513 * Get a specific item from the complete list
514 *
515 * @param {number} index of the list item
516 */
517 item : function (index) {
518 return this._items[index]
519 },
520
521
Nils Diewald86dad5b2015-01-28 15:09:07 +0000522 /**
523 * Get a specific item from the filtered list
524 *
525 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000526 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000527 */
528 liveItem : function (index) {
529 if (this._list === undefined)
530 if (!this._initList())
531 return;
532
533 return this._items[this._list[index]];
534 },
535
Nils Diewald86dad5b2015-01-28 15:09:07 +0000536
537 /**
538 * Get a specific item from the visible list
539 *
540 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000541 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000542 */
543 shownItem : function (index) {
544 if (index >= this.limit())
545 return;
546 return this.liveItem(this._offset + index);
547 },
548
549
Nils Diewald2fe12e12015-03-06 16:47:06 +0000550 /**
551 * Get the length of the full list
552 */
553 length : function () {
554 return this._items.length;
555 },
556
557
558 /**
559 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000560 */
561 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000562
Nils Diewald86dad5b2015-01-28 15:09:07 +0000563 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000564 if (this._position === -1)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000565 return;
566
Nils Diewald5975d702015-03-09 17:45:42 +0000567 var newItem;
568
Nils Diewald86dad5b2015-01-28 15:09:07 +0000569 // Set new live item
Nils Diewalde8518f82015-03-18 22:41:49 +0000570 if (!this._prefix.active()) {
571 var oldItem = this.liveItem(this._position);
572 oldItem.active(false);
573 };
574
575 this._position++;
576
Nils Diewald5975d702015-03-09 17:45:42 +0000577 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000578
Nils Diewald5975d702015-03-09 17:45:42 +0000579 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000580 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000581
582 // Activate prefix
583 var prefix = this._prefix;
584
585 // Mark prefix
586 if (prefix.isSet() && !prefix.active()) {
587 this._position--;
588 prefix.active(true);
589 return;
590 }
591 else {
592 this._offset = 0;
593 this._position = 0;
594 newItem = this.liveItem(0);
595 this._showItems(0);
596 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000597 }
598
599 // The next element is outside the view - roll down
Nils Diewald2fe12e12015-03-06 16:47:06 +0000600 else if (this._position >= (this.limit() + this._offset)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000601 this._removeFirst();
602 this._offset++;
603 this._append(this._list[this._position]);
604 };
Nils Diewald5975d702015-03-09 17:45:42 +0000605
606 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000607 newItem.active(true);
608 },
609
Nils Diewalde8518f82015-03-18 22:41:49 +0000610 /*
611 * Page down to the first item on the next page
612 */
613 /*
614 nextPage : function () {
615
616 // Prefix is active
617 if (this._prefix.active()) {
618 this._prefix.active(false);
619 }
620
621 // Last item is chosen
622 else if (this._position >= this.limit() + this._offset) {
623
624 this._position = this.limit() + this._offset - 1;
625 newItem = this.liveItem(this._position);
626 var oldItem = this.liveItem(this._position--);
627 oldItem.active(false);
628 }
629
630 // Last item of page is chosen
631 else if (0) {
632
633 // Jump to last item
634 else {
635 var oldItem = this.liveItem(this._position);
636 oldItem.active(false);
637
638 this._position = this.limit() + this._offset - 1;
639 newItem = this.liveItem(this._position);
640 };
641
642 newItem.active(true);
643 },
644 */
645
Nils Diewald86dad5b2015-01-28 15:09:07 +0000646
647 /*
648 * Make the previous item in the menu active
649 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000650 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000651
Nils Diewald2fe12e12015-03-06 16:47:06 +0000652 // No active element set
Nils Diewalde8518f82015-03-18 22:41:49 +0000653 if (this._position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000654 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000655 // TODO: Choose last item
656 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000657
Nils Diewald5975d702015-03-09 17:45:42 +0000658 var newItem;
659
Nils Diewald86dad5b2015-01-28 15:09:07 +0000660 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000661 if (!this._prefix.active()) {
662 var oldItem = this.liveItem(this._position--);
663 oldItem.active(false);
664 };
665
Nils Diewald5975d702015-03-09 17:45:42 +0000666 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000667
668 // The previous element is undefined - roll to bottom
669 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000670
671 // Activate prefix
672 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000673 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000674
675 // Normalize offset
676 this._offset = this._offset < 0 ? 0 : this._offset;
677
Nils Diewald2fe12e12015-03-06 16:47:06 +0000678 this._position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000679
680 if (prefix.isSet() && !prefix.active()) {
681 this._position++;
682 prefix.active(true);
683 return;
684 }
685 else {
686 newItem = this.liveItem(this._position);
687 this._showItems(this._offset);
688 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000689 }
690
691 // The previous element is outside the view - roll up
692 else if (this._position < this._offset) {
693 this._removeLast();
694 this._offset--;
695 this._prepend(this._list[this._position]);
696 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000697
Nils Diewald5975d702015-03-09 17:45:42 +0000698 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000699 newItem.active(true);
700 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000701
702
Nils Diewald7148c6f2015-05-04 15:07:53 +0000703 /**
704 * Length of the filtered item list.
705 */
Nils Diewald5975d702015-03-09 17:45:42 +0000706 liveLength : function () {
707 if (this._list === undefined)
708 this._initList();
709 return this._list.length;
710 },
711
712
Nils Diewald2fe12e12015-03-06 16:47:06 +0000713 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000714 _removeFirst : function () {
715 this.item(this._list[this._offset]).lowlight();
Akronc7448732016-04-27 14:06:58 +0200716 // leave lengthField/prefix
717 this._element.removeChild(this._element.children[2]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000718 },
719
Nils Diewald2fe12e12015-03-06 16:47:06 +0000720
721 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000722 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000723 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000724 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000725 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000726 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000727});