Add possibility to define icons for plugin-buttons

Change-Id: I4a30fa72953ae6136d5c5056c992bdadac135ffe
diff --git a/Changes b/Changes
index 6156b4d..09f4811 100755
--- a/Changes
+++ b/Changes
@@ -1,4 +1,4 @@
-0.39 2020-09-08
+0.39 2020-09-14
         - Add information on secret file to Readme.
         - Change default API endpoint to korap.ids-mannheim.de.
         - Fix label for toggle plugins.
@@ -9,6 +9,7 @@
         - Added query form API to plugin server.
         - Exclude search results from robot indices.
         - Added responsive 'news' style.
+        - Add support for icons in plugin definition (hebasta).
 
         WARNING: If you relied on the former default API endpoint
           being http://localhost:9999/, this will break your
diff --git a/dev/demo/export.js b/dev/demo/export.js
index 09e126f..d7548b2 100644
--- a/dev/demo/export.js
+++ b/dev/demo/export.js
@@ -1,7 +1,7 @@
 /**
  * Export Demo
  * 
- * @author Helge 
+ * @author Helge
  * 
  */
 
@@ -12,49 +12,50 @@
   }
 });
 
-
-require(['plugin/server', 'panel/result', 'init'], function (pluginClass, resultPanelClass){
+require([ 'plugin/server', 'panel/result', 'init' ], function(pluginClass, resultPanelClass) {
 
   KorAP.Plugin = pluginClass.create();
 
   // Add services container to head
   document.head.appendChild(KorAP.Plugin.element());
-  
-  //Register result plugin
-  KorAP.Plugin.register({
-     'name' : 'Export',
-     'desc' : 'Exports Kalamar results',
-     // 'about' : 'https://localhost:5678/',
-     'embed' : [{
-       'panel' : 'result',
-       'title' : 'Export',
-       'classes' : ['export'],
-       'onClick' : {
-         'action' : 'addWidget',
-         'template' : 'http://localhost:7777/res/export.html'
-       }
-     }]
-   }); 
 
-   //Add result panel
-   var show = {};
-   var resultPanel = resultPanelClass.create(show);    
-   
-   var resultInfo = document.getElementById('resultinfo');
-   if (resultInfo != null) {
-     // Move buttons to resultinfo
+  // Register result plugin
+  KorAP.Plugin.register({
+    'name' : 'Export',
+    'desc' : 'Exports Kalamar results',
+    // 'about' : 'https://localhost:5678/',
+    'embed' : [ {
+      'panel' : 'result',
+      'title' : 'exports KWICs and snippets',
+      //Unicode-Code of the plugins button-icon, Font: Font Awesome 
+      'icon' : "\uf019",
+      'classes' : [ 'button-icon', 'plugin' ],
+      'onClick' : {
+        'action' : 'addWidget',
+        'template' : 'http://localhost:7777/res/export.html'
+      }
+    } ]
+  });
+
+  //Add result panel
+  var show = {};
+  var resultPanel = resultPanelClass.create(show);
+
+  var resultInfo = document.getElementById('resultinfo');
+  if (resultInfo != null) {
+    // Move buttons to resultinfo
     resultInfo.appendChild(resultPanel.actions.element());
     // The views are at the top of the search results
     var sb = document.getElementById('search');
     sb.insertBefore(resultPanel.element(), sb.firstChild);
-    };
-    
-    // There is a koralQuery
-    if (KorAP.koralQuery !== undefined) {    
+  };
+
+  // There is a koralQuery
+  if (KorAP.koralQuery !== undefined) {
 
     if (KorAP.koralQuery["errors"]) {
       var errors = KorAP.koralQuery["errors"];
-      for (var i in errors) {
+      for ( var i in errors) {
 
         // Malformed query
         if (errors[i][0] === 302 && errors[i][2]) {
@@ -66,9 +67,9 @@
         // no query
         else if (errors[i][0] === 301) {
           obj.hint = hintClass.create();
-          obj.hint.alert(0, errors[i][1]);      
-          }
+          obj.hint.alert(0, errors[i][1]);
         }
-      };
-    };  
+      }
+    };
+  };
 });
diff --git a/dev/demo/plugin-serverdemo.js b/dev/demo/plugin-serverdemo.js
index 52d6609..d606cff 100644
--- a/dev/demo/plugin-serverdemo.js
+++ b/dev/demo/plugin-serverdemo.js
@@ -13,7 +13,8 @@
   'embed' : [{
     'panel' : 'result',
     'title' : 'Export',
-    'classes' : ['export'],
+    'icon' : "\uf019",
+    'classes' : [ 'button-icon', 'plugin', 'export' ],
     'onClick' : {
       'action' : 'addWidget',
       'template' : 'http://localhost:3003/demo/plugin-client.html',
diff --git a/dev/js/spec/buttongroupSpec.js b/dev/js/spec/buttongroupSpec.js
index 9183759..a386c1f 100644
--- a/dev/js/spec/buttongroupSpec.js
+++ b/dev/js/spec/buttongroupSpec.js
@@ -106,6 +106,20 @@
       expect(fun.count).toEqual(1);
     });
 
+    it('should add icon', function () {
+      var group = buttonGroupClass.create();
+      expect(group.element().classList.contains('button-group')).toBeTruthy();
+
+      group.add('Meta', ['meta'], function (e) {}, 'metaicon');
+
+      var btn = group.element().firstChild;
+      expect(btn.tagName).toEqual('SPAN');
+      expect(btn.getAttribute('data-icon')).toEqual('metaicon');
+      expect(btn.classList.contains('meta')).toBeTruthy();
+      expect(btn.innerText).toEqual('Meta');
+    });
+
+    
     it('should open lists', function () {
       
     });
diff --git a/dev/js/src/buttongroup.js b/dev/js/src/buttongroup.js
index 01e5a1c..f7acfb7 100644
--- a/dev/js/src/buttongroup.js
+++ b/dev/js/src/buttongroup.js
@@ -31,9 +31,9 @@
 
     
     /**
-     * Upgrade this object to another object,
+     * Upgrade this object to another object, 
      * while private data stays intact.
-     *
+     * 
      * @param {Object} An object with properties.
      */
     upgradeTo : function (props) {
@@ -46,15 +46,21 @@
     
     /**
      * Add button in order
-     *
+     * 
      * Returns the button element
      */
-    add : function (title, classes, cb) {
+    // TODO: Accept an object instead of a list
+    add : function (title, classes, cb, icon) {
       var b = this._element.addE('span');
       b.setAttribute('title',title);
       if (classes !== undefined) {
         b.classList.add.apply(b.classList, classes);
       };
+     
+      if (icon !== undefined){ 
+        b.setAttribute('data-icon', icon);
+      };
+     
       b.addE('span').addT(title);
 
       var that = this;
@@ -75,7 +81,7 @@
     
     /**
      * Add button that spawns a list in order.
-     *
+     * 
      * Returns the list object.
      */
     addList : function (title, classes, itemClass = defaultItemClass) {
@@ -136,8 +142,8 @@
     },
 
     /**
-     * Bind an object to all callbacks of the button group.
-     * To get the button element inside the callback,
+     * Bind an object to all callbacks of the button group. 
+     * To get the button element inside the callback, 
      * use this.button
      */
     bind : function (obj) {
diff --git a/dev/js/src/plugin/server.js b/dev/js/src/plugin/server.js
index 7a2d305..90dcd7c 100644
--- a/dev/js/src/plugin/server.js
+++ b/dev/js/src/plugin/server.js
@@ -133,8 +133,8 @@
         let title = embed["title"];        
         let panel = embed["panel"];
         let onClick = embed["onClick"];
-
-
+        let icon = embed["icon"];
+        
         if (!panel || !(buttons[panel] || buttonsSingle[panel]))
           throw new Error("Panel for plugin is invalid");        
 
@@ -160,20 +160,20 @@
 
           // Add to dynamic button list (e.g. for matches)
           if (buttons[panel]) {
-            buttons[panel].push([title, embed["classes"], cb]);
+            buttons[panel].push([title, embed["classes"], cb, icon]);
           }
 
           // 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);
+            KorAP.Panel[panel].actions.add(title, embed["classes"], cb, icon);
           }
 
           // Add to static button list (e.g. for query) not yet loaded
           else {
-            buttonsSingle[panel].push([title, embed["classes"], cb]);
+            buttonsSingle[panel].push([title, embed["classes"], cb, icon]);
           }
         }
-
+        //TODO There is no possibility to add icons to an plugin toggle button right now. 
         else if (onClick["action"] == "toggle") {
 
           // Todo: Initially false
diff --git a/dev/scss/main/buttongroup.scss b/dev/scss/main/buttongroup.scss
index 49df159..b78ed88 100644
--- a/dev/scss/main/buttongroup.scss
+++ b/dev/scss/main/buttongroup.scss
@@ -132,4 +132,7 @@
   > span {
     @include blind;
   }
+  &[data-icon]::after {
+    content: attr(data-icon);
+  }
 }
diff --git a/dev/scss/main/resultinfo.scss b/dev/scss/main/resultinfo.scss
index 67b4157..5556239 100644
--- a/dev/scss/main/resultinfo.scss
+++ b/dev/scss/main/resultinfo.scss
@@ -98,6 +98,9 @@
 
 // Define icons for result buttons
 .button-icon {
+
+  // TODO:
+  //   Add icon via data-icon
   &.align.left::after {
     content: $fa-left-align;
   }