Open menus in buttongroups at different positions

Change-Id: Ica00912ebbd670680dadb65e3480342572895701
diff --git a/Changes b/Changes
index 27c9af0..24aebd0 100755
--- a/Changes
+++ b/Changes
@@ -30,6 +30,7 @@
         - Fold all top-level navigation items.
         - Add plugin service to redirect to a certain location.
         - Add support for pagination information to retrieve by plugins.
+        - Buttongroup menus can be opened at different positions.
 
 0.42 2021-06-18
         - Added GitHub based CI for perl.
diff --git a/dev/js/spec/buttongroupSpec.js b/dev/js/spec/buttongroupSpec.js
index c92832e..258d847 100644
--- a/dev/js/spec/buttongroupSpec.js
+++ b/dev/js/spec/buttongroupSpec.js
@@ -1,4 +1,4 @@
-define(['buttongroup','state'], function (buttonGroupClass, stateClass) {
+define(['buttongroup','buttongroup/menu','menu/item','state'], function (buttonGroupClass, buttonGroupMenuClass, defaultItemClass, stateClass) {
 
   var FunObj = {
     count : 0,
@@ -285,4 +285,46 @@
       expect(btn.tagName).toEqual('C3');
     });
   });
+
+  describe('KorAP.ButtonGroup.Menu', function () {
+    it('should reposition', function () {
+      const menu = buttonGroupMenuClass.create([["hallo", undefined, function () {}]], defaultItemClass);
+      const div = document.createElement('div');
+
+      document.body.appendChild(div);
+
+      div.style.position = 'absolute';
+      div.style.display = 'block';
+      div.style.left = '14px';
+      div.style.top = '20px';
+      div.style.width = '40px';
+      div.style.height = '30px';
+
+      menu.show();
+
+      // 000
+      menu._repos(div);
+      let elem = menu.element();
+      const fffl = elem.style.left;
+      const ffft = elem.style.top;
+
+      // 100
+      menu.openAt(true, false, false);
+      menu._repos(div);
+      elem = menu.element();
+      const tffl = elem.style.left;
+      expect(tffl).not.toEqual(fffl);
+
+
+      // 011
+      menu.openAt(false, true, true);
+      menu._repos(div);
+      elem = menu.element();
+      const fttt = elem.style.top;
+      expect(fttt).not.toEqual(ffft);
+
+      document.body.removeChild(div);
+      document.body.removeChild(elem);
+    });
+  });
 });
diff --git a/dev/js/src/buttongroup.js b/dev/js/src/buttongroup.js
index 040637f..721fdcb 100644
--- a/dev/js/src/buttongroup.js
+++ b/dev/js/src/buttongroup.js
@@ -25,6 +25,21 @@
       const cl = e.classList;
       if (classes) {
         cl.add.apply(cl,classes);
+        classes.forEach(i => {
+          switch (i) {
+          case "open-menu-below" : {
+            this._omBelow = true;
+            break;
+          }
+          case "open-menu-outside" : {
+            this._omOutside = true;
+            break;
+          }
+          case "open-menu-left" : {
+            this._omLeft = true;
+          }
+          }
+        })
       };
       cl.add('button-group');
       this._el = e;
@@ -119,6 +134,13 @@
      */
     addList : function (title, data, itemClass = defaultItemClass) {
       const list = treeMenuClass.create([], itemClass);
+
+      list.openAt(
+        this._omLeft ? true : false,
+        this._omBelow ? true : false,
+        this._omOutside ? true : false,
+      );
+      
       this.add(title, data, function (e) {
         list.show();
         list.button(this.button);
diff --git a/dev/js/src/buttongroup/menu.js b/dev/js/src/buttongroup/menu.js
index 4a4abfc..8edc7ef 100644
--- a/dev/js/src/buttongroup/menu.js
+++ b/dev/js/src/buttongroup/menu.js
@@ -32,9 +32,22 @@
       // Add menu to body
       document.getElementsByTagName('body')[0].appendChild(e);
 
+      this._left = false;
+      this._below = false;
+      this._outside = false;
+
       return obj;
     },
 
+    /**
+     * Open the menu at a certain position
+     * relative to the button.
+     */
+    openAt : function (left, below, outside) {
+      this._left = left ? true : false;
+      this._below = below ? true : false;
+      this._outside = outside ? true : false;
+    },
 
     /**
      * The panel object of the menu,
@@ -108,12 +121,38 @@
 
     _repos : function (e) {
       const bounding = e.getBoundingClientRect();
-      this._el.style.left = bounding.left + "px";
-      this._el.style.top = (
-        bounding.top +
-          bounding.height -
-          this._el.clientHeight
-      ) + "px";
+      const s = this._el.style;
+
+      let left = window.pageXOffset;
+      let top = 0;
+
+      s.right = 'auto';
+      s.bottom = 'auto';
+
+      left += bounding.left;
+      
+      if (this._left) {
+        left += bounding.width - this._el.clientWidth
+      };
+
+      if (this._below) {
+        top += bounding.bottom;
+        
+        if (!this._outside) {
+          top -= bounding.height;
+        };
+
+      } else {
+        top -= this._el.clientHeight;
+        if (this._outside) {
+          top += bounding.top;
+        } else {
+          top += bounding.bottom;
+        };
+      };
+
+      s.left = left + "px";
+      s.top = top + "px";
     }
   };
 });