blob: b9672c13e993cd2a0893f1d78c7d9b1d0956ab0d [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!
Akron5240b8c2016-05-20 09:17:41 +02009 * TODO: What is _pos and what is position?
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',
Akron9905e2a2016-05-10 16:06:44 +020015 'menu/slider',
Nils Diewald0e6992a2015-04-14 20:13:52 +000016 'util'
17], function (defaultItemClass,
Akronc7448732016-04-27 14:06:58 +020018 defaultPrefixClass,
Akron9905e2a2016-05-10 16:06:44 +020019 defaultLengthFieldClass,
20 sliderClass) {
Nils Diewaldfda29d92015-01-22 17:28:01 +000021
Nils Diewald0e6992a2015-04-14 20:13:52 +000022 // Default maximum number of menu items
23 var menuLimit = 8;
24
25 function _codeFromEvent (e) {
26 if (e.charCode && (e.keyCode == 0))
27 return e.charCode
28 return e.keyCode;
Nils Diewald59c02fc2015-03-07 01:29:09 +000029 };
30
Nils Diewald86dad5b2015-01-28 15:09:07 +000031
32 /**
33 * List of items for drop down menu (complete).
34 * Only a sublist of the menu is filtered (live).
35 * Only a sublist of the filtered menu is visible (shown).
36 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000037 return {
Nils Diewald86dad5b2015-01-28 15:09:07 +000038 /**
39 * Create new Menu based on the action prefix
40 * and a list of menu items.
41 *
42 * @this {Menu}
43 * @constructor
44 * @param {string} Context prefix
45 * @param {Array.<Array.<string>>} List of menu items
46 */
47 create : function (params) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000048 return Object.create(this)._init(params);
Nils Diewald86dad5b2015-01-28 15:09:07 +000049 },
50
Akron5240b8c2016-05-20 09:17:41 +020051 // Initialize list
52 _init : function (itemClass, prefixClass, lengthFieldClass, params) {
53 var that = this;
54 this._itemClass = itemClass || defaultItemClass;
55
56 // Add prefix object
57 if (prefixClass !== undefined) {
58 this._prefix = prefixClass.create();
59 }
60 else {
61 this._prefix = defaultPrefixClass.create();
62 };
63 this._prefix._menu = this;
64
65 // Add lengthField object
66 if (lengthFieldClass !== undefined) {
67 this._lengthField = lengthFieldClass.create();
68 }
69 else {
70 this._lengthField = defaultLengthFieldClass.create();
71 };
72 this._lengthField._menu = this;
73
74 // Initialize slider
75 this._slider = sliderClass.create(this);
76
77 // Create the element
78 var e = document.createElement("ul");
79 e.style.opacity = 0;
80 e.style.outline = 0;
81 e.setAttribute('tabindex', 0);
82 e.classList.add('menu');
83 e.classList.add('roll');
84 e.appendChild(this._prefix.element());
85 e.appendChild(this._lengthField.element());
86 e.appendChild(this._slider.element());
87
88 // This has to be cleaned up later on
89 e["menu"] = this;
90
91 // Arrow keys
92 e.addEventListener(
93 'keydown',
94 function (ev) {
95 that._keydown(ev)
96 },
97 false
98 );
99
100 // Strings
101 e.addEventListener(
102 'keypress',
103 function (ev) {
104 that._keypress(ev)
105 },
106 false
107 );
108
109 // Mousewheel
110 e.addEventListener(
111 'wheel',
112 function (ev) {
113 that._mousewheel(ev)
114 },
115 false
116 );
117
118 this._element = e;
119 this.active = false;
120 // this.selected = undefined;
121 this._items = new Array();
122 var i = 0;
123
124 // Initialize item list based on parameters
125 for (i in params) {
126 var obj = this._itemClass.create(params[i]);
127
128 // This may become circular
129 obj["_menu"] = this;
130 this._lengthField.add(params[i]);
131 this._items.push(obj);
132 };
133
134 this._limit = menuLimit;
135 this._slider.length(this.liveLength());
136 this._slider.limit(this._limit);
137
138 this.position = 0; // position in the active list
139 this._active = -1; // active item in the item list
140 this._firstActive = false; // Show the first item active always?
141 this._reset();
142 return this;
143 },
144
145 // Initialize the item list
146 _initList : function () {
147
148 // Create a new list
149 if (this._list === undefined) {
150 this._list = [];
151 }
152 else if (this._list.length !== 0) {
153 this._boundary(false);
154 this._list.length = 0;
155 };
156
157 // Offset is initially zero
158 this._offset = 0;
159
160 // There is no prefix set
161 if (this.prefix().length <= 0) {
162
163 // add all items to the list and lowlight
164 for (var i = 0; i < this._items.length; i++) {
165 this._list.push(i);
166 this._items[i].lowlight();
167 };
168
169 return true;
170 };
171
172 /*
173 * There is a prefix set, so filter the list!
174 */
175 var pos;
176 var paddedPrefix = " " + this.prefix();
177
178 // Iterate over all items and choose preferred matching items
179 // i.e. the matching happens at the word start
180 for (pos = 0; pos < this._items.length; pos++) {
181 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
182 this._list.push(pos);
183 };
184
185 // The list is empty - so lower your expectations
186 // Iterate over all items and choose matching items
187 // i.e. the matching happens anywhere in the word
188 if (this._list.length == 0) {
189 for (pos = 0; pos < this._items.length; pos++) {
190 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
191 this._list.push(pos);
192 };
193 };
194
195 // Filter was successful - yeah!
196 return this._list.length > 0 ? true : false;
197 },
198
199
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000200 /**
201 * Destroy this menu
202 * (in case you don't trust the
203 * mark and sweep GC)!
204 */
205 destroy : function () {
Akron47c086c2016-05-18 21:22:06 +0200206
Akron5240b8c2016-05-20 09:17:41 +0200207 // Remove circular reference to "this" in menu
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000208 if (this._element != undefined)
209 delete this._element["menu"];
210
Akron5240b8c2016-05-20 09:17:41 +0200211 // Remove circular reference to "this" in items
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000212 for (var i = 0; i < this._items.length; i++) {
213 delete this._items[i]["_menu"];
214 };
Akron5240b8c2016-05-20 09:17:41 +0200215
216 // Remove circular reference to "this" in prefix
Nils Diewald5c5a7472015-04-02 22:13:38 +0000217 delete this._prefix['_menu'];
Akron5240b8c2016-05-20 09:17:41 +0200218 delete this._lengthField['_menu'];
219 delete this._slider['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000220 },
221
Nils Diewald7148c6f2015-05-04 15:07:53 +0000222
223 /**
224 * Focus on this menu.
225 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000226 focus : function () {
227 this._element.focus();
228 },
229
Nils Diewald7148c6f2015-05-04 15:07:53 +0000230
Nils Diewald59c02fc2015-03-07 01:29:09 +0000231 // mouse wheel treatment
232 _mousewheel : function (e) {
233 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000234
235 delta = e.deltaY / 120;
236 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000237 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +0000238 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000239 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000240 e.halt();
241 },
242
Nils Diewald7148c6f2015-05-04 15:07:53 +0000243
Nils Diewald59c02fc2015-03-07 01:29:09 +0000244 // Arrow key and prefix treatment
Nils Diewald47f366b2015-04-15 20:06:35 +0000245 _keydown : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000246 var code = _codeFromEvent(e);
247
Nils Diewald59c02fc2015-03-07 01:29:09 +0000248 switch (code) {
249 case 27: // 'Esc'
250 e.halt();
251 this.hide();
252 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000253
Nils Diewald59c02fc2015-03-07 01:29:09 +0000254 case 38: // 'Up'
255 e.halt();
256 this.prev();
257 break;
Nils Diewald5975d702015-03-09 17:45:42 +0000258 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +0000259 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000260 this.prev();
261 break;
262 case 40: // 'Down'
263 e.halt();
264 this.next();
265 break;
266 case 34: // 'Page down'
267 e.halt();
268 this.next();
269 break;
270 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000271 if (this._prefix.active())
272 break;
273
Akronf86eaea2016-05-13 18:02:27 +0200274 var item = this.liveItem(this.position);
Akron5ef4fa02015-06-02 16:25:14 +0200275
Nils Diewald5975d702015-03-09 17:45:42 +0000276 if (item["further"] !== undefined) {
277 item["further"].bind(item).apply();
Nils Diewald5975d702015-03-09 17:45:42 +0000278 };
Akron5ef4fa02015-06-02 16:25:14 +0200279
280 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +0000281 break;
282 case 13: // 'Enter'
283
284 // Click on prefix
285 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000286 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000287
288 // Click on item
289 else
Akronf86eaea2016-05-13 18:02:27 +0200290 this.liveItem(this.position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000291 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000292 break;
293 case 8: // 'Backspace'
Nils Diewald7148c6f2015-05-04 15:07:53 +0000294 this._prefix.chop();
Nils Diewald5975d702015-03-09 17:45:42 +0000295 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000296 e.halt();
297 break;
Nils Diewald47f366b2015-04-15 20:06:35 +0000298 };
299 },
Nils Diewald59c02fc2015-03-07 01:29:09 +0000300
Nils Diewald47f366b2015-04-15 20:06:35 +0000301 // Add characters to prefix
302 _keypress : function (e) {
303 var c = String.fromCharCode(_codeFromEvent(e)).toLowerCase();
Nils Diewald5975d702015-03-09 17:45:42 +0000304
Nils Diewald47f366b2015-04-15 20:06:35 +0000305 // Add prefix
306 this._prefix.add(c);
307
308 if (!this.show()) {
309 this.prefix('').show();
310 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000311 };
312 },
313
Akron47c086c2016-05-18 21:22:06 +0200314 /**
Akron5240b8c2016-05-20 09:17:41 +0200315 * Show a screen with a given offset
316 * in the viewport.
Akron47c086c2016-05-18 21:22:06 +0200317 */
318 screen : function (nr) {
319 if (this._offset === nr)
320 return;
Akron5240b8c2016-05-20 09:17:41 +0200321 this.unshow();
322 this._offset = nr;
Akron47c086c2016-05-18 21:22:06 +0200323 this._showItems(nr);
324 },
325
Nils Diewald2fe12e12015-03-06 16:47:06 +0000326 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000327 * Get the associated dom element.
Nils Diewald2fe12e12015-03-06 16:47:06 +0000328 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000329 element : function () {
330 return this._element;
331 },
332
Nils Diewald2fe12e12015-03-06 16:47:06 +0000333 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000334 * Get the creator class for items
Nils Diewald2fe12e12015-03-06 16:47:06 +0000335 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000336 itemClass : function () {
337 return this._itemClass;
338 },
339
340 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +0000341 * Get and set the numerical value
342 * for the maximum number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000343 */
344 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000345 if (arguments.length === 1) {
Akron5240b8c2016-05-20 09:17:41 +0200346 if (this._limit !== limit) {
347 this._limit = limit;
348 this._slider.limit(limit);
349 };
Nils Diewald5975d702015-03-09 17:45:42 +0000350 return this;
351 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000352 return this._limit;
353 },
354
Nils Diewald7148c6f2015-05-04 15:07:53 +0000355
Nils Diewald86dad5b2015-01-28 15:09:07 +0000356 /**
357 * Upgrade this object to another object,
358 * while private data stays intact.
359 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000360 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000361 */
362 upgradeTo : function (props) {
363 for (var prop in props) {
364 this[prop] = props[prop];
365 };
366 return this;
367 },
368
Nils Diewald7148c6f2015-05-04 15:07:53 +0000369
Nils Diewald86dad5b2015-01-28 15:09:07 +0000370 /**
371 * Filter the list and make it visible
372 *
373 * @param {string} Prefix for filtering the list
374 */
Nils Diewald5975d702015-03-09 17:45:42 +0000375 show : function () {
Nils Diewalde8518f82015-03-18 22:41:49 +0000376
Nils Diewald86dad5b2015-01-28 15:09:07 +0000377 // Initialize the list
378 if (!this._initList())
Nils Diewald59c02fc2015-03-07 01:29:09 +0000379 return false;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000380
Akron5240b8c2016-05-20 09:17:41 +0200381 // show menu based on initial offset
382 this._unmark(); // Unmark everything that was marked before
383 this.unshow(); // Delete everything that is shown
384 this._showItems(0); // Show new item list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000385
386 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000387 // Todo: Or the last element chosen
Akron47c086c2016-05-18 21:22:06 +0200388 if (this._firstActive) {
Nils Diewald20f7ace2015-05-07 12:51:34 +0000389 this.liveItem(0).active(true);
Akron47c086c2016-05-18 21:22:06 +0200390 this.position = 0;
391 this._active = 0;
392 }
Akroncb351d62016-05-19 23:10:33 +0200393
Akron47c086c2016-05-18 21:22:06 +0200394 else {
395 this.position = -1;
396 }
Akron37513a62015-11-17 01:07:11 +0100397
Akron5240b8c2016-05-20 09:17:41 +0200398 // The prefix is not active
Nils Diewalde8518f82015-03-18 22:41:49 +0000399 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000400
Akron5240b8c2016-05-20 09:17:41 +0200401
402 // finally show the element
Nils Diewald2fe12e12015-03-06 16:47:06 +0000403 this._element.style.opacity = 1;
404
Akron5240b8c2016-05-20 09:17:41 +0200405 // Show the slider
Akron9905e2a2016-05-10 16:06:44 +0200406 this._slider.show();
407
Akron37513a62015-11-17 01:07:11 +0100408 // Iterate to the active item
409 if (this._active !== -1 && !this._prefix.isSet()) {
Akronf86eaea2016-05-13 18:02:27 +0200410 while (this._list[this.position] < this._active) {
Akron5240b8c2016-05-20 09:17:41 +0200411
412 // TODO. Improve this by moving using screen!
Akron37513a62015-11-17 01:07:11 +0100413 this.next();
414 };
415 };
416
Nils Diewald86dad5b2015-01-28 15:09:07 +0000417 // Add classes for rolling menus
418 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000419 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000420 },
421
Nils Diewald7148c6f2015-05-04 15:07:53 +0000422
423 /**
424 * Hide the menu and call the onHide callback.
425 */
Nils Diewald2fe12e12015-03-06 16:47:06 +0000426 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000427 this.active = false;
Akron5240b8c2016-05-20 09:17:41 +0200428 this._unmark();
429 this.unshow();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000430 this._element.style.opacity = 0;
Nils Diewald7148c6f2015-05-04 15:07:53 +0000431 this._prefix.clear();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000432 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000433 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000434 },
435
Nils Diewald7148c6f2015-05-04 15:07:53 +0000436 /**
437 * Function released when the menu hides.
438 * This method is expected to be overridden.
439 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000440 onHide : function () {},
441
Nils Diewald7148c6f2015-05-04 15:07:53 +0000442
Nils Diewald86dad5b2015-01-28 15:09:07 +0000443 /**
444 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000445 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000446 */
Nils Diewald5975d702015-03-09 17:45:42 +0000447 prefix : function (pref) {
448 if (arguments.length === 1) {
449 this._prefix.value(pref);
450 return this;
451 };
452 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000453 },
454
Akronc7448732016-04-27 14:06:58 +0200455 /**
456 * Get the lengthField object.
457 */
458 lengthField : function () {
459 return this._lengthField;
460 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000461
Akron5240b8c2016-05-20 09:17:41 +0200462 /**
463 * Get the associated slider object.
464 */
465 slider : function () {
466 return this._slider;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000467 },
468
Akron5240b8c2016-05-20 09:17:41 +0200469
Nils Diewald86dad5b2015-01-28 15:09:07 +0000470 /**
471 * Delete all visible items from the menu element
472 */
Akron5240b8c2016-05-20 09:17:41 +0200473 unshow : function () {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000474 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000475
Nils Diewald2fe12e12015-03-06 16:47:06 +0000476 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000477 var children = this._element.childNodes;
Akronc7448732016-04-27 14:06:58 +0200478 // Leave the prefix and lengthField
Akron9905e2a2016-05-10 16:06:44 +0200479 for (var i = children.length - 1; i >= 3; i--) {
Nils Diewald5975d702015-03-09 17:45:42 +0000480 this._element.removeChild(
481 children[i]
482 );
483 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000484 },
485
Nils Diewald2fe12e12015-03-06 16:47:06 +0000486 /**
487 * Get a specific item from the complete list
488 *
489 * @param {number} index of the list item
490 */
491 item : function (index) {
492 return this._items[index]
493 },
494
495
Nils Diewald86dad5b2015-01-28 15:09:07 +0000496 /**
497 * Get a specific item from the filtered list
498 *
499 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000500 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000501 */
502 liveItem : function (index) {
503 if (this._list === undefined)
504 if (!this._initList())
505 return;
506
507 return this._items[this._list[index]];
508 },
509
Nils Diewald86dad5b2015-01-28 15:09:07 +0000510
511 /**
Akron5240b8c2016-05-20 09:17:41 +0200512 * Get a specific item from the viewport list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000513 *
514 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000515 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000516 */
517 shownItem : function (index) {
518 if (index >= this.limit())
519 return;
520 return this.liveItem(this._offset + index);
521 },
522
523
Nils Diewald2fe12e12015-03-06 16:47:06 +0000524 /**
525 * Get the length of the full list
526 */
527 length : function () {
528 return this._items.length;
529 },
530
531
532 /**
Akron5240b8c2016-05-20 09:17:41 +0200533 * Length of the filtered item list.
534 */
535 liveLength : function () {
536 if (this._list === undefined)
537 this._initList();
538 return this._list.length;
539 },
540
541
542 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000543 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000544 */
545 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000546
Nils Diewald86dad5b2015-01-28 15:09:07 +0000547 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000548 var newItem;
549
Akron47c086c2016-05-18 21:22:06 +0200550 if (this.position !== -1) {
551 // Set new live item
552 if (!this._prefix.active()) {
553 var oldItem = this.liveItem(this.position);
554 oldItem.active(false);
555 };
Nils Diewalde8518f82015-03-18 22:41:49 +0000556 };
557
Akronf86eaea2016-05-13 18:02:27 +0200558 this.position++;
Akron47c086c2016-05-18 21:22:06 +0200559 this._active = this.position;
Nils Diewalde8518f82015-03-18 22:41:49 +0000560
Akronf86eaea2016-05-13 18:02:27 +0200561 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000562
Nils Diewald5975d702015-03-09 17:45:42 +0000563 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000564 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000565
566 // Activate prefix
567 var prefix = this._prefix;
568
569 // Mark prefix
570 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200571 this.position--;
Nils Diewald5975d702015-03-09 17:45:42 +0000572 prefix.active(true);
Akron47c086c2016-05-18 21:22:06 +0200573 this._active = -1;
Nils Diewald5975d702015-03-09 17:45:42 +0000574 return;
575 }
576 else {
577 this._offset = 0;
Akronf86eaea2016-05-13 18:02:27 +0200578 this.position = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000579 newItem = this.liveItem(0);
Akron47c086c2016-05-18 21:22:06 +0200580 this._active = 0;
Akron5240b8c2016-05-20 09:17:41 +0200581 this._unmark();
582 this.unshow();
Nils Diewald5975d702015-03-09 17:45:42 +0000583 this._showItems(0);
584 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000585 }
586
587 // The next element is outside the view - roll down
Akronf86eaea2016-05-13 18:02:27 +0200588 else if (this.position >= (this.limit() + this._offset)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000589 this._removeFirst();
590 this._offset++;
Akronf86eaea2016-05-13 18:02:27 +0200591 this._append(this._list[this.position]);
Akron5240b8c2016-05-20 09:17:41 +0200592 this._slider.offset(this._offset);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000593 };
Nils Diewald5975d702015-03-09 17:45:42 +0000594
595 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000596 newItem.active(true);
597 },
598
Nils Diewalde8518f82015-03-18 22:41:49 +0000599 /*
Nils Diewald86dad5b2015-01-28 15:09:07 +0000600 * Make the previous item in the menu active
601 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000602 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000603
Nils Diewald2fe12e12015-03-06 16:47:06 +0000604 // No active element set
Akronf86eaea2016-05-13 18:02:27 +0200605 if (this.position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000606 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000607 // TODO: Choose last item
608 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000609
Nils Diewald5975d702015-03-09 17:45:42 +0000610 var newItem;
611
Nils Diewald86dad5b2015-01-28 15:09:07 +0000612 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000613 if (!this._prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200614 var oldItem = this.liveItem(this.position--);
Nils Diewald2d210752015-03-09 19:01:15 +0000615 oldItem.active(false);
616 };
617
Akronf86eaea2016-05-13 18:02:27 +0200618 newItem = this.liveItem(this.position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000619
620 // The previous element is undefined - roll to bottom
621 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000622
623 // Activate prefix
624 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000625 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000626
627 // Normalize offset
628 this._offset = this._offset < 0 ? 0 : this._offset;
629
Akronf86eaea2016-05-13 18:02:27 +0200630 this.position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000631
632 if (prefix.isSet() && !prefix.active()) {
Akronf86eaea2016-05-13 18:02:27 +0200633 this.position++;
Nils Diewald5975d702015-03-09 17:45:42 +0000634 prefix.active(true);
635 return;
636 }
637 else {
Akronf86eaea2016-05-13 18:02:27 +0200638 newItem = this.liveItem(this.position);
Akron5240b8c2016-05-20 09:17:41 +0200639 this._unmark();
640 this.unshow();
Nils Diewald5975d702015-03-09 17:45:42 +0000641 this._showItems(this._offset);
642 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000643 }
644
645 // The previous element is outside the view - roll up
Akronf86eaea2016-05-13 18:02:27 +0200646 else if (this.position < this._offset) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000647 this._removeLast();
648 this._offset--;
Akronf86eaea2016-05-13 18:02:27 +0200649 this._prepend(this._list[this.position]);
Akron5240b8c2016-05-20 09:17:41 +0200650 // set the slider to the new offset
651 this._slider.offset(this._offset);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000652 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000653
Nils Diewald5975d702015-03-09 17:45:42 +0000654 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000655 newItem.active(true);
656 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000657
658
Akron5240b8c2016-05-20 09:17:41 +0200659 // Unmark all items
660 _unmark : function () {
661 for (var i in this._list) {
662 var item = this._items[this._list[i]];
663 item.lowlight();
664 item.active(false);
665 };
666 },
667
668
669 // Reset chosen item and prefix
670 _reset : function () {
671 this._offset = 0;
672 this._pos = 0;
673 this._prefix.value('');
674 },
675
676
677 // Set boundary for viewport
678 _boundary : function (bool) {
679 this.item(this._list[0]).noMore(bool);
680 this.item(this._list[this._list.length - 1]).noMore(bool);
681 },
682
683
684 // Append Items that should be shown
685 _showItems : function (off) {
686
687 // Use list
688 var shown = 0;
689 var i;
690
691 for (i in this._list) {
692
693 // Don't show - it's before offset
694 shown++;
695 if (shown <= off)
696 continue;
697
698 var itemNr = this._list[i];
699 var item = this.item(itemNr);
700 this._append(itemNr);
701
702 // this._offset))
703 if (shown >= (this.limit() + off))
704 break;
705 };
706
707 // set the slider to the new offset
708 this._slider.offset(this._offset);
709 },
710
711
712 // Append item to the shown list based on index
713 _append : function (i) {
714 var item = this.item(i);
715
716 // Highlight based on prefix
717 if (this.prefix().length > 0)
718 item.highlight(this.prefix());
719
720 // Append element
721 this.element().appendChild(item.element());
722 },
723
724
725 // Prepend item to the shown list based on index
726 _prepend : function (i) {
727 var item = this.item(i);
728
729 // Highlight based on prefix
730 if (this.prefix().length > 0)
731 item.highlight(this.prefix());
732
733 var e = this.element();
734 // Append element after lengthField/prefix/slider
735 e.insertBefore(
736 item.element(),
737 e.children[3]
738 );
Nils Diewald5975d702015-03-09 17:45:42 +0000739 },
740
741
Nils Diewald2fe12e12015-03-06 16:47:06 +0000742 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000743 _removeFirst : function () {
744 this.item(this._list[this._offset]).lowlight();
Akron9905e2a2016-05-10 16:06:44 +0200745 // leave lengthField/prefix/slider
746 this._element.removeChild(this._element.children[3]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000747 },
748
Nils Diewald2fe12e12015-03-06 16:47:06 +0000749
750 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000751 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000752 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000753 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000754 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000755 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000756});
Akron5240b8c2016-05-20 09:17:41 +0200757
758
759 /*
760 * Page down to the first item on the next page
761 */
762 /*
763 nextPage : function () {
764
765 // Prefix is active
766 if (this._prefix.active()) {
767 this._prefix.active(false);
768 }
769
770 // Last item is chosen
771 else if (this.position >= this.limit() + this._offset) {
772
773 this.position = this.limit() + this._offset - 1;
774 newItem = this.liveItem(this.position);
775 var oldItem = this.liveItem(this.position--);
776 oldItem.active(false);
777 }
778
779 // Last item of page is chosen
780 else if (0) {
781
782 // Jump to last item
783 else {
784 var oldItem = this.liveItem(this.position);
785 oldItem.active(false);
786
787 this.position = this.limit() + this._offset - 1;
788 newItem = this.liveItem(this.position);
789 };
790
791 newItem.active(true);
792 },
793 */