blob: a8cf26afaedaca1b744a8a0799de3d559568e710 [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,
18 lengthFieldClass) {
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
Nils Diewald5975d702015-03-09 17:45:42 +0000157 _init : function (itemClass, prefixClass, 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
171 this._lengthField = lengthFieldClass.create();
172 this._lengthField._menu = this;
173
174
Nils Diewald5975d702015-03-09 17:45:42 +0000175 var e = document.createElement("ul");
Nils Diewald59c02fc2015-03-07 01:29:09 +0000176 e.style.opacity = 0;
177 e.style.outline = 0;
178 e.setAttribute('tabindex', 0);
Nils Diewald5c5a7472015-04-02 22:13:38 +0000179 e.classList.add('menu');
Nils Diewald58141332015-04-07 16:18:45 +0000180 e.classList.add('roll');
Nils Diewald5975d702015-03-09 17:45:42 +0000181 e.appendChild(this._prefix.element());
Akronc7448732016-04-27 14:06:58 +0200182 e.appendChild(this._lengthField.element());
Nils Diewald86dad5b2015-01-28 15:09:07 +0000183
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000184 // This has to be cleaned up later on
185 e["menu"] = this;
186
Nils Diewald59c02fc2015-03-07 01:29:09 +0000187 // Arrow keys
188 e.addEventListener(
Nils Diewald47f366b2015-04-15 20:06:35 +0000189 'keydown',
190 function (ev) {
191 that._keydown(ev)
192 },
193 false
194 );
195
196 // Strings
197 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000198 'keypress',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000199 function (ev) {
Nils Diewald5975d702015-03-09 17:45:42 +0000200 that._keypress(ev)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000201 },
202 false
203 );
204
205 // Mousewheel
206 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000207 'wheel',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000208 function (ev) {
209 that._mousewheel(ev)
Nils Diewald2fe12e12015-03-06 16:47:06 +0000210 },
211 false
212 );
213
Nils Diewald5975d702015-03-09 17:45:42 +0000214 this._element = e;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000215 this.active = false;
Akron37513a62015-11-17 01:07:11 +0100216 // this.selected = undefined;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000217 this._items = new Array();
218 var i;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000219
220 // Initialize item list based on parameters
Nils Diewald86dad5b2015-01-28 15:09:07 +0000221 for (i in params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000222 var obj = this._itemClass.create(params[i]);
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000223
224 // This may become circular
225 obj["_menu"] = this;
Akronc7448732016-04-27 14:06:58 +0200226 this._lengthField.add(params[i][0]);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000227 this._items.push(obj);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000228 };
Akronc7448732016-04-27 14:06:58 +0200229
Nils Diewald0e6992a2015-04-14 20:13:52 +0000230 this._limit = menuLimit;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000231 this._position = 0; // position in the active list
232 this._active = -1; // active item in the item list
Nils Diewald20f7ace2015-05-07 12:51:34 +0000233 this._firstActive = false; // Show the first item active always?
Nils Diewald86dad5b2015-01-28 15:09:07 +0000234 this._reset();
235 return this;
236 },
237
Nils Diewald2fe12e12015-03-06 16:47:06 +0000238 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000239 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000240 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000241 element : function () {
242 return this._element;
243 },
244
Nils Diewald7148c6f2015-05-04 15:07:53 +0000245
Nils Diewald2fe12e12015-03-06 16:47:06 +0000246 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000247 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000248 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000249 itemClass : function () {
250 return this._itemClass;
251 },
252
253 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000254 * Get and set the numerical value
255 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000256 */
257 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000258 if (arguments.length === 1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000259 this._limit = limit;
Nils Diewald5975d702015-03-09 17:45:42 +0000260 return this;
261 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000262 return this._limit;
263 },
264
Nils Diewald7148c6f2015-05-04 15:07:53 +0000265
Nils Diewald86dad5b2015-01-28 15:09:07 +0000266 /**
267 * Upgrade this object to another object,
268 * while private data stays intact.
269 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000270 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000271 */
272 upgradeTo : function (props) {
273 for (var prop in props) {
274 this[prop] = props[prop];
275 };
276 return this;
277 },
278
Nils Diewald7148c6f2015-05-04 15:07:53 +0000279
Nils Diewald2fe12e12015-03-06 16:47:06 +0000280 // Reset chosen item and prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000281 _reset : function () {
282 this._offset = 0;
283 this._pos = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000284 this._prefix.value('');
Nils Diewald86dad5b2015-01-28 15:09:07 +0000285 },
286
Nils Diewald7148c6f2015-05-04 15:07:53 +0000287
Nils Diewald86dad5b2015-01-28 15:09:07 +0000288 /**
289 * Filter the list and make it visible
290 *
291 * @param {string} Prefix for filtering the list
292 */
Nils Diewald5975d702015-03-09 17:45:42 +0000293 show : function () {
Nils Diewalde8518f82015-03-18 22:41:49 +0000294
Nils Diewald86dad5b2015-01-28 15:09:07 +0000295 // Initialize the list
296 if (!this._initList())
Nils Diewald59c02fc2015-03-07 01:29:09 +0000297 return false;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000298
Nils Diewald2fe12e12015-03-06 16:47:06 +0000299 // show based on initial offset
Nils Diewald86dad5b2015-01-28 15:09:07 +0000300 this._showItems(0);
301
302 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000303 // Todo: Or the last element chosen
Nils Diewald20f7ace2015-05-07 12:51:34 +0000304 if (this._firstActive)
305 this.liveItem(0).active(true);
306
Akron37513a62015-11-17 01:07:11 +0100307 this._position = 0;
308
Nils Diewalde8518f82015-03-18 22:41:49 +0000309 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000310
Nils Diewald2fe12e12015-03-06 16:47:06 +0000311 this._element.style.opacity = 1;
312
Akron37513a62015-11-17 01:07:11 +0100313 // Iterate to the active item
314 if (this._active !== -1 && !this._prefix.isSet()) {
315 while (this._list[this._position] < this._active) {
316 this.next();
317 };
318 };
319
Nils Diewald86dad5b2015-01-28 15:09:07 +0000320 // Add classes for rolling menus
321 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000322 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000323 },
324
Nils Diewald7148c6f2015-05-04 15:07:53 +0000325
326 /**
327 * Hide the menu and call the onHide callback.
328 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000329 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000330 this.active = false;
331 this.delete();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000332 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000333 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000334 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000335 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000336 },
337
Nils Diewald7148c6f2015-05-04 15:07:53 +0000338 /**
339 * Function released when the menu hides.
340 * This method is expected to be overridden.
341 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000342 onHide : function () {},
343
Nils Diewald2fe12e12015-03-06 16:47:06 +0000344 // Initialize the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000345 _initList : function () {
346
Nils Diewald2fe12e12015-03-06 16:47:06 +0000347 // Create a new list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000348 if (this._list === undefined) {
349 this._list = [];
350 }
351 else if (this._list.length != 0) {
352 this._boundary(false);
353 this._list.length = 0;
354 };
355
356 // Offset is initially zero
357 this._offset = 0;
358
Nils Diewald2fe12e12015-03-06 16:47:06 +0000359 // There is no prefix set
Nils Diewald86dad5b2015-01-28 15:09:07 +0000360 if (this.prefix().length <= 0) {
Nils Diewald5975d702015-03-09 17:45:42 +0000361 var i = 0;
362 for (; i < this._items.length; i++)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000363 this._list.push(i);
Nils Diewald5975d702015-03-09 17:45:42 +0000364 while (this._items[++i] !== undefined) {
365 this._items[i].lowlight();
Nils Diewald5975d702015-03-09 17:45:42 +0000366 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000367 return true;
368 };
369
Nils Diewald2fe12e12015-03-06 16:47:06 +0000370 // There is a prefix set, so filter the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000371 var pos;
372 var paddedPrefix = " " + this.prefix();
373
Nils Diewald2fe12e12015-03-06 16:47:06 +0000374 // Iterate over all items and choose preferred matching items
375 // i.e. the matching happens at the word start
Nils Diewald86dad5b2015-01-28 15:09:07 +0000376 for (pos = 0; pos < this._items.length; pos++) {
377 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
378 this._list.push(pos);
379 };
380
Nils Diewald2fe12e12015-03-06 16:47:06 +0000381 // The list is empty - so lower your expectations
382 // Iterate over all items and choose matching items
383 // i.e. the matching happens anywhere in the word
Nils Diewald86dad5b2015-01-28 15:09:07 +0000384 if (this._list.length == 0) {
385 for (pos = 0; pos < this._items.length; pos++) {
386 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
387 this._list.push(pos);
388 };
389 };
390
Nils Diewald2fe12e12015-03-06 16:47:06 +0000391 // Filter was successful - yeah!
Nils Diewald86dad5b2015-01-28 15:09:07 +0000392 return this._list.length > 0 ? true : false;
393 },
394
395 // Set boundary for viewport
396 _boundary : function (bool) {
397 this.item(this._list[0]).noMore(bool);
398 this.item(this._list[this._list.length - 1]).noMore(bool);
399 },
400
Nils Diewald7148c6f2015-05-04 15:07:53 +0000401
Nils Diewald86dad5b2015-01-28 15:09:07 +0000402 /**
403 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000404 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000405 */
Nils Diewald5975d702015-03-09 17:45:42 +0000406 prefix : function (pref) {
407 if (arguments.length === 1) {
408 this._prefix.value(pref);
409 return this;
410 };
411 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000412 },
413
Akronc7448732016-04-27 14:06:58 +0200414 /**
415 * Get the lengthField object.
416 */
417 lengthField : function () {
418 return this._lengthField;
419 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000420
Nils Diewald2fe12e12015-03-06 16:47:06 +0000421 // Append Items that should be shown
Nils Diewald86dad5b2015-01-28 15:09:07 +0000422 _showItems : function (offset) {
423 this.delete();
424
425 // Use list
426 var shown = 0;
427 var i;
428 for (i in this._list) {
429
430 // Don't show - it's before offset
431 if (shown++ < offset)
432 continue;
433
434 this._append(this._list[i]);
435
436 if (shown >= (this.limit() + this._offset))
437 break;
438 };
439 },
440
441 /**
442 * Delete all visible items from the menu element
443 */
444 delete : function () {
445 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000446
Nils Diewald5975d702015-03-09 17:45:42 +0000447 /*
Nils Diewald2fe12e12015-03-06 16:47:06 +0000448 // Iterate over all visible items
Nils Diewald86dad5b2015-01-28 15:09:07 +0000449 for (var i = 0; i <= this.limit(); i++) {
450
Nils Diewald5975d702015-03-09 17:45:42 +0000451 // there is a visible element
452 // unhighlight!
Nils Diewald59c02fc2015-03-07 01:29:09 +0000453 if (child = this.shownItem(i)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000454 child.lowlight();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000455 child.active(false);
456 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000457 };
Nils Diewald5975d702015-03-09 17:45:42 +0000458 */
459
460 for (var i in this._list) {
461 var item = this._items[this._list[i]];
462 item.lowlight();
463 item.active(false);
464 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000465
Nils Diewald2fe12e12015-03-06 16:47:06 +0000466 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000467 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200468 // Leave the prefix and lengthField
469 for (var i = children.length - 1; i >= 2; i--) {
Nils Diewald5975d702015-03-09 17:45:42 +0000470 this._element.removeChild(
471 children[i]
472 );
473 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000474 },
475
476
477 // Append item to the shown list based on index
478 _append : function (i) {
479 var item = this.item(i);
480
481 // Highlight based on prefix
482 if (this.prefix().length > 0)
483 item.highlight(this.prefix());
484
485 // Append element
486 this.element().appendChild(item.element());
487 },
488
489
Nils Diewald2fe12e12015-03-06 16:47:06 +0000490 // Prepend item to the shown list based on index
491 _prepend : function (i) {
492 var item = this.item(i);
493
494 // Highlight based on prefix
495 if (this.prefix().length > 0)
496 item.highlight(this.prefix());
497
498 var e = this.element();
Akronc7448732016-04-27 14:06:58 +0200499 // Append element after lengthFiled/prefix
Nils Diewald2fe12e12015-03-06 16:47:06 +0000500 e.insertBefore(
501 item.element(),
Akronc7448732016-04-27 14:06:58 +0200502 e.children[2]
Nils Diewald2fe12e12015-03-06 16:47:06 +0000503 );
504 },
505
506
507 /**
508 * Get a specific item from the complete list
509 *
510 * @param {number} index of the list item
511 */
512 item : function (index) {
513 return this._items[index]
514 },
515
516
Nils Diewald86dad5b2015-01-28 15:09:07 +0000517 /**
518 * Get a specific item from the filtered list
519 *
520 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000521 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000522 */
523 liveItem : function (index) {
524 if (this._list === undefined)
525 if (!this._initList())
526 return;
527
528 return this._items[this._list[index]];
529 },
530
Nils Diewald86dad5b2015-01-28 15:09:07 +0000531
532 /**
533 * Get a specific item from the visible list
534 *
535 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000536 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000537 */
538 shownItem : function (index) {
539 if (index >= this.limit())
540 return;
541 return this.liveItem(this._offset + index);
542 },
543
544
Nils Diewald2fe12e12015-03-06 16:47:06 +0000545 /**
546 * Get the length of the full list
547 */
548 length : function () {
549 return this._items.length;
550 },
551
552
553 /**
554 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000555 */
556 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000557
Nils Diewald86dad5b2015-01-28 15:09:07 +0000558 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000559 if (this._position === -1)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000560 return;
561
Nils Diewald5975d702015-03-09 17:45:42 +0000562 var newItem;
563
Nils Diewald86dad5b2015-01-28 15:09:07 +0000564 // Set new live item
Nils Diewalde8518f82015-03-18 22:41:49 +0000565 if (!this._prefix.active()) {
566 var oldItem = this.liveItem(this._position);
567 oldItem.active(false);
568 };
569
570 this._position++;
571
Nils Diewald5975d702015-03-09 17:45:42 +0000572 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000573
Nils Diewald5975d702015-03-09 17:45:42 +0000574 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000575 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000576
577 // Activate prefix
578 var prefix = this._prefix;
579
580 // Mark prefix
581 if (prefix.isSet() && !prefix.active()) {
582 this._position--;
583 prefix.active(true);
584 return;
585 }
586 else {
587 this._offset = 0;
588 this._position = 0;
589 newItem = this.liveItem(0);
590 this._showItems(0);
591 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000592 }
593
594 // The next element is outside the view - roll down
Nils Diewald2fe12e12015-03-06 16:47:06 +0000595 else if (this._position >= (this.limit() + this._offset)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000596 this._removeFirst();
597 this._offset++;
598 this._append(this._list[this._position]);
599 };
Nils Diewald5975d702015-03-09 17:45:42 +0000600
601 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000602 newItem.active(true);
603 },
604
Nils Diewalde8518f82015-03-18 22:41:49 +0000605 /*
606 * Page down to the first item on the next page
607 */
608 /*
609 nextPage : function () {
610
611 // Prefix is active
612 if (this._prefix.active()) {
613 this._prefix.active(false);
614 }
615
616 // Last item is chosen
617 else if (this._position >= this.limit() + this._offset) {
618
619 this._position = this.limit() + this._offset - 1;
620 newItem = this.liveItem(this._position);
621 var oldItem = this.liveItem(this._position--);
622 oldItem.active(false);
623 }
624
625 // Last item of page is chosen
626 else if (0) {
627
628 // Jump to last item
629 else {
630 var oldItem = this.liveItem(this._position);
631 oldItem.active(false);
632
633 this._position = this.limit() + this._offset - 1;
634 newItem = this.liveItem(this._position);
635 };
636
637 newItem.active(true);
638 },
639 */
640
Nils Diewald86dad5b2015-01-28 15:09:07 +0000641
642 /*
643 * Make the previous item in the menu active
644 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000645 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000646
Nils Diewald2fe12e12015-03-06 16:47:06 +0000647 // No active element set
Nils Diewalde8518f82015-03-18 22:41:49 +0000648 if (this._position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000649 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000650 // TODO: Choose last item
651 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000652
Nils Diewald5975d702015-03-09 17:45:42 +0000653 var newItem;
654
Nils Diewald86dad5b2015-01-28 15:09:07 +0000655 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000656 if (!this._prefix.active()) {
657 var oldItem = this.liveItem(this._position--);
658 oldItem.active(false);
659 };
660
Nils Diewald5975d702015-03-09 17:45:42 +0000661 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000662
663 // The previous element is undefined - roll to bottom
664 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000665
666 // Activate prefix
667 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000668 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000669
670 // Normalize offset
671 this._offset = this._offset < 0 ? 0 : this._offset;
672
Nils Diewald2fe12e12015-03-06 16:47:06 +0000673 this._position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000674
675 if (prefix.isSet() && !prefix.active()) {
676 this._position++;
677 prefix.active(true);
678 return;
679 }
680 else {
681 newItem = this.liveItem(this._position);
682 this._showItems(this._offset);
683 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000684 }
685
686 // The previous element is outside the view - roll up
687 else if (this._position < this._offset) {
688 this._removeLast();
689 this._offset--;
690 this._prepend(this._list[this._position]);
691 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000692
Nils Diewald5975d702015-03-09 17:45:42 +0000693 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000694 newItem.active(true);
695 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000696
697
Nils Diewald7148c6f2015-05-04 15:07:53 +0000698 /**
699 * Length of the filtered item list.
700 */
Nils Diewald5975d702015-03-09 17:45:42 +0000701 liveLength : function () {
702 if (this._list === undefined)
703 this._initList();
704 return this._list.length;
705 },
706
707
Nils Diewald2fe12e12015-03-06 16:47:06 +0000708 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000709 _removeFirst : function () {
710 this.item(this._list[this._offset]).lowlight();
Akronc7448732016-04-27 14:06:58 +0200711 // leave lengthField/prefix
712 this._element.removeChild(this._element.children[2]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000713 },
714
Nils Diewald2fe12e12015-03-06 16:47:06 +0000715
716 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000717 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000718 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000719 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000720 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000721 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000722});