Support VC references in VC builder to fix #62

Change-Id: Iec84c12ade2f64e8bbbd3d42b9e52788a0fba3fe
diff --git a/dev/js/src/vc.js b/dev/js/src/vc.js
index 082ede0..b0ebc44 100644
--- a/dev/js/src/vc.js
+++ b/dev/js/src/vc.js
@@ -44,299 +44,326 @@
    and various field names with the prefix 'VC_'
  */
 
-define([ 'vc/unspecified', 'vc/doc', 'vc/docgroup', 'vc/menu', 'vc/statistic',
-    'datepicker', 'buttongroup', 'panel', 'view/corpstatv', 'util', ],
-    function(unspecDocClass, docClass, docGroupClass, menuClass, statClass,
-        dpClass, buttonGrClass, panelClass, corpStatVClass) {
-      "use strict";
+define([
+  'vc/unspecified',
+  'vc/doc',
+  'vc/docgroup',
+  'vc/docgroupref',
+  'vc/menu',
+  'vc/statistic',
+  'datepicker',
+  'buttongroup',
+  'panel',
+  'view/corpstatv',
+  'util'
+], function(
+  unspecDocClass,
+  docClass,
+  docGroupClass,
+  docGroupRefClass,
+  menuClass,
+  statClass,
+  dpClass,
+  buttonGrClass,
+  panelClass,
+  corpStatVClass) {
+  "use strict";
 
-      KorAP._validUnspecMatchRE = new RegExp(
-          "^(?:eq|ne|contains(?:not)?|excludes)$");
-      KorAP._validStringMatchRE = new RegExp("^(?:eq|ne)$");
-      KorAP._validTextMatchRE = KorAP._validUnspecMatchRE;
-      KorAP._validTextOnlyMatchRE = new RegExp(
-          "^(?:contains(?:not)?|excludes)$");
-      KorAP._overrideStyles = false;
-      // KorAP._validDateMatchRE is defined in datepicker.js!
+  KorAP._validUnspecMatchRE = new RegExp(
+    "^(?:eq|ne|contains(?:not)?|excludes)$");
+  KorAP._validStringMatchRE = new RegExp("^(?:eq|ne)$");
+  KorAP._validTextMatchRE = KorAP._validUnspecMatchRE;
+  KorAP._validTextOnlyMatchRE = new RegExp(
+    "^(?:contains(?:not)?|excludes)$");
+  KorAP._overrideStyles = false;
+  // KorAP._validDateMatchRE is defined in datepicker.js!
 
-      const loc = KorAP.Locale;
-      loc.SHOW_STAT = loc.SHOW_STAT || 'Statistics';
-      loc.VERB_SHOWSTAT = loc.VERB_SHOWSTAT || 'Corpus Statistics';
+  const loc = KorAP.Locale;
+  loc.SHOW_STAT = loc.SHOW_STAT || 'Statistics';
+  loc.VERB_SHOWSTAT = loc.VERB_SHOWSTAT || 'Corpus Statistics';
 
-      KorAP._vcKeyMenu = undefined;
-      KorAP._vcDatePicker = dpClass.create();
+  KorAP._vcKeyMenu = undefined;
+  KorAP._vcDatePicker = dpClass.create();
 
-      // Create match menus ....
-      KorAP._vcMatchopMenu = {
-        'string' : menuClass.create([ [ 'eq', null ], [ 'ne', null ] ]),
-        'text' : menuClass.create([ [ 'eq', null ], // Requires exact match
-        [ 'ne', null ], [ 'contains', null ], // Requires token sequence match
-        [ 'containsnot', null ] ]),
-        'date' : menuClass.create([ [ 'eq', null ], [ 'ne', null ],
-            [ 'geq', null ], [ 'leq', null ] ]),
-        'regex' : menuClass.create([ [ 'eq', null ], [ 'ne', null ] ])
+  // Create match menus ....
+  KorAP._vcMatchopMenu = {
+    'string' : menuClass.create([
+      [ 'eq', null ],
+      [ 'ne', null ]
+    ]),
+    'text' : menuClass.create([
+      [ 'eq', null ], // Requires exact match
+      [ 'ne', null ],
+      [ 'contains', null ], // Requires token sequence match
+      [ 'containsnot', null ]
+    ]),
+    'date' : menuClass.create([
+      [ 'eq', null ],
+      [ 'ne', null ],
+      [ 'geq', null ],
+      [ 'leq', null ]
+    ]),
+    'regex' : menuClass.create([
+      [ 'eq', null ],
+      [ 'ne', null ]
+    ])
+  };
+
+  /**
+   * Virtual Collection
+   */
+  return {
+
+    /**
+     * The JSON-LD type of the virtual collection
+     */
+    ldType : function() {
+      return null;
+    },
+
+    // Initialize virtual collection
+    _init : function(keyList) {
+
+      // Inject localized css styles
+      if (!KorAP._overrideStyles) {
+        var sheet = KorAP.newStyleSheet();
+
+        // Add css rule for OR operations
+        sheet.insertRule('.vc .docGroup[data-operation=or] > .doc::before,'
+                         + '.vc .docGroup[data-operation=or] > .docGroup::before '
+                         + '{ content: "' + loc.OR + '" }', 0);
+
+        // Add css rule for AND operations
+        sheet.insertRule(
+          '.vc .docGroup[data-operation=and] > .doc::before,'
+            + '.vc .docGroup[data-operation=and] > .docGroup::before '
+            + '{ content: "' + loc.AND + '" }', 1);
+
+        KorAP._overrideStyles = true;
       };
 
-      /**
-       * Virtual Collection
-       */
-      return {
+      // Create key menu
+      KorAP._vcKeyMenu = menuClass.create(keyList);
+      KorAP._vcKeyMenu.limit(6);
 
-        /**
-         * The JSON-LD type of the virtual collection
-         */
-        ldType : function() {
-          return null;
-        },
+      return this;
+    },
 
-        // Initialize virtual collection
-        _init : function(keyList) {
+    /**
+     * Create a new virtual collection.
+     */
+    create : function(keyList) {
+      var obj = Object.create(this)._init(keyList);
+      obj._root = unspecDocClass.create(obj);
+      return obj;
+    },
 
-          // Inject localized css styles
-          if (!KorAP._overrideStyles) {
-            var sheet = KorAP.newStyleSheet();
-
-            // Add css rule for OR operations
-            sheet.insertRule('.vc .docGroup[data-operation=or] > .doc::before,'
-                + '.vc .docGroup[data-operation=or] > .docGroup::before '
-                + '{ content: "' + loc.OR + '" }', 0);
-
-            // Add css rule for AND operations
-            sheet.insertRule(
-                '.vc .docGroup[data-operation=and] > .doc::before,'
-                    + '.vc .docGroup[data-operation=and] > .docGroup::before '
-                    + '{ content: "' + loc.AND + '" }', 1);
-
-            KorAP._overrideStyles = true;
-          }
-          ;
-
-          // Create key menu
-          KorAP._vcKeyMenu = menuClass.create(keyList);
-          KorAP._vcKeyMenu.limit(6);
-
-          return this;
-        },
-
-        /**
-         * Create a new virtual collection.
-         */
-        create : function(keyList) {
-          var obj = Object.create(this)._init(keyList);
-          obj._root = unspecDocClass.create(obj);
-          return obj;
-        },
-
-        /**
-         * Create and render a new virtual collection based on a KoralQuery
-         * collection document
-         */
-        fromJson : function(json) {
-          if (json !== undefined) {
-            // Parse root document
-            if (json['@type'] == 'koral:doc') {
-              this._root = docClass.create(this, json);
-            }
-            // parse root group
-            else if (json['@type'] == 'koral:docGroup') {
-              this._root = docGroupClass.create(this, json);
-            }
-            // Unknown collection type
-            else {
-              KorAP.log(813, "Collection type is not supported");
-              return;
-            }
-            ;
-          }
-
-          else {
-            // Add unspecified object
-            this._root = unspecDocClass.create(this);
-          }
-          ;
-
-          // Init element and update
-          this.update();
-
-          return this;
-        },
-
-        // Check if the virtual corpus contains a rewrite
-        // This is a class method
-        checkRewrite : function(json) {
-
-          // There is a rewrite attribute
-          if (json['rewrites'] !== undefined) {
-            return true;
-          }
-
-          // There is a group to check for rewrites
-          else if (json['@type'] === 'koral:docGroup') {
-            var ops = json['operands'];
-            if (ops === undefined)
-              return false;
-
-            for ( var i in ops) {
-
-              // "this" is the class
-              if (this.checkRewrite(ops[i])) {
-                return true;
-              }
-              ;
-            }
-            ;
-          }
-          ;
-          return false;
-        },
-
-        /**
-         * Clean the virtual document to uspecified doc.
-         */
-        clean : function() {
-          if (this._root.ldType() !== "non") {
-            this._root.destroy();
-            this.root(unspecDocClass.create(this));
-          }
-          ;
-          return this;
-        },
-
-        /**
-         * Get or set the root object of the virtual collection.
-         */
-        root : function(obj) {
-          if (arguments.length === 1) {
-            var e = this.element();
-
-            if (e.firstChild !== null) {
-              if (e.firstChild !== obj.element()) {
-                e.replaceChild(obj.element(), e.firstChild);
-              }
-              ;
-            }
-
-            // Append root element
-            else {
-              e.appendChild(obj.element());
-            }
-            ;
-
-            // Update parent child relations
-            this._root = obj;
-            obj.parent(this);
-
-            this.update();
-          }
-          ;
-          return this._root;
-        },
-
-        /**
-         * Get the element associated with the virtual collection
-         */
-        element : function() {
-
-          
-          if (this._element !== undefined) {
-            return this._element;
-          }
-          ;
-
-          this._element = document.createElement('div');
-          this._element.setAttribute('class', 'vc');
-
-          // Initialize root
-          this._element.appendChild(this._root.element());
-          
-          /*
-           * TODO by Helge Hack! additional div, because statistic button is
-           * removed after choosing and/or/x in vc builder. REMOVE this lines
-           * after solving the problem!!!!
-           */
-          this._element.addE('div');
-          this._element.addE('div');
-          this._element.addE('div');
-          
-          // Add panel to display corpus statistic, ...
-          this.addVcInfPanel();
-      
-          return this._element;
-        },
-
-        /**
-         * Update the whole object based on the underlying data structure
-         */
-        update : function() {
-          this._root.update();
-          return this;
-        },
-
-        /**
-         * Make the vc persistant by injecting the current timestamp as a
-         * creation date limit criterion.
-         */
-        makePersistant : function() {
-          // this.root().wrapOnRoot('and');
-          var todayStr = KorAP._vcDatePicker.today();
-          var doc = docClass.create();
-          var root = this.root();
-
-          if (root.ldType() === 'docGroup' && root.operation === 'and') {
-            root.append(cond);
-          } else {
-            root.wrapOnRoot('and');
-            root.append(doc);
-          }
-          ;
-
-          doc.key("creationDate");
-          doc.type("date");
-          doc.matchop("leq");
-          doc.value(todayStr);
-
-          /*
-           * { "@type" : "koral:doc", "key" : "creationDate", "type" :
-           * "type:date", "match" : "match:leq", "value" : todayStr }
-           * this.root().append(cond);
-           */
-          this.update();
-        },
-
-        /**
-         * Get the generated json string
-         */
-        toJson : function() {
-          return this._root.toJson();
-        },
-
-        /**
-         * Get the generated query string
-         */
-        toQuery : function() {
-          return this._root.toQuery();
-        },
-
-
-       /*
-        * Add panel to display virtual corpus information
-        */
-        addVcInfPanel : function() {
-
-          var dv = this._element.addE('div');
-          var panel = panelClass.create([ 'vcinfo' ]);
-          dv.appendChild(panel.element());
- 
-          var that = this;
-          var actions = panel.actions;
-          var statView;
-                    
-          actions.add(loc.SHOW_STAT, [ 'statistic' ], function() {
-            if (statView === undefined || !statView.shown()) {
-              statView = corpStatVClass.create(that);
-              panel.add(statView);
-            }
-          });
+    /**
+     * Create and render a new virtual collection based on a KoralQuery
+     * collection document
+     */
+    fromJson : function(json) {
+      if (json !== undefined) {
+        // Parse root document
+        if (json['@type'] == 'koral:doc') {
+          this._root = docClass.create(this, json);
         }
+        // parse root group
+        else if (json['@type'] == 'koral:docGroup') {
+          this._root = docGroupClass.create(this, json);
+        }
+
+        // parse root reference
+        else if (json['@type'] == 'koral:docGroupRef') {
+          this._root = docGroupRefClass.create(this, json);
+        }
+        
+        // Unknown collection type
+        else {
+          KorAP.log(813, "Collection type is not supported");
+          return;
+        };
+      }
+
+      else {
+        // Add unspecified object
+        this._root = unspecDocClass.create(this);
       };
-    });
+
+      // Init element and update
+      this.update();
+
+      return this;
+    },
+
+    // Check if the virtual corpus contains a rewrite
+    // This is a class method
+    checkRewrite : function(json) {
+
+      // There is a rewrite attribute
+      if (json['rewrites'] !== undefined) {
+        return true;
+      }
+
+      // There is a group to check for rewrites
+      else if (json['@type'] === 'koral:docGroup') {
+        var ops = json['operands'];
+        if (ops === undefined)
+          return false;
+
+        for ( var i in ops) {
+
+          // "this" is the class
+          if (this.checkRewrite(ops[i])) {
+            return true;
+          };
+        };
+      };
+      return false;
+    },
+
+    /**
+     * Clean the virtual document to uspecified doc.
+     */
+    clean : function() {
+      if (this._root.ldType() !== "non") {
+        this._root.destroy();
+        this.root(unspecDocClass.create(this));
+      };
+      return this;
+    },
+
+    /**
+     * Get or set the root object of the virtual collection.
+     */
+    root : function(obj) {
+      if (arguments.length === 1) {
+        var e = this.element();
+
+        if (e.firstChild !== null) {
+          if (e.firstChild !== obj.element()) {
+            e.replaceChild(obj.element(), e.firstChild);
+          };
+        }
+
+        // Append root element
+        else {
+          e.appendChild(obj.element());
+        };
+
+        // Update parent child relations
+        this._root = obj;
+        obj.parent(this);
+
+        this.update();
+      };
+      return this._root;
+    },
+
+    /**
+     * Get the element associated with the virtual collection
+     */
+    element : function() {
+
+      
+      if (this._element !== undefined) {
+        return this._element;
+      };
+
+      this._element = document.createElement('div');
+      this._element.setAttribute('class', 'vc');
+
+      // Initialize root
+      this._element.appendChild(this._root.element());
+      
+      /*
+       * TODO by Helge Hack! additional div, because statistic button is
+       * removed after choosing and/or/x in vc builder. REMOVE this lines
+       * after solving the problem!!!!
+       */
+      this._element.addE('div');
+      this._element.addE('div');
+      this._element.addE('div');
+      
+      // Add panel to display corpus statistic, ...
+      this.addVcInfPanel();
+      
+      return this._element;
+    },
+
+    /**
+     * Update the whole object based on the underlying data structure
+     */
+    update : function() {
+      this._root.update();
+      return this;
+    },
+
+    /**
+     * Make the vc persistant by injecting the current timestamp as a
+     * creation date limit criterion.
+     * THIS IS CURRENTLY NOT USED
+     */
+    makePersistant : function() {
+      // this.root().wrapOnRoot('and');
+      var todayStr = KorAP._vcDatePicker.today();
+      var doc = docClass.create();
+      var root = this.root();
+
+      if (root.ldType() === 'docGroup' && root.operation === 'and') {
+        root.append(cond);
+      } else {
+        root.wrapOnRoot('and');
+        root.append(doc);
+      };
+
+      doc.key("creationDate");
+      doc.type("date");
+      doc.matchop("leq");
+      doc.value(todayStr);
+
+      /*
+       * { "@type" : "koral:doc", "key" : "creationDate", "type" :
+       * "type:date", "match" : "match:leq", "value" : todayStr }
+       * this.root().append(cond);
+       */
+      this.update();
+    },
+
+    /**
+     * Get the generated json string
+     */
+    toJson : function() {
+      return this._root.toJson();
+    },
+
+    /**
+     * Get the generated query string
+     */
+    toQuery : function() {
+      return this._root.toQuery();
+    },
+
+
+    /*
+     * Add panel to display virtual corpus information
+     */
+    addVcInfPanel : function() {
+
+      var dv = this._element.addE('div');
+      var panel = panelClass.create([ 'vcinfo' ]);
+      dv.appendChild(panel.element());
+      
+      var that = this;
+      var actions = panel.actions;
+      var statView;
+      
+      actions.add(loc.SHOW_STAT, [ 'statistic' ], function() {
+        if (statView === undefined || !statView.shown()) {
+          statView = corpStatVClass.create(that);
+          panel.add(statView);
+        }
+      });
+    }
+  };
+});
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();
       });