Use requirejs for clientside scripting
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;
+});