New menu class that has an entry at the very end, similar to the input
text prefix, that is always available

Change-Id: I03d8f689e37021d3daac6bf34f7d35f0e4d71999
diff --git a/dev/js/src/alwaysentry.js b/dev/js/src/alwaysentry.js
new file mode 100644
index 0000000..f3f7b42
--- /dev/null
+++ b/dev/js/src/alwaysentry.js
@@ -0,0 +1,43 @@
+/**
+ * An entry in menus that is always
+ * displayed, with a string and onClick functionality
+ * uses menu/prefix.js as prototype and doesn't change much, all
+ * functionality comes from the alwaysmenu
+ * 
+ * 
+ * @author Leo Repp
+ */
+
+
+"use strict";
+
+define([
+  'menu/prefix'
+], function (prefixClass) {
+  
+  return {
+
+    /**
+     * Create new always visible menu entry object.
+     * Like a prefix Object, always visible, for some defined action
+     */
+    create : function (text) {
+      const obj = prefixClass.create()
+        .upgradeTo(this)
+        ._init();
+      obj._el.innerHTML = text || "Speichern";
+      //dont forget to adjust alwaysMenuSpec - alwaysEntry!
+      return obj;
+    },
+
+    _update : function () {
+      if (this._string.length!==0){ // I assume that this is a sufficient criterium for infering that the prefix is active
+        this._el.style.bottom="-27px";
+      } else if (this._string.length===0) {
+        this._el.style.bottom="0px";
+      }
+      return this._string; // No need to change the text (=innerHTML)
+    },
+
+  };
+});
diff --git a/dev/js/src/alwaysmenu.js b/dev/js/src/alwaysmenu.js
new file mode 100644
index 0000000..4bcf9da
--- /dev/null
+++ b/dev/js/src/alwaysmenu.js
@@ -0,0 +1,546 @@
+/**
+ * 
+ * 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);
+},
+
+
+  };
+});
diff --git a/dev/js/src/menu.js b/dev/js/src/menu.js
index 01bf7d8..088618b 100644
--- a/dev/js/src/menu.js
+++ b/dev/js/src/menu.js
@@ -60,6 +60,7 @@
         params = {};
 
       const t = this;
+      t._notItemElements=3;
 
       t._itemClass = params["itemClass"] || defaultItemClass;
 
@@ -127,7 +128,7 @@
 
       t._limit = menuLimit;
       
-      t._items = new Array();
+      t._items = new Array(); //all childNodes, i.e. ItemClass, prefixClass
 
       // TODO:
       // Make this separate from _init
@@ -142,7 +143,7 @@
     readItems : function (list) {
       const t = this;
 
-      t._list = undefined;
+      t._list = undefined; //filtered List containing all itemClass items
 
       // Remove circular reference to "this" in items
       for (let i = 0; i < t._items.length; i++) {
@@ -179,6 +180,7 @@
     
     // Initialize the item list
     _initList : function () {
+      // Upon change also update alwaysmenu.js please
       const t = this;
 
       // Create a new list
@@ -258,6 +260,7 @@
      * mark and sweep GC)!
      */
     destroy : function () {
+      // Upon change also update alwaysmenu.js please
       const t = this;
 
       // Remove circular reference to "this" in menu
@@ -327,6 +330,7 @@
 
     // Arrow key and prefix treatment
     _keydown : function (e) {
+      //Upon change also update alwaysmenu.js please
       const t = this;
 
       switch (_codeFromEvent(e)) {
@@ -489,6 +493,7 @@
      * @param {string} Prefix for filtering the list
      */
     show : function (active) {
+      //Upon change please also update alwaysmenu.js (only two lines new there)
       const t = this;
 
       // show menu based on initial offset
@@ -623,6 +628,7 @@
         this._el.removeChild(liElements[0]);
       };
      },
+      
 
 
     /**
@@ -686,6 +692,7 @@
      * Make the next item in the filtered menu active
      */
     next : function () {
+      //Upon change please update alwaysmenu.js next
       const t = this;
 
       // No list
@@ -742,6 +749,7 @@
      * Make the previous item in the menu active
      */
     prev : function () {
+      //Upon Change please update alwaysmenu.js prev
       const t = this;
 
       // No list
@@ -870,7 +878,7 @@
 
         // Remove the HTML node from the first item
         // leave lengthField/prefix/slider
-        t._el.removeChild(t._el.children[3]);
+        t._el.removeChild(t._el.children[this._notItemElements]);
 
         t._append(
           t._list[t.offset + t.limit() - 1]
@@ -943,7 +951,7 @@
       // Append element after lengthField/prefix/slider
       e.insertBefore(
         item.element(),
-        e.children[3]
+        e.children[this._notItemElements]
       );
     }
   };