blob: 0e7fc44b1d801ac8bd3924123714061a5850d421 [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',
14 'util'
15], function (defaultItemClass,
16 defaultPrefixClass) {
Nils Diewaldfda29d92015-01-22 17:28:01 +000017
Nils Diewald0e6992a2015-04-14 20:13:52 +000018 // Default maximum number of menu items
19 var menuLimit = 8;
20
21 function _codeFromEvent (e) {
22 if (e.charCode && (e.keyCode == 0))
23 return e.charCode
24 return e.keyCode;
Nils Diewald59c02fc2015-03-07 01:29:09 +000025 };
26
Nils Diewald86dad5b2015-01-28 15:09:07 +000027
28 /**
29 * List of items for drop down menu (complete).
30 * Only a sublist of the menu is filtered (live).
31 * Only a sublist of the filtered menu is visible (shown).
32 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000033 return {
Nils Diewald86dad5b2015-01-28 15:09:07 +000034 /**
35 * Create new Menu based on the action prefix
36 * and a list of menu items.
37 *
38 * @this {Menu}
39 * @constructor
40 * @param {string} Context prefix
41 * @param {Array.<Array.<string>>} List of menu items
42 */
43 create : function (params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000044 return Object.create(this)._init(params);
Nils Diewald86dad5b2015-01-28 15:09:07 +000045 },
46
Nils Diewald6e43ffd2015-03-25 18:55:39 +000047 /**
48 * Destroy this menu
49 * (in case you don't trust the
50 * mark and sweep GC)!
51 */
52 destroy : function () {
53 if (this._element != undefined)
54 delete this._element["menu"];
55
56 for (var i = 0; i < this._items.length; i++) {
57 delete this._items[i]["_menu"];
58 };
Nils Diewald5c5a7472015-04-02 22:13:38 +000059 delete this._prefix['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +000060 },
61
Nils Diewald7148c6f2015-05-04 15:07:53 +000062
63 /**
64 * Focus on this menu.
65 */
Nils Diewald2fe12e12015-03-06 16:47:06 +000066 focus : function () {
67 this._element.focus();
68 },
69
Nils Diewald7148c6f2015-05-04 15:07:53 +000070
Nils Diewald59c02fc2015-03-07 01:29:09 +000071 // mouse wheel treatment
72 _mousewheel : function (e) {
73 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +000074
75 delta = e.deltaY / 120;
76 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000077 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +000078 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000079 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +000080 e.halt();
81 },
82
Nils Diewald7148c6f2015-05-04 15:07:53 +000083
Nils Diewald59c02fc2015-03-07 01:29:09 +000084 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +000085 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +000086 var code = _codeFromEvent(e);
87
Nils Diewald59c02fc2015-03-07 01:29:09 +000088 switch (code) {
89 case 27: // 'Esc'
90 e.halt();
91 this.hide();
92 break;
Nils Diewald5975d702015-03-09 17:45:42 +000093
Nils Diewald59c02fc2015-03-07 01:29:09 +000094 case 38: // 'Up'
95 e.halt();
96 this.prev();
97 break;
Nils Diewald5975d702015-03-09 17:45:42 +000098 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +000099 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000100 this.prev();
101 break;
102 case 40: // 'Down'
103 e.halt();
104 this.next();
105 break;
106 case 34: // 'Page down'
107 e.halt();
108 this.next();
109 break;
110 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000111 if (this._prefix.active())
112 break;
113
Nils Diewald5975d702015-03-09 17:45:42 +0000114 var item = this.liveItem(this._position);
Akron5ef4fa02015-06-02 16:25:14 +0200115
Nils Diewald5975d702015-03-09 17:45:42 +0000116 if (item["further"] !== undefined) {
117 item["further"].bind(item).apply();
Nils Diewald5975d702015-03-09 17:45:42 +0000118 };
Akron5ef4fa02015-06-02 16:25:14 +0200119
120 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000121 break;
122 case 13: // 'Enter'
123
124 // Click on prefix
125 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000126 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000127
128 // Click on item
129 else
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000130 this.liveItem(this._position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000131 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000132 break;
133 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000134 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000135 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000136 e.halt();
137 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000138 };
139 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000140
Nils Diewald47f366b2015-04-15 20:06:35 +0000141 // Add characters to prefix
142 _keypress : function (e) {
143 var c = String.fromCharCode(_codeFromEvent(e)).toLowerCase();
Nils Diewald5975d702015-03-09 17:45:42 +0000144
Nils Diewald47f366b2015-04-15 20:06:35 +0000145 // Add prefix
146 this._prefix.add(c);
147
148 if (!this.show()) {
149 this.prefix('').show();
150 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000151 };
152 },
153
Nils Diewald2fe12e12015-03-06 16:47:06 +0000154 // Initialize list
Nils Diewald5975d702015-03-09 17:45:42 +0000155 _init : function (itemClass, prefixClass, params) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000156 var that = this;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000157 this._itemClass = itemClass || defaultItemClass;
Nils Diewald5975d702015-03-09 17:45:42 +0000158
159 if (prefixClass !== undefined)
160 this._prefix = prefixClass.create();
161 else
Nils Diewald0e6992a2015-04-14 20:13:52 +0000162 this._prefix = defaultPrefixClass.create();
Nils Diewald5975d702015-03-09 17:45:42 +0000163
Nils Diewald5c5a7472015-04-02 22:13:38 +0000164 this._prefix._menu = this;
165
Nils Diewald5975d702015-03-09 17:45:42 +0000166 var e = document.createElement("ul");
Nils Diewald59c02fc2015-03-07 01:29:09 +0000167 e.style.opacity = 0;
168 e.style.outline = 0;
169 e.setAttribute('tabindex', 0);
Nils Diewald5c5a7472015-04-02 22:13:38 +0000170 e.classList.add('menu');
Nils Diewald58141332015-04-07 16:18:45 +0000171 e.classList.add('roll');
Nils Diewald5975d702015-03-09 17:45:42 +0000172 e.appendChild(this._prefix.element());
Nils Diewald86dad5b2015-01-28 15:09:07 +0000173
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000174 // This has to be cleaned up later on
175 e["menu"] = this;
176
Nils Diewald59c02fc2015-03-07 01:29:09 +0000177 // Arrow keys
178 e.addEventListener(
Nils Diewald47f366b2015-04-15 20:06:35 +0000179 'keydown',
180 function (ev) {
181 that._keydown(ev)
182 },
183 false
184 );
185
186 // Strings
187 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000188 'keypress',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000189 function (ev) {
Nils Diewald5975d702015-03-09 17:45:42 +0000190 that._keypress(ev)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000191 },
192 false
193 );
194
195 // Mousewheel
196 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000197 'wheel',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000198 function (ev) {
199 that._mousewheel(ev)
Nils Diewald2fe12e12015-03-06 16:47:06 +0000200 },
201 false
202 );
203
Nils Diewald5975d702015-03-09 17:45:42 +0000204 this._element = e;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000205 this.active = false;
206 this._items = new Array();
207 var i;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000208
209 // Initialize item list based on parameters
Nils Diewald86dad5b2015-01-28 15:09:07 +0000210 for (i in params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000211 var obj = this._itemClass.create(params[i]);
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000212
213 // This may become circular
214 obj["_menu"] = this;
215
Nils Diewald2fe12e12015-03-06 16:47:06 +0000216 this._items.push(obj);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000217 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000218 this._limit = menuLimit;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000219 this._position = 0; // position in the active list
220 this._active = -1; // active item in the item list
Nils Diewald20f7ace2015-05-07 12:51:34 +0000221 this._firstActive = false; // Show the first item active always?
Nils Diewald86dad5b2015-01-28 15:09:07 +0000222 this._reset();
223 return this;
224 },
225
Nils Diewald2fe12e12015-03-06 16:47:06 +0000226 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000227 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000228 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000229 element : function () {
230 return this._element;
231 },
232
Nils Diewald7148c6f2015-05-04 15:07:53 +0000233
Nils Diewald2fe12e12015-03-06 16:47:06 +0000234 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000235 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000236 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000237 itemClass : function () {
238 return this._itemClass;
239 },
240
241 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000242 * Get and set the numerical value
243 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000244 */
245 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000246 if (arguments.length === 1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000247 this._limit = limit;
Nils Diewald5975d702015-03-09 17:45:42 +0000248 return this;
249 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000250 return this._limit;
251 },
252
Nils Diewald7148c6f2015-05-04 15:07:53 +0000253
Nils Diewald86dad5b2015-01-28 15:09:07 +0000254 /**
255 * Upgrade this object to another object,
256 * while private data stays intact.
257 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000258 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000259 */
260 upgradeTo : function (props) {
261 for (var prop in props) {
262 this[prop] = props[prop];
263 };
264 return this;
265 },
266
Nils Diewald7148c6f2015-05-04 15:07:53 +0000267
Nils Diewald2fe12e12015-03-06 16:47:06 +0000268 // Reset chosen item and prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000269 _reset : function () {
270 this._offset = 0;
271 this._pos = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000272 this._prefix.value('');
Nils Diewald86dad5b2015-01-28 15:09:07 +0000273 },
274
Nils Diewald7148c6f2015-05-04 15:07:53 +0000275
Nils Diewald86dad5b2015-01-28 15:09:07 +0000276 /**
277 * Filter the list and make it visible
278 *
279 * @param {string} Prefix for filtering the list
280 */
Nils Diewald5975d702015-03-09 17:45:42 +0000281 show : function () {
Nils Diewalde8518f82015-03-18 22:41:49 +0000282
Nils Diewald86dad5b2015-01-28 15:09:07 +0000283 // Initialize the list
284 if (!this._initList())
Nils Diewald59c02fc2015-03-07 01:29:09 +0000285 return false;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000286
Nils Diewald2fe12e12015-03-06 16:47:06 +0000287 // show based on initial offset
Nils Diewald86dad5b2015-01-28 15:09:07 +0000288 this._showItems(0);
289
290 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000291 // Todo: Or the last element chosen
Nils Diewald20f7ace2015-05-07 12:51:34 +0000292 if (this._firstActive)
293 this.liveItem(0).active(true);
294
Nils Diewalde8518f82015-03-18 22:41:49 +0000295 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000296
Nils Diewald86dad5b2015-01-28 15:09:07 +0000297 this._active = this._list[0];
Nils Diewald5975d702015-03-09 17:45:42 +0000298 this._position = 0;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000299 this._element.style.opacity = 1;
300
Nils Diewald86dad5b2015-01-28 15:09:07 +0000301 // Add classes for rolling menus
302 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000303 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000304 },
305
Nils Diewald7148c6f2015-05-04 15:07:53 +0000306
307 /**
308 * Hide the menu and call the onHide callback.
309 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000310 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000311 this.active = false;
312 this.delete();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000313 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000314 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000315 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000316 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000317 },
318
Nils Diewald7148c6f2015-05-04 15:07:53 +0000319 /**
320 * Function released when the menu hides.
321 * This method is expected to be overridden.
322 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000323 onHide : function () {},
324
Nils Diewald2fe12e12015-03-06 16:47:06 +0000325 // Initialize the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000326 _initList : function () {
327
Nils Diewald2fe12e12015-03-06 16:47:06 +0000328 // Create a new list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000329 if (this._list === undefined) {
330 this._list = [];
331 }
332 else if (this._list.length != 0) {
333 this._boundary(false);
334 this._list.length = 0;
335 };
336
337 // Offset is initially zero
338 this._offset = 0;
339
Nils Diewald2fe12e12015-03-06 16:47:06 +0000340 // There is no prefix set
Nils Diewald86dad5b2015-01-28 15:09:07 +0000341 if (this.prefix().length <= 0) {
Nils Diewald5975d702015-03-09 17:45:42 +0000342 var i = 0;
343 for (; i < this._items.length; i++)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000344 this._list.push(i);
Nils Diewald5975d702015-03-09 17:45:42 +0000345 while (this._items[++i] !== undefined) {
346 this._items[i].lowlight();
Nils Diewald2488d052015-04-09 21:46:02 +0000347 // console.log(this._item);
Nils Diewald5975d702015-03-09 17:45:42 +0000348 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000349 return true;
350 };
351
Nils Diewald2fe12e12015-03-06 16:47:06 +0000352 // There is a prefix set, so filter the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000353 var pos;
354 var paddedPrefix = " " + this.prefix();
355
Nils Diewald2fe12e12015-03-06 16:47:06 +0000356 // Iterate over all items and choose preferred matching items
357 // i.e. the matching happens at the word start
Nils Diewald86dad5b2015-01-28 15:09:07 +0000358 for (pos = 0; pos < this._items.length; pos++) {
359 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
360 this._list.push(pos);
361 };
362
Nils Diewald2fe12e12015-03-06 16:47:06 +0000363 // The list is empty - so lower your expectations
364 // Iterate over all items and choose matching items
365 // i.e. the matching happens anywhere in the word
Nils Diewald86dad5b2015-01-28 15:09:07 +0000366 if (this._list.length == 0) {
367 for (pos = 0; pos < this._items.length; pos++) {
368 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
369 this._list.push(pos);
370 };
371 };
372
Nils Diewald2fe12e12015-03-06 16:47:06 +0000373 // Filter was successful - yeah!
Nils Diewald86dad5b2015-01-28 15:09:07 +0000374 return this._list.length > 0 ? true : false;
375 },
376
377 // Set boundary for viewport
378 _boundary : function (bool) {
379 this.item(this._list[0]).noMore(bool);
380 this.item(this._list[this._list.length - 1]).noMore(bool);
381 },
382
Nils Diewald7148c6f2015-05-04 15:07:53 +0000383
Nils Diewald86dad5b2015-01-28 15:09:07 +0000384 /**
385 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000386 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000387 */
Nils Diewald5975d702015-03-09 17:45:42 +0000388 prefix : function (pref) {
389 if (arguments.length === 1) {
390 this._prefix.value(pref);
391 return this;
392 };
393 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000394 },
395
Nils Diewald7148c6f2015-05-04 15:07:53 +0000396
Nils Diewald2fe12e12015-03-06 16:47:06 +0000397 // Append Items that should be shown
Nils Diewald86dad5b2015-01-28 15:09:07 +0000398 _showItems : function (offset) {
399 this.delete();
400
401 // Use list
402 var shown = 0;
403 var i;
404 for (i in this._list) {
405
406 // Don't show - it's before offset
407 if (shown++ < offset)
408 continue;
409
410 this._append(this._list[i]);
411
412 if (shown >= (this.limit() + this._offset))
413 break;
414 };
415 },
416
417 /**
418 * Delete all visible items from the menu element
419 */
420 delete : function () {
421 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000422
Nils Diewald5975d702015-03-09 17:45:42 +0000423 /*
Nils Diewald2fe12e12015-03-06 16:47:06 +0000424 // Iterate over all visible items
Nils Diewald86dad5b2015-01-28 15:09:07 +0000425 for (var i = 0; i <= this.limit(); i++) {
426
Nils Diewald5975d702015-03-09 17:45:42 +0000427 // there is a visible element
428 // unhighlight!
Nils Diewald59c02fc2015-03-07 01:29:09 +0000429 if (child = this.shownItem(i)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000430 child.lowlight();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000431 child.active(false);
432 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000433 };
Nils Diewald5975d702015-03-09 17:45:42 +0000434 */
435
436 for (var i in this._list) {
437 var item = this._items[this._list[i]];
438 item.lowlight();
439 item.active(false);
440 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000441
Nils Diewald2fe12e12015-03-06 16:47:06 +0000442 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000443 var children = this._element.childNodes;
444 for (var i = children.length - 1; i >= 1; i--) {
445 this._element.removeChild(
446 children[i]
447 );
448 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000449 },
450
451
452 // Append item to the shown list based on index
453 _append : function (i) {
454 var item = this.item(i);
455
456 // Highlight based on prefix
457 if (this.prefix().length > 0)
458 item.highlight(this.prefix());
459
460 // Append element
461 this.element().appendChild(item.element());
462 },
463
464
Nils Diewald2fe12e12015-03-06 16:47:06 +0000465 // Prepend item to the shown list based on index
466 _prepend : function (i) {
467 var item = this.item(i);
468
469 // Highlight based on prefix
470 if (this.prefix().length > 0)
471 item.highlight(this.prefix());
472
473 var e = this.element();
474 // Append element
475 e.insertBefore(
476 item.element(),
Nils Diewald5975d702015-03-09 17:45:42 +0000477 e.children[1]
Nils Diewald2fe12e12015-03-06 16:47:06 +0000478 );
479 },
480
481
482 /**
483 * Get a specific item from the complete list
484 *
485 * @param {number} index of the list item
486 */
487 item : function (index) {
488 return this._items[index]
489 },
490
491
Nils Diewald86dad5b2015-01-28 15:09:07 +0000492 /**
493 * Get a specific item from the filtered list
494 *
495 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000496 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000497 */
498 liveItem : function (index) {
499 if (this._list === undefined)
500 if (!this._initList())
501 return;
502
503 return this._items[this._list[index]];
504 },
505
Nils Diewald86dad5b2015-01-28 15:09:07 +0000506
507 /**
508 * Get a specific item from the visible list
509 *
510 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000511 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000512 */
513 shownItem : function (index) {
514 if (index >= this.limit())
515 return;
516 return this.liveItem(this._offset + index);
517 },
518
519
Nils Diewald2fe12e12015-03-06 16:47:06 +0000520 /**
521 * Get the length of the full list
522 */
523 length : function () {
524 return this._items.length;
525 },
526
527
528 /**
529 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000530 */
531 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000532
Nils Diewald86dad5b2015-01-28 15:09:07 +0000533 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000534 if (this._position === -1)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000535 return;
536
Nils Diewald5975d702015-03-09 17:45:42 +0000537 var newItem;
538
Nils Diewald86dad5b2015-01-28 15:09:07 +0000539 // Set new live item
Nils Diewalde8518f82015-03-18 22:41:49 +0000540 if (!this._prefix.active()) {
541 var oldItem = this.liveItem(this._position);
542 oldItem.active(false);
543 };
544
545 this._position++;
546
Nils Diewald5975d702015-03-09 17:45:42 +0000547 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000548
Nils Diewald5975d702015-03-09 17:45:42 +0000549 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000550 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000551
552 // Activate prefix
553 var prefix = this._prefix;
554
555 // Mark prefix
556 if (prefix.isSet() && !prefix.active()) {
557 this._position--;
558 prefix.active(true);
559 return;
560 }
561 else {
562 this._offset = 0;
563 this._position = 0;
564 newItem = this.liveItem(0);
565 this._showItems(0);
566 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000567 }
568
569 // The next element is outside the view - roll down
Nils Diewald2fe12e12015-03-06 16:47:06 +0000570 else if (this._position >= (this.limit() + this._offset)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000571 this._removeFirst();
572 this._offset++;
573 this._append(this._list[this._position]);
574 };
Nils Diewald5975d702015-03-09 17:45:42 +0000575
576 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000577 newItem.active(true);
578 },
579
Nils Diewalde8518f82015-03-18 22:41:49 +0000580 /*
581 * Page down to the first item on the next page
582 */
583 /*
584 nextPage : function () {
585
586 // Prefix is active
587 if (this._prefix.active()) {
588 this._prefix.active(false);
589 }
590
591 // Last item is chosen
592 else if (this._position >= this.limit() + this._offset) {
593
594 this._position = this.limit() + this._offset - 1;
595 newItem = this.liveItem(this._position);
596 var oldItem = this.liveItem(this._position--);
597 oldItem.active(false);
598 }
599
600 // Last item of page is chosen
601 else if (0) {
602
603 // Jump to last item
604 else {
605 var oldItem = this.liveItem(this._position);
606 oldItem.active(false);
607
608 this._position = this.limit() + this._offset - 1;
609 newItem = this.liveItem(this._position);
610 };
611
612 newItem.active(true);
613 },
614 */
615
Nils Diewald86dad5b2015-01-28 15:09:07 +0000616
617 /*
618 * Make the previous item in the menu active
619 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000620 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000621
Nils Diewald2fe12e12015-03-06 16:47:06 +0000622 // No active element set
Nils Diewalde8518f82015-03-18 22:41:49 +0000623 if (this._position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000624 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000625 // TODO: Choose last item
626 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000627
Nils Diewald5975d702015-03-09 17:45:42 +0000628 var newItem;
629
Nils Diewald86dad5b2015-01-28 15:09:07 +0000630 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000631 if (!this._prefix.active()) {
632 var oldItem = this.liveItem(this._position--);
633 oldItem.active(false);
634 };
635
Nils Diewald5975d702015-03-09 17:45:42 +0000636 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000637
638 // The previous element is undefined - roll to bottom
639 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000640
641 // Activate prefix
642 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000643 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000644
645 // Normalize offset
646 this._offset = this._offset < 0 ? 0 : this._offset;
647
Nils Diewald2fe12e12015-03-06 16:47:06 +0000648 this._position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000649
650 if (prefix.isSet() && !prefix.active()) {
651 this._position++;
652 prefix.active(true);
653 return;
654 }
655 else {
656 newItem = this.liveItem(this._position);
657 this._showItems(this._offset);
658 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000659 }
660
661 // The previous element is outside the view - roll up
662 else if (this._position < this._offset) {
663 this._removeLast();
664 this._offset--;
665 this._prepend(this._list[this._position]);
666 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000667
Nils Diewald5975d702015-03-09 17:45:42 +0000668 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000669 newItem.active(true);
670 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000671
672
Nils Diewald7148c6f2015-05-04 15:07:53 +0000673 /**
674 * Length of the filtered item list.
675 */
Nils Diewald5975d702015-03-09 17:45:42 +0000676 liveLength : function () {
677 if (this._list === undefined)
678 this._initList();
679 return this._list.length;
680 },
681
682
Nils Diewald2fe12e12015-03-06 16:47:06 +0000683 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000684 _removeFirst : function () {
685 this.item(this._list[this._offset]).lowlight();
Nils Diewald5975d702015-03-09 17:45:42 +0000686 this._element.removeChild(this._element.children[1]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000687 },
688
Nils Diewald2fe12e12015-03-06 16:47:06 +0000689
690 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000691 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000692 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000693 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000694 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000695 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000696});