Added operators
diff --git a/public/css/vc.css b/public/css/vc.css
new file mode 100644
index 0000000..0474e36
--- /dev/null
+++ b/public/css/vc.css
@@ -0,0 +1,141 @@
+@charset "utf-8";
+
+/*
+ sidebar: #496000
+ orange: #f4eebb
+*/
+
+body {
+  background-color: #7ba400;
+  color: white;
+  font-family: tahoma, verdana, arial;
+}
+
+.vc span + span {
+  margin-left: 5pt;
+}
+
+.vc .docGroup {
+  position: relative;
+  background-color: #7ba400;
+  color: white;
+  margin-left: 2em;
+  display: inline-block;
+  border-radius: 10px;
+  border-width: 0 3px 0 3px;
+  border-style: solid;
+  padding-left: 10px;
+  padding-right: 10px;
+}
+
+.vc .operators {
+  opacity: 0;
+  white-space: nowrap;
+  padding: 0;
+  font-size: 0;
+  line-height: 0;
+}
+
+.vc .operators > span,
+.vc .docGroup::before {
+  font-size: 9pt;
+  line-height: 1.3em;
+}
+
+.vc .operators > span {
+  padding: 0 4px;
+  display: inline-block;
+}
+
+.vc .operators > span.and {
+  background-color: white;
+  color: #7ba400;
+}
+
+.vc .operators > span.or {
+  background-color: #ffa500;
+  color: white;
+}
+
+.vc .operators > span.delete {
+  background-color: red;
+  border-radius: inherit;
+  color: white;
+}
+
+.vc .docGroup[data-operation=and]::before,
+.vc .docGroup[data-operation=and] .operators {
+  background-color: white;
+}
+
+.vc .docGroup[data-operation=or] {
+  border-color: #ffa500;
+}
+
+.vc .docGroup[data-operation=or]::before,
+.vc .docGroup[data-operation=or] .operators {
+  border-color: #ffa500;
+  background-color: #ffa500;
+  color: white;
+}
+
+.vc .docGroup::before,
+.vc .operators {
+  color: #7ba400;
+  border-color: white;
+  text-align: center;
+  font-weight: bold;
+  border-style: solid;
+}
+
+.vc .docGroup::before,
+.vc .docGroup > .operators {
+  position: absolute;
+  display: block;
+  top: 10px;
+  vertical-align: middle;
+}
+
+
+
+.vc .docGroup::before {
+  width: 3em;
+  /* width */
+  left: -3em;
+  /* border sizes (own and reals) */
+  margin-left: -5px;
+  border-width: 2px 0 2px 2px;
+  border-top-left-radius: 7px;
+  border-bottom-left-radius: 7px;
+}
+
+.vc .docGroup[data-operation=and]::before {
+  content: "und";
+}
+
+.vc .docGroup[data-operation=or]::before {
+  content: "oder";
+}
+
+.vc .doc > .operators {
+  display: inline-block;
+  border-width: 2px 2px 2px 2px;
+  border-radius: 7px;
+}
+
+.vc .docGroup > .operators {
+  left: 3px;
+  border-width: 2px 2px 2px 0;
+  border-top-right-radius: 7px;
+  border-bottom-right-radius: 7px;
+  margin-left: 100%;
+}
+
+.vc .docGroup:hover {
+  background-color: rgba(255,255,255,0.2);
+}
+
+.vc .docGroup:hover > .operators,
+.vc .doc:hover > .operators {
+  opacity: 1;
+}
diff --git a/public/js/demo/vc.html b/public/js/demo/vc.html
index 054b969..6c306b0 100644
--- a/public/js/demo/vc.html
+++ b/public/js/demo/vc.html
@@ -2,30 +2,13 @@
 <html>
   <head>
     <title>Virtual Collection demo</title>
+    <meta charset="utf-8" />
     <script src="../src/vc.js"></script>
-    <style>
-div {
-  padding: 2pt;
-  padding-left: 10pt;
-  border: 2px solid black;
-}
-
-span {
-  margin: 2pt;
-  padding: 2pt;
-  color: white;
-  background-color: green;
-}
-
-div.undefined {
-  background-color: red;
-  height: 12pt;
-  min-width: 100px;
-}
-    </style>
+    <link href="../../css/vc.css" rel="stylesheet" type="text/css"></link>
   </head>
   <body>
     <div id="vc"></div>
+
     <script>
     var json = {
       "@type":"korap:docGroup",
@@ -57,11 +40,8 @@
         }
       ]
     };
-
-//      var vc = KorAP.VirtualCollection.render();
-      var vc = KorAP.VirtualCollection.render(json);
-      console.log(vc);
-      document.getElementById('vc').appendChild(vc.element());
+    var vc = KorAP.VirtualCollection.render(json);
+    document.getElementById('vc').appendChild(vc.element());
     </script>
   </body>
 </html>
diff --git a/public/js/spec/vcSpec.js b/public/js/spec/vcSpec.js
index 18d59b6..ce2ca4a 100644
--- a/public/js/spec/vcSpec.js
+++ b/public/js/spec/vcSpec.js
@@ -303,10 +303,10 @@
     expect(op2.matchop()).toEqual("eq");
 
     // Create empty group
-    var newGroup = docGroup.appendOperand(KorAP.DocGroup.create());
+    var newGroup = docGroup.append(KorAP.DocGroup.create());
     newGroup.operation('or');
-    newGroup.appendOperand(docFactory.create());
-    newGroup.appendOperand(docFactory.create({
+    newGroup.append(docFactory.create());
+    newGroup.append(docFactory.create({
       "type" : "type:regex",
       "key" : "title",
       "value" : "^e.+?$",
@@ -375,6 +375,44 @@
   });
 });
 
+describe('KorAP.UnspecifiedDoc', function () {
+  it('should be initializable', function () {
+    var docElement = KorAP.UnspecifiedDoc.create().element();
+    expect(docElement.getAttribute('class')).toEqual('unspecified');
+    expect(docElement.firstChild.firstChild.data).toEqual('⋯');
+    expect(docElement.lastChild.getAttribute('class')).toEqual('operators');
+
+    // Not removable
+    expect(docElement.lastChild.children.length).toEqual(0);
+  });
+
+  it('should be removable, when no root', function () {
+    var docGroup = KorAP.DocGroup.create();
+    docGroup.operation('or');
+    expect(docGroup.operation()).toEqual('or');
+
+    docGroup.append({
+      "@type": 'korap:doc',
+      "key": 'pubDate',
+      "match": 'match:eq',
+      "value": '2014-12-05',
+      "type": 'type:date'      
+    });
+
+    expect(docGroup.element().getAttribute('class')).toEqual('docGroup');
+    expect(docGroup.element().children[0].getAttribute('class')).toEqual('doc');
+
+    // Add unspecified object
+    docGroup.append();
+    var unspec = docGroup.element().children[1];
+    expect(unspec.getAttribute('class')).toEqual('unspecified');
+
+    // Removable
+    expect(unspec.lastChild.children.length).toEqual(1);
+    expect(unspec.lastChild.children[0].getAttribute('class')).toEqual('delete');
+  });
+});
+
 describe('KorAP.Doc element', function () {
   it('should be initializable', function () {
     var docElement = KorAP.Doc.create(undefined, {
@@ -494,6 +532,27 @@
     expect(e.getAttribute('class')).toEqual('docGroup');
     expect(e.getAttribute('data-operation')).toEqual('or');
 
+    expect(e.children[0].getAttribute('class')).toEqual('doc');
+    var docop = e.children[0].lastChild;
+    expect(docop.getAttribute('class')).toEqual('operators');
+    expect(docop.children[0].getAttribute('class')).toEqual('and');
+    expect(docop.children[1].getAttribute('class')).toEqual('or');
+    expect(docop.children[2].getAttribute('class')).toEqual('delete');
+
+    expect(e.children[1].getAttribute('class')).toEqual('docGroup');
+    expect(e.children[1].getAttribute('data-operation')).toEqual('and');
+
+    // This and-operation can be "or"ed or "delete"d
+    var secop = e.children[1].children[2];
+    expect(secop.getAttribute('class')).toEqual('operators');
+    expect(secop.children[0].getAttribute('class')).toEqual('or');
+    expect(secop.children[1].getAttribute('class')).toEqual('delete');
+
+    // This or-operation can be "and"ed or "delete"d
+    expect(e.children[2].getAttribute('class')).toEqual('operators');
+    expect(e.lastChild.getAttribute('class')).toEqual('operators');
+    expect(e.lastChild.children[0].getAttribute('class')).toEqual('and');
+    expect(e.lastChild.children[1].getAttribute('class')).toEqual('delete');
 
   });
 });
@@ -502,7 +561,10 @@
   it('should be initializable', function () {
     var vc = KorAP.VirtualCollection.render();
     expect(vc.element().getAttribute('class')).toEqual('vc');
-    expect(vc.root().element().getAttribute('class')).toEqual('undefined');
+    expect(vc.root().element().getAttribute('class')).toEqual('unspecified');
+
+    // Not removable
+    expect(vc.root().element().lastChild.children.length).toEqual(0);
   });
 
   it('should be based on a doc', function () {
@@ -566,8 +628,50 @@
     expect(second.matchop()).toEqual('eq');
   });
 
-  xit('should be based on a nested docGroup', function () {
-  });
+  it('should be based on a nested docGroup', function () {
+    var vc = KorAP.VirtualCollection.render({
+      "@type" : "korap:docGroup",
+      "operation" : "operation:or",
+      "operands" : [
+	{
+	  "@type": 'korap:doc',
+	  "key" : 'author',
+	  "match": 'match:eq',
+	  "value": 'Max Birkendale',
+	  "type": 'type:string'
+	},
+	{
+	  "@type" : "korap:docGroup",
+	  "operation" : "operation:and",
+	  "operands" : [
+	    {
+	      "@type": 'korap:doc',
+	      "key": 'pubDate',
+	      "match": 'match:geq',
+	      "value": '2014-05-12',
+	      "type": 'type:date'
+	    },
+	    {
+	      "@type": 'korap:doc',
+	      "key": 'pubDate',
+	      "match": 'match:leq',
+	      "value": '2014-12-05',
+	      "type": 'type:date'
+	    }
+	  ]
+	}
+      ]
+    });
+    expect(vc.element().getAttribute('class')).toEqual('vc');
+    expect(vc.element().firstChild.getAttribute('class')).toEqual('docGroup');
+    expect(vc.element().firstChild.children[0].getAttribute('class')).toEqual('doc');
+    var dg = vc.element().firstChild.children[1];
+    expect(dg.getAttribute('class')).toEqual('docGroup');
+    expect(dg.children[0].getAttribute('class')).toEqual('doc');
+    expect(dg.children[1].getAttribute('class')).toEqual('doc');
+    expect(dg.children[2].getAttribute('class')).toEqual('operators');
+    expect(vc.element().firstChild.children[2].getAttribute('class')).toEqual('operators');
+  });    
 });
 
 describe('KorAP.Operators', function () {
diff --git a/public/js/src/vc.js b/public/js/src/vc.js
index 771152d..fe702a7 100644
--- a/public/js/src/vc.js
+++ b/public/js/src/vc.js
@@ -2,6 +2,7 @@
 
 // TODO: Implement a working localization solution!
 // TODO: Support 'update' method to update elements on change
+// TODO: Implement "toQuery"
 
 /*
  * Error codes:
@@ -18,7 +19,6 @@
   813: "Collection type is not supported" (like 713)
 */
 
-
 (function (KorAP) {
   "use strict";
 
@@ -38,6 +38,17 @@
   loc.OR  = loc.OR  || 'or';
   loc.DEL = loc.DEL || '×';
 
+  function _bool (bool) {
+    return (bool === undefined || bool === false) ? false : true;
+  };
+
+  function _removeChildren (node) {
+    // Remove everything underneath
+    while (node.firstChild) {
+      node.removeChild(node.firstChild);
+    };
+  };
+
   KorAP.VirtualCollection = {
     _root : undefined,
     create : function () {
@@ -105,9 +116,7 @@
       var op = this._element;
 
       // Remove everything underneath
-      while (op.firstChild) {
-	op.removeChild(op.firstChild);
-      };
+      _removeChildren(op);
 
       // Add and button
       if (this._and === true) {
@@ -132,7 +141,8 @@
 	delE.appendChild(document.createTextNode(KorAP.Locale.DEL));
 	op.appendChild(delE);
       };
-      return true;
+
+      return op;
     },
     element : function () {
 
@@ -149,17 +159,17 @@
     },
     and : function (bool) {
       if (arguments.length === 1)
-	this._and = (bool === undefined || bool === false) ? false : true;
+	this._and = _bool(bool);
       return this._and;
     },
     or : function (bool) {
       if (arguments.length === 1)
-	this._or = (bool === undefined || bool === false) ? false : true;
+	this._or = _bool(bool);
       return this._or;
     },
     del : function (bool) {
       if (arguments.length === 1)
-	this._del = (bool === undefined || bool === false) ? false : true;
+	this._del = _bool(bool);
       return this._del;
     }
   };
@@ -169,17 +179,46 @@
    * Unspecified criterion
    */
   KorAP.UnspecifiedDoc = {
+    _ldType : "doc",
     create : function (parent) {
       var obj = Object.create(KorAP.JsonLD).upgradeTo(KorAP.UnspecifiedDoc);
       if (parent !== undefined)
 	obj._parent = parent;
       return obj;
     },
+    update : function () {
+      if (this._element === undefined)
+	return this.element();
+
+      _removeChildren(this._element);
+
+      var ellipsis = document.createElement('span');
+      ellipsis.appendChild(document.createTextNode('⋯'));
+      this._element.appendChild(ellipsis);
+
+      // Set operators
+      var op = this.operators(
+	false,
+	false,
+	// No delete object, if this is the root
+	this._parent !== undefined ? true : false
+      );
+
+      this._element.appendChild(
+	op.element()
+      );
+
+
+      return this.element();
+    },
     element : function () {
       if (this._element !== undefined)
 	return this._element;
+
       this._element = document.createElement('div');
-      this._element.setAttribute('class', 'undefined');
+      this._element.setAttribute('class', 'unspecified');
+
+      this.update();
       return this._element;
     }
   };
@@ -198,13 +237,9 @@
 	obj._parent = parent;
       return obj;
     },
-    element : function () {
-      if (this._element !== undefined)
-	return this._element;
-
-      this._element = document.createElement('div');
-      var e = this._element;
-      e.setAttribute('class', 'doc');
+    update : function () {
+      if (this._element === undefined)
+	return this.element();
 
       // Added key
       var key = document.createElement('span');
@@ -225,18 +260,37 @@
       if (this.value())
 	value.appendChild(document.createTextNode(this.value()));
 
+      var e = this._element;
+
       // Add spans
       e.appendChild(key);
       e.appendChild(matchop);
       e.appendChild(value);
+
+      // Set operators
+      var op = this.operators(true, true, true);
+
+      e.appendChild(op.element());
+
       return e;
     },
 
+    element : function () {
+      if (this._element !== undefined)
+	return this._element;
+
+      this._element = document.createElement('div');
+      this._element.setAttribute('class', 'doc');
+
+      this.update();
+      return this._element;
+    },
+
     // Wrap a new operation around the doc element
     wrap : function (op) {
       var group = KorAP.DocGroup.create(undefined);
-      group.appendOperand(this);
-      group.appendOperand(null);
+      group.append(this);
+      group.append(null);
       this.parent(group);
 /*
       var div = document.createElement('div');
@@ -398,16 +452,30 @@
 	obj._parent = parent;
       return obj;
     },
-    appendOperand : function (operand) {
+    append : function (operand) {
+
+      // Append unspecified object
+      if (operand === undefined) {
+
+	// Be aware of cyclic structures!
+	operand = KorAP.UnspecifiedDoc.create(this);
+	this._operands.push(operand);
+	this.update();
+	return operand;
+      };
+
       switch (operand["@type"]) {
       case undefined:
 	if (operand["ldType"] !== undefined) {
 	  if (operand.ldType() !== 'doc' &&
-		   operand.ldType() !== 'docGroup') {
+	      operand.ldType() !== 'docGroup') {
 	    KorAP.log(812, "Operand not supported in document group");
 	    return;
 	  };
+	  // Be aware of cyclic structures!
+	  operand.parent(this);
 	  this._operands.push(operand);
+	  this.update();
 	  return operand;
 	};
 
@@ -415,17 +483,21 @@
 	return;
 
       case "korap:doc":
-	var doc = KorAP.Doc.create().fromJson(operand);
+	// Be aware of cyclic structures!
+	var doc = KorAP.Doc.create(this, operand);
 	if (doc === undefined)
 	  return;
 	this._operands.push(doc);
+	this.update();
 	return doc;
 
       case "korap:docGroup":
-	var docGroup = KorAP.DocGroup.create().fromJson(operand);
+	// Be aware of cyclic structures!
+	var docGroup = KorAP.DocGroup.create(this, operand);
 	if (docGroup === undefined)
 	  return;
 	this._operands.push(docGroup);
+	this.update();
 	return docGroup;
 
       default:
@@ -433,19 +505,43 @@
 	return;
       };
     },
+    update : function () {
+      if (this._element === undefined)
+	return this.element();
+
+      var op = this._element;
+      op.setAttribute('data-operation', this.operation());
+
+      _removeChildren(op);
+
+      // Append operands
+      for (var i in this.operands()) {
+	op.appendChild(
+	  this.getOperand(i).element()
+	);
+      };
+
+      // Set operators
+      var op = this.operators(
+	this.operation() == 'and' ? false : true,
+	this.operation() == 'or'  ? false : true,
+	true
+      );
+
+      this._element.appendChild(
+	op.element()
+      );
+
+      return op;
+    },
     element : function () {
       if (this._element !== undefined)
 	return this._element;
 
       this._element = document.createElement('div');
       this._element.setAttribute('class', 'docGroup');
-      this._element.setAttribute('data-operation', this.operation());
 
-      for (var i in this.operands()) {
-	this._element.appendChild(
-	  this.getOperand(i).element()
-	);
-      };
+      this.update();
 
       return this._element;
     },
@@ -497,7 +593,7 @@
       // Add all documents
       for (var i in json["operands"]) {
 	var operand = json["operands"][i];
-	this.appendOperand(operand);
+	this.append(operand);
       };
     
       return this;
@@ -541,6 +637,20 @@
       if (arguments.length === 1)
 	this._parent = obj;
       return this._parent;
+    },
+    operators : function (and, or, del) {
+      if (arguments === 0)
+	return this._ops;
+      this._ops = KorAP.Operators.create(
+	and, or, del
+      );
+      return this._ops;
+    },
+    toJson : function () {
+      return {
+	// Unspecified object
+	"@type" : "korap:" + this.ldType()
+      };
     }
   };