blob: 4b95ae950ed0e438848907f9062994d63f60ae38 [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);
115 if (item["further"] !== undefined) {
116 item["further"].bind(item).apply();
117 e.halt();
118 };
119 break;
120 case 13: // 'Enter'
121
122 // Click on prefix
123 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000124 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000125
126 // Click on item
127 else
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000128 this.liveItem(this._position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000129 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000130 break;
131 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000132 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000133 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000134 e.halt();
135 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000136 };
137 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000138
Nils Diewald47f366b2015-04-15 20:06:35 +0000139 // Add characters to prefix
140 _keypress : function (e) {
141 var c = String.fromCharCode(_codeFromEvent(e)).toLowerCase();
Nils Diewald5975d702015-03-09 17:45:42 +0000142
Nils Diewald47f366b2015-04-15 20:06:35 +0000143 // Add prefix
144 this._prefix.add(c);
145
146 if (!this.show()) {
147 this.prefix('').show();
148 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000149 };
150 },
151
Nils Diewald2fe12e12015-03-06 16:47:06 +0000152 // Initialize list
Nils Diewald5975d702015-03-09 17:45:42 +0000153 _init : function (itemClass, prefixClass, params) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000154 var that = this;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000155 this._itemClass = itemClass || defaultItemClass;
Nils Diewald5975d702015-03-09 17:45:42 +0000156
157 if (prefixClass !== undefined)
158 this._prefix = prefixClass.create();
159 else
Nils Diewald0e6992a2015-04-14 20:13:52 +0000160 this._prefix = defaultPrefixClass.create();
Nils Diewald5975d702015-03-09 17:45:42 +0000161
Nils Diewald5c5a7472015-04-02 22:13:38 +0000162 this._prefix._menu = this;
163
Nils Diewald5975d702015-03-09 17:45:42 +0000164 var e = document.createElement("ul");
Nils Diewald59c02fc2015-03-07 01:29:09 +0000165 e.style.opacity = 0;
166 e.style.outline = 0;
167 e.setAttribute('tabindex', 0);
Nils Diewald5c5a7472015-04-02 22:13:38 +0000168 e.classList.add('menu');
Nils Diewald58141332015-04-07 16:18:45 +0000169 e.classList.add('roll');
Nils Diewald5975d702015-03-09 17:45:42 +0000170 e.appendChild(this._prefix.element());
Nils Diewald86dad5b2015-01-28 15:09:07 +0000171
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000172 // This has to be cleaned up later on
173 e["menu"] = this;
174
Nils Diewald59c02fc2015-03-07 01:29:09 +0000175 // Arrow keys
176 e.addEventListener(
Nils Diewald47f366b2015-04-15 20:06:35 +0000177 'keydown',
178 function (ev) {
179 that._keydown(ev)
180 },
181 false
182 );
183
184 // Strings
185 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000186 'keypress',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000187 function (ev) {
Nils Diewald5975d702015-03-09 17:45:42 +0000188 that._keypress(ev)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000189 },
190 false
191 );
192
193 // Mousewheel
194 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000195 'wheel',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000196 function (ev) {
197 that._mousewheel(ev)
Nils Diewald2fe12e12015-03-06 16:47:06 +0000198 },
199 false
200 );
201
Nils Diewald5975d702015-03-09 17:45:42 +0000202 this._element = e;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000203 this.active = false;
204 this._items = new Array();
205 var i;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000206
207 // Initialize item list based on parameters
Nils Diewald86dad5b2015-01-28 15:09:07 +0000208 for (i in params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000209 var obj = this._itemClass.create(params[i]);
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000210
211 // This may become circular
212 obj["_menu"] = this;
213
Nils Diewald2fe12e12015-03-06 16:47:06 +0000214 this._items.push(obj);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000215 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000216 this._limit = menuLimit;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000217 this._position = 0; // position in the active list
218 this._active = -1; // active item in the item list
Nils Diewald20f7ace2015-05-07 12:51:34 +0000219 this._firstActive = false; // Show the first item active always?
Nils Diewald86dad5b2015-01-28 15:09:07 +0000220 this._reset();
221 return this;
222 },
223
Nils Diewald2fe12e12015-03-06 16:47:06 +0000224 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000225 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000226 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000227 element : function () {
228 return this._element;
229 },
230
Nils Diewald7148c6f2015-05-04 15:07:53 +0000231
Nils Diewald2fe12e12015-03-06 16:47:06 +0000232 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000233 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000234 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000235 itemClass : function () {
236 return this._itemClass;
237 },
238
239 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000240 * Get and set the numerical value
241 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000242 */
243 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000244 if (arguments.length === 1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000245 this._limit = limit;
Nils Diewald5975d702015-03-09 17:45:42 +0000246 return this;
247 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000248 return this._limit;
249 },
250
Nils Diewald7148c6f2015-05-04 15:07:53 +0000251
Nils Diewald86dad5b2015-01-28 15:09:07 +0000252 /**
253 * Upgrade this object to another object,
254 * while private data stays intact.
255 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000256 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000257 */
258 upgradeTo : function (props) {
259 for (var prop in props) {
260 this[prop] = props[prop];
261 };
262 return this;
263 },
264
Nils Diewald7148c6f2015-05-04 15:07:53 +0000265
Nils Diewald2fe12e12015-03-06 16:47:06 +0000266 // Reset chosen item and prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000267 _reset : function () {
268 this._offset = 0;
269 this._pos = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000270 this._prefix.value('');
Nils Diewald86dad5b2015-01-28 15:09:07 +0000271 },
272
Nils Diewald7148c6f2015-05-04 15:07:53 +0000273
Nils Diewald86dad5b2015-01-28 15:09:07 +0000274 /**
275 * Filter the list and make it visible
276 *
277 * @param {string} Prefix for filtering the list
278 */
Nils Diewald5975d702015-03-09 17:45:42 +0000279 show : function () {
Nils Diewalde8518f82015-03-18 22:41:49 +0000280
Nils Diewald86dad5b2015-01-28 15:09:07 +0000281 // Initialize the list
282 if (!this._initList())
Nils Diewald59c02fc2015-03-07 01:29:09 +0000283 return false;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000284
Nils Diewald2fe12e12015-03-06 16:47:06 +0000285 // show based on initial offset
Nils Diewald86dad5b2015-01-28 15:09:07 +0000286 this._showItems(0);
287
288 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000289 // Todo: Or the last element chosen
Nils Diewald20f7ace2015-05-07 12:51:34 +0000290 if (this._firstActive)
291 this.liveItem(0).active(true);
292
Nils Diewalde8518f82015-03-18 22:41:49 +0000293 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000294
Nils Diewald86dad5b2015-01-28 15:09:07 +0000295 this._active = this._list[0];
Nils Diewald5975d702015-03-09 17:45:42 +0000296 this._position = 0;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000297 this._element.style.opacity = 1;
298
Nils Diewald86dad5b2015-01-28 15:09:07 +0000299 // Add classes for rolling menus
300 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000301 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000302 },
303
Nils Diewald7148c6f2015-05-04 15:07:53 +0000304
305 /**
306 * Hide the menu and call the onHide callback.
307 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000308 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000309 this.active = false;
310 this.delete();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000311 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000312 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000313 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000314 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000315 },
316
Nils Diewald7148c6f2015-05-04 15:07:53 +0000317 /**
318 * Function released when the menu hides.
319 * This method is expected to be overridden.
320 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000321 onHide : function () {},
322
Nils Diewald2fe12e12015-03-06 16:47:06 +0000323 // Initialize the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000324 _initList : function () {
325
Nils Diewald2fe12e12015-03-06 16:47:06 +0000326 // Create a new list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000327 if (this._list === undefined) {
328 this._list = [];
329 }
330 else if (this._list.length != 0) {
331 this._boundary(false);
332 this._list.length = 0;
333 };
334
335 // Offset is initially zero
336 this._offset = 0;
337
Nils Diewald2fe12e12015-03-06 16:47:06 +0000338 // There is no prefix set
Nils Diewald86dad5b2015-01-28 15:09:07 +0000339 if (this.prefix().length <= 0) {
Nils Diewald5975d702015-03-09 17:45:42 +0000340 var i = 0;
341 for (; i < this._items.length; i++)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000342 this._list.push(i);
Nils Diewald5975d702015-03-09 17:45:42 +0000343 while (this._items[++i] !== undefined) {
344 this._items[i].lowlight();
Nils Diewald2488d052015-04-09 21:46:02 +0000345 // console.log(this._item);
Nils Diewald5975d702015-03-09 17:45:42 +0000346 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000347 return true;
348 };
349
Nils Diewald2fe12e12015-03-06 16:47:06 +0000350 // There is a prefix set, so filter the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000351 var pos;
352 var paddedPrefix = " " + this.prefix();
353
Nils Diewald2fe12e12015-03-06 16:47:06 +0000354 // Iterate over all items and choose preferred matching items
355 // i.e. the matching happens at the word start
Nils Diewald86dad5b2015-01-28 15:09:07 +0000356 for (pos = 0; pos < this._items.length; pos++) {
357 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
358 this._list.push(pos);
359 };
360
Nils Diewald2fe12e12015-03-06 16:47:06 +0000361 // The list is empty - so lower your expectations
362 // Iterate over all items and choose matching items
363 // i.e. the matching happens anywhere in the word
Nils Diewald86dad5b2015-01-28 15:09:07 +0000364 if (this._list.length == 0) {
365 for (pos = 0; pos < this._items.length; pos++) {
366 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
367 this._list.push(pos);
368 };
369 };
370
Nils Diewald2fe12e12015-03-06 16:47:06 +0000371 // Filter was successful - yeah!
Nils Diewald86dad5b2015-01-28 15:09:07 +0000372 return this._list.length > 0 ? true : false;
373 },
374
375 // Set boundary for viewport
376 _boundary : function (bool) {
377 this.item(this._list[0]).noMore(bool);
378 this.item(this._list[this._list.length - 1]).noMore(bool);
379 },
380
Nils Diewald7148c6f2015-05-04 15:07:53 +0000381
Nils Diewald86dad5b2015-01-28 15:09:07 +0000382 /**
383 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000384 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000385 */
Nils Diewald5975d702015-03-09 17:45:42 +0000386 prefix : function (pref) {
387 if (arguments.length === 1) {
388 this._prefix.value(pref);
389 return this;
390 };
391 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000392 },
393
Nils Diewald7148c6f2015-05-04 15:07:53 +0000394
Nils Diewald2fe12e12015-03-06 16:47:06 +0000395 // Append Items that should be shown
Nils Diewald86dad5b2015-01-28 15:09:07 +0000396 _showItems : function (offset) {
397 this.delete();
398
399 // Use list
400 var shown = 0;
401 var i;
402 for (i in this._list) {
403
404 // Don't show - it's before offset
405 if (shown++ < offset)
406 continue;
407
408 this._append(this._list[i]);
409
410 if (shown >= (this.limit() + this._offset))
411 break;
412 };
413 },
414
415 /**
416 * Delete all visible items from the menu element
417 */
418 delete : function () {
419 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000420
Nils Diewald5975d702015-03-09 17:45:42 +0000421 /*
Nils Diewald2fe12e12015-03-06 16:47:06 +0000422 // Iterate over all visible items
Nils Diewald86dad5b2015-01-28 15:09:07 +0000423 for (var i = 0; i <= this.limit(); i++) {
424
Nils Diewald5975d702015-03-09 17:45:42 +0000425 // there is a visible element
426 // unhighlight!
Nils Diewald59c02fc2015-03-07 01:29:09 +0000427 if (child = this.shownItem(i)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000428 child.lowlight();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000429 child.active(false);
430 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000431 };
Nils Diewald5975d702015-03-09 17:45:42 +0000432 */
433
434 for (var i in this._list) {
435 var item = this._items[this._list[i]];
436 item.lowlight();
437 item.active(false);
438 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000439
Nils Diewald2fe12e12015-03-06 16:47:06 +0000440 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000441 var children = this._element.childNodes;
442 for (var i = children.length - 1; i >= 1; i--) {
443 this._element.removeChild(
444 children[i]
445 );
446 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000447 },
448
449
450 // Append item to the shown list based on index
451 _append : function (i) {
452 var item = this.item(i);
453
454 // Highlight based on prefix
455 if (this.prefix().length > 0)
456 item.highlight(this.prefix());
457
458 // Append element
459 this.element().appendChild(item.element());
460 },
461
462
Nils Diewald2fe12e12015-03-06 16:47:06 +0000463 // Prepend item to the shown list based on index
464 _prepend : function (i) {
465 var item = this.item(i);
466
467 // Highlight based on prefix
468 if (this.prefix().length > 0)
469 item.highlight(this.prefix());
470
471 var e = this.element();
472 // Append element
473 e.insertBefore(
474 item.element(),
Nils Diewald5975d702015-03-09 17:45:42 +0000475 e.children[1]
Nils Diewald2fe12e12015-03-06 16:47:06 +0000476 );
477 },
478
479
480 /**
481 * Get a specific item from the complete list
482 *
483 * @param {number} index of the list item
484 */
485 item : function (index) {
486 return this._items[index]
487 },
488
489
Nils Diewald86dad5b2015-01-28 15:09:07 +0000490 /**
491 * Get a specific item from the filtered list
492 *
493 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000494 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000495 */
496 liveItem : function (index) {
497 if (this._list === undefined)
498 if (!this._initList())
499 return;
500
501 return this._items[this._list[index]];
502 },
503
Nils Diewald86dad5b2015-01-28 15:09:07 +0000504
505 /**
506 * Get a specific item from the visible list
507 *
508 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000509 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000510 */
511 shownItem : function (index) {
512 if (index >= this.limit())
513 return;
514 return this.liveItem(this._offset + index);
515 },
516
517
Nils Diewald2fe12e12015-03-06 16:47:06 +0000518 /**
519 * Get the length of the full list
520 */
521 length : function () {
522 return this._items.length;
523 },
524
525
526 /**
527 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000528 */
529 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000530
Nils Diewald86dad5b2015-01-28 15:09:07 +0000531 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000532 if (this._position === -1)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000533 return;
534
Nils Diewald5975d702015-03-09 17:45:42 +0000535 var newItem;
536
Nils Diewald86dad5b2015-01-28 15:09:07 +0000537 // Set new live item
Nils Diewalde8518f82015-03-18 22:41:49 +0000538 if (!this._prefix.active()) {
539 var oldItem = this.liveItem(this._position);
540 oldItem.active(false);
541 };
542
543 this._position++;
544
Nils Diewald5975d702015-03-09 17:45:42 +0000545 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000546
Nils Diewald5975d702015-03-09 17:45:42 +0000547 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000548 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000549
550 // Activate prefix
551 var prefix = this._prefix;
552
553 // Mark prefix
554 if (prefix.isSet() && !prefix.active()) {
555 this._position--;
556 prefix.active(true);
557 return;
558 }
559 else {
560 this._offset = 0;
561 this._position = 0;
562 newItem = this.liveItem(0);
563 this._showItems(0);
564 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000565 }
566
567 // The next element is outside the view - roll down
Nils Diewald2fe12e12015-03-06 16:47:06 +0000568 else if (this._position >= (this.limit() + this._offset)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000569 this._removeFirst();
570 this._offset++;
571 this._append(this._list[this._position]);
572 };
Nils Diewald5975d702015-03-09 17:45:42 +0000573
574 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000575 newItem.active(true);
576 },
577
Nils Diewalde8518f82015-03-18 22:41:49 +0000578 /*
579 * Page down to the first item on the next page
580 */
581 /*
582 nextPage : function () {
583
584 // Prefix is active
585 if (this._prefix.active()) {
586 this._prefix.active(false);
587 }
588
589 // Last item is chosen
590 else if (this._position >= this.limit() + this._offset) {
591
592 this._position = this.limit() + this._offset - 1;
593 newItem = this.liveItem(this._position);
594 var oldItem = this.liveItem(this._position--);
595 oldItem.active(false);
596 }
597
598 // Last item of page is chosen
599 else if (0) {
600
601 // Jump to last item
602 else {
603 var oldItem = this.liveItem(this._position);
604 oldItem.active(false);
605
606 this._position = this.limit() + this._offset - 1;
607 newItem = this.liveItem(this._position);
608 };
609
610 newItem.active(true);
611 },
612 */
613
Nils Diewald86dad5b2015-01-28 15:09:07 +0000614
615 /*
616 * Make the previous item in the menu active
617 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000618 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000619
Nils Diewald2fe12e12015-03-06 16:47:06 +0000620 // No active element set
Nils Diewalde8518f82015-03-18 22:41:49 +0000621 if (this._position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000622 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000623 // TODO: Choose last item
624 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000625
Nils Diewald5975d702015-03-09 17:45:42 +0000626 var newItem;
627
Nils Diewald86dad5b2015-01-28 15:09:07 +0000628 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000629 if (!this._prefix.active()) {
630 var oldItem = this.liveItem(this._position--);
631 oldItem.active(false);
632 };
633
Nils Diewald5975d702015-03-09 17:45:42 +0000634 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000635
636 // The previous element is undefined - roll to bottom
637 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000638
639 // Activate prefix
640 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000641 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000642
643 // Normalize offset
644 this._offset = this._offset < 0 ? 0 : this._offset;
645
Nils Diewald2fe12e12015-03-06 16:47:06 +0000646 this._position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000647
648 if (prefix.isSet() && !prefix.active()) {
649 this._position++;
650 prefix.active(true);
651 return;
652 }
653 else {
654 newItem = this.liveItem(this._position);
655 this._showItems(this._offset);
656 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000657 }
658
659 // The previous element is outside the view - roll up
660 else if (this._position < this._offset) {
661 this._removeLast();
662 this._offset--;
663 this._prepend(this._list[this._position]);
664 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000665
Nils Diewald5975d702015-03-09 17:45:42 +0000666 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000667 newItem.active(true);
668 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000669
670
Nils Diewald7148c6f2015-05-04 15:07:53 +0000671 /**
672 * Length of the filtered item list.
673 */
Nils Diewald5975d702015-03-09 17:45:42 +0000674 liveLength : function () {
675 if (this._list === undefined)
676 this._initList();
677 return this._list.length;
678 },
679
680
Nils Diewald2fe12e12015-03-06 16:47:06 +0000681 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000682 _removeFirst : function () {
683 this.item(this._list[this._offset]).lowlight();
Nils Diewald5975d702015-03-09 17:45:42 +0000684 this._element.removeChild(this._element.children[1]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000685 },
686
Nils Diewald2fe12e12015-03-06 16:47:06 +0000687
688 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000689 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000690 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000691 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000692 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000693 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000694});