Added element objects and remove ecmascript 6 features, as they prevent easy inheritance
diff --git a/public/js/demo/vc.html b/public/js/demo/vc.html
new file mode 100644
index 0000000..054b969
--- /dev/null
+++ b/public/js/demo/vc.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Virtual Collection demo</title>
+    <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>
+  </head>
+  <body>
+    <div id="vc"></div>
+    <script>
+    var json = {
+      "@type":"korap:docGroup",
+      "operation":"operation:or",
+      "operands":[
+        {
+          "@type":"korap:docGroup",
+          "operation":"operation:and",
+          "operands":[
+            {
+              "@type":"korap:doc",
+              "key":"Titel",
+              "value":"Baum",
+              "match":"match:eq"
+            },
+            {
+              "@type":"korap:doc",
+              "key":"Veröffentlichungsort",
+              "value":"hihi",
+              "match":"match:eq"
+            }
+          ]
+        },
+        {
+          "@type":"korap:doc",
+          "key":"Untertitel",
+          "value":"huhu",
+          "match":"match:eq"
+        }
+      ]
+    };
+
+//      var vc = KorAP.VirtualCollection.render();
+      var vc = KorAP.VirtualCollection.render(json);
+      console.log(vc);
+      document.getElementById('vc').appendChild(vc.element());
+    </script>
+  </body>
+</html>
diff --git a/public/js/src/vc.js b/public/js/src/vc.js
index 26fa281..9ff6325 100644
--- a/public/js/src/vc.js
+++ b/public/js/src/vc.js
@@ -1,18 +1,27 @@
 var KorAP = KorAP || {};
-/*
-  704: "Operation needs operand list"
-  701: "JSON-LD group has no @type attribute" 
 
+// Todo: Implement a working localization solution!
+// Todo: Refactor out the distinction between DocElement and Doc,
+//       DocGroupElement and DocGroup
+
+/*
+ * Error codes:
+  701: "JSON-LD group has no @type attribute" 
+  704: "Operation needs operand list"
   802: "Match type is not supported by value type"
   804: "Unknown value type"
   805: "Value is invalid"
   806: "Value is not a valid date string"
   807: "Value is not a valid regular expression"
-
-// new:
   810: "Unknown document group operation" (like 711)
   811: "Document group expects operation" (like 703) 
   812: "Operand not supported in document group" (like 744)
+  813: "Collection type is not supported" (like 713)
+*/
+
+
+/*
+  - TODO: Support 'update' method to update elements on change
 */
 
 (function (KorAP) {
@@ -25,58 +34,358 @@
 
   KorAP._validStringMatchRE = new RegExp("^(?:eq|ne|contains)$");
   KorAP._validRegexMatchRE  = new RegExp("^(?:eq|ne)$");
-  KorAP._validDateMatchRE   = new RegExp("^(?:since|until|eq)$");
+  KorAP._validDateMatchRE   = new RegExp("^[lg]?eq$");
   KorAP._validDateRE        = new RegExp("^(?:\\d{4})(?:-\\d\\d(?:-\\d\\d)?)?$");
   KorAP._validGroupOpRE     = new RegExp("^(?:and|or)$");
 
-  /*
+  var loc = (KorAP.Locale = KorAP.Locale || {} );
+  loc.AND = loc.AND || 'and';
+  loc.OR  = loc.OR  || 'or';
+  loc.DEL = loc.DEL || '×';
+
   KorAP.VirtualCollection = {
+    _root : undefined,
     create : function () {
       return Object.create(KorAP.VirtualCollection);
     },
-    fromJson : function (json) {
+    render : function (json) {
+      var obj = Object.create(KorAP.VirtualCollection);
+
+      if (json !== undefined) {
+	// Root object
+	if (json['@type'] == 'korap:doc') {
+	  obj._root = KorAP.DocElement.create(undefined, json);
+	}
+	else if (json['@type'] == 'korap:docGroup') {
+	  obj._root = KorAP.DocGroupElement.create(undefined, json);
+	}
+	else {
+	  KorAP.log(813, "Collection type is not supported");
+	  return;
+	};
+      }
+
+      else {
+	// Add unspecified object
+	obj._root = KorAP.UnspecifiedDocElement.create();
+      };
+
+      // Add root element to root node
+      obj.element().appendChild(
+	obj._root.element()
+      );
+
+      return obj;
     },
-    render : function (element) {
-      // ...
-    }
-  };
-  */
-  // Make KorAP.DocElement inherit from KorAP.Doc
-  KorAP.DocElement = {
-    create : function (param) {
-      return Object.create(Korap.DocElement)._init(param);
+    root : function () {
+      return this._root;
     },
-    _init : function () {
-      KorAP.Doc.create(param)
+    element : function () {
+      if (this._element !== undefined)
+	return this._element;
+
+      this._element = document.createElement('div');
+      this._element.setAttribute('class', 'vc');
+      return this._element;
     }
   };
 
+  KorAP.Operators = {
+    create : function (and, or, del) {
+      var op = Object.create(KorAP.Operators);
+      op.and(and);
+      op.or(or);
+      op.del(del);
+      return op;
+    },
+    update : function () {
+
+      // Init the element
+      if (this._element === undefined)
+	return this.element();
+
+      var op = this._element;
+
+      // Remove everything underneath
+      while (op.firstChild) {
+	op.removeChild(op.firstChild);
+      };
+
+      // Add and button
+      if (this._and === true) {
+	var andE = document.createElement('span');
+	andE.setAttribute('class', 'and');
+	andE.appendChild(document.createTextNode(KorAP.Locale.AND));
+	op.appendChild(andE);
+      };
+
+      // Add or button
+      if (this._or === true) {
+	var orE = document.createElement('span');
+	orE.setAttribute('class', 'or');
+	orE.appendChild(document.createTextNode(KorAP.Locale.OR));
+	op.appendChild(orE);
+      };
+
+      // Add delete button
+      if (this._del === true) {
+	var delE = document.createElement('span');
+	delE.setAttribute('class', 'delete');
+	delE.appendChild(document.createTextNode(KorAP.Locale.DEL));
+	op.appendChild(delE);
+      };
+      return true;
+    },
+    element : function () {
+
+      // Return existing element
+      if (this._element !== undefined)
+	return this._element;
+
+      this._element = document.createElement('div');
+      this._element.setAttribute('class', 'operators');
+
+      // Init elements
+      this.update();
+      return this._element;
+    },
+    and : function (bool) {
+      if (arguments.length === 1)
+	this._and = (bool === undefined || bool === false) ? false : true;
+      return this._and;
+    },
+    or : function (bool) {
+      if (arguments.length === 1)
+	this._or = (bool === undefined || bool === false) ? false : true;
+      return this._or;
+    },
+    del : function (bool) {
+      if (arguments.length === 1)
+	this._del = (bool === undefined || bool === false) ? false : true;
+      return this._del;
+    }
+  };
+
+
+  /**
+   * Unspecified criterion
+   */
+  KorAP.UnspecifiedDocElement = {
+    _obj : function () { return KorAP.UnspecifiedDocElement; },
+    _objType : 'UnspecifiedDocElement',
+    create : function (parent) {
+      var obj = Object.create(KorAP.JsonLD).extend(KorAP.UnspecifiedDocElement);
+      if (parent !== undefined)
+	obj._parent = parent;
+      return obj;
+    },
+    element : function () {
+      if (this._element !== undefined)
+	return this._element;
+      this._element = document.createElement('div');
+      this._element.setAttribute('class', 'undefined');
+      return this._element;
+    }
+  };
+
+
+  KorAP.DocElement = {
+    _obj : function () { return KorAP.DocElement; },
+    _objType : 'DocElement',
+
+    create : function (parent, json) {
+      var obj = KorAP.Doc.create().extend(KorAP.DocElement).fromJson(json);
+      if (parent !== undefined)
+	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');
+
+      // Added key
+      var key = document.createElement('span');
+      key.setAttribute('class', 'key');
+      if (this.key())
+	key.appendChild(document.createTextNode(this.key()));
+
+      // Added match operator
+      var matchop = document.createElement('span');
+      matchop.setAttribute('data-type', this.type());
+      matchop.setAttribute('class', 'match');
+      matchop.appendChild(document.createTextNode(this.matchop()));
+
+      // Added match operator
+      var value = document.createElement('span');
+      value.setAttribute('data-type', this.type());
+      value.setAttribute('class', 'value');
+      if (this.value())
+	value.appendChild(document.createTextNode(this.value()));
+
+      // Add spans
+      e.appendChild(key);
+      e.appendChild(matchop);
+      e.appendChild(value);
+      return e;
+    },
+
+    // parent, element
+    // Wrap a new operation around the doc element
+    wrap : function (op) {
+      var group = KorAP.DocGroupElement.create(undefined);
+      group.appendOperand(this);
+      group.appendOperand(null);
+      this.parent(group);
+/*
+      var div = document.createElement('div');
+      div.setAttribute('data-operation', op);
+      var parent = this.element.parent;
+      parent.removeChild(this.element);
+      parent.appendChild(div);
+      div.appendChild(this.element);
+      return div;
+*/
+    }
+  };
+
+  KorAP.DocGroupElement = {
+    _obj : function () { return KorAP.DocGroupElement; },
+    _objType : 'DocGroupElement',
+
+    create : function (parent, json) {
+      var obj = KorAP.DocGroup.create().extend(KorAP.DocGroupElement).fromJson(json);
+      if (parent !== undefined)
+	obj._parent = parent;
+      return obj;
+    },
+    appendOperand : function (operand) {
+      switch (operand["@type"]) {
+      case undefined:
+	if (operand["objType"]) {
+	  if (operand.objType() === 'Doc') {
+	    operand = operand.upgrade(KorAP.DocElement);
+	  }
+	  else if (operand.objType() === 'DocGroup') {
+	    operand = operand.upgrade(KorAP.DocElementGroup);
+	  }
+	  else if (operand.objType() !== 'DocElement' &&
+		   operand.objType() !== 'DocElementGroup') {
+	    KorAP.log(812, "Operand not supported in document group");
+	    return;
+	  };
+	  this._operands.push(operand);
+	  return operand;
+	};
+
+	KorAP.log(701, "JSON-LD group has no @type attribute");
+	return;
+
+      case "korap:doc":
+	var doc = KorAP.DocElement.create().fromJson(operand);
+	if (doc === undefined)
+	  return;
+	this._operands.push(doc);
+	return doc;
+
+      case "korap:docGroup":
+	var docGroup = KorAP.DocGroupElement.create().fromJson(operand);
+	if (docGroup === undefined)
+	  return;
+	this._operands.push(docGroup);
+	return docGroup;
+
+      default:
+	KorAP.log(812, "Operand not supported in document group");
+	return;
+      };
+    },
+    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()
+	);
+      };
+
+      return this._element;
+    },
+  };
+
+
+  // Abstract JsonLD object
+  KorAP.JsonLD = {
+    _obj : function () { return KorAP.JsonLD; },
+    _objType : 'JsonLD',
+    create : function () {
+      return Object.create(KorAP.JsonLD);
+    },
+
+    // Extend this object with another object
+    // Private data will be lost
+    extend : function (props) {
+      var jld = this._obj().create();
+      for (var prop in props) {
+	jld[prop] = props[prop];
+      };
+      return jld;
+    },
+
+    // Upgrade this object to another object
+    // Private data stays intact
+    upgrade : function (props) {
+      for (var prop in props) {
+	this[prop] = props[prop];
+      };
+      return this;
+    },
+    ldType : function (type) {
+      if (arguments.length === 1)
+	this._ldType = type;
+      return this._ldType;
+    },
+    objType : function () {
+      return this._objType;
+    },
+    parent : function (obj) {
+      if (arguments.length === 1)
+	this._parent = obj;
+      return this._parent;
+    }
+  };
+
+
   KorAP.DocGroup = {
+    _ldType : "docGroup",
+    _obj : function () { return KorAP.DocGroup; },
+    _objType : 'DocGroup',
     create : function (type) {
-      var docGroup = Object.create(KorAP.DocGroup);
-      docGroup.operation = type;
+      var docGroup = Object.create(KorAP.JsonLD).extend(KorAP.DocGroup);
+      if (type !== undefined)
+	docGroup.operation(type);
       docGroup._operands = [];
-      docGroup.ldType = "docGroup";
       return docGroup;
     },
 
     // Deserialize from json
     fromJson : function (json) {
-      return Object.create(KorAP.DocGroup)._init(json);
-    },
-
-    _init : function (json) {
-
       if (json === undefined)
-	return KorAP.DocGroup.create("and");
+	return this;
 
-      if (json["@type"] != "korap:docGroup") {
+      if (json["@type"] !== "korap:docGroup") {
 	KorAP.log(701, "JSON-LD group has no @type attribute");
 	return;
       };
 
-      this.ldType = "docGroup";
-
       if (json["operation"] === undefined ||
 	  typeof json["operation"] !== 'string') {
 	KorAP.log(811, "Document group expects operation");
@@ -84,9 +393,8 @@
       };
 
       var operation = json["operation"];
-      this.operation = operation.replace(/^operation:/,'');
 
-      this._operands = [];
+      this.operation(operation.replace(/^operation:/,''));
 
       if (json["operands"] === undefined ||
 	  !(json["operands"] instanceof Array)) {
@@ -94,72 +402,72 @@
 	return;
       };
 
-      var operands = json["operands"];
-
       // Add all documents
-      for (var i in operands) {
-	var operand = operands[i];
-
-	switch (operand["@type"]) {
-	case undefined:
-	  KorAP.log(701, "JSON-LD group has no @type attribute");
-	  return;
-	case "korap:doc":
-	  var doc = KorAP.Doc.fromJson(operand);
-	  if (doc === undefined)
-	    return;
-	  this.appendOperand(doc);
-	  break;
-	case "korap:docGroup":
-	  var docGroup = KorAP.DocGroup.fromJson(operand);
-	  if (docGroup === undefined)
-	    return;
-	  this.appendOperand(docGroup);
-	  break;
-	default:
-	  KorAP.log(812, "Operand not supported in document group");
+      for (var i in json["operands"]) {
+	var operand = json["operands"][i];
+	this.appendOperand(operand);
+      };
+    
+      return this;
+    },
+    operation : function (op) {
+      if (arguments.length === 1) {
+	if (KorAP._validGroupOpRE.test(op)) {
+	  this._op = op;
+	}
+	else {
+	  KorAP.log(810, "Unknown operation type");
 	  return;
 	};
       };
-      return this;
-    },
-    set operation (op) {
-      if (KorAP._validGroupOpRE.test(op)) {
-	this._op = op;
-      }
-      else {
-	KorAP.log(810, "Unknown operation type");
-	return;
-      };
-    },
-    get operation () {
       return this._op || 'and';
     },
-    get operands () {
+    operands : function () {
       return this._operands;
     },
-    appendOperand : function (obj) {
-      this._operands.push(obj)
-      return obj;
+    appendOperand : function (operand) {
+      switch (operand["@type"]) {
+      case undefined:
+	if (operand["objType"] && (
+	  operand.objType() === 'Doc' ||
+	    operand.objType() === 'DocGroup')) {
+	  this._operands.push(operand);
+	  return operand;
+	};
+	KorAP.log(701, "JSON-LD group has no @type attribute");
+	return;
+
+      case "korap:doc":
+	var doc = KorAP.Doc.create().fromJson(operand);
+	if (doc === undefined)
+	  return;
+	this._operands.push(doc);
+	return doc;
+
+      case "korap:docGroup":
+	var docGroup = KorAP.DocGroup.create().fromJson(operand);
+	if (docGroup === undefined)
+	  return;
+	this._operands.push(docGroup);
+	return docGroup;
+
+      default:
+	KorAP.log(812, "Operand not supported in document group");
+	return;
+      };
     },
     getOperand : function (index) {
       return this._operands[index];
     },
-    set ldType (type) {
-      this._ldtype = type;
-    },
-    get ldType () {
-      return this._ldType || "docGroup";
-    },
     toJson : function () {
-      var operands = new Array();
+      var opArray = new Array();
       for (var i in this._operands) {
-	operands.push(this._operands[i].toJson());
+	opArray.push(this._operands[i].toJson());
       };
       return {
-	"@type" : "korap:" + this.ldType,
-	"operation" : "operation:" + this.operation,
-	"operands" : operands
+	"@type"     : "korap:" + this.ldType(),
+	"operation" : "operation:" + this.operation(),
+	"operands"  : opArray
       };
     }
   };
@@ -168,31 +476,25 @@
    * Virtual collection doc criterion.
    */
   KorAP.Doc = {
-
+    _ldType : "doc",
+    _obj : function () { return KorAP.Doc; },
+    _objType : 'Doc',
     // Create new
     create : function () {
-      var doc = Object.create(KorAP.Doc);
-      doc.ldType = "doc";
-      return doc;
+      return Object.create(KorAP.JsonLD).extend(KorAP.Doc);
     },
 
     // Deserialize from json
     fromJson : function (json) {
-      return Object.create(KorAP.Doc)._init(json);
-    },
-
-    // Init new doc criterion or deserialize fro JSON-LD
-    _init : function (json) {
       if (json === undefined)
-	return this.create();
+	return this;
+      // return this.create();
 
       if (json["@type"] !== "korap:doc") {
 	KorAP.log(701, "JSON-LD group has no @type attribute");
 	return;
       };
 
-      this.ldType = "doc";
-
       if (json["value"] === undefined ||
 	  typeof json["value"] != 'string') {
 	KorAP.log(805, "Value is invalid");
@@ -204,12 +506,13 @@
 	  typeof json["key"] === 'string') {
 
 	// Set key
-	this.key = json["key"];
+	this.key(json["key"]);
 
 	// Set match operation
 	if (json["match"] !== undefined) {
-	  if (typeof json["match"] === 'string')
-	    this.matchop = json["match"];
+	  if (typeof json["match"] === 'string') {
+	    this.matchop(json["match"]);
+	  }
 	  else {
 	    KorAP.log(802, "Match type is not supported by value type");
 	    return;
@@ -219,32 +522,32 @@
 	// Key is a string
 	if (json["type"] === undefined ||
 	    json["type"] == "type:string") {
-	  this.type = "string";
+	  this.type("string");
 
 	  // Check match type
-	  if (!KorAP._validStringMatchRE.test(this.matchop)) {
+	  if (!KorAP._validStringMatchRE.test(this.matchop())) {
 	    KorAP.log(802, "Match type is not supported by value type");
 	    return;
 	  };
 
 	  // Set string value
-	  this.value = json["value"];
+	  this.value(json["value"]);
 	}
 
 	// Key is a date
 	else if (json["type"] === "type:date") {
-	  this.type = "date";
+	  this.type("date");
 
 	  if (json["value"] !== undefined &&
 	      KorAP._validDateRE.test(json["value"])) {
 
-	    if (!KorAP._validDateMatchRE.test(this.matchop)) {
+	    if (!KorAP._validDateMatchRE.test(this.matchop())) {
 	      KorAP.log(802, "Match type is not supported by value type");
 	      return;
 	    };
 
 	    // Set value
-	    this.value = json["value"];
+	    this.value(json["value"]);
 	  }
 	  else {
 	    KorAP.log(806, "Value is not a valid date string");
@@ -254,25 +557,26 @@
 
 	// Key is a regular expression
 	else if (json["type"] === "type:regex") {
-	  this.type = "regex";
+	  this.type("regex");
 
 	  try {
 
 	    // Try to create a regular expression
 	    var check = new RegExp(json["value"]);
 
-	    if (!KorAP._validRegexMatchRE.test(this.matchop)) {
+	    if (!KorAP._validRegexMatchRE.test(this.matchop())) {
 	      KorAP.log(802, "Match type is not supported by value type");
 	      return;
 	    };
 
-	    this.value = json["value"];
+	    this.value(json["value"]);
 	  }
+
 	  catch (e) {
 	    KorAP.log(807, "Value is not a valid regular expression");
 	    return;
 	  };
-	  this.type = "regex";
+	  this.type("regex");
 	}
 
 	else {
@@ -283,48 +587,36 @@
 
       return this;
     },
-    set key (value) {
-      this._key = value;
-    },
-    get key () {
+    key : function (value) {
+      if (arguments.length === 1)
+	this._key = value;
       return this._key;
     },
-    set matchop (match) {
-      this._matchop = match.replace(/^match:/, '');
-    },
-    get matchop () {
+    matchop : function (match) {
+      if (arguments.length === 1)
+	this._matchop = match.replace(/^match:/, '');
       return this._matchop || "eq";
     },
-    set type (type) {
-      this._type = type;
-    },
-    get type () {
+    type : function (type) {
+      if (arguments.length === 1)
+	this._type = type;
       return this._type || "string";
     },
-    set value (value) {
-      this._value = value;
-    },
-    get value () {
+    value : function (value) {
+      if (arguments.length === 1)
+	this._value = value;
       return this._value;
     },
-
-    // Todo: Redundant and should be inherited
-    set ldType (type) {
-      this._ldtype = type;
-    },
-    get ldType () {
-      return this._ldType || "doc";
-    },
     toJson : function () {
-      if (!this.matchop || !this.key)
+      if (!this.matchop() || !this.key())
 	return {};
       
       return {
-	"@type" : "korap:" + this.ldType,
-	"key"   : this.key,
-	"match" : "match:" + this.matchop,
-	"value" : this.value || '',
-	"type"  : "type:" + this.type
+	"@type" : "korap:" + this.ldType(),
+	"key"   : this.key(),
+	"match" : "match:" + this.matchop(),
+	"value" : this.value() || '',
+	"type"  : "type:" + this.type()
       };
     }
   };