Add corpusByMatch fragment to VC builder (Issue #27)

Change-Id: I3c7ecb434572412203bb6055b4ce8f2947975306
diff --git a/Changes b/Changes
index 9dcf937..2294ffc 100755
--- a/Changes
+++ b/Changes
@@ -1,10 +1,11 @@
-0.31 2018-11-18
+0.31 2018-11-20
         - Update to Mojolicious >= 8.06.
         - Made Authentication/Authorization a separated Kalamar::Plugin::Auth
-          (deprecated moth helpers from Kalamar::Plugin::KalamarUser).
+          (deprecated most helpers from Kalamar::Plugin::KalamarUser).
         - Introduced abstract 'korap_request' helper.
         - Introduce 'sidebar' content_block.
         - Introduce 'headerButtonGroup' content_block.
+        - Added corpusByMatch assistant (#27).
 
 0.30 2018-11-13
         - Rewrote backend:
diff --git a/dev/js/spec/corpusByMatchSpec.js b/dev/js/spec/corpusByMatchSpec.js
index 6984d1d..cdf941f 100644
--- a/dev/js/spec/corpusByMatchSpec.js
+++ b/dev/js/spec/corpusByMatchSpec.js
@@ -33,7 +33,7 @@
   return meta;
 };
 
-define(['match/corpusByMatch'], function (cbmClass) {  
+define(['match/corpusByMatch', 'vc'], function (cbmClass, vcClass) {  
   describe('KorAP.CorpusByMatch', function () {
 
     it('should be initializable', function () {
@@ -61,12 +61,48 @@
       metaTable.querySelector("dl dt[title=author] + dd").click();
       expect(metaTable.parentNode.querySelector("div.vc.fragment")).not.toBeNull();
 
-      expect(cbm.toQuery()).toEqual('(author = "Sprachpfleger, u.a.")');
+      expect(cbm.toQuery()).toEqual('author = "Sprachpfleger, u.a."');
 
       // click on list:
       metaTable.querySelector("dl dt[title=foundries] + dd div:nth-of-type(3)").click();
 
-      expect(cbm.toQuery()).toEqual('(author = "Sprachpfleger, u.a." & foundries = "corenlp/morpho")');
+      expect(cbm.toQuery()).toEqual('author = "Sprachpfleger, u.a." & foundries = "corenlp/morpho"');
     });
+
+    it('should be pushed to global VC builder', function () {
+
+      // Create global VC object
+      KorAP.vc = vcClass.create().fromJson({
+        "@type" : 'koral:docGroup',
+        'operation' : 'operation:or',
+        'operands' : [
+          {
+            '@type' : 'koral:doc',
+            'key' : 'title',
+            'value' : 'Hello World!'
+          },
+          {
+            '@type' : 'koral:doc',
+            'key' : 'foo',
+            'value' : 'bar'
+          }
+        ]
+      });
+
+      expect(KorAP.vc.toQuery()).toEqual('title = "Hello World!" | foo = "bar"');
+
+      let cbm = cbmClass.create(metaTableFactory());
+      expect(cbm.toQuery()).toEqual("");
+
+      cbm.add("author", "Peter", "type:string");
+      expect(cbm.toQuery()).toEqual('author = "Peter"');
+
+      cbm.add("pubDate", "2018-11-20", "type:date");
+      expect(cbm.toQuery()).toEqual('author = "Peter" & pubDate in 2018-11-20');
+
+      cbm.toVcBuilder();
+
+      expect(KorAP.vc.toQuery()).toEqual('(title = "Hello World!" | foo = "bar") & author = "Peter" & pubDate in 2018-11-20');
+    })
   });
 });
diff --git a/dev/js/src/init.js b/dev/js/src/init.js
index e2d89b9..34a838a 100644
--- a/dev/js/src/init.js
+++ b/dev/js/src/init.js
@@ -263,19 +263,19 @@
 
       vc.onOpen = function () {
         vcname.classList.add('active');
+
+        var view = d.getElementById('vc-view');
+        if (!view.firstChild)
+          view.appendChild(this.element());
+        
         show['collection'] = true;
       };
       
       var vcclick = function () {
-
         if (vc.isOpen()) {
           vc.minimize()
         }
         else {
-          var view = d.getElementById('vc-view');
-          if (!view.firstChild)
-            view.appendChild(vc.element());
-
           vc.open();
         };
       };
diff --git a/dev/js/src/match/corpusByMatch.js b/dev/js/src/match/corpusByMatch.js
index 050ecb7..a9bcf58 100644
--- a/dev/js/src/match/corpusByMatch.js
+++ b/dev/js/src/match/corpusByMatch.js
@@ -30,11 +30,45 @@
         "click", this.clickOnMeta.bind(this), false
       );
 
-      this._fragment = vcFragmentClass.create();
+      this._fragment = vcFragmentClass.create();      
+
+      this._fragment.element().addEventListener(
+        "click", this.toVcBuilder.bind(this), true
+      );
 
       return this;
     },
 
+    /**
+     * Join fragment with VC
+     */
+    toVcBuilder : function (e) {
+      if (e)
+        e.stopPropagation();
+
+      if (this._fragment.isEmpty())
+        return;
+
+      let vc = KorAP.vc;
+      if (!vc) {
+        console.log("Global VC not established");
+        return;
+      };
+
+      for (let doc of this._fragment.documents()) {
+        vc.addRequired(doc);
+        console.log("Add " + doc.toQuery());
+      };
+
+      if (!vc.isOpen()) {
+        vc.open();
+      };
+
+      // Scroll to top
+      window.scrollTo(0, 0);
+    },
+
+    // Event handler for meta constraint creation
     clickOnMeta : function (e) {
       e.stopPropagation();
       if (e.target === e.currentTarget) {
@@ -65,16 +99,16 @@
         return;
 
       type = type || "type:string";
-      
+
       // 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);
+          this.remove(key, value);
         }
         else {
           target.classList.add("chosen");
-          this._fragment.add(key, value);
+          this.add(key, value, type);
         };
 
         // Check if the fragment is empty
@@ -95,6 +129,17 @@
       }
     },
 
+    // Add constraint
+    add : function (key, value, type) {
+      type = type.replace(/^type:/, '');
+      this._fragment.add(key, value, type);
+    },
+
+    // Remove constraint
+    remove : function (key, value) {
+      this._fragment.remove(key, value);
+    },
+    
     // Stringify annotation
     toQuery : function () {
       return this._fragment.toQuery();
diff --git a/dev/js/src/vc/fragment.js b/dev/js/src/vc/fragment.js
index a03e845..2d2504d 100644
--- a/dev/js/src/vc/fragment.js
+++ b/dev/js/src/vc/fragment.js
@@ -6,7 +6,7 @@
  * @author Nils Diewald
  */
 
-define(['util'], function () {
+define(['vc/doc', 'util'], function (docClass) {
   "use strict";
 
   const loc = KorAP.Locale;
@@ -72,6 +72,7 @@
       return;
     },
 
+
     /**
      * Check, if the fragment contains any constraints
      */
@@ -79,12 +80,6 @@
       return this._operands.length > 0 ? false : true;
     },
     
-    /**
-     * Add fragment constraints to VC.
-     */
-    mergeWithVC : function () {
-    },
-
 
     /**
      * Get the element associated with the virtual corpus
@@ -107,6 +102,27 @@
 
 
     /**
+     * Return operands as document objects
+     */
+    documents : function () {
+      return this._operands.map(
+        function (item) {
+          let doc = docClass.create();
+          doc.key(item[0]);
+          doc.matchop("eq");
+          doc.value(item[1]);
+          if (item[2] === "date") {
+            doc.type("date");
+          }
+          else {
+            doc.type("string");
+          };
+          return doc;
+        }
+      );
+    },
+
+    /**
      * Update the whole object based on the underlying data structure
      */
     update : function() {
@@ -147,17 +163,22 @@
       return this;
     },
 
+    
+    /**
+     * Stringification
+     */
     toQuery : function () {
       if (this._operands.length === 0)
         return '';
 
-      let str = '(' + this._operands.map(
+      return this._operands.map(
         function (item) {
+          if (item[2] === "date") {
+            return item[0] + ' in ' + item[1];
+          };
           return item[0] + ' = "' + new String(item[1]).quote() + '"';
         }
       ).join(" & ");
-      
-      return str + ')';
     }
   }
 });
diff --git a/package.json b/package.json
index 400f99b..542e3b3 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.31.0",
+  "version": "0.31.1",
   "pluginVersion": "0.1",
   "repository" : {
     "type": "git",