blob: 4bcf9da2b2aa913192c9a388d7315438556db169 [file] [log] [blame]
/**
*
* A Version of the menu class, that
* has an always displayed entry that can be
* clicked and contains text
*
* This entry button may or may not be displayed on top of objects
* lying under (>y) this menu. See alwaysentry update: negative absolute
* y coordinate.
*
* @author Leo Repp
*/
"use strict";
define([
'menu',
'menu/item',
'menu/prefix',
'menu/lengthField',
'alwaysentry',
'util'
], function (
menuClass,
defaultItemClass,
defaultPrefixClass,
defaultLengthFieldClass,
defaultAlwaysEntryClass) {
return {
/**
* Create new menu with an always visible entry.
* @this {AlwaysMenu}
* @constructor
* @param {Object["like this"]} params Object with attributes prefixCLass, itemClass, lengthFieldClass, alwaysEntryClass
* @param {Array.<Array.<string>>} list list of menu items
*/
create : function (list, params) {
const obj = menuClass.create(list,params)
.upgradeTo(this)
._init(list, params);
obj._el.classList.add('alwaysmenu');
//add entry object and allow for own entryClasses
if (params!==undefined && params["alwaysEntryClass"] !== undefined) {
obj._entry = params["alwaysEntryClass"].create();
} else {
obj._entry=defaultAlwaysEntryClass.create();
}
obj._entry._menu=obj;
obj._notItemElements=4;
// add entry to HTML element
obj._el.appendChild(obj._entry.element());
return obj;
},
/**
* Destroy this menu
* (in case you don't trust the
* mark and sweep GC)!
*/
destroy : function () {
//based on menu.js
const t = this;
// Remove circular reference to "this" in menu
if (t._el != undefined)
delete t._el["menu"];
// Remove circular reference to "this" in items
t._items.forEach(function(i) {
delete i["_menu"];
});
// Remove circular reference to "this" in prefix
delete t._prefix['_menu'];
delete t._lengthField['_menu'];
delete t._slider['_menu'];
delete t._entry['_menu'];
},
// Arrow key and prefix treatment
_keydown : function (e) {
//based on menu.js
const t = this;
switch (_codeFromEvent(e)) {
case 27: // 'Esc'
e.halt();
t.hide();
break;
case 38: // 'Up'
e.halt();
t.prev();
break;
case 33: // 'Page up'
e.halt();
t.pageUp();
break;
case 40: // 'Down'
e.halt();
t.next();
break;
case 34: // 'Page down'
e.halt();
t.pageDown();
break;
case 39: // 'Right'
// "Use" this item
if (t._prefix.active())
break;
else if (t._entry.active()){
break;
};
const item = t.liveItem(t.position);
if (item["further"] !== undefined) {
item["further"].bind(item).apply();
};
e.halt();
break;
case 13: // 'Enter'
// Click on prefix
if (t._prefix.active())
t._prefix.onclick(e);
//Click on entry
else if (t._entry.active())
t._entry.onclick(e);
// Click on item
else
t.liveItem(t.position).onclick(e);
e.halt();
break;
case 8: // 'Backspace'
t._prefix.chop();
t._entry.chop();
t.show();
e.halt();
break;
};
},
// Add characters to prefix
_keypress : function (e) {
if (e.charCode !== 0) {
e.halt();
// Add prefix
this._prefix.add(
String.fromCharCode(_codeFromEvent(e))
);
this._entry.add(
String.fromCharCode(_codeFromEvent(e))
);
this.show();
};
},
/**
* Filter the list and make it visible.
* This is always called once the prefix changes.
*
* @param {string} Prefix for filtering the list
*/
show : function (active) {
//only two new lines compared to menu.js show method (see NEW LINE)
const t = this;
// show menu based on initial offset
t._unmark(); // Unmark everything that was marked before
t.removeItems();
// Initialize the list
if (!t._initList()) {
// The prefix is not active
t._prefix.active(true);
//FIRST NEW LINE
t._entry.active(false);
// finally show the element
t._el.classList.add('visible');
return true;
};
let offset = 0;
// Set a chosen value to active and move the viewport
if (arguments.length === 1) {
// Normalize active value
if (active < 0) {
active = 0;
}
else if (active >= t.liveLength()) {
active = t.liveLength() - 1;
};
// Item is outside the first viewport
if (active >= t._limit) {
offset = active;
const newOffset = t.liveLength() - t._limit;
if (offset > newOffset) {
offset = newOffset;
};
};
t.position = active;
}
// Choose the first item
else if (t._firstActive) {
t.position = 0;
}
// Choose no item
else {
t.position = -1;
};
t.offset = offset;
t._showItems(offset); // Show new item list
// Make chosen value active
if (t.position !== -1) {
t.liveItem(t.position).active(true);
};
// The prefix is not active
t._prefix.active(false);
//SECOND NEW LINE
t._entry.active(false);
// finally show the element
t._el.classList.add('visible');
// Add classes for rolling menus
t._boundary(true);
return true;
},
/**
* Hide the menu and call the onHide callback.
*/
hide : function () {
if (!this.dontHide) {
this.removeItems();
this._prefix.clear();
this._entry.clear();
this.onHide();
this._el.classList.remove('visible');
}
// this._el.blur();
},
/**
* The alwaysEntry object
* the menu is attached to.
*/
alwaysEntry : function () {
return this._entry;
},
/**
* Get/Set the alwaysEntry Text
*/
alwaysEntryValue : function (value) {
if (arguments.length === 1) {
this._entry.value(value);
return this;
};
return this._entry.value();
},
/**
* Delete all visible items from the menu element
*/
/**
* Make the next item in the filtered menu active
*/
next : function () {
//Hohe zyklomatische Komplexität
const t = this;
// Activate prefix and entry
const prefix = this._prefix;
const entry = this._entry;
// No list
if (t.liveLength() === 0){ //switch between entry and prefix
if (!prefix.isSet()){//It is entry and it will stay entry
entry.active(true);
prefix.active(false);//Question: do we need to activate entry?
return;
};
if (prefix.active() && !entry.active()){
t.position = 2; // ?
prefix.active(false);
entry.active(true); //activate entry
return;
}
else if (!prefix.active() && entry.active()){
t.position = 1; // ?
prefix.active(true); //activate prefix
entry.active(false);
return;
};
//otherwise: confusion
return;
};
// liveLength!=0
// Deactivate old item
if (t.position !== -1 && !t._prefix.active() && !t._entry.active()) {
t.liveItem(t.position).active(false);
};
// Get new active item
t.position++;
let newItem = t.liveItem(t.position);
// The next element is undefined - roll to top or to prefix or to entry
if (newItem === undefined) {
if ( !entry.active() ){ //if entry is active we definetly go to first item next
if (prefix.isSet() && !prefix.active()){ //prefix is next and exists
t.position=t.liveLength()+1;
prefix.active(true); //activate prefix
entry.active(false);
return;
}
else if ( (prefix.isSet() && prefix.active()) || // we had prefix
(!prefix.isSet() && !prefix.active()) ){ //or it isnt there
t.position=t.liveLength()+2;
prefix.active(false);
entry.active(true); //activate entry
return;
};
}
// Choose first item
else {
newItem = t.liveItem(0);
// choose first item
t.position = 0;
t._showItems(0);
// we reach point A from here
};
}
// The next element is after the viewport - roll down
else if (t.position >= (t.limit() + t.offset)) {
t.screen(t.position - t.limit() + 1);
}
// The next element is before the viewport - roll up
else if (t.position <= t.offset) {
t.screen(t.position);
};
//Point A
t._prefix.active(false);
t._entry.active(false);
newItem.active(true);
},
/*
* Make the previous item in the menu active
*/
prev : function () {
const t = this;
// Activate prefix and entry
const prefix = this._prefix;
const entry = this._entry;
// No list
if (t.liveLength() === 0){ //switch between entry and prefix
if (!prefix.isSet()){//It is entry and it will stay entry
entry.active(true);
prefix.active(false);//Question: do we need to activate entry?
return;
};
if (prefix.active() && !entry.active()){
t.position = 2; // ?
prefix.active(false);
entry.active(true); //activate entry
return;
}
else if (!prefix.active() && entry.active()){
t.position = 1; // ?
prefix.active(true); //activate prefix
entry.active(false);
return;
};
//otherwise: confusion
};
// Deactivate old item
if (!prefix.active() && !entry.active()) {
// No active element set
if (t.position === -1) {
t.position = t.liveLength();
}
// deactivate active element
else {
t.liveItem(t.position--).active(false); //returns before decrement
};
};
// Get new active item
let newItem = t.liveItem(t.position);
// The previous element is undefined - roll to bottom
if (newItem === undefined) {
let offset = t.liveLength() - t.limit();
// Normalize offset
offset = offset < 0 ? 0 : offset;
// Choose the last item
t.position = t.liveLength() - 1;
if(!entry.active()){
if (prefix.isSet() && prefix.active()){
// we were on prefix and now choose last item
newItem = t.liveItem(t.position);
t._showItems(offset);
}
else if(!prefix.active()){
// we need to loop around: pick entry
t.position=t.liveLength()+2;
prefix.active(false);
entry.active(true); //activate entry
return;
};
//otherwise confusion
} else {
if(prefix.isSet()){ // we had entry and thus now need prefix
t.position=t.liveLength()+1;
prefix.active(true); //activate prefix
entry.active(false);
return;
} else { // we had entry but there is no prefix
newItem = t.liveItem(t.position);
t._showItems(offset); // Choose last item
};
};
}
// The previous element is before the view - roll up
else if (t.position < t.offset) {
t.screen(t.position);
}
// The previous element is after the view - roll down
else if (t.position >= (t.limit() + t.offset)) {
t.screen(t.position - t.limit() + 2);
};
t._prefix.active(false);
t._entry.active(false);
newItem.active(true);
},
// Append Items that should be shown
_showItems : function (off) {
const t = this;
// optimization: scroll down one step
if (t.offset === (off - 1)) {
t.offset = off;
// Remove the HTML node from the first item
// leave lengthField/prefix/slider
t._el.removeChild(t._el.children[this._notItemElements]);
t._append(
t._list[t.offset + t.limit() - 1]
);
}
// optimization: scroll up one step
else if (t.offset === (off + 1)) {
t.offset = off;
// Remove the HTML node from the last item
t._el.removeChild(t._el.lastChild);
t._prepend(t._list[t.offset]);
}
else {
t.offset = off;
// Remove all items
t.removeItems();
// Use list
let shown = 0;
for (let i = 0; i < t._list.length; i++) {
// Don't show - it's before offset
shown++;
if (shown <= off)
continue;
t._append(t._list[i]);
if (shown >= (t.limit() + off))
break;
};
};
// set the slider to the new offset
t._slider.offset(t.offset);
},
};
});