blob: 31ddc856659ba21467c6ff93da545d737ccb6485 [file] [log] [blame]
var KorAP = KorAP || {};
// TODO: Implement a working localization solution!
// TODO: Support 'update' method to update elements on change
// TODO: Implement "toQuery"
/*
* 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"
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)
*/
(function (KorAP) {
"use strict";
// Default log message
KorAP.log = KorAP.log || function (type, msg) {
console.log(type + ": " + msg);
};
KorAP._validStringMatchRE = new RegExp("^(?:eq|ne|contains)$");
KorAP._validRegexMatchRE = new RegExp("^(?:eq|ne)$");
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 || '×';
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 () {
return Object.create(KorAP.VirtualCollection);
},
render : function (json) {
var obj = Object.create(KorAP.VirtualCollection);
if (json !== undefined) {
// Root object
if (json['@type'] == 'korap:doc') {
obj._root = KorAP.Doc.create(undefined, json);
}
else if (json['@type'] == 'korap:docGroup') {
obj._root = KorAP.DocGroup.create(undefined, json);
}
else {
KorAP.log(813, "Collection type is not supported");
return;
};
}
else {
// Add unspecified object
obj._root = KorAP.UnspecifiedDoc.create();
};
// Add root element to root node
obj.element().appendChild(
obj._root.element()
);
return obj;
},
root : function () {
return this._root;
},
element : function () {
if (this._element !== undefined)
return this._element;
this._element = document.createElement('div');
this._element.setAttribute('class', 'vc');
return this._element;
}
};
KorAP._or = function (e) {
var obj = this.parentNode.refTo;
};
KorAP._and = function (e) {
var obj = this.parentNode.refTo;
};
KorAP._delete = function (e) {
var obj = this.parentNode.refTo;
obj.parent().delOperand(obj);
// Todo: CLEAR ALL THE THINGS!
};
/**
* Operators for criteria
*/
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;
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', KorAP._and, false);
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.addEventListener('click', KorAP._or, false);
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));
delE.addEventListener('click', KorAP._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;
}
};
/**
* 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', 'unspecified');
this.update();
return this._element;
}
};
/**
* Virtual collection doc criterion.
*/
KorAP.Doc = {
_ldType : "doc",
_obj : function () { return KorAP.Doc; },
create : function (parent, json) {
var obj = Object(KorAP.JsonLD).create().upgradeTo(KorAP.Doc).fromJson(json);
if (parent !== undefined)
obj._parent = parent;
return obj;
},
update : function () {
if (this._element === undefined)
return this.element();
// 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()));
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.append(this);
group.append(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;
*/
},
// Deserialize from json
fromJson : function (json) {
if (json === undefined)
return this;
if (json["@type"] !== "korap:doc") {
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 (!KorAP._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;
};
};
return this;
},
key : function (value) {
if (arguments.length === 1)
this._key = value;
return this._key;
},
matchop : function (match) {
if (arguments.length === 1)
this._matchop = match.replace(/^match:/, '');
return this._matchop || "eq";
},
type : function (type) {
if (arguments.length === 1)
this._type = type;
return this._type || "string";
},
value : function (value) {
if (arguments.length === 1)
this._value = value;
return this._value;
},
toJson : function () {
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()
};
}
};
/**
* Virtual collection group criterion.
*/
KorAP.DocGroup = {
_ldType : "docGroup",
create : function (parent, json) {
var obj = Object.create(KorAP.JsonLD).upgradeTo(KorAP.DocGroup);
obj._operands = [];
obj.fromJson(json);
if (parent !== undefined)
obj._parent = parent;
return obj;
},
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') {
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;
};
KorAP.log(701, "JSON-LD group has no @type attribute");
return;
case "korap:doc":
// 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":
// 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:
KorAP.log(812, "Operand not supported in document group");
return;
};
},
update : function () {
if (this._operands.length == 1) {
if (this.parent() !== undefined) {
return this.parent().replaceOperand(
this,
this.getOperand(0)
);
};
};
if (this._element === undefined)
return this;
var group = this._element;
group.setAttribute('data-operation', this.operation());
_removeChildren(group);
// Append operands
for (var i in this._operands) {
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');
this.update();
return this._element;
},
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._op || 'and';
},
operands : function () {
return this._operands;
},
getOperand : function (index) {
return this._operands[index];
},
// Replace operand
replaceOperand : function (group, obj) {
for (var i in this._operands) {
if (this._operands[i] === group) {
this._operands[i] = obj;
group.destroy();
return this.update();
}
};
return false;
},
// Delete operand from group
delOperand : function (obj) {
for (var i in this._operands) {
if (this._operands[i] === obj) {
this._operands.splice(i,1);
// Destroy object for cyclic references
obj.destroy();
// Todo: Update has to check
// that this may mean the group is empty etc.
return this.update();
};
};
// Operand not found
return undefined;
},
// Deserialize from json
fromJson : function (json) {
if (json === undefined)
return this;
if (json["@type"] !== "korap:docGroup") {
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 in this._operands) {
opArray.push(this._operands[i].toJson());
};
return {
"@type" : "korap:" + this.ldType(),
"operation" : "operation:" + this.operation(),
"operands" : opArray
};
}
};
// Abstract JsonLD object
KorAP.JsonLD = {
create : function () {
return Object.create(KorAP.JsonLD);
},
/**
* 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;
return this._parent;
},
// Destroy object - especially for
// acyclic structures!
// I'm a paranoid chicken!
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 in this._operands)
this.getOperand(i).destroy();
};
},
// Be aware! This may be cyclic
operators : function (and, or, del) {
if (arguments === 0)
return this._ops;
this._ops = KorAP.Operators.create(
and, or, del
);
this._ops.parent(this);
return this._ops;
},
toJson : function () {
return {
// Unspecified object
"@type" : "korap:" + this.ldType()
};
}
};
}(this.KorAP));