Added query panel for plugin capabilities

Change-Id: I5766cf45aa711ebea9a6f9f3dfa51e706ebd0d0d
diff --git a/Changes b/Changes
index d11686a..0027b2e 100755
--- a/Changes
+++ b/Changes
@@ -1,4 +1,4 @@
-0.37 2019-10-27
+0.37 2019-11-19
         - Removed deprecated 'kalamar_test_port' helper.
         - Separated KalamarHelpers and KalamarPages.
         - Renamed 'doc_link_to' to 'embedded_link_to'
@@ -14,7 +14,8 @@
         - Deprecated 'doc_navi' helper in favor of 'navigation'
           helper.
         - Added 'navi->set' and 'navi->add' helper.
-        - Added settings skeleton. 
+        - Added settings skeleton.
+        - Added query panel for query views.
 
 0.36 2019-09-19
         - Rename all cookies to be independent
diff --git a/dev/demo/hint.html b/dev/demo/hint.html
index 38c4a99..3e8f71e 100644
--- a/dev/demo/hint.html
+++ b/dev/demo/hint.html
@@ -8,7 +8,7 @@
   </head>
   <body>
     <header>
-      <form autocomplete="off" action="/kalamar">
+      <form id="searchform" autocomplete="off" action="/kalamar">
 	<div id="searchbar">
 	  <input type="search"
 		 placeholder="Find ..."
@@ -18,6 +18,7 @@
 		 autofocus="autofocus" />
 	  <button type="submit"><span>Go</span></button>
 	</div>
+  <div id="vc-view"></div>
       </form>
     </header>
 
diff --git a/dev/demo/hintdemo.js b/dev/demo/hintdemo.js
index e33faab..ee676ef 100644
--- a/dev/demo/hintdemo.js
+++ b/dev/demo/hintdemo.js
@@ -7,11 +7,52 @@
 
 var hint = undefined;
 
-require(['hint','hint/foundries/cnx','lib/domReady'], function (hintClass, hintArray, domReady) {
+require(['plugin/server','panel/query', 'hint','hint/foundries/cnx','lib/domReady'], function (pluginClass, queryPanelClass, hintClass, hintArray, domReady) {
   KorAP.hintArray = hintArray;
   KorAP.Hint = null;
+  
   domReady(function() {
+
     KorAP.Hint = hintClass.create();
+
+    /**
+     * Add query panel
+     */
+    var queryPanel = queryPanelClass.create();
+
+    // Get input field
+    var sform = document.getElementById("searchform");
+    var vcView = document.getElementById('vc-view')
+    if (sform && vcView) {
+      // The views are below the query bar
+      sform.insertBefore(queryPanel.element(),vcView);
+      KorAP.Panel = KorAP.Panel || {};
+      KorAP.Panel['query'] = queryPanel;
+    }
+
+    // Load plugin server
+    KorAP.Plugin = pluginClass.create();
+
+    // Register match plugin
+    KorAP.Plugin.register({
+      'name' : 'Example New',
+      'desc' : 'Some content about cats',
+      'embed' : [{
+        'panel' : 'query',
+        'title' : 'Translate',
+        'classes' : ['translate'],
+        'onClick' : {
+          "template" : "http://localhost:3003/demo/plugin-client.html"
+        }
+      },{
+        'panel' : 'query',
+        'title' : 'Glemm',
+        'classes' : ['glemm'],
+        'onClick' : {
+          "template" : "http://localhost:3003/demo/plugin-client.html"
+        }
+      }]
+    });
   });
 });
 
diff --git a/dev/demo/panel.html b/dev/demo/panel.html
index f309cbd..35f78e7 100644
--- a/dev/demo/panel.html
+++ b/dev/demo/panel.html
@@ -18,7 +18,7 @@
 });
 
 
-require(['buttongroup', 'panel', 'view/koralquery'], function (btnClass, panelClass, kqClass) {
+require(['buttongroup', 'panel', 'view/result/koralquery'], function (btnClass, panelClass, kqClass) {
   KorAP.koralQuery = {
     '@type' : "https://beispiel",
     'key' : 'Cool'
diff --git a/dev/js/spec/pluginSpec.js b/dev/js/spec/pluginSpec.js
index 0694070..f7514bd 100644
--- a/dev/js/spec/pluginSpec.js
+++ b/dev/js/spec/pluginSpec.js
@@ -1,4 +1,4 @@
-define(['plugin/server','plugin/widget','panel'], function (pluginServerClass, widgetClass, panelClass) {
+define(['plugin/server','plugin/widget','panel', 'panel/query'], function (pluginServerClass, widgetClass, panelClass, queryPanelClass) {
 
   describe('KorAP.Plugin.Server', function () {
 
@@ -8,7 +8,6 @@
       manager.destroy();
     });
 
-
     it('should add a widget', function () {
       var manager = pluginServerClass.create();
       var panel = panelClass.create();
@@ -44,7 +43,6 @@
       widget.close();
 
       expect(panelE.getElementsByClassName('view').length).toEqual(0);
-
       manager.destroy();
     });
 
@@ -71,9 +69,46 @@
           }]
         })}
       ).toThrow(new Error("Panel for plugin is invalid"));
+      manager.destroy();
+    });
+
+    it('should accept valid registrations for matches', function () {
+      var manager = pluginServerClass.create();
+
+      manager.register({
+        name : 'Check',
+        embed : [{
+          panel : 'match',
+          title : 'Translate',
+          onClick : {
+            template : 'test'
+          }
+        }]
+      });
+
+      expect(manager.buttonGroup('match').length).toEqual(1);
+      manager.destroy();
+    });
+
+    it('should accept valid registrations for query temporary', function () {
+      var manager = pluginServerClass.create();
+
+      manager.register({
+        name : 'Check',
+        embed : [{
+          panel : 'query',
+          title : 'Translate',
+          onClick : {
+            template : 'test'
+          }
+        }]
+      });
+
+      expect(manager.buttonGroup('query').length).toEqual(1);
+      manager.destroy();
     });
   });
-
+  
   describe('KorAP.Plugin.Widget', function () {
     it('should be initializable', function () {
       expect(function () { widgetClass.create() }).toThrow(new Error("Widget not well defined"));
@@ -124,4 +159,36 @@
       expect(iframe.style.height).toEqual('9px');
     });
   });
+
+  describe('KorAP.Plugin.QueryPanel', function () {
+    it('should establish a query plugin', function () {
+      var queryPanel = queryPanelClass.create();
+
+      var div = document.createElement('div');
+
+      div.appendChild(queryPanel.element());
+      KorAP.Panel = KorAP.Panel || {};
+      KorAP.Panel['query'] = queryPanel;
+
+      // Register plugin afterwards
+      var manager = pluginServerClass.create();
+
+      manager.register({
+        name : 'Check',
+        embed : [{
+          panel : 'query',
+          title : 'Translate',
+          onClick : {
+            template : 'test'
+          }
+        }]
+      });
+
+      expect(manager.buttonGroup('query').length).toEqual(0);
+
+      // Clean up
+      KorAP.Panel['query'] = undefined;
+      manager.destroy();
+    });
+  });
 });
diff --git a/dev/js/src/init.js b/dev/js/src/init.js
index bafa473..82d9de9 100644
--- a/dev/js/src/init.js
+++ b/dev/js/src/init.js
@@ -26,6 +26,7 @@
   'session',
   'selectMenu',
   'panel/result',
+  'panel/query',
   'tour/tours',
   'api',
   'mailToChiffre',
@@ -40,6 +41,7 @@
              sessionClass,
              selectMenuClass,
              resultPanelClass,
+             queryPanelClass,
              tourClass) {
 
   const d = document;
@@ -376,6 +378,22 @@
     // Add the hinthelper to the KorAP object to make it manipulatable globally
     KorAP.Hint = obj.hint;
 
+
+    /**
+     * Add query panel
+     */
+    var queryPanel = queryPanelClass.create();
+
+    // Get input field
+    var sform = d.getElementById("searchform");
+    var vcView = d.getElementById('vc-view')
+    if (sform && vcView) {
+      // The views are below the query bar
+      sform.insertBefore(queryPanel.element(), vcView);
+      KorAP.Panel = KorAP.Panel || {};
+      KorAP.Panel['query'] = queryPanel;
+    }
+    
     return obj;
   });
   
diff --git a/dev/js/src/panel/query.js b/dev/js/src/panel/query.js
new file mode 100644
index 0000000..aed2a91
--- /dev/null
+++ b/dev/js/src/panel/query.js
@@ -0,0 +1,39 @@
+/**
+ * The query panel
+ *
+ * @author Nils Diewald
+ */
+define([
+  'panel'
+], function (panelClass) {
+
+  const d = document;
+
+  // Localization values
+  const loc = KorAP.Locale;
+  
+  return {
+    create : function (opened) {
+      return Object.create(panelClass)._init(['query']).upgradeTo(this)._init(opened);
+    },
+
+    // Initialize panel
+    _init : function (opened) {
+      this._opened = opened;
+
+      // If plugins are enabled, add all buttons for the query panel
+      if (KorAP.Plugin) {
+        var queryButtons = KorAP.Plugin.buttonGroup("query");
+
+        // Add all matchbuttons in order
+        for (i in queryButtons) {
+          a.add.apply(a, queryButtons[i]);
+        };
+
+        KorAP.Plugin.clearButtons("query")
+      };
+      
+      return this;
+    }
+  }
+});
diff --git a/dev/js/src/plugin/server.js b/dev/js/src/plugin/server.js
index 1afc747..69318b0 100644
--- a/dev/js/src/plugin/server.js
+++ b/dev/js/src/plugin/server.js
@@ -11,6 +11,8 @@
 define(["plugin/widget", "util"], function (widgetClass) {
   "use strict";
 
+  KorAP.Panel = KorAP.Panel || {};
+
   // Contains all widgets to address with
   // messages to them
   var widgets = {};
@@ -19,12 +21,18 @@
   // TODO:
   //   These should better be panels and every panel
   //   has a buttonGroup
+
+  // List of panels with dynamic buttons, i.e.
+  // panels that may occur multiple times.
   var buttons = {
     match : []
   };
-  var panels = {
-    match : 1
-  };
+
+  // List of panels with static buttons, i.e.
+  // panels that occur only once.
+  var buttonsSingle = {
+    query : []
+  }
   
   // This is a counter to limit acceptable incoming messages
   // to a certain amount. For every message, this counter will
@@ -110,7 +118,7 @@
 
         var panel = embed["panel"];
 
-        if (!panel || !buttons[panel])
+        if (!panel || !(buttons[panel] || buttonsSingle[panel]))
           throw new Error("Panel for plugin is invalid");
 
         var onClick = embed["onClick"];
@@ -134,7 +142,20 @@
             plugin["widgets"].push(id);
           };
 
-          buttons[panel].push([title, embed["classes"], cb]);
+          // Add to dynamic button list (e.g. for matches)
+          if (buttons[panel]) {
+            buttons[panel].push([title, embed["classes"], cb]);
+          }
+
+          // Add to static button list (e.g. for query) already loaded
+          else if (KorAP.Panel[panel]) {
+            KorAP.Panel[panel].actions.add(title, embed["classes"], cb);
+          }
+
+          // Add to static button list (e.g. for query) not yet loaded
+          else {
+            buttonsSingle[panel].push([title, embed["classes"], cb]);
+          }
         };
       };
     },
@@ -152,7 +173,23 @@
      * Get named button group - better rename to "action"
      */
     buttonGroup : function (name) {
-      return buttons[name];
+      if (buttons[name] != undefined) {
+        return buttons[name];
+      } else if (buttonsSingle[name] != undefined) {
+        return buttonsSingle[name];
+      };
+      return [];
+    },
+
+    /**
+     * Clear named button group - better rename to "action"
+     */
+    clearButtonGroup : function (name) {
+      if (buttons[name] != undefined) {
+        buttons[name] = [];
+      } else if (buttonsSingle[name] != undefined) {
+        buttonsSingle[name] = [];
+      }
     },
     
     /**
@@ -298,6 +335,12 @@
         widgets[w].close();
       };
       widgets = {};
+      for (let b in buttons) {
+        buttons[b] = [];
+      };
+      for (let b in buttonsSingle) {
+        buttonsSingle[b] = [];
+      };
       this._removeListener();
     }
   };
diff --git a/dev/scss/header/searchbar.scss b/dev/scss/header/searchbar.scss
index 3776931..ab86913 100644
--- a/dev/scss/header/searchbar.scss
+++ b/dev/scss/header/searchbar.scss
@@ -2,16 +2,18 @@
 @import "../util";
 
 $border-size: 2px;
+$qmargin: 3px;
 // $right-padding: 60px;
 
 
+
 /**
  * Input field
  */
 #q-field {
   width: 100%;
   margin: 0;
-  margin-bottom: 3px;
+  margin-bottom: $qmargin;
   display: block;
 
   &::-webkit-search-cancel-button {
@@ -26,9 +28,11 @@
 #searchbar {
   position: relative;
   width: 100%;
+  padding: 0;
   padding-right: $right-distance + $button-width;
   button[type=submit] {
     position: absolute;
+    padding: 0;
     right: $right-distance;
     &::after {
       content: $fa-search;
@@ -71,3 +75,21 @@
     }
   }
 }
+
+
+.query.panel {
+  padding-right: $right-distance + $button-width;
+}
+
+// Specify result button group layout
+
+.query.button-group.button-panel {
+  width: auto;
+  text-align: right;
+  vertical-align: top;
+  display: block;
+  line-height: 1.3em;
+  > span {
+    box-shadow: none;
+  }
+}
diff --git a/package.json b/package.json
index 4aba320..93e213f 100755
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "name": "Kalamar",
   "description": "Mojolicious-based Frontend for KorAP",
   "license": "BSD-2-Clause",
-  "version": "0.37.0",
+  "version": "0.37.1",
   "pluginVersion": "0.1",
   "repository": {
     "type": "git",