Support VC references in VC builder to fix #62

Change-Id: Iec84c12ade2f64e8bbbd3d42b9e52788a0fba3fe
diff --git a/dev/js/src/vc/doc.js b/dev/js/src/vc/doc.js
index 15c7287..7c4f98a 100644
--- a/dev/js/src/vc/doc.js
+++ b/dev/js/src/vc/doc.js
@@ -62,77 +62,79 @@
       // Get element
       var e = this._element;
 
+      // Check if there is a change in the underlying data
+      if (!this.__changed)
+        return e;
+
       // Set ref - TODO: Cleanup!
       e.refTo = this;
 
-      // Check if there is a change in the underlying data
-      if (this.__changed) {
 
-        // Was rewritten
-        if (this.rewrites() !== undefined) {
-          e.classList.add("rewritten");
-        };
-
-        // Added key
-        this._keyE = document.createElement('span');
-        this._keyE.setAttribute('class', 'key');
-
-        // Change key
-        this._keyE.addEventListener('click', this._changeKey.bind(this));
-
-        if (this.key()) {
-          var k = this.key();
-          if (loc['VC_' + k] !== undefined)
-            k = loc['VC_' + k];
-          this._keyE.addT(k);
-        };
-
-        // Added match operator
-        this._matchopE = document.createElement('span');
-        this._matchopE.setAttribute('data-type', this.type());
-        this._matchopE.setAttribute('class', 'match');
-        this._matchopE.addT(this.matchop());
-
-        // Change matchop
-        this._matchopE.addEventListener(
-          'click',
-          this._changeMatchop.bind(this)
-        );
-
-        // Added value operator
-        this._valueE = document.createElement('span');
-        this._valueE.setAttribute('data-type', this.type());
-        this._valueE.setAttribute('class', 'value');
-        if (this.value()) {
-          this._valueE.addT(this.value());
-        }
-        else {
-          this._valueE.addT(loc.EMPTY);
-        };
-
-        // Change value
-        this._valueE.addEventListener(
-          'click',
-          this._changeValue.bind(this)
-        );
-
-
-        // Remove all element children
-        _removeChildren(e);
-
-        // Add spans
-        e.appendChild(this._keyE);
-        e.appendChild(this._matchopE);
-        e.appendChild(this._valueE);
-
-        this.__changed = false;
+      // Was rewritten
+      if (this.rewrites() !== undefined) {
+        e.classList.add("rewritten");
       };
 
+      // Added key
+      this._keyE = document.createElement('span');
+      this._keyE.setAttribute('class', 'key');
+
+      // Change key
+      this._keyE.addEventListener('click', this._changeKey.bind(this));
+
+      if (this.key()) {
+        var k = this.key();
+        if (loc['VC_' + k] !== undefined)
+          k = loc['VC_' + k];
+        this._keyE.addT(k);
+      };
+
+      // Added match operator
+      this._matchopE = document.createElement('span');
+      this._matchopE.setAttribute('data-type', this.type());
+      this._matchopE.setAttribute('class', 'match');
+      this._matchopE.addT(this.matchop());
+
+      // Change matchop
+      this._matchopE.addEventListener(
+        'click',
+        this._changeMatchop.bind(this)
+      );
+
+      // Added value operator
+      this._valueE = document.createElement('span');
+      this._valueE.setAttribute('data-type', this.type());
+      this._valueE.setAttribute('class', 'value');
+
+      if (this.value()) {
+        this._valueE.addT(this.value());
+      }
+      else {
+        this._valueE.addT(loc.EMPTY);
+      };
+
+      // Change value
+      this._valueE.addEventListener(
+        'click',
+        this._changeValue.bind(this)
+      );
+
+      // Remove all element children
+      _removeChildren(e);
+
+      // Add spans
+      e.appendChild(this._keyE);
+      e.appendChild(this._matchopE);
+      e.appendChild(this._valueE);
+
+      this.__changed = false;
+
       if (this._rewrites !== undefined) {
         e.appendChild(this._rewrites.element());
       };
 
       if (this._parent !== undefined) {
+        
         // Set operators
         var op = this.operators(
           true,
diff --git a/dev/js/src/vc/docgroup.js b/dev/js/src/vc/docgroup.js
index 58926f2..d3097e9 100644
--- a/dev/js/src/vc/docgroup.js
+++ b/dev/js/src/vc/docgroup.js
@@ -8,10 +8,12 @@
   'vc/jsonld',
   'vc/unspecified',
   'vc/doc',
+  'vc/docgroupref',
   'util'
 ], function (jsonldClass,
-	     unspecClass,
-	     docClass) {
+	           unspecClass,
+	           docClass,
+            docGroupRefClass) {
 
   const _validGroupOpRE = new RegExp("^(?:and|or)$");
 
@@ -41,6 +43,10 @@
 
     // The doc is already set in the group
     _duplicate : function (operand) {
+
+      // TODO:
+      //   Also check for duplicate docGroupRefs!
+      
       if (operand.ldType() !== 'doc')
 	      return null;
 
@@ -73,7 +79,8 @@
 	      // No @type defined
 	      if (operand["ldType"] !== undefined) {
 	        if (operand.ldType() !== 'doc' &&
-	            operand.ldType() !== 'docGroup') {
+	            operand.ldType() !== 'docGroup' &&
+              operand.ldType() !== 'docGroupRef') {
 	          KorAP.log(812, "Operand not supported in document group");
 	          return;
 	        };
@@ -127,6 +134,28 @@
 	      this._operands.push(docGroup);
 	      return docGroup;
 
+      case "koral:docGroupRef":
+      
+        var docGroupRef = docGroupRefClass.create(this, operand);
+      
+        if (docGroupRef === undefined) {
+          return
+        };
+
+        // TODO:
+        //   Currently this doesn't do anything meaningful,
+        //   as duplicate only checks on docs for the moment
+        /*
+	      var dupl = this._duplicate(doc);
+	      if (dupl === null) {
+	        this._operands.push(doc);
+	        return doc;
+	      };
+	      return dupl;
+        */
+	      this._operands.push(docGroupRef);
+        return docGroupRef;
+
       default:
 	      KorAP.log(812, "Operand not supported in document group");
 	      return;
@@ -229,6 +258,7 @@
 	        // Just insert a doc or ...
 	        if (newOp.ldType() === "doc" ||
 	            newOp.ldType() === "non" ||
+              newOp.ldType() === 'docGroupRef' ||
 	            // ... insert a group of a different operation
 	            // (i.e. "and" in "or"/"or" in "and")
 	            newOp.operation() != this.operation()) {
diff --git a/dev/js/src/vc/docgroupref.js b/dev/js/src/vc/docgroupref.js
new file mode 100644
index 0000000..43bb0ca
--- /dev/null
+++ b/dev/js/src/vc/docgroupref.js
@@ -0,0 +1,269 @@
+/**
+ * A reference to another VC.
+ * Inherits everything from jsonld
+ */
+define([
+  'vc/jsonld',
+  'vc/rewritelist',
+  'vc/stringval',
+  'util'
+], function (jsonldClass, rewriteListClass, stringValClass) {
+
+  const loc = KorAP.Locale;
+  loc.EMPTY = loc.EMPTY || '⋯';
+
+  return {
+
+    // The ld-type
+    _ldType : "docGroupRef",
+
+    /**
+     * Create new unspecified criterion
+     * with a link to the parent object
+     */
+    create : function (parent, json) {
+      var obj = Object(jsonldClass)
+          .create().
+	        upgradeTo(this)
+          .fromJson(json);
+
+      if (obj === undefined) {
+        console.log(json);
+      };
+
+      if (parent !== undefined)
+	      obj._parent = parent;
+
+      obj.__changed = true;
+      return obj;
+    },
+
+
+    /**
+     * Update the element
+     */
+    update : function () {
+      if (this._element === undefined)
+        return this.element();
+
+      var e = this._element;
+
+      // Check if there is a change in the underlying data
+      if (!this.__changed)
+        return e;
+
+      // Set ref - TODO: Cleanup!
+      e.refTo = this;
+
+      // Was rewritten
+      if (this.rewrites() !== undefined) {
+        e.classList.add("rewritten");
+      };
+
+      var refTitle = document.createElement('span');
+      refTitle.classList.add('key','fixed');
+      refTitle.addT('@referTo');
+
+      // Added value operator
+      this._refE = document.createElement('span');
+      this._refE.setAttribute('data-type', "string");
+      this._refE.setAttribute('class', 'value');
+      if (this.ref()) {
+        this._refE.addT(this.ref());
+      }
+      else {
+        this._refE.addT(loc.EMPTY);
+      };
+
+      // Change value
+      this._refE.addEventListener(
+        'click',
+        this._changeRef.bind(this)
+      );
+
+      // Remove all element children
+      _removeChildren(e);
+
+      // Add spans
+      e.appendChild(refTitle);
+      e.appendChild(this._refE);
+
+      this.__changed = false;
+
+      if (this._rewrites !== undefined) {
+        e.appendChild(this._rewrites.element());
+      };
+
+      if (this._parent !== undefined) {
+        // Set operators
+        var op = this.operators(
+          true,
+          true,
+          true
+        );
+
+        // Append new operators
+        e.appendChild(op.element());
+      };  
+
+      return this.element();
+    },
+
+
+    /**
+     * Get the associated element
+     */
+    element : function () {
+      if (this._element !== undefined)
+	      return this._element;
+      this._element = document.createElement('div');
+      this._element.setAttribute('class', 'doc groupref');
+      this.update();
+      return this._element;
+    },
+
+
+    /**
+     * Get or set the value
+     */
+    ref : function (ref) {
+      if (arguments.length === 1) {
+        this._ref = ref;
+        this._changed();
+        return this;
+      };
+      return this._ref;
+    },
+
+
+    // Click on the reference operator, show me the option
+    _changeRef : function (e) {
+      var that = this;
+
+      var str = stringValClass.create(this.ref(), false, false);
+      var strElem = str.element();
+
+      str.store = function (ref, regex) {
+        that.ref(ref);
+          
+        that._element.removeChild(
+          this._element
+        );
+        that.update();
+      };
+
+      // Insert element
+      this._element.insertBefore(
+        strElem,
+        this._refE
+      );
+
+      str.focus();
+    },
+    
+
+    /**
+     * Wrap a new operation around the doc element.
+     * This is copypasta from doc.js
+     */
+    wrap : function (op) {
+      var parent = this.parent();
+      var group = require('vc/docgroup').create(parent);
+      group.operation(op);
+      group.append(this);
+      group.append();
+      return parent.replaceOperand(this, group).update();
+    },
+
+    /**
+     * Deserialize from json
+     */
+    fromJson : function (json) {
+      if (json === undefined)
+        return this;
+
+      if (json["@type"] === undefined) {
+        KorAP.log(701, "JSON-LD group has no @type attribute");
+        return;
+      };
+
+      if (json["ref"] === undefined ||
+          typeof json["ref"] != 'string') {
+        KorAP.log(821, "Reference is missing");
+        return;
+      };
+
+      this.ref(json["ref"]);
+
+      // Rewrite coming from the server
+      if (json["rewrites"] !== undefined) {
+        this.rewrite(json["rewrites"]);
+      };
+
+      return this;
+    },
+
+
+    /**
+     * Click on the unspecified object
+     */
+    onclick : function () {
+      console.log("Do not support click on this");
+    },
+
+    // TODO: This is identical to doc.js
+    rewrites : function () {
+      return this._rewrites;
+    },
+
+    // TODO: This is identical to doc.js
+    rewrite : function (value) {
+      if (typeof value === 'string') {
+        value = [{
+          "@type" : "koral:rewrite",
+          "operation" : "operation:" + value,
+          "src" : "Kalamar"
+        }];
+      };
+      this._rewrites = rewriteListClass.create(value);
+    },
+
+
+    // Mark the underlying data as being changed.
+    // This is important for rerendering the dom.
+    // This will also remove rewrite markers, when the data
+    // change happened by the user
+    _changed : function () {
+      this.__changed = true;
+
+      if (this._rewrites === undefined)
+        return;
+
+        delete this["_rewrites"];
+
+      if (this._element === undefined)
+        return;
+
+      this._element.classList.remove("rewritten");
+    },
+
+    toJson : function () {
+      if (!this.ref)
+        return {};
+      
+      return {
+        "@type" : "koral:" + this.ldType(),
+        "ref"  : this.ref()
+      };
+    },
+
+    
+    toQuery : function () {
+      if (!this.ref())
+        return "";
+
+      // Build doc string based on key
+      return 'referTo "' + this.ref().quote() + '"';
+    }
+  };
+});
diff --git a/dev/js/src/vc/jsonld.js b/dev/js/src/vc/jsonld.js
index 846d55c..545652b 100644
--- a/dev/js/src/vc/jsonld.js
+++ b/dev/js/src/vc/jsonld.js
@@ -38,12 +38,15 @@
     // acyclic structures!
     // I'm paranoid!
     destroy : function () {
+      
       if (this._ops != undefined) {
 	      this._ops._parent = undefined;
-	      if (this._ops._element !== undefined)
+	      if (this._ops._element !== undefined) {
 	        this._ops._element.refTo = undefined;
+        };
 	      this._ops = undefined;
       };
+
       if (this._element !== undefined)
 	      this._element = undefined;
       
diff --git a/dev/js/src/vc/operators.js b/dev/js/src/vc/operators.js
index 1d4dc94..50605a3 100644
--- a/dev/js/src/vc/operators.js
+++ b/dev/js/src/vc/operators.js
@@ -31,7 +31,7 @@
 	      return obj.wrapOnRoot();
       };
     }
-    else if (obj.ldType() === 'doc') {
+    else if (obj.ldType() === 'doc' || obj.ldType() === 'docGroupRef') {
 
       if (parent.ldType() === null) {
 	      return obj.wrapOnRoot(type);
@@ -71,7 +71,7 @@
 
   return {
     create : function (and, or, del) {
-
+      
       // Inherit from buttonGroupClass
       var op = Object(buttonGroupClass).create(['operators']).upgradeTo(this);
       op.and(and);
diff --git a/dev/js/src/vc/stringval.js b/dev/js/src/vc/stringval.js
index 10daaee..77c6d90 100644
--- a/dev/js/src/vc/stringval.js
+++ b/dev/js/src/vc/stringval.js
@@ -7,24 +7,38 @@
    * Create new string value helper.
    */
   create : function () {
+    var regexOp = true;
     var regex = false;
     var value = '';
-    if (arguments.length == 2) {
-      regex = arguments[1];
-    };
+
+    // Set value
     if (arguments.length >= 1) {
       if (arguments[0] !== undefined)
         value = arguments[0];
     };
-    return Object.create(this)._init(value, regex);
+
+    // Set regex
+    if (arguments.length >= 2) {
+      if (arguments[1] !== undefined)
+        regex = arguments[1];
+    };
+
+    // Set regexOp
+    if (arguments.length >= 3) {
+      regexOp = arguments[2];
+      if (regexOp === false) {
+        regex = false;
+      }
+    };
+    return Object.create(this)._init(value, regex, regexOp);
   },
   
 
   // Initialize the string value
-  _init : function (value, regex) {
-    this.element();
+  _init : function (value, regex, regexOp) {
     this.value(value);
     this.regex(regex);
+    this._regexOp(regexOp);
     return this;
   },
 
@@ -47,6 +61,18 @@
     return this._regex;
   },
 
+  _regexOp : function (regexOp) {
+    if (arguments.length === 1) {
+      if (regexOp) {
+        this.__regexOp = true;
+      }
+      else {
+        this.__regexOp = false;
+      };
+      this._update();
+    };
+    return this.__regexOp;
+  },
 
   /**
    * Toggle the regex, make it either true,
@@ -65,18 +91,20 @@
   value : function (val) {
     if (arguments.length === 1) {
       this._value = val;
-      this._input.value = val;
+      // this._input.value = val;
       this._update();
     };
     return this._value;
   },
 
-
   // Update dom element
   _update : function () {
+    if (this._element === undefined)
+      return;
+ 
     this._value = this._input.value;
 
-    if (this._regex) {
+    if (this._regexOp() && this._regex) {
       this._element.classList.add('regex');
     }
     else {
@@ -92,7 +120,6 @@
    */
   store : function (v,r) {},
 
-
   /**
    * Put focus on element
    */
@@ -126,16 +153,18 @@
     };
 
     // Add regex button
-    var re = e.addE('div');
-    re.addEventListener(
-      'click',
-      function (ev) {
-	      this.toggleRegex();
-        // ev.halt();
-      }.bind(this),
-      true
-    );
-    re.addT('RE');
+    if (this._regexOp()) {
+      var re = e.addE('div');
+      re.addEventListener(
+        'click',
+        function (ev) {
+	        this.toggleRegex();
+          // ev.halt();
+        }.bind(this),
+        true
+      );
+      re.addT('RE');
+    };
 
     // If the focus is not on the text field anymore,
     // delegate focus to
diff --git a/dev/js/src/vc/unspecified.js b/dev/js/src/vc/unspecified.js
index 56d0869..6ffa0e3 100644
--- a/dev/js/src/vc/unspecified.js
+++ b/dev/js/src/vc/unspecified.js
@@ -5,8 +5,9 @@
 define([
   'vc/jsonld',
   'vc/doc',
+  'vc/docgroupref',
   'util'
-], function (jsonldClass, docClass) {
+], function (jsonldClass, docClass, docGroupRefClass) {
 
   // Localize empty string
   var loc = KorAP.Locale;
@@ -40,9 +41,18 @@
       if (this._parent === undefined)
 	      return null;
 
+      var newDoc;
+      var keyType = KorAP._vcKeyMenu.typeOf(v);
+
       // Set JSON-LD type
-      var newDoc = docClass.create(this._parent);
-      newDoc.key(v);
+      if (keyType && keyType === 'ref') {
+        newDoc = docGroupRefClass.create(this._parent);
+      }
+      else {
+        newDoc = docClass.create(this._parent);
+        newDoc.key(v);
+        newDoc.type(keyType);
+      };
   
       // Unspecified document on root
       if (this._parent.ldType() === null) {
@@ -82,7 +92,8 @@
       this._element.refTo = this;
 
       // Set operators
-      if (this._parent !== undefined && this.parent().ldType() !== null) {
+      if (this._parent !== undefined &&
+          this.parent().ldType() !== null) {
 	      var op = this.operators(
 	        false,
 	        false,
@@ -127,9 +138,9 @@
       var that = this;
 
       // Set released method
-      menu.released(function (key, type) {
+      menu.released(function (key) {
 	      // Set chosen key and type - will return a doc
-	      that.key(key).type(type).update();
+	      that.key(key).update();
 	      this.hide();
       });