Add and remove constraints from the corpusByMatch assistant

Change-Id: I508312e6c431d0602471eb75fd06e845ffd63631
diff --git a/dev/js/spec/matchSpec.js b/dev/js/spec/matchSpec.js
index b23e309..4b865bc 100644
--- a/dev/js/spec/matchSpec.js
+++ b/dev/js/spec/matchSpec.js
@@ -58,7 +58,7 @@
     {
       "@type": "koral:field",
       "key": "textClass",
-      "type": "type:keywords",
+      "type": "type:keywords", // May not be necessary
       "value": ["kultur", "film"]
     },
     {
@@ -714,7 +714,8 @@
 		expect(mel.children[0].tagName).toEqual('DIV');
 		expect(mel.children[0].children[0].tagName).toEqual('DT');
 		expect(mel.children[0].children[0].attributes[0].name).toEqual('title');
-		expect(mel.children[0].children[1].tagName).toEqual('DD');
+    expect(mel.children[0].children[1].tagName).toEqual('DD');
+    expect(mel.children[0].children[1].getAttribute('data-type')).toEqual('type:text')
 		
 		expect(mel.children[0].children[0].firstChild.nodeValue).toEqual('author');
 		expect(mel.children[0].children[1].firstChild.nodeValue).toEqual('Sprachpfleger, u.a.');
@@ -727,6 +728,7 @@
 	 it('keywords should be formatted', function(){
 		
 		 //type:string or type:keyword should b not relevant
+     expect(mel.children[1].children[1].getAttribute('data-type')).toEqual('type:string')
 		 expect(mel.children[1].children[1].classList.contains('metakeyvalues')).toBeTruthy;
 		 expect(mel.children[1].children[1].children[0].tagName).toEqual('DIV');
 		 expect(mel.children[1].children[1].children[0].firstChild.nodeValue).toEqual('corenlp');
diff --git a/dev/js/spec/vcSpec.js b/dev/js/spec/vcSpec.js
index 61c3099..5e813aa 100644
--- a/dev/js/spec/vcSpec.js
+++ b/dev/js/spec/vcSpec.js
@@ -2879,14 +2879,14 @@
       var e = f.element();
       expect(e.classList.contains('vc')).toBeTruthy();
       expect(e.classList.contains('fragment')).toBeTruthy();
-      expect(e.children.length).toEqual(0);
+      expect(e.firstChild.children.length).toEqual(0);
     });
 
     it('should be expansable', function () {
       var f = fragmentClass.create();
       f.add("author", "Peter");
 
-      var root = f.element().firstChild;
+      var root = f.element().lastChild.firstChild;
 
       expect(root.classList.contains("doc")).toBeTruthy();
       expect(root.children[0].tagName).toEqual("SPAN");
@@ -2898,7 +2898,7 @@
 
       f.add("title", "Example");
 
-      root = f.element().firstChild;
+      root = f.element().lastChild.firstChild;
 
       expect(root.classList.contains("docGroup")).toBeTruthy();
 
@@ -2924,14 +2924,14 @@
 
     it('should be reducible', function () {
       var f = fragmentClass.create();
-      expect(f.element().children.length).toEqual(0);
+      expect(f.element().lastChild.children.length).toEqual(0);
 
       f.add("author", "Peter");
       f.add("title", "Example");
 
-      expect(f.element().children.length).toEqual(1);
+      expect(f.element().lastChild.children.length).toEqual(1);
       
-      var root = f.element().firstChild;
+      var root = f.element().lastChild.firstChild;
       
       expect(root.classList.contains("docGroup")).toBeTruthy();
 
@@ -2939,7 +2939,7 @@
 
       f.remove("author","Peter");
 
-      root = f.element().firstChild;
+      root = f.element().lastChild.firstChild;
       expect(root.classList.contains("doc")).toBeTruthy();
 
       expect(root.children[0].tagName).toEqual("SPAN");
@@ -2951,7 +2951,7 @@
 
       f.remove("title","Example");
 
-      expect(f.element().children.length).toEqual(0);
+      expect(f.element().lastChild.children.length).toEqual(0);
     });
   });
 });
diff --git a/dev/js/src/match/corpusByMatch.js b/dev/js/src/match/corpusByMatch.js
index 1c0bead..59038a0 100644
--- a/dev/js/src/match/corpusByMatch.js
+++ b/dev/js/src/match/corpusByMatch.js
@@ -1,6 +1,6 @@
 define(['vc/fragment', 'util'], function (vcFragmentClass) {
   "use strict";
-
+  
   return {
     /**
      * Constructor
@@ -11,6 +11,8 @@
 
     // Initialize corpusByMatch
     _init : function (meta) {
+
+      // Meta is an element <dl />
       if (meta === undefined) {
         throw new Error('Missing parameters');
       }
@@ -24,11 +26,73 @@
       // Remember the meta table
       this._meta = meta;
 
+      this._meta.addEventListener(
+        "click", this.clickOnMeta.bind(this), false
+      );
+
       this._fragment = vcFragmentClass.create();
 
       return this;
     },
 
+    clickOnMeta : function (e) {
+      e.stopPropagation();
+      if (e.target === e.currentTarget) {
+        return;
+      };
+
+      // Get event target
+      let target = e.target;
+
+      let key, value, type;
+
+      // Meta information is a single value
+      if (target.tagName === 'DD') {
+        type = target.getAttribute("data-type");
+        key  = target.previousSibling.innerText;
+        value = target.innerText;
+      }
+
+      // Meta information is in a list
+      else if (target.tagName === 'DIV') {
+        type = target.parentNode.getAttribute("data-type");
+        key = target.parentNode.previousSibling.innerText;
+        value = target.innerText;
+      };
+
+      // Ignore stored types
+      if (type === "type:store")
+        return;
+
+      // Add or remove the constraint to the fragment
+      if (key && value) {
+        if (target.classList.contains("chosen")) {
+          target.classList.remove("chosen");
+          this._fragment.remove(key, value);
+        }
+        else {
+          target.classList.add("chosen");
+          this._fragment.add(key, value);
+        };
+
+        // Check if the fragment is empty
+        // If empty - hide!
+        if (!this._fragment.isEmpty()) {
+          this._meta.parentNode.insertBefore(
+            this._fragment.element(),
+            this._meta.nextSibling
+          );
+        }
+
+        // Otherwise show!
+        else {
+          this._meta.parentNode.removeChild(
+            this._fragment.element()
+          );
+        };
+      }
+    },
+
     // Stringify annotation
     toString : function () {
       return '';
diff --git a/dev/js/src/match/meta.js b/dev/js/src/match/meta.js
index d4e481e..e273a39 100644
--- a/dev/js/src/match/meta.js
+++ b/dev/js/src/match/meta.js
@@ -81,7 +81,8 @@
           dt.setAttribute("title", k);
           
           let metaDescr = field["value"];
-          metaDD =  metaL.addE('dd');
+          metaDD = metaL.addE('dd');
+          metaDD.setAttribute('data-type', field["type"]);
           
           if(metaDescr instanceof Array){
         	  metaDD.classList.add("metakeyvalues");  
diff --git a/dev/js/src/vc/fragment.js b/dev/js/src/vc/fragment.js
index 9b87ee2..a623549 100644
--- a/dev/js/src/vc/fragment.js
+++ b/dev/js/src/vc/fragment.js
@@ -9,6 +9,9 @@
 define(['util'], function () {
   "use strict";
 
+  const loc = KorAP.Locale;
+  loc.NEW_CONSTRAINT = loc.NEW_CONSTRAINT || 'New Constraint';
+  
   // Create a VC doc
   function _doc (op) {
     var doc = document.createElement('div');
@@ -91,8 +94,14 @@
         return this._element;
       };
 
+      // Initialize element
       this._element = document.createElement('div');
       this._element.classList.add('vc', 'fragment');
+
+      // Prepend info text
+      this._element.addE('span').addT(loc.NEW_CONSTRAINT + ':');
+      this._frag = this._element.addE('div');
+      
       return this._element;
     },
 
@@ -125,7 +134,8 @@
         root = _doc(this._operands[0]);
       };
 
-      const e = this.element();
+      this.element();
+      const e = this._frag;
       if (l === 0) {
         _removeChildren(e);
       }
diff --git a/dev/scss/base/fragment.scss b/dev/scss/base/fragment.scss
index 5c59a43..4cab900 100644
--- a/dev/scss/base/fragment.scss
+++ b/dev/scss/base/fragment.scss
@@ -10,7 +10,7 @@
     style: solid;
     radius: $standard-border-radius;
   }
-  padding: 2pt 4pt !important;
+  padding: 4pt 6pt !important;
   margin: {
     // left: $border-size;
     left: $left-distance;
diff --git a/dev/scss/header/vc.scss b/dev/scss/header/vc.scss
index cf70ecc..2a4a3b8 100644
--- a/dev/scss/header/vc.scss
+++ b/dev/scss/header/vc.scss
@@ -346,6 +346,9 @@
 }
 
 .vc.fragment {
+  > div > div.docGroup {
+    margin-left: 0;
+  }
   .doc {
     margin-right: 2em;
   }
diff --git a/dev/scss/main/matchinfo.scss b/dev/scss/main/matchinfo.scss
index 2116512..7268d33 100644
--- a/dev/scss/main/matchinfo.scss
+++ b/dev/scss/main/matchinfo.scss
@@ -60,6 +60,7 @@
   }
   tr {
     outline: none;
+    // equal to dd.chosen
     td.chosen,
     th.chosen,
     div.chosen {
@@ -215,6 +216,9 @@
 
   > div {
     border-color: $dark-orange;
+    > * {
+      padding: .2em;
+    }
     > dt {
       background: $darker-orange;
       color: $nearly-white;
@@ -222,6 +226,25 @@
     }
     > dd {
       background-color: $light-orange;
+      cursor: pointer;
+    }
+    > dd[data-type="type:store"] {
+      background-color: $middle-orange;
+
+      cursor: default;
+    }
+
+    > dd.metakeyvalues {
+      padding:0;
+      > div {
+        padding: .2em;
+      }
+    }
+
+    // equal to td.chosen
+    > dd.chosen, > dd > div.chosen {
+      background-color: $light-green !important;
+      color: $nearly-white;
     }
   }
 }