blob: cc4285539c81cb399d598bef2cb06e02f9f6b64e [file] [log] [blame]
Nils Diewald2fe12e12015-03-06 16:47:06 +00001/**
Nils Diewald7148c6f2015-05-04 15:07:53 +00002 * Scrollable drop-down menus with view filter.
Nils Diewald2fe12e12015-03-06 16:47:06 +00003 *
4 * @author Nils Diewald
5 */
Nils Diewald2488d052015-04-09 21:46:02 +00006/*
Nils Diewald0e6992a2015-04-14 20:13:52 +00007 * TODO: space is not a valid prefix!
Nils Diewald7148c6f2015-05-04 15:07:53 +00008 * TODO: Prefix should be case sensitive!
Nils Diewald2488d052015-04-09 21:46:02 +00009 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000010define([
11 'menu/item',
12 'menu/prefix',
13 'util'
14], function (defaultItemClass,
15 defaultPrefixClass) {
Nils Diewaldfda29d92015-01-22 17:28:01 +000016
Nils Diewald0e6992a2015-04-14 20:13:52 +000017 // Default maximum number of menu items
18 var menuLimit = 8;
19
20 function _codeFromEvent (e) {
21 if (e.charCode && (e.keyCode == 0))
22 return e.charCode
23 return e.keyCode;
Nils Diewald59c02fc2015-03-07 01:29:09 +000024 };
25
Nils Diewald86dad5b2015-01-28 15:09:07 +000026
27 /**
28 * List of items for drop down menu (complete).
29 * Only a sublist of the menu is filtered (live).
30 * Only a sublist of the filtered menu is visible (shown).
31 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000032 return {
Nils Diewald86dad5b2015-01-28 15:09:07 +000033 /**
34 * Create new Menu based on the action prefix
35 * and a list of menu items.
36 *
37 * @this {Menu}
38 * @constructor
39 * @param {string} Context prefix
40 * @param {Array.<Array.<string>>} List of menu items
41 */
42 create : function (params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000043 return Object.create(this)._init(params);
Nils Diewald86dad5b2015-01-28 15:09:07 +000044 },
45
Nils Diewald6e43ffd2015-03-25 18:55:39 +000046 /**
47 * Destroy this menu
48 * (in case you don't trust the
49 * mark and sweep GC)!
50 */
51 destroy : function () {
52 if (this._element != undefined)
53 delete this._element["menu"];
54
55 for (var i = 0; i < this._items.length; i++) {
56 delete this._items[i]["_menu"];
57 };
Nils Diewald5c5a7472015-04-02 22:13:38 +000058 delete this._prefix['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +000059 },
60
Nils Diewald7148c6f2015-05-04 15:07:53 +000061
62 /**
63 * Focus on this menu.
64 */
Nils Diewald2fe12e12015-03-06 16:47:06 +000065 focus : function () {
66 this._element.focus();
67 },
68
Nils Diewald7148c6f2015-05-04 15:07:53 +000069
Nils Diewald59c02fc2015-03-07 01:29:09 +000070 // mouse wheel treatment
71 _mousewheel : function (e) {
72 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +000073
74 delta = e.deltaY / 120;
75 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000076 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +000077 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000078 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +000079 e.halt();
80 },
81
Nils Diewald7148c6f2015-05-04 15:07:53 +000082
Nils Diewald59c02fc2015-03-07 01:29:09 +000083 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +000084 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +000085 var code = _codeFromEvent(e);
86
Nils Diewald59c02fc2015-03-07 01:29:09 +000087 switch (code) {
88 case 27: // 'Esc'
89 e.halt();
90 this.hide();
91 break;
Nils Diewald5975d702015-03-09 17:45:42 +000092
Nils Diewald59c02fc2015-03-07 01:29:09 +000093 case 38: // 'Up'
94 e.halt();
95 this.prev();
96 break;
Nils Diewald5975d702015-03-09 17:45:42 +000097 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +000098 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +000099 this.prev();
100 break;
101 case 40: // 'Down'
102 e.halt();
103 this.next();
104 break;
105 case 34: // 'Page down'
106 e.halt();
107 this.next();
108 break;
109 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000110 if (this._prefix.active())
111 break;
112
Nils Diewald5975d702015-03-09 17:45:42 +0000113 var item = this.liveItem(this._position);
114 if (item["further"] !== undefined) {
115 item["further"].bind(item).apply();
116 e.halt();
117 };
118 break;
119 case 13: // 'Enter'
120
121 // Click on prefix
122 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000123 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000124
125 // Click on item
126 else
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000127 this.liveItem(this._position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000128 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000129 break;
130 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000131 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000132 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000133 e.halt();
134 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000135 };
136 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000137
Nils Diewald47f366b2015-04-15 20:06:35 +0000138 // Add characters to prefix
139 _keypress : function (e) {
140 var c = String.fromCharCode(_codeFromEvent(e)).toLowerCase();
Nils Diewald5975d702015-03-09 17:45:42 +0000141
Nils Diewald47f366b2015-04-15 20:06:35 +0000142 // Add prefix
143 this._prefix.add(c);
144
145 if (!this.show()) {
146 this.prefix('').show();
147 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000148 };
149 },
150
Nils Diewald2fe12e12015-03-06 16:47:06 +0000151 // Initialize list
Nils Diewald5975d702015-03-09 17:45:42 +0000152 _init : function (itemClass, prefixClass, params) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000153 var that = this;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000154 this._itemClass = itemClass || defaultItemClass;
Nils Diewald5975d702015-03-09 17:45:42 +0000155
156 if (prefixClass !== undefined)
157 this._prefix = prefixClass.create();
158 else
Nils Diewald0e6992a2015-04-14 20:13:52 +0000159 this._prefix = defaultPrefixClass.create();
Nils Diewald5975d702015-03-09 17:45:42 +0000160
Nils Diewald5c5a7472015-04-02 22:13:38 +0000161 this._prefix._menu = this;
162
Nils Diewald5975d702015-03-09 17:45:42 +0000163 var e = document.createElement("ul");
Nils Diewald59c02fc2015-03-07 01:29:09 +0000164 e.style.opacity = 0;
165 e.style.outline = 0;
166 e.setAttribute('tabindex', 0);
Nils Diewald5c5a7472015-04-02 22:13:38 +0000167 e.classList.add('menu');
Nils Diewald58141332015-04-07 16:18:45 +0000168 e.classList.add('roll');
Nils Diewald5975d702015-03-09 17:45:42 +0000169 e.appendChild(this._prefix.element());
Nils Diewald86dad5b2015-01-28 15:09:07 +0000170
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000171 // This has to be cleaned up later on
172 e["menu"] = this;
173
Nils Diewald59c02fc2015-03-07 01:29:09 +0000174 // Arrow keys
175 e.addEventListener(
Nils Diewald47f366b2015-04-15 20:06:35 +0000176 'keydown',
177 function (ev) {
178 that._keydown(ev)
179 },
180 false
181 );
182
183 // Strings
184 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000185 'keypress',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000186 function (ev) {
Nils Diewald5975d702015-03-09 17:45:42 +0000187 that._keypress(ev)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000188 },
189 false
190 );
191
192 // Mousewheel
193 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000194 'wheel',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000195 function (ev) {
196 that._mousewheel(ev)
Nils Diewald2fe12e12015-03-06 16:47:06 +0000197 },
198 false
199 );
200
Nils Diewald5975d702015-03-09 17:45:42 +0000201 this._element = e;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000202 this.active = false;
203 this._items = new Array();
204 var i;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000205
206 // Initialize item list based on parameters
Nils Diewald86dad5b2015-01-28 15:09:07 +0000207 for (i in params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000208 var obj = this._itemClass.create(params[i]);
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000209
210 // This may become circular
211 obj["_menu"] = this;
212
Nils Diewald2fe12e12015-03-06 16:47:06 +0000213 this._items.push(obj);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000214 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000215 this._limit = menuLimit;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000216 this._position = 0; // position in the active list
217 this._active = -1; // active item in the item list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000218 this._reset();
219 return this;
220 },
221
Nils Diewald2fe12e12015-03-06 16:47:06 +0000222 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000223 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000224 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000225 element : function () {
226 return this._element;
227 },
228
Nils Diewald7148c6f2015-05-04 15:07:53 +0000229
Nils Diewald2fe12e12015-03-06 16:47:06 +0000230 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000231 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000232 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000233 itemClass : function () {
234 return this._itemClass;
235 },
236
237 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000238 * Get and set the numerical value
239 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000240 */
241 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000242 if (arguments.length === 1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000243 this._limit = limit;
Nils Diewald5975d702015-03-09 17:45:42 +0000244 return this;
245 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000246 return this._limit;
247 },
248
Nils Diewald7148c6f2015-05-04 15:07:53 +0000249
Nils Diewald86dad5b2015-01-28 15:09:07 +0000250 /**
251 * Upgrade this object to another object,
252 * while private data stays intact.
253 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000254 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000255 */
256 upgradeTo : function (props) {
257 for (var prop in props) {
258 this[prop] = props[prop];
259 };
260 return this;
261 },
262
Nils Diewald7148c6f2015-05-04 15:07:53 +0000263
Nils Diewald2fe12e12015-03-06 16:47:06 +0000264 // Reset chosen item and prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000265 _reset : function () {
266 this._offset = 0;
267 this._pos = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000268 this._prefix.value('');
Nils Diewald86dad5b2015-01-28 15:09:07 +0000269 },
270
Nils Diewald7148c6f2015-05-04 15:07:53 +0000271
Nils Diewald86dad5b2015-01-28 15:09:07 +0000272 /**
273 * Filter the list and make it visible
274 *
275 * @param {string} Prefix for filtering the list
276 */
Nils Diewald5975d702015-03-09 17:45:42 +0000277 show : function () {
Nils Diewalde8518f82015-03-18 22:41:49 +0000278
Nils Diewald86dad5b2015-01-28 15:09:07 +0000279 // Initialize the list
280 if (!this._initList())
Nils Diewald59c02fc2015-03-07 01:29:09 +0000281 return false;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000282
Nils Diewald2fe12e12015-03-06 16:47:06 +0000283 // show based on initial offset
Nils Diewald86dad5b2015-01-28 15:09:07 +0000284 this._showItems(0);
285
286 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000287 // Todo: Or the last element chosen
Nils Diewald86dad5b2015-01-28 15:09:07 +0000288 this.liveItem(0).active(true);
Nils Diewalde8518f82015-03-18 22:41:49 +0000289 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000290
Nils Diewald86dad5b2015-01-28 15:09:07 +0000291 this._active = this._list[0];
Nils Diewald5975d702015-03-09 17:45:42 +0000292 this._position = 0;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000293 this._element.style.opacity = 1;
294
Nils Diewald86dad5b2015-01-28 15:09:07 +0000295 // Add classes for rolling menus
296 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000297 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000298 },
299
Nils Diewald7148c6f2015-05-04 15:07:53 +0000300
301 /**
302 * Hide the menu and call the onHide callback.
303 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000304 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000305 this.active = false;
306 this.delete();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000307 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000308 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000309 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000310 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000311 },
312
Nils Diewald7148c6f2015-05-04 15:07:53 +0000313 /**
314 * Function released when the menu hides.
315 * This method is expected to be overridden.
316 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000317 onHide : function () {},
318
Nils Diewald2fe12e12015-03-06 16:47:06 +0000319 // Initialize the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000320 _initList : function () {
321
Nils Diewald2fe12e12015-03-06 16:47:06 +0000322 // Create a new list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000323 if (this._list === undefined) {
324 this._list = [];
325 }
326 else if (this._list.length != 0) {
327 this._boundary(false);
328 this._list.length = 0;
329 };
330
331 // Offset is initially zero
332 this._offset = 0;
333
Nils Diewald2fe12e12015-03-06 16:47:06 +0000334 // There is no prefix set
Nils Diewald86dad5b2015-01-28 15:09:07 +0000335 if (this.prefix().length <= 0) {
Nils Diewald5975d702015-03-09 17:45:42 +0000336 var i = 0;
337 for (; i < this._items.length; i++)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000338 this._list.push(i);
Nils Diewald5975d702015-03-09 17:45:42 +0000339 while (this._items[++i] !== undefined) {
340 this._items[i].lowlight();
Nils Diewald2488d052015-04-09 21:46:02 +0000341 // console.log(this._item);
Nils Diewald5975d702015-03-09 17:45:42 +0000342 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000343 return true;
344 };
345
Nils Diewald2fe12e12015-03-06 16:47:06 +0000346 // There is a prefix set, so filter the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000347 var pos;
348 var paddedPrefix = " " + this.prefix();
349
Nils Diewald2fe12e12015-03-06 16:47:06 +0000350 // Iterate over all items and choose preferred matching items
351 // i.e. the matching happens at the word start
Nils Diewald86dad5b2015-01-28 15:09:07 +0000352 for (pos = 0; pos < this._items.length; pos++) {
353 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
354 this._list.push(pos);
355 };
356
Nils Diewald2fe12e12015-03-06 16:47:06 +0000357 // The list is empty - so lower your expectations
358 // Iterate over all items and choose matching items
359 // i.e. the matching happens anywhere in the word
Nils Diewald86dad5b2015-01-28 15:09:07 +0000360 if (this._list.length == 0) {
361 for (pos = 0; pos < this._items.length; pos++) {
362 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
363 this._list.push(pos);
364 };
365 };
366
Nils Diewald2fe12e12015-03-06 16:47:06 +0000367 // Filter was successful - yeah!
Nils Diewald86dad5b2015-01-28 15:09:07 +0000368 return this._list.length > 0 ? true : false;
369 },
370
371 // Set boundary for viewport
372 _boundary : function (bool) {
373 this.item(this._list[0]).noMore(bool);
374 this.item(this._list[this._list.length - 1]).noMore(bool);
375 },
376
Nils Diewald7148c6f2015-05-04 15:07:53 +0000377
Nils Diewald86dad5b2015-01-28 15:09:07 +0000378 /**
379 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000380 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000381 */
Nils Diewald5975d702015-03-09 17:45:42 +0000382 prefix : function (pref) {
383 if (arguments.length === 1) {
384 this._prefix.value(pref);
385 return this;
386 };
387 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000388 },
389
Nils Diewald7148c6f2015-05-04 15:07:53 +0000390
Nils Diewald2fe12e12015-03-06 16:47:06 +0000391 // Append Items that should be shown
Nils Diewald86dad5b2015-01-28 15:09:07 +0000392 _showItems : function (offset) {
393 this.delete();
394
395 // Use list
396 var shown = 0;
397 var i;
398 for (i in this._list) {
399
400 // Don't show - it's before offset
401 if (shown++ < offset)
402 continue;
403
404 this._append(this._list[i]);
405
406 if (shown >= (this.limit() + this._offset))
407 break;
408 };
409 },
410
411 /**
412 * Delete all visible items from the menu element
413 */
414 delete : function () {
415 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000416
Nils Diewald5975d702015-03-09 17:45:42 +0000417 /*
Nils Diewald2fe12e12015-03-06 16:47:06 +0000418 // Iterate over all visible items
Nils Diewald86dad5b2015-01-28 15:09:07 +0000419 for (var i = 0; i <= this.limit(); i++) {
420
Nils Diewald5975d702015-03-09 17:45:42 +0000421 // there is a visible element
422 // unhighlight!
Nils Diewald59c02fc2015-03-07 01:29:09 +0000423 if (child = this.shownItem(i)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000424 child.lowlight();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000425 child.active(false);
426 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000427 };
Nils Diewald5975d702015-03-09 17:45:42 +0000428 */
429
430 for (var i in this._list) {
431 var item = this._items[this._list[i]];
432 item.lowlight();
433 item.active(false);
434 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000435
Nils Diewald2fe12e12015-03-06 16:47:06 +0000436 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000437 var children = this._element.childNodes;
438 for (var i = children.length - 1; i >= 1; i--) {
439 this._element.removeChild(
440 children[i]
441 );
442 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000443 },
444
445
446 // Append item to the shown list based on index
447 _append : function (i) {
448 var item = this.item(i);
449
450 // Highlight based on prefix
451 if (this.prefix().length > 0)
452 item.highlight(this.prefix());
453
454 // Append element
455 this.element().appendChild(item.element());
456 },
457
458
Nils Diewald2fe12e12015-03-06 16:47:06 +0000459 // Prepend item to the shown list based on index
460 _prepend : function (i) {
461 var item = this.item(i);
462
463 // Highlight based on prefix
464 if (this.prefix().length > 0)
465 item.highlight(this.prefix());
466
467 var e = this.element();
468 // Append element
469 e.insertBefore(
470 item.element(),
Nils Diewald5975d702015-03-09 17:45:42 +0000471 e.children[1]
Nils Diewald2fe12e12015-03-06 16:47:06 +0000472 );
473 },
474
475
476 /**
477 * Get a specific item from the complete list
478 *
479 * @param {number} index of the list item
480 */
481 item : function (index) {
482 return this._items[index]
483 },
484
485
Nils Diewald86dad5b2015-01-28 15:09:07 +0000486 /**
487 * Get a specific item from the filtered list
488 *
489 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000490 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000491 */
492 liveItem : function (index) {
493 if (this._list === undefined)
494 if (!this._initList())
495 return;
496
497 return this._items[this._list[index]];
498 },
499
Nils Diewald86dad5b2015-01-28 15:09:07 +0000500
501 /**
502 * Get a specific item from the visible list
503 *
504 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000505 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000506 */
507 shownItem : function (index) {
508 if (index >= this.limit())
509 return;
510 return this.liveItem(this._offset + index);
511 },
512
513
Nils Diewald2fe12e12015-03-06 16:47:06 +0000514 /**
515 * Get the length of the full list
516 */
517 length : function () {
518 return this._items.length;
519 },
520
521
522 /**
523 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000524 */
525 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000526
Nils Diewald86dad5b2015-01-28 15:09:07 +0000527 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000528 if (this._position === -1)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000529 return;
530
Nils Diewald5975d702015-03-09 17:45:42 +0000531 var newItem;
532
Nils Diewald86dad5b2015-01-28 15:09:07 +0000533 // Set new live item
Nils Diewalde8518f82015-03-18 22:41:49 +0000534 if (!this._prefix.active()) {
535 var oldItem = this.liveItem(this._position);
536 oldItem.active(false);
537 };
538
539 this._position++;
540
Nils Diewald5975d702015-03-09 17:45:42 +0000541 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000542
Nils Diewald5975d702015-03-09 17:45:42 +0000543 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000544 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000545
546 // Activate prefix
547 var prefix = this._prefix;
548
549 // Mark prefix
550 if (prefix.isSet() && !prefix.active()) {
551 this._position--;
552 prefix.active(true);
553 return;
554 }
555 else {
556 this._offset = 0;
557 this._position = 0;
558 newItem = this.liveItem(0);
559 this._showItems(0);
560 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000561 }
562
563 // The next element is outside the view - roll down
Nils Diewald2fe12e12015-03-06 16:47:06 +0000564 else if (this._position >= (this.limit() + this._offset)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000565 this._removeFirst();
566 this._offset++;
567 this._append(this._list[this._position]);
568 };
Nils Diewald5975d702015-03-09 17:45:42 +0000569
570 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000571 newItem.active(true);
572 },
573
Nils Diewalde8518f82015-03-18 22:41:49 +0000574 /*
575 * Page down to the first item on the next page
576 */
577 /*
578 nextPage : function () {
579
580 // Prefix is active
581 if (this._prefix.active()) {
582 this._prefix.active(false);
583 }
584
585 // Last item is chosen
586 else if (this._position >= this.limit() + this._offset) {
587
588 this._position = this.limit() + this._offset - 1;
589 newItem = this.liveItem(this._position);
590 var oldItem = this.liveItem(this._position--);
591 oldItem.active(false);
592 }
593
594 // Last item of page is chosen
595 else if (0) {
596
597 // Jump to last item
598 else {
599 var oldItem = this.liveItem(this._position);
600 oldItem.active(false);
601
602 this._position = this.limit() + this._offset - 1;
603 newItem = this.liveItem(this._position);
604 };
605
606 newItem.active(true);
607 },
608 */
609
Nils Diewald86dad5b2015-01-28 15:09:07 +0000610
611 /*
612 * Make the previous item in the menu active
613 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000614 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000615
Nils Diewald2fe12e12015-03-06 16:47:06 +0000616 // No active element set
Nils Diewalde8518f82015-03-18 22:41:49 +0000617 if (this._position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000618 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000619 // TODO: Choose last item
620 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000621
Nils Diewald5975d702015-03-09 17:45:42 +0000622 var newItem;
623
Nils Diewald86dad5b2015-01-28 15:09:07 +0000624 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000625 if (!this._prefix.active()) {
626 var oldItem = this.liveItem(this._position--);
627 oldItem.active(false);
628 };
629
Nils Diewald5975d702015-03-09 17:45:42 +0000630 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000631
632 // The previous element is undefined - roll to bottom
633 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000634
635 // Activate prefix
636 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000637 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000638
639 // Normalize offset
640 this._offset = this._offset < 0 ? 0 : this._offset;
641
Nils Diewald2fe12e12015-03-06 16:47:06 +0000642 this._position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000643
644 if (prefix.isSet() && !prefix.active()) {
645 this._position++;
646 prefix.active(true);
647 return;
648 }
649 else {
650 newItem = this.liveItem(this._position);
651 this._showItems(this._offset);
652 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000653 }
654
655 // The previous element is outside the view - roll up
656 else if (this._position < this._offset) {
657 this._removeLast();
658 this._offset--;
659 this._prepend(this._list[this._position]);
660 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000661
Nils Diewald5975d702015-03-09 17:45:42 +0000662 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000663 newItem.active(true);
664 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000665
666
Nils Diewald7148c6f2015-05-04 15:07:53 +0000667 /**
668 * Length of the filtered item list.
669 */
Nils Diewald5975d702015-03-09 17:45:42 +0000670 liveLength : function () {
671 if (this._list === undefined)
672 this._initList();
673 return this._list.length;
674 },
675
676
Nils Diewald2fe12e12015-03-06 16:47:06 +0000677 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000678 _removeFirst : function () {
679 this.item(this._list[this._offset]).lowlight();
Nils Diewald5975d702015-03-09 17:45:42 +0000680 this._element.removeChild(this._element.children[1]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000681 },
682
Nils Diewald2fe12e12015-03-06 16:47:06 +0000683
684 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000685 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000686 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000687 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000688 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000689 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000690});