Use requirejs for clientside scripting
diff --git a/dev/js/src/vc/doc.js b/dev/js/src/vc/doc.js
new file mode 100644
index 0000000..61c6439
--- /dev/null
+++ b/dev/js/src/vc/doc.js
@@ -0,0 +1,363 @@
+/**
+ * Document criterion
+ */
+
+/**
+ * Criterion in a KorAP.Doc
+ */
+function _changeKey () {
+  var doc = this.parentNode.refTo;
+  var key = doc.element().firstChild;
+  key.appendChild(fieldMenu.element());
+  fieldMenu.show();
+  fieldMenu.focus();
+  // key, matchop, type, value
+};
+
+define([
+  'vc/jsonld', 'vc/menu', 'vc/rewritelist'], function (jsonldClass, menuClass, rewriteListClass) {
+  var fieldMenu = menuClass.create([
+    ['Titel', 'title', 'string'],
+    ['Untertitel', 'subTitle', 'string'],
+    ['Veröffentlichungsdatum', 'pubDate', 'date'],
+    ['Autor', 'author', 'string']
+  ]);
+
+  fieldMenu.limit(5);
+
+  _validRegexMatchRE  = new RegExp("^(?:eq|ne)$");
+
+  return {
+    _ldType : "doc",
+    _obj : function () { return '???'; /*KorAP.Doc*/ },
+    
+    create : function (parent, json) {
+      var obj = Object(jsonldClass).
+	create().
+	upgradeTo(this).
+	fromJson(json);
+      
+      if (parent !== undefined)
+	obj._parent = parent;
+      
+      obj.__changed = true;
+      return obj;
+    },
+
+    update : function () {
+      if (this._element === undefined)
+	return this.element();
+      
+      // Get element
+      var e = this._element;
+
+      // Set ref - TODO: Cleanup!
+      e.refTo = this;
+
+      // Check if there is a change
+      if (this.__changed) {
+
+	// Was rewritten
+	if (this.rewrites() !== undefined) {
+	  e.classList.add("rewritten");
+	};
+
+	// Added key
+	var key = document.createElement('span');
+	key.setAttribute('class', 'key');
+
+	// Change key
+	key.addEventListener('click', _changeKey, false);
+
+	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())
+	  );
+
+	// Remove all element children
+	_removeChildren(e);
+
+	// Add spans
+	e.appendChild(key);
+	e.appendChild(matchop);
+	e.appendChild(value);
+
+	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 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 parent = this.parent();
+      var group = KorAP.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["value"] === undefined ||
+	  typeof json["value"] != 'string') {
+	KorAP.log(805, "Value is invalid");
+	return;
+      };
+
+      // There is a defined key
+      if (json["key"] !== undefined &&
+	  typeof json["key"] === 'string') {
+
+	// Set key
+	this.key(json["key"]);
+
+	// Set match operation
+	if (json["match"] !== undefined) {
+	  if (typeof json["match"] === 'string') {
+	    this.matchop(json["match"]);
+	  }
+	  else {
+	    KorAP.log(802, "Match type is not supported by value type");
+	    return;
+	  };
+	};
+
+	// Key is a string
+	if (json["type"] === undefined ||
+	    json["type"] == "type:string") {
+	  this.type("string");
+
+	  // Check match type
+	  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"]);
+	}
+
+	// Key is a date
+	else if (json["type"] === "type:date") {
+	  this.type("date");
+
+	  if (json["value"] !== undefined &&
+	      KorAP._validDateRE.test(json["value"])) {
+
+	    if (!KorAP._validDateMatchRE.test(this.matchop())) {
+	      KorAP.log(802, "Match type is not supported by value type");
+	      return;
+	    };
+
+	    // Set value
+	    this.value(json["value"]);
+	  }
+	  else {
+	    KorAP.log(806, "Value is not a valid date string");
+	    return;
+	  };
+	}
+
+	// Key is a regular expression
+	else if (json["type"] === "type:regex") {
+	  this.type("regex");
+
+	  try {
+
+	    // Try to create a regular expression
+	    var check = new RegExp(json["value"]);
+
+	    if (!_validRegexMatchRE.test(this.matchop())) {
+	      KorAP.log(802, "Match type is not supported by value type");
+	      return;
+	    };
+
+	    this.value(json["value"]);
+	  }
+
+	  catch (e) {
+	    KorAP.log(807, "Value is not a valid regular expression");
+	    return;
+	  };
+	  this.type("regex");
+	}
+
+	else {
+	  KorAP.log(804, "Unknown value type");
+	  return;
+	};
+      };
+
+      if (json["rewrites"] !== undefined) {
+	this._rewrites = rewriteListClass.create(json["rewrites"]);
+      };
+      
+      return this;
+    },
+
+    key : function (value) {
+      if (arguments.length === 1) {
+	this._key = value;
+	this._changed();
+	return this;
+      };
+      return this._key;
+    },
+
+    matchop : function (match) {
+      if (arguments.length === 1) {
+	this._matchop = match.replace(/^match:/, '');
+	this._changed();
+	return this;
+      };
+      return this._matchop || "eq";
+    },
+
+    type : function (type) {
+      if (arguments.length === 1) {
+	this._type = type;
+	this._changed();
+	return this;
+      };
+      return this._type || "string";
+    },
+
+    value : function (value) {
+      if (arguments.length === 1) {
+	this._value = value;
+	this._changed();
+	return this;
+      };
+      return this._value;
+    },
+
+    rewrites : function () {
+      return this._rewrites;
+    },
+
+    _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.matchop() || !this.key())
+	return {};
+      
+      return {
+	"@type" : "koral:" + this.ldType(),
+	"key"   : this.key(),
+	"match" : "match:" + this.matchop(),
+	"value" : this.value() || '',
+	"type"  : "type:" + this.type()
+      };
+    },
+
+    toQuery : function () {
+      if (!this.matchop() || !this.key())
+	return "";
+
+      // Build doc string based on key
+      var string = this.key() + ' ';
+
+      // Add match operator
+      switch (this.matchop()) {
+      case "ne":
+	string += '!=';
+	break;
+      case "contains":
+	string += '~';
+	break;
+      case "excludes":
+	string += '!~';
+	break;
+      case "geq":
+	string += 'since';
+	break;
+      case "leq":
+	string += 'until';
+	break;
+      default:
+	string += (this.type() == 'date') ? 'in' : '=';
+	break;
+      };
+
+      string += ' ';
+
+      // Add value
+      switch (this.type()) {
+      case "date":
+	return string + this.value();
+	break;
+      case "regex":
+	return string + '/' + this.value() + '/';
+	break;
+      case "string":
+	return string + '"' + this.value().replace(KorAP._quote, '\\$1') + '"';
+	break;
+      };
+
+      return "";
+    }
+  };
+});
diff --git a/dev/js/src/vc/docgroup.js b/dev/js/src/vc/docgroup.js
new file mode 100644
index 0000000..5e5f010
--- /dev/null
+++ b/dev/js/src/vc/docgroup.js
@@ -0,0 +1,343 @@
+/**
+ * Document group criterion
+ */
+define([
+  'vc/jsonld',
+  'vc/unspecified',
+  'vc/doc',
+  'util'
+], function (jsonldClass,
+	     unspecClass,
+	     docClass) {
+
+  var _validGroupOpRE = new RegExp("^(?:and|or)$");
+
+  var docGroupClass = {
+    _ldType : "docGroup",
+
+    create : function (parent, json) {
+      var obj = Object.create(jsonldClass).upgradeTo(this);
+      obj._operands = [];
+      obj.fromJson(json);
+      if (parent !== undefined)
+	obj._parent = parent;
+      return obj;
+    },
+    
+    newAfter : function (obj) {
+      for (var i = 0; i < this._operands.length; i++) {
+	if (this._operands[i] === obj) {
+	  var operand = unspecClass.create(this);
+	  this._operands.splice(i + 1, 0, operand);
+	  return this.update();
+	};
+      };
+    },
+
+    // The doc is already set in the group
+    _duplicate : function (operand) {
+      if (operand.ldType() !== 'doc')
+	return null;
+
+      for (var i = 0; i < this._operands.length; i++) {
+	var op = this.getOperand(i);
+	if (op.ldType() === 'doc'
+	    && operand.key() === op.key()
+	    && operand.matchop() === op.matchop()
+	    && operand.value() === op.value()) {
+	  return op;
+	};
+      };
+      return null;
+    },
+    
+    append : function (operand) {
+      
+      // Append unspecified object
+      if (operand === undefined) {
+
+	// Be aware of cyclic structures!
+	operand = unspecClass.create(this);
+	this._operands.push(operand);
+	return operand;
+      };
+      
+      switch (operand["@type"]) {
+	
+      case undefined:
+	// No @type defined
+	if (operand["ldType"] !== undefined) {
+	  if (operand.ldType() !== 'doc' &&
+	      operand.ldType() !== 'docGroup') {
+	    KorAP.log(812, "Operand not supported in document group");
+	    return;
+	  };
+
+	  // Be aware of cyclic structures!
+	  operand.parent(this);
+
+	  var dupl = this._duplicate(operand);
+	  if (dupl === null) {
+	    this._operands.push(operand);
+	    return operand;
+	  };
+	  return dupl;
+	};
+
+	KorAP.log(701, "JSON-LD group has no @type attribute");
+	return;
+
+      case "koral:doc":
+	// Be aware of cyclic structures!
+	var doc = docClass.create(this, operand);
+	if (doc === undefined)
+	  return;
+	var dupl = this._duplicate(doc);
+	if (dupl === null) {
+	  this._operands.push(doc);
+	  return doc;
+	};
+	return dupl;
+
+      case "koral:docGroup":
+	// Be aware of cyclic structures!
+	var docGroup = docGroupClass.create(this, operand);
+	if (docGroup === undefined)
+	  return;
+
+	// Flatten group
+	if (docGroup.operation() === this.operation()) {
+	  for (var op in docGroup.operands()) {
+	    op = docGroup.getOperand(op);
+	    var dupl = this._duplicate(op);
+	    if (dupl === null) {
+	      this._operands.push(op);
+	      op.parent(this);
+	    };
+	  };
+	  docGroup._operands = [];
+	  docGroup.destroy();
+	  return this;
+	};
+	this._operands.push(docGroup);
+	return docGroup;
+
+      default:
+	KorAP.log(812, "Operand not supported in document group");
+	return;
+      };
+    },
+
+    update : function () {
+      // There is only one operand in group
+      
+      if (this._operands.length === 1) {
+	
+	var parent = this.parent();
+	var op = this.getOperand(0);
+	
+	// This will prevent destruction of
+	// the operand
+	this._operands = [];
+
+	// Parent is a group
+	if (parent.ldType() !== null)
+	  return parent.replaceOperand(this, op).update();
+
+	// Parent is vc
+	else {
+	  this.destroy();
+	  // Cyclic madness
+	  parent.root(op);
+	  op.parent(parent);
+	  return parent.root();
+	};
+      };
+
+      if (this._element === undefined)
+	return this;
+
+      var group = this._element;
+      group.setAttribute('data-operation', this.operation());
+
+      _removeChildren(group);
+
+      // Append operands
+      for (var i = 0; i < this._operands.length; i++) {
+	group.appendChild(
+	  this.getOperand(i).element()
+	);
+      };
+
+      // Set operators
+      var op = this.operators(
+	this.operation() == 'and' ? false : true,
+	this.operation() == 'or'  ? false : true,
+	true
+      );
+
+      group.appendChild(op.element());
+
+      return this;
+    },
+
+    element : function () {
+      if (this._element !== undefined)
+	return this._element;
+
+      this._element = document.createElement('div');
+      this._element.setAttribute('class', 'docGroup');
+
+      // Update the object - including optimization
+      this.update();
+
+      return this._element;
+    },
+
+    operation : function (op) {
+      if (arguments.length === 1) {
+	if (_validGroupOpRE.test(op)) {
+	  this._op = op;
+	}
+	else {
+	  KorAP.log(810, "Unknown operation type");
+	  return;
+	};
+      };
+      return this._op || 'and';
+    },
+
+    operands : function () {
+      return this._operands;
+    },
+
+    getOperand : function (index) {
+      return this._operands[index];
+    },
+
+    // Replace operand
+    replaceOperand : function (oldOp, newOp) {
+      
+      for (var i = 0; i < this._operands.length; i++) {
+	if (this._operands[i] === oldOp) {
+	  
+	  // Just insert a doc or ...
+	  if (newOp.ldType() === "doc" ||
+	      newOp.ldType() === "non" ||
+	      // ... insert a group of a different operation
+	      // (i.e. "and" in "or"/"or" in "and")
+	      newOp.operation() != this.operation()) {
+	    this._operands[i] = newOp;
+	    newOp.parent(this);
+	  }
+
+	  // Flatten group
+	  else {
+	    // Remove old group
+	    this._operands.splice(i, 1);
+
+	    // Inject new operands
+	    for (var op in newOp.operands().reverse()) {
+	      op = newOp.getOperand(op);
+	      this._operands.splice(i, 0, op);
+	      op.parent(this);
+	    };
+	    // Prevent destruction of operands
+	    newOp._operands = [];
+	    newOp.destroy();
+	  };
+	  oldOp.destroy();
+	  return this;
+	}
+      };
+      return false;
+    },
+
+    // Delete operand from group
+    delOperand : function (obj) {
+      for (var i = 0; i < this._operands.length; i++) {
+	if (this._operands[i] === obj) {
+	  
+	  // Delete identified operand
+	  this._operands.splice(i,1);
+
+	  // Destroy object for cyclic references
+	  obj.destroy();
+
+	  return this;
+	};
+      };
+
+      // Operand not found
+      return undefined;
+    },
+    
+    // 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["operation"] === undefined ||
+	  typeof json["operation"] !== 'string') {
+	KorAP.log(811, "Document group expects operation");
+	return;
+      };
+
+      var operation = json["operation"];
+
+      this.operation(operation.replace(/^operation:/,''));
+
+      if (json["operands"] === undefined ||
+	  !(json["operands"] instanceof Array)) {
+	KorAP.log(704, "Operation needs operand list")
+	return;
+      };
+
+      // Add all documents
+      for (var i in json["operands"]) {
+	var operand = json["operands"][i];
+	this.append(operand);
+      };
+      
+      return this;
+    },
+
+    toJson : function () {
+      var opArray = new Array();
+      for (var i = 0; i < this._operands.length; i++) {
+	if (this._operands[i].ldType() !== 'non')
+	  opArray.push(this._operands[i].toJson());
+      };
+      return {
+	"@type"     : "koral:" + this.ldType(),
+	"operation" : "operation:" + this.operation(),
+	"operands"  : opArray
+      };
+    },
+
+    toQuery : function (brackets) {
+      var list = this._operands
+	.filter(function (op) {
+	  return op.ldType() !== 'non';
+	})
+	.map(function (op) {
+	  return (op.ldType() === 'docGroup') ?
+	    op.toQuery(true) :
+	    op.toQuery();
+	});
+      
+      if (list.length === 1)
+	return list.join('');
+      else {
+	var str = list.join(this.operation() === 'or' ? ' | ' : ' & ');
+	return brackets ? '(' + str + ')' : str;
+      };
+    }
+  };
+  return docGroupClass;
+});
diff --git a/dev/js/src/vc/item.js b/dev/js/src/vc/item.js
new file mode 100644
index 0000000..bfefe38
--- /dev/null
+++ b/dev/js/src/vc/item.js
@@ -0,0 +1,44 @@
+// Field menu item
+define(['menu/item'], function (itemClass) {
+  return {
+    create : function (params) {
+      return Object.create(itemClass)
+	.upgradeTo(this)
+	._init(params);
+    },
+
+    _init : function (params) {
+      if (params[0] === undefined)
+	throw new Error("Missing parameters");
+
+      this._name  = params[0];
+      this._value = params[1];
+      this._type  = params[2];
+
+      this._lcField = ' ' + this._name.toLowerCase();
+
+      return this;
+    },
+
+    name : function () {
+      return this._name;
+    },
+
+    type : function () {
+      return this._type;
+    },
+
+    element : function () {
+      // already defined
+      if (this._element !== undefined)
+	return this._element;
+
+      // Create list item
+      var li = document.createElement("li");
+      li.setAttribute("data-type", this._type);
+      li.setAttribute("data-value", this._value);
+      li.appendChild(document.createTextNode(this._name));
+      return this._element = li;
+    }
+  };
+});
diff --git a/dev/js/src/vc/jsonld.js b/dev/js/src/vc/jsonld.js
new file mode 100644
index 0000000..324e7db
--- /dev/null
+++ b/dev/js/src/vc/jsonld.js
@@ -0,0 +1,99 @@
+/**
+ * Abstract JsonLD criterion object
+ */
+define(['vc/operators'], function (operatorsClass) {
+  return {
+    __changed : false,
+    
+    create : function () {
+      return Object.create(this);
+    },
+
+    /**
+     * Upgrade this object to another object
+     * while private data stays intact
+     */
+    upgradeTo : 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;
+    },
+    
+    parent : function (obj) {
+      if (arguments.length === 1) {
+	this._parent = obj;
+	this.__changed = true;
+      };
+      return this._parent;
+    },
+
+    // Destroy object - especially for
+    // acyclic structures!
+    // I'm paranoid!
+    destroy : function () {
+      if (this._ops != undefined) {
+	this._ops._parent = undefined;
+	if (this._ops._element !== undefined)
+	  this._ops._element.refTo = undefined;
+	this._ops = undefined;
+      };
+      if (this._element !== undefined)
+	this._element = undefined;
+      
+      // In case of a group, destroy all operands
+      if (this._operands !== undefined) {
+	for (var i = 0; i < this._operands.length; i++)
+	  this.getOperand(i).destroy();
+	this._operands = [];
+      };
+    },
+    
+    // Wrap a new operation around the root group element 
+    wrapOnRoot : function (op) {
+      var parent = this.parent();
+      
+      var group = require('vc/docgroup').create(parent);
+      if (arguments.length === 1)
+	group.operation(op);
+      else
+	group.operation(
+	  this.operation() === 'and' ? 'or' : 'and'
+	);
+      group.append(this);
+      this.parent(group);
+      group.append();
+      group.element(); // Init (seems to be necessary)
+      parent.root(group);
+      return this.parent();
+    },
+
+    // Be aware! This may be cyclic
+    operators : function (and, or, del) {
+      if (arguments === 0)
+	return this._ops;
+      this._ops = operatorsClass.create(
+	and, or, del
+      );
+      this._ops.parent(this);
+      return this._ops;
+    },
+
+    toJson : function () {
+      return {
+	// Unspecified object
+	"@type" : "koral:" + this.ldType()
+      };
+    },
+
+    toQuery : function () {
+      return '';
+    }
+  };
+});
diff --git a/dev/js/src/vc/menu.js b/dev/js/src/vc/menu.js
new file mode 100644
index 0000000..6f6d2bd
--- /dev/null
+++ b/dev/js/src/vc/menu.js
@@ -0,0 +1,10 @@
+// Field menu
+define(['menu', 'menu/item'], function (menuClass, itemClass) {
+  return {
+    create : function (params) {
+      return Object.create(menuClass)
+	.upgradeTo(this)
+	._init(itemClass, undefined, params)
+    }
+  };
+});
diff --git a/dev/js/src/vc/operators.js b/dev/js/src/vc/operators.js
new file mode 100644
index 0000000..312f75d
--- /dev/null
+++ b/dev/js/src/vc/operators.js
@@ -0,0 +1,168 @@
+/**
+ * Operators for criteria
+ */
+define(['util'], function () {
+
+  var loc = KorAP.Locale;
+  loc.AND   = loc.AND   || 'and';
+  loc.OR    = loc.OR    || 'or';
+  loc.DEL   = loc.DEL   || '×';
+
+
+  // Utility for analysing boolean values
+  function _bool (bool) {
+    return (bool === undefined || bool === null || bool === false) ? false : true;
+  };
+
+
+  // Add new unspecified document
+  function _add (obj, type) {
+    var ref = obj.parentNode.refTo;
+    var parent = ref.parent();
+
+    if (ref.ldType() === 'docGroup') {
+
+      // Check that the action differs from the type
+      if (ref.operation() === type)
+	return;
+
+      if (parent.ldType() !== null) {
+	return parent.newAfter(ref);
+      }
+      else {
+	// The group is on root - wrap
+	return ref.wrapOnRoot();
+      };
+    }
+    else if (ref.ldType() === 'doc') {
+
+      if (parent.ldType() === null) {
+	return ref.wrapOnRoot(type);
+      }
+      else if (parent.operation() === type) {
+	return parent.newAfter(ref);
+      }
+      else {
+	return ref.wrap(type);
+      };
+    };
+  };
+
+
+  // Add doc with 'and' relation
+  var _and = function () {
+    return _add(this, 'and');
+  };
+
+
+  // Add doc with 'or' relation
+  var _or = function () {
+    return _add(this, 'or');
+  };
+
+
+  // Remove doc or docGroup
+  var _delete = function () {
+    var ref = this.parentNode.refTo;
+    if (ref.parent().ldType() !== null) {
+      return ref.parent().delOperand(ref).update();
+    }
+    else {
+      ref.parent().clean();
+    };
+  };
+
+
+  return {
+    create : function (and, or, del) {
+      var op = Object.create(this);
+      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;
+
+      op.refTo = this.parent();
+
+      // Remove everything underneath
+      _removeChildren(op);
+      
+      // Add and button
+      if (this._and === true) {
+	var andE = document.createElement('span');
+	andE.setAttribute('class', 'and');
+	andE.addEventListener('click', _and, false);
+	andE.appendChild(
+	  document.createTextNode(loc.AND)
+	);
+	op.appendChild(andE);
+      };
+
+      // Add or button
+      if (this._or === true) {
+	var orE = document.createElement('span');
+	orE.setAttribute('class', 'or');
+	orE.addEventListener('click', _or, false);
+	orE.appendChild(document.createTextNode(loc.OR));
+	op.appendChild(orE);
+      };
+
+      // Add delete button
+      if (this._del === true) {
+	var delE = document.createElement('span');
+	delE.setAttribute('class', 'delete');
+	delE.appendChild(document.createTextNode(loc.DEL));
+	delE.addEventListener('click', _delete, false);
+	op.appendChild(delE);
+      };
+
+      return op;
+    },
+
+    // Be aware! This may be cyclic
+    parent : function (obj) {
+      if (arguments.length === 1)
+	this._parent = obj;
+      return this._parent;
+    },
+
+    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(bool);
+      return this._and;
+    },
+
+    or : function (bool) {
+      if (arguments.length === 1)
+	this._or = _bool(bool);
+      return this._or;
+    },
+
+    del : function (bool) {
+      if (arguments.length === 1)
+	this._del = _bool(bool);
+      return this._del;
+    }
+  };
+});
diff --git a/dev/js/src/vc/rewrite.js b/dev/js/src/vc/rewrite.js
new file mode 100644
index 0000000..79ba4b3
--- /dev/null
+++ b/dev/js/src/vc/rewrite.js
@@ -0,0 +1,100 @@
+/**
+ * Implementation of rewrite objects.
+ */
+define(['vc/jsonld', 'util'], function (jsonldClass) {
+
+  var _validRewriteOpRE   = new RegExp("^(?:injec|modifica)tion$");
+  var _quote              = new RegExp("([\"\\\\])", 'g');
+
+  return {
+    // Construction method
+    create : function (json) {
+      var obj = Object(jsonldClass).
+	create().
+	upgradeTo(this).
+	fromJson(json);
+      return obj;
+    },
+
+    // Get or set source
+    src : function (string) {
+      if (arguments.length === 1)
+	this._src = string;
+      return this._src;
+    },
+    
+    // Get or set operation
+    operation : function (op) {
+      if (arguments.length === 1) {
+	if (_validRewriteOpRE.test(op)) {
+	  this._op = op;
+	}
+	else {
+	  KorAP.log(814, "Unknown rewrite operation");
+	  return;
+	};
+      };
+      return this._op || 'injection';
+    },
+
+    // Get or set scope
+    scope : function (attr) {
+      if (arguments.length === 1)
+	this._scope = attr;
+      return this._scope;
+    },
+
+    // Serialize from Json
+    fromJson : function (json) {
+      if (json === undefined)
+	return this;
+
+      // Missing @type
+      if (json["@type"] === undefined) {
+	KorAP.log(701, "JSON-LD group has no @type attribute");
+	return;
+      };
+      
+      // Missing source
+      if (json["src"] === undefined ||
+	  typeof json["src"] !== 'string') {
+	KorAP.log(815, "Rewrite expects source");
+	return;
+      };
+
+      // Set source
+      this.src(json["src"]);
+
+      // Set operation
+      if (json["operation"] !== undefined) {
+	var operation = json["operation"];
+	this.operation(operation.replace(/^operation:/,''));
+      };
+
+      // Set scope
+      if (json["scope"] !== undefined &&
+	  typeof json["scope"] === 'string')
+	this.scope(json["scope"]);
+
+      return this;
+    },
+    
+    toString : function () {
+      var str = '';
+      var op = this.operation();
+      str += op.charAt(0).toUpperCase() + op.slice(1);
+      str += ' of ' + (
+	this._scope === null ?
+	  'object' :
+	  '"' +
+	  this.scope().replace(_quote, '\\$1') +
+	  '"'
+      );
+      str += ' by ' +
+	'"' +
+	this.src().replace(_quote, '\\$1') +
+	'"';
+      return str;
+    }
+  };
+});
diff --git a/dev/js/src/vc/rewritelist.js b/dev/js/src/vc/rewritelist.js
new file mode 100644
index 0000000..af3c05a
--- /dev/null
+++ b/dev/js/src/vc/rewritelist.js
@@ -0,0 +1,49 @@
+define(['vc/jsonld', 'vc/rewrite'], function (jsonldClass, rewriteClass) {
+  return {
+    // Construction method
+    create : function (json) {
+      var obj = Object(jsonldClass).
+	create().
+	upgradeTo(this).
+	fromJson(json);
+      return obj;
+    },
+    fromJson : function (json) {
+      this._list = new Array();
+      for (var i = 0; i < json.length; i++) {
+	this._list.push(
+	  rewriteClass.create(json[i])
+	);
+      };
+      return this;
+    },
+    element : function () {
+      if (this._element !== undefined)
+	return this._element;
+
+      this._element = document.createElement('div');
+      this._element.setAttribute('class', 'rewrite');
+      for (var x in this._list) {
+	var rewrite = this._list[x];
+	var span = document.createElement('span');
+
+	// Set class attribute
+	span.setAttribute('class', rewrite.operation());
+
+	// Append source information
+	span.appendChild(document.createTextNode(rewrite.src()));
+
+	// Append scope information
+	if (rewrite.scope() !== undefined) {
+	  span.appendChild(
+	    document.createTextNode(
+	      ': ' + rewrite.scope()
+	    )
+	  );
+	};
+	this._element.appendChild(span);
+      };
+      return this._element;
+    }
+  };
+});
diff --git a/dev/js/src/vc/unspecified.js b/dev/js/src/vc/unspecified.js
new file mode 100644
index 0000000..9efd007
--- /dev/null
+++ b/dev/js/src/vc/unspecified.js
@@ -0,0 +1,89 @@
+/**
+ * Unspecified criterion
+ */
+define(['vc/jsonld', 'vc/doc', 'util'], function (jsonldClass, docClass) {
+
+  var loc = KorAP.Locale;
+  loc.EMPTY = loc.EMPTY || '⋯';
+
+  return {
+    _ldType : "non",
+    create : function (parent) {
+      var obj = Object.create(jsonldClass).
+	upgradeTo(this);
+
+      if (parent !== undefined)
+	obj._parent = parent;
+
+      return obj;
+    },
+
+    // Set key - replace
+    key : function (v) {
+
+      // Not replaceable
+      if (this._parent === undefined)
+	return null;
+
+      // Set JSON-LD type
+      var newDoc = docClass.create(this._parent, {
+	"@type" : "koral:doc",
+	"value" : "",
+	"key"   : v
+      });
+
+      // Unspecified document on root
+      if (this._parent.ldType() === null) {
+	this._parent.root(newDoc);
+	this.destroy();
+      }
+
+      // Unspecified document in group
+      else {
+	this._parent.replaceOperand(this, newDoc);
+      };
+      this._parent.update();
+      return newDoc;
+    },
+
+    update : function () {
+
+      if (this._element === undefined)
+	return this.element();
+
+      // Remove element content
+      _removeChildren(this._element);
+
+      var ellipsis = document.createElement('span');
+      ellipsis.appendChild(document.createTextNode(loc.EMPTY));
+      this._element.appendChild(ellipsis);
+
+      // Set ref - TODO: Cleanup!
+      this._element.refTo = this;
+
+      // Set operators
+      if (this._parent !== undefined && this.parent().ldType() !== null) {
+	var op = this.operators(
+	  false,
+	  false,
+	  true
+	);
+	
+	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', 'doc unspecified');
+      this.update();
+      return this._element;
+    },
+  };
+});