blob: 4bcf9da2b2aa913192c9a388d7315438556db169 [file] [log] [blame]
Leo Repp56904d22021-04-26 15:53:22 +02001/**
2 *
3 * A Version of the menu class, that
4 * has an always displayed entry that can be
5 * clicked and contains text
6 *
7 * This entry button may or may not be displayed on top of objects
8 * lying under (>y) this menu. See alwaysentry update: negative absolute
9 * y coordinate.
10 *
11 * @author Leo Repp
12 */
13
14"use strict";
15define([
16 'menu',
17 'menu/item',
18 'menu/prefix',
19 'menu/lengthField',
20 'alwaysentry',
21 'util'
22], function (
23 menuClass,
24 defaultItemClass,
25 defaultPrefixClass,
26 defaultLengthFieldClass,
27 defaultAlwaysEntryClass) {
28
29 return {
30
31 /**
32 * Create new menu with an always visible entry.
33 * @this {AlwaysMenu}
34 * @constructor
35 * @param {Object["like this"]} params Object with attributes prefixCLass, itemClass, lengthFieldClass, alwaysEntryClass
36 * @param {Array.<Array.<string>>} list list of menu items
37 */
38 create : function (list, params) {
39 const obj = menuClass.create(list,params)
40 .upgradeTo(this)
41 ._init(list, params);
42
43 obj._el.classList.add('alwaysmenu');
44
45 //add entry object and allow for own entryClasses
46 if (params!==undefined && params["alwaysEntryClass"] !== undefined) {
47 obj._entry = params["alwaysEntryClass"].create();
48 } else {
49 obj._entry=defaultAlwaysEntryClass.create();
50 }
51 obj._entry._menu=obj;
52 obj._notItemElements=4;
53 // add entry to HTML element
54 obj._el.appendChild(obj._entry.element());
55
56 return obj;
57 },
58
59
60 /**
61 * Destroy this menu
62 * (in case you don't trust the
63 * mark and sweep GC)!
64 */
65 destroy : function () {
66 //based on menu.js
67 const t = this;
68
69 // Remove circular reference to "this" in menu
70 if (t._el != undefined)
71 delete t._el["menu"];
72
73 // Remove circular reference to "this" in items
74 t._items.forEach(function(i) {
75 delete i["_menu"];
76 });
77
78 // Remove circular reference to "this" in prefix
79 delete t._prefix['_menu'];
80 delete t._lengthField['_menu'];
81 delete t._slider['_menu'];
82 delete t._entry['_menu'];
83 },
84
85
86 // Arrow key and prefix treatment
87 _keydown : function (e) {
88 //based on menu.js
89 const t = this;
90
91 switch (_codeFromEvent(e)) {
92
93 case 27: // 'Esc'
94 e.halt();
95 t.hide();
96 break;
97
98 case 38: // 'Up'
99 e.halt();
100 t.prev();
101 break;
102
103 case 33: // 'Page up'
104 e.halt();
105 t.pageUp();
106 break;
107
108 case 40: // 'Down'
109 e.halt();
110 t.next();
111 break;
112
113 case 34: // 'Page down'
114 e.halt();
115 t.pageDown();
116 break;
117
118 case 39: // 'Right'
119 // "Use" this item
120 if (t._prefix.active())
121 break;
122
123
124 else if (t._entry.active()){
125 break;
126 };
127
128 const item = t.liveItem(t.position);
129
130 if (item["further"] !== undefined) {
131 item["further"].bind(item).apply();
132 };
133
134 e.halt();
135 break;
136
137 case 13: // 'Enter'
138 // Click on prefix
139 if (t._prefix.active())
140 t._prefix.onclick(e);
141 //Click on entry
142 else if (t._entry.active())
143 t._entry.onclick(e);
144 // Click on item
145 else
146 t.liveItem(t.position).onclick(e);
147 e.halt();
148 break;
149
150 case 8: // 'Backspace'
151 t._prefix.chop();
152 t._entry.chop();
153 t.show();
154 e.halt();
155 break;
156 };
157 },
158
159 // Add characters to prefix
160 _keypress : function (e) {
161 if (e.charCode !== 0) {
162 e.halt();
163
164 // Add prefix
165 this._prefix.add(
166 String.fromCharCode(_codeFromEvent(e))
167 );
168 this._entry.add(
169 String.fromCharCode(_codeFromEvent(e))
170 );
171
172 this.show();
173 };
174 },
175
176 /**
177 * Filter the list and make it visible.
178 * This is always called once the prefix changes.
179 *
180 * @param {string} Prefix for filtering the list
181 */
182 show : function (active) {
183 //only two new lines compared to menu.js show method (see NEW LINE)
184 const t = this;
185
186 // show menu based on initial offset
187 t._unmark(); // Unmark everything that was marked before
188 t.removeItems();
189
190 // Initialize the list
191 if (!t._initList()) {
192
193 // The prefix is not active
194 t._prefix.active(true);
195 //FIRST NEW LINE
196 t._entry.active(false);
197
198 // finally show the element
199 t._el.classList.add('visible');
200
201 return true;
202 };
203
204 let offset = 0;
205
206 // Set a chosen value to active and move the viewport
207 if (arguments.length === 1) {
208
209 // Normalize active value
210 if (active < 0) {
211 active = 0;
212 }
213 else if (active >= t.liveLength()) {
214 active = t.liveLength() - 1;
215 };
216
217 // Item is outside the first viewport
218 if (active >= t._limit) {
219 offset = active;
220 const newOffset = t.liveLength() - t._limit;
221 if (offset > newOffset) {
222 offset = newOffset;
223 };
224 };
225
226 t.position = active;
227 }
228
229 // Choose the first item
230 else if (t._firstActive) {
231 t.position = 0;
232 }
233
234 // Choose no item
235 else {
236 t.position = -1;
237 };
238
239 t.offset = offset;
240 t._showItems(offset); // Show new item list
241
242 // Make chosen value active
243 if (t.position !== -1) {
244 t.liveItem(t.position).active(true);
245 };
246
247 // The prefix is not active
248 t._prefix.active(false);
249 //SECOND NEW LINE
250 t._entry.active(false);
251
252 // finally show the element
253 t._el.classList.add('visible');
254
255 // Add classes for rolling menus
256 t._boundary(true);
257
258 return true;
259 },
260
261 /**
262 * Hide the menu and call the onHide callback.
263 */
264 hide : function () {
265 if (!this.dontHide) {
266 this.removeItems();
267 this._prefix.clear();
268 this._entry.clear();
269 this.onHide();
270 this._el.classList.remove('visible');
271 }
272 // this._el.blur();
273 },
274
275
276 /**
277 * The alwaysEntry object
278 * the menu is attached to.
279 */
280 alwaysEntry : function () {
281 return this._entry;
282 },
283
284 /**
285 * Get/Set the alwaysEntry Text
286 */
287 alwaysEntryValue : function (value) {
288 if (arguments.length === 1) {
289 this._entry.value(value);
290 return this;
291 };
292 return this._entry.value();
293 },
294
295 /**
296 * Delete all visible items from the menu element
297 */
298
299 /**
300 * Make the next item in the filtered menu active
301 */
302 next : function () {
303 //Hohe zyklomatische Komplexität
304 const t = this;
305 // Activate prefix and entry
306 const prefix = this._prefix;
307 const entry = this._entry;
308
309 // No list
310 if (t.liveLength() === 0){ //switch between entry and prefix
311 if (!prefix.isSet()){//It is entry and it will stay entry
312 entry.active(true);
313 prefix.active(false);//Question: do we need to activate entry?
314 return;
315 };
316 if (prefix.active() && !entry.active()){
317 t.position = 2; // ?
318 prefix.active(false);
319 entry.active(true); //activate entry
320 return;
321 }
322 else if (!prefix.active() && entry.active()){
323 t.position = 1; // ?
324 prefix.active(true); //activate prefix
325 entry.active(false);
326 return;
327 };
328 //otherwise: confusion
329 return;
330 };
331
332 // liveLength!=0
333 // Deactivate old item
334 if (t.position !== -1 && !t._prefix.active() && !t._entry.active()) {
335 t.liveItem(t.position).active(false);
336 };
337
338 // Get new active item
339 t.position++;
340 let newItem = t.liveItem(t.position);
341
342 // The next element is undefined - roll to top or to prefix or to entry
343 if (newItem === undefined) {
344
345 if ( !entry.active() ){ //if entry is active we definetly go to first item next
346 if (prefix.isSet() && !prefix.active()){ //prefix is next and exists
347 t.position=t.liveLength()+1;
348 prefix.active(true); //activate prefix
349 entry.active(false);
350 return;
351 }
352 else if ( (prefix.isSet() && prefix.active()) || // we had prefix
353 (!prefix.isSet() && !prefix.active()) ){ //or it isnt there
354 t.position=t.liveLength()+2;
355 prefix.active(false);
356 entry.active(true); //activate entry
357 return;
358 };
359 }
360
361 // Choose first item
362 else {
363 newItem = t.liveItem(0);
364 // choose first item
365 t.position = 0;
366 t._showItems(0);
367 // we reach point A from here
368 };
369 }
370
371 // The next element is after the viewport - roll down
372 else if (t.position >= (t.limit() + t.offset)) {
373 t.screen(t.position - t.limit() + 1);
374 }
375
376 // The next element is before the viewport - roll up
377 else if (t.position <= t.offset) {
378 t.screen(t.position);
379 };
380
381 //Point A
382 t._prefix.active(false);
383 t._entry.active(false);
384 newItem.active(true);
385 },
386
387
388 /*
389 * Make the previous item in the menu active
390 */
391 prev : function () {
392 const t = this;
393 // Activate prefix and entry
394 const prefix = this._prefix;
395 const entry = this._entry;
396
397 // No list
398 if (t.liveLength() === 0){ //switch between entry and prefix
399 if (!prefix.isSet()){//It is entry and it will stay entry
400 entry.active(true);
401 prefix.active(false);//Question: do we need to activate entry?
402 return;
403 };
404
405 if (prefix.active() && !entry.active()){
406 t.position = 2; // ?
407 prefix.active(false);
408 entry.active(true); //activate entry
409 return;
410 }
411 else if (!prefix.active() && entry.active()){
412 t.position = 1; // ?
413 prefix.active(true); //activate prefix
414 entry.active(false);
415 return;
416 };
417 //otherwise: confusion
418 };
419
420 // Deactivate old item
421 if (!prefix.active() && !entry.active()) {
422
423 // No active element set
424 if (t.position === -1) {
425 t.position = t.liveLength();
426 }
427
428 // deactivate active element
429 else {
430 t.liveItem(t.position--).active(false); //returns before decrement
431 };
432 };
433
434 // Get new active item
435 let newItem = t.liveItem(t.position);
436
437 // The previous element is undefined - roll to bottom
438 if (newItem === undefined) {
439
440
441 let offset = t.liveLength() - t.limit();
442
443 // Normalize offset
444 offset = offset < 0 ? 0 : offset;
445
446 // Choose the last item
447 t.position = t.liveLength() - 1;
448
449 if(!entry.active()){
450 if (prefix.isSet() && prefix.active()){
451 // we were on prefix and now choose last item
452 newItem = t.liveItem(t.position);
453 t._showItems(offset);
454 }
455 else if(!prefix.active()){
456 // we need to loop around: pick entry
457 t.position=t.liveLength()+2;
458 prefix.active(false);
459 entry.active(true); //activate entry
460 return;
461 };
462 //otherwise confusion
463 } else {
464 if(prefix.isSet()){ // we had entry and thus now need prefix
465 t.position=t.liveLength()+1;
466 prefix.active(true); //activate prefix
467 entry.active(false);
468 return;
469 } else { // we had entry but there is no prefix
470 newItem = t.liveItem(t.position);
471 t._showItems(offset); // Choose last item
472 };
473 };
474 }
475
476 // The previous element is before the view - roll up
477 else if (t.position < t.offset) {
478 t.screen(t.position);
479 }
480
481 // The previous element is after the view - roll down
482 else if (t.position >= (t.limit() + t.offset)) {
483 t.screen(t.position - t.limit() + 2);
484 };
485
486 t._prefix.active(false);
487 t._entry.active(false);
488 newItem.active(true);
489 },
490// Append Items that should be shown
491_showItems : function (off) {
492 const t = this;
493
494 // optimization: scroll down one step
495 if (t.offset === (off - 1)) {
496 t.offset = off;
497
498 // Remove the HTML node from the first item
499 // leave lengthField/prefix/slider
500 t._el.removeChild(t._el.children[this._notItemElements]);
501
502 t._append(
503 t._list[t.offset + t.limit() - 1]
504 );
505 }
506
507 // optimization: scroll up one step
508 else if (t.offset === (off + 1)) {
509 t.offset = off;
510
511 // Remove the HTML node from the last item
512 t._el.removeChild(t._el.lastChild);
513
514 t._prepend(t._list[t.offset]);
515 }
516
517 else {
518 t.offset = off;
519
520 // Remove all items
521 t.removeItems();
522
523 // Use list
524 let shown = 0;
525
526 for (let i = 0; i < t._list.length; i++) {
527
528 // Don't show - it's before offset
529 shown++;
530 if (shown <= off)
531 continue;
532
533 t._append(t._list[i]);
534
535 if (shown >= (t.limit() + off))
536 break;
537 };
538 };
539
540 // set the slider to the new offset
541 t._slider.offset(t.offset);
542},
543
544
545 };
546});