Support dynamic menu extensions

Change-Id: I5c65fd9a259d57890fd031800cc4e83b8c58eaee
diff --git a/Changes b/Changes
index eae7731..a2aeefb 100755
--- a/Changes
+++ b/Changes
@@ -9,6 +9,7 @@
         - Remove 'X-Frame-Options' in favor of 'frame-ancestors'
           as a CSP rule.
         - Fix CSS compression for new SASS compiler.
+        - Support dynamic menu extensions.
 
 0.42 2021-06-18
         - Added GitHub based CI for perl.
diff --git a/dev/js/spec/menuSpec.js b/dev/js/spec/menuSpec.js
index 5d115a8..e1c6fc8 100644
--- a/dev/js/spec/menuSpec.js
+++ b/dev/js/spec/menuSpec.js
@@ -1491,6 +1491,43 @@
       });
 
       xit('should scroll to a chosen value after prefixing, if the chosen value is live');
+
+      it('should be expendable', function () {
+        var menu = menuClass.create([]);
+        let entryData = 'empty';
+        menu.readItems([
+          ['a', '', function () { entryData = 'a' }],
+          ['bb', '', function () { entryData = 'bb' }],
+          ['ccc', '', function () { entryData = 'ccc' }],
+        ]);
+
+        expect(menu.limit(3).show(3)).toBe(true);
+        expect(menu.shownItem(0).lcField()).toEqual(' a');
+        expect(menu.shownItem(1).lcField()).toEqual(' bb');
+        expect(menu.shownItem(2).lcField()).toEqual(' ccc');
+        expect(entryData).toEqual('empty');
+        menu.shownItem(1).element().click();
+        expect(entryData).toEqual('bb');
+        expect(menu.lengthField().element().innerText).toEqual("a--bb--ccc--")
+        expect(menu.slider().length()).toEqual(3);
+
+        let obj = menu.itemClass().create(
+          ['dddd','',function () { entryData = 'dddd'} ]
+        );
+        menu.append(obj)
+
+        expect(menu.limit(2).show(1)).toBe(true);
+        expect(menu.shownItem(0).lcField()).toEqual(' a');
+        expect(menu.shownItem(1).lcField()).toEqual(' bb');
+        menu.next();
+        expect(menu.shownItem(1).lcField()).toEqual(' ccc');
+        menu.next();
+        expect(menu.shownItem(1).lcField()).toEqual(' dddd');
+        menu.next();
+        expect(menu.shownItem(0).lcField()).toEqual(' a');
+        expect(menu.lengthField().element().innerText).toEqual("a--bb--ccc--dddd--")
+        expect(menu.slider().length()).toEqual(4);
+      });
     });
 
     describe('KorAP.Prefix', function () {
diff --git a/dev/js/src/menu.js b/dev/js/src/menu.js
index e26d19b..6aff7fa 100644
--- a/dev/js/src/menu.js
+++ b/dev/js/src/menu.js
@@ -177,6 +177,21 @@
       t.offset = 0;
       t.position = 0;
     },
+
+    // Append item to list
+    append : function (item) {
+      const t = this;
+      // This is cyclic!
+      item["_menu"] = t;
+      t._list = undefined;
+      t.removeItems();
+      t._items.push(item);
+      t._lengthField.add([item.content().data]);
+      t._slider.length(t.liveLength()).reInit();
+      t._firstActive = false;
+      t.offset = 0;
+      t.position = 0;
+    },
     
     // Initialize the item list
     _initList : function () {