Introduce pagination panel (fixes #141)

Change-Id: Ib8522c4d154b96630abcd23c7115b2fb4633bdfb
diff --git a/Changes b/Changes
index b7bcfac..4835496 100755
--- a/Changes
+++ b/Changes
@@ -38,6 +38,7 @@
         - Added DemoServer plugin.
         - Support CSP disabling.
         - Update intro.js (#109; hebasta)
+        - Introduce pagination panel.
 
 0.42 2021-06-18
         - Added GitHub based CI for perl.
diff --git a/dev/demo/all.html b/dev/demo/all.html
index d8eec7e..593efe7 100644
--- a/dev/demo/all.html
+++ b/dev/demo/all.html
@@ -16,6 +16,25 @@
   </head>
   <body class="no-js">
     <div id="kalamar-bg"></div>
+
+    <aside tabindex="0">
+      <div>
+	<fieldset>
+	  <form>
+	    <legend><span>Anmelden</span></legend>
+ 	    <input type="text" name="handle" placeholder="Benutzername" />
+	    <div>
+	      <input type="password" name="pwd" placeholder="Passwort" />
+	      <button type="submit"><span>Go</span></button>
+	    </div>
+	  </form>
+	  <ul style="font-size: 80%">
+	    <li><a href="#">Registrieren</a></li>
+	    <li><a href="#">Passwort vergessen?</a></li>
+	  </ul>
+	</fieldset>
+      </div>
+
     <header>
       <a href="/" class="logo" tabindex="-1"><h1><span>KorAP - Corpus Analysis Platform</span></h1></a>
 
@@ -66,24 +85,6 @@
       </form>
     </header>
 
-    <aside tabindex="0">
-      <div>
-	<fieldset>
-	  <form>
-	    <legend><span>Anmelden</span></legend>
- 	    <input type="text" name="handle" placeholder="Benutzername" />
-	    <div>
-	      <input type="password" name="pwd" placeholder="Passwort" />
-	      <button type="submit"><span>Go</span></button>
-	    </div>
-	  </form>
-	  <ul style="font-size: 80%">
-	    <li><a href="#">Registrieren</a></li>
-	    <li><a href="#">Passwort vergessen?</a></li>
-	  </ul>
-	</fieldset>
-      </div>
-
 <!--
       <h2>Einstellungen</h2>
       <a>Deutsch|Englisch</a>
@@ -91,7 +92,11 @@
     </aside>
 
     <main>
-      <div id="pagination" class="button-group button-panel">
+      <div id="pagination"
+           class="button-group button-panel"
+           data-page="1"
+           data-total="52230"
+           data-count="25">
 	<a rel="prev"><span><span>&lt;</span></span></a>
 	<a rel="self"><span>1</span></a>
 	<a href="#2" tabindex="4"><span>2</span></a>
diff --git a/dev/js/spec/buttongroupSpec.js b/dev/js/spec/buttongroupSpec.js
index 07183f2..d35abfe 100644
--- a/dev/js/spec/buttongroupSpec.js
+++ b/dev/js/spec/buttongroupSpec.js
@@ -123,7 +123,8 @@
       var group = buttonGroupClass.create();
       expect(group.element().classList.contains('button-group')).toBeTruthy();
 
-      var list = group.addList('More', {'cls':['more']});
+      var b = group.addList('More', {'cls':['more']});
+      var list = b.list;
 
       list.readItems([
         ['cool', 'cool', function () { }],
@@ -152,7 +153,8 @@
       var group = buttonGroupClass.create();
       expect(group.element().classList.contains('button-group')).toBeTruthy();
 
-      var list = group.addList('More', {'cls':['more']});
+      var b = group.addList('More', {'cls':['more']});
+      var list = b.list;
 
       let x = 'empty';
       list.add('Meta1', {'cls':['meta'], 'icon': 'metaicon'}, function (e) {
diff --git a/dev/js/spec/panelSpec.js b/dev/js/spec/panelSpec.js
index 8618369..e53c464 100644
--- a/dev/js/spec/panelSpec.js
+++ b/dev/js/spec/panelSpec.js
@@ -1,4 +1,4 @@
-define(['panel','view','panel/result','util'], function (panelClass,viewClass, resultClass) {
+define(['panel','view','panel/result','panel/pagination','util'], function (panelClass,viewClass,resultClass,paginationClass) {
 
   var controlStr = "";
 
@@ -186,4 +186,40 @@
       document.body.removeChild(div);
     });
   });
+
+  describe('KorAP.Panel.Pagination', function () {
+    it('should be initializable', function () {
+      // Create pagination element for pagination information
+      let p = document.createElement('div');
+      p.setAttribute('id', 'pagination')
+      p.setAttribute('data-page',3);
+      p.setAttribute('data-total',30);
+      p.setAttribute('data-count',25);
+
+      document.body.appendChild(p);
+
+      // Create pagination class object
+      var pagination = paginationClass.create();
+      let list = pagination.actions().element();
+      expect(list.classList.contains('button-group-list')).toBeTruthy();
+      expect(list.classList.contains('visible')).toBeFalsy();
+
+      pagination.addRandomPage();
+
+      let clicked = false;
+      pagination.actions().add(
+        "test", {}, function () { clicked = true }
+      );
+      
+      pagination.buttonGroup().element().firstChild.click();
+      expect(list.classList.contains('visible')).toBeTruthy();
+
+      expect(clicked).toBeFalsy();
+      pagination.actions().element().children[4].click();
+      expect(clicked).toBeTruthy();
+      
+      document.body.removeChild(p);
+      document.body.removeChild(pagination.actions().element());
+    });
+  });
 });
diff --git a/dev/js/src/buttongroup.js b/dev/js/src/buttongroup.js
index 85c6555..31f60c8 100644
--- a/dev/js/src/buttongroup.js
+++ b/dev/js/src/buttongroup.js
@@ -129,12 +129,13 @@
         this._omOutside ? true : false,
       );
       
-      this.add(title, data, function (e) {
+      let b = this.add(title, data, function (e) {
         list.show();
         list.button(this.button);
         list.focus();
       });
-      return list;
+      b.list = list;
+      return b;
     },
 
     /**
diff --git a/dev/js/src/init.js b/dev/js/src/init.js
index 54ffd98..724cfb3 100644
--- a/dev/js/src/init.js
+++ b/dev/js/src/init.js
@@ -28,6 +28,7 @@
   'selectMenu',
   'panel/result',
   'panel/query',
+  'panel/pagination',
   'tour/tours',
   'plugin/server',
   'pipe',
@@ -46,6 +47,7 @@
              selectMenuClass,
              resultPanelClass,
              queryPanelClass,
+             paginationPanelClass,
              tourClass,
              pluginClass,
              pipeClass) {
@@ -255,7 +257,7 @@
      * Add result panel
      */
     var resultPanel = resultPanelClass.create(show);
-
+   
     if (resultInfo != null) {
 
       // Move buttons to resultinfo
@@ -435,7 +437,18 @@
       // The views are below the query bar
       sform.insertBefore(queryPanel.element(), vcView);
       KorAP.Panel['query'] = queryPanel;
-    }
+    };
+
+
+    /**
+     * Add pagination panel
+     */
+    const paginationPanel = paginationPanelClass.create();
+
+    if (paginationPanel) {
+      paginationPanel.addRandomPage();
+      KorAP.Panel['pagination'] = paginationPanel;
+    };
 
 
     /**
diff --git a/dev/js/src/loc/de.js b/dev/js/src/loc/de.js
index a7c0080..dec8970 100644
--- a/dev/js/src/loc/de.js
+++ b/dev/js/src/loc/de.js
@@ -74,6 +74,7 @@
   loc.TOUR_treeb = loc.TOUR_tree || "Anzeige weiterer Ansichten"
   loc.TOUR_tree = loc.TOUR_tree || "Weitere Ansichten können als Baum- oder Bogenansichten angezeigt werden.";
   loc.TOUR_tourdone = "Viel Spaß mit KorAP!";
-  
-  
+
+  // Pagination panel
+  loc.RANDOM_PAGE = 'Zufallsseite';
 });
diff --git a/dev/js/src/panel/pagination.js b/dev/js/src/panel/pagination.js
new file mode 100644
index 0000000..f084e51
--- /dev/null
+++ b/dev/js/src/panel/pagination.js
@@ -0,0 +1,92 @@
+/**
+ * The pagination panel
+ *
+ * @author Nils Diewald
+ */
+"use strict";
+
+define([
+  'panel',
+  'buttongroup',
+  'pageInfo'
+], function (panelClass, buttonGroupClass, pageInfoClass) {
+
+  const d = document;
+
+  // Localization values
+  const loc = KorAP.Locale;
+  loc.RANDOM_PAGE = loc.RANDOM_PAGE || 'Random page';
+
+  
+  return {
+    create : function () {
+      return Object.create(panelClass)._init(['pagination']).upgradeTo(this)._init();
+    },
+
+    // Initialize panel
+    _init : function () {
+
+      // Override existing button group
+      // This allows actions.add() ... to work for list items in server.js
+      let pagEl = document.getElementById("pagination");
+      if (pagEl === null) {
+        return null;
+      };
+
+      // Do not show if no pages exist
+      if (pagEl.getAttribute('data-total') === '0') {
+        return null;
+      };
+
+      this._actions.panel = undefined;
+      this._actions.clear();
+
+      const bg = buttonGroupClass.adopt(pagEl);
+      bg._omOutside = true;
+      bg._omLeft = true;
+      
+      bg.anchor(pagEl.lastElementChild);
+
+      let button = bg.addList("More", {'cls':['button-group-list','button-icon']});
+      this._actions = button.list;
+      this._bg = bg;
+
+      button.setAttribute('data-icon',"\uf0c9");
+
+      // Warning: This is circular
+      this._actions.panel = this;
+     
+      this.prepend = true;
+      
+      return this;
+    },
+
+    /**
+     * The buttongroup holding the pagination panel.
+     * This differs from action, as action contains the list.
+     */
+    buttonGroup : function () {
+      return this._bg;
+    },
+
+    
+    /**
+     * Add random paginator to list
+     */
+    addRandomPage : function () {
+      const pi = pageInfoClass.create();
+      
+      const button = this.actions().add(
+        loc.RANDOM_PAGE,
+        {},
+        function () {
+          if (pi.total() > 0) {
+            const sp = new URLSearchParams(window.location.search);
+            sp.set("p", Math.floor(Math.random() * pi.total()) + 1);
+            window.location.search = sp.toString();
+          };
+        }
+      )
+    }
+  }
+});
diff --git a/dev/scss/main/pagination.scss b/dev/scss/main/pagination.scss
index d41fb73..d09162d 100644
--- a/dev/scss/main/pagination.scss
+++ b/dev/scss/main/pagination.scss
@@ -30,8 +30,9 @@
     line-height: 0;
     
     > * {
-        font-size: 10pt;
+        font-size: 10pt !important;
         line-height: 2em;
+        vertical-align: bottom;
 
         &:not(:link):not([rel=self]) {
             color: $choose-blind-color;