Select Git revision
-
VISHESH0932 authoredVISHESH0932 authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
caosdb.js 39.80 KiB
/*
* ** header v3.0
* This file is a part of the LinkAhead Project.
*
* Copyright (C) 2018-2020 Alexander Schlemmer
* Copyright (C) 2018 Research Group Biomedical Physics,
* Max-Planck-Institute for Dynamics and Self-Organization Göttingen
* Copyright (C) 2019-2022 IndiScale GmbH (info@indiscale.com)
* Copyright (C) 2019-2022 Timm Fitschen (t.fitschen@indiscale.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ** end header
*/
'use strict';
/**
* JavaScript client for LinkAhead
*
* Dependency: jquery
* Dependency: webcaosdb
*
* TODO:
* - Check whether everything works when previews are opened.
*/
/**
* Return true iff the current session has an authenticated user. This is the
* case, iff there is a `user` attribute in the server's response and
* subsequently a DIV.caosdb-user-name somewhere in the dom tree.
*
* @return {boolean}
*/
function isAuthenticated() {
return document.getElementsByClassName("caosdb-user-name").length > 0 && document.getElementsByClassName("caosdb-user-name")[0].innerText.trim().length > 0;
}
/**
* Return the name of the user currently logged in.
*
* @return Name of the user.
*/
function getUserName() {
const userElement = document.getElementsByClassName("caosdb-user-name")[0];
if (userElement) {
return userElement.innerText;
} else {
throw new Error("No user is logged in.");
}
}
/**
* Return the realm of the user currently logged in.
*
* @return Realm of the user.
*/
function getUserRealm() {
return document.getElementsByClassName("caosdb-user-realm")[0].innerText;
}
/**
* Return the roles of the user currently logged in.
*
* @return Array containing the roles of the user.
*/
function getUserRoles() {
return Array.from(document.getElementsByClassName("caosdb-user-role")).map(el => el.innerText);
}
/**
* Find out, whether the user that is currently logged in is an administrator.
*
* @return true if the role administration is present.
*/
function userIsAdministrator() {
return userHasRole("administration");
}
/**
* Return true if the user has the role `anonymous`.
*/
function userIsAnonymous() {
return userHasRole("anonymous");
}
/**
* Return true if the user has the role `role`.
*
* @param role
*/
function userHasRole(role) {
return getUserRoles().filter(el => el == role).length > 0;
}
/**
* Return all entities in the current document.
*
* @return A list of document elements.
*/
function getEntities() {
return document.getElementsByClassName("caosdb-entity-panel");
}
/**
* Return the role (Record, RecordType, ...) of element.
* @return A String holding the role or undefined if the role could not be found.
*/
function getEntityRole(element) {
if ($(element).is("[data-entity-role]")) {
return $(element).attr("data-entity-role");
}
if ($(element).is(".caosdb-f-entity-role")) {
return $(element).text();
}
let res = $(element).find("[data-entity-role]");
if (res.length == 1) {
return res.attr("data-entity-role");
}
res = $(element).find(".caosdb-f-entity-role");
if (res.length == 1) {
return res.text();
}
return undefined;
}
/**
* Return the unit of entity or undefined.
*
* @param {HTMLElement} entity
* @return {String}
*/
function getEntityUnit(entity) {
var res = $(entity).find("input.caosdb-f-entity-unit");
if (res.length == 1) {
var x = res.val();
return (x == '' ? undefined : x);
}
return getEntityHeadingAttribute(entity, "unit");
}
/**
* Return the datatype of element.
* If the corresponding data-attribute can not be found undefined is returned.
* @return A string containing the datatype of the element.
*/
function getEntityDatatype(element) {
var is_list = ($(element).find("input.caosdb-f-entity-is-list:checked").length > 0);
var res = $(element).find("select.caosdb-f-entity-datatype");
if (res.length == 1) {
var x = res.val();
if (typeof x !== 'undefined' && x != '' && is_list) {
return "LIST<" + x + ">";
}
return (x == '' ? undefined : x);
}
res = $(element).find(".caosdb-entity-panel-heading[data-entity-datatype]");
if (res.length == 1) {
var x = res.attr("data-entity-datatype");
return (x == '' ? undefined : x);
}
return undefined;
}
/**
* Return the data type of a entity-property element or undefined.
*
* @param {HTMLElement} element - the entity-property element.
*
* @throws {Error} if the data type could not uniquely be determined.
*
* @return {string} the data type
*/
function getPropertyDatatype(element) {
var dt_elem = findElementByConditions(element,
x => x.classList.contains("caosdb-property-datatype"),
x => x.classList.contains("caosdb-preview-container"));
if (dt_elem.length == 1) {
return $(dt_elem[0]).text();
} else if (dt_elem.length > 1) {
throw new Error("The datatype of this property could not uniquely be determined.");
}
// no datatype
return undefined;
}
/**
* Return the name of element.
* If the corresponding label can not be found or the label is ambigious undefined is returned.
* @return A string containing the name of the element.
*/
function getEntityName(element) {
// TODO deprecated class name
if ($(element).find('[data-entity-name]').length == 1) {
return $(element).find('[data-entity-name]')[0].dataset.entityName;
} else if (typeof $(element).find('.caosdb-f-entity-name').val() !== 'undefined') {
return $(element).find('.caosdb-f-entity-name').val();
}
var res = element.getElementsByClassName("caosdb-label-name");
if (res.length == 1) {
return res[0].textContent;
}
return undefined;
}
/**
* Return the path of an entity.
*
* This attribute is always set for file entities.
*
* If the corresponding label can not be found or the label is ambigious undefined is returned.
*
* @param {HTMLElement} entity - entity in HTML representation.
* @return A string containing the path of the entity.
*/
function getEntityPath(element) {
if ($(element).find('[data-entity-path]').length == 1) {
return $(element).find('[data-entity-path]')[0].dataset.entityPath;
}
const path = $(element).find('.caosdb-f-entity-path').val();
if (typeof path !== 'undefined') {
return path;
}
return getEntityHeadingAttribute(element, "path");
}
/**
* Return the checksum of an entity.
*
* This attribute is always set for file entities.
*
* If the corresponding label can not be found or the label is ambigious undefined is returned.
*
* @param {HTMLElement} entity - entity in HTML representation.
* @return A string containing the checksum of the entity.
*/
function getEntityChecksum(element) {
const checksum = $(element).find('.caosdb-f-entity-checksum').val();
if (typeof checksum !== 'undefined') {
return checksum;
}
return getEntityHeadingAttribute(element, "checksum");
}
/**
* Return the size of element. This attribute is always set for file entities.
* If the corresponding label can not be found or the label is ambigious undefined is returned.
* @return A string containing the size of the element.
*/
function getEntitySize(element) {
// TODO: check if this if block is needed
// it is analogous to getEntityDescription
// if ($(element).find('[data-entity-size]').length == 1) {
// return $(element).find('[data-entity-size]')[0].dataset.entitySize;
// }
if (typeof $(element).find('.caosdb-f-entity-size').val() !== 'undefined') {
// This is needed for the edit mode to work properly:
return $(element).find('.caosdb-f-entity-size').val();
}
return getEntityHeadingAttribute(element, "size");
}
/**
* Return the id of an entity.
* @param element The element holding the entity.
* @return The id of the entity as a String or undefined if no ID could be found.
* @throws An error if the ID was ambigous.
*/
function getEntityID(element) {
if (typeof element.dataset.entityId !== 'undefined') {
return element.dataset.entityId;
}
// var res = element.getElementsByClassName("caosdb-id");
var res = findElementByConditions(element, x => x.classList.contains("caosdb-id"),
x => x.classList.contains("caosdb-entity-panel-body"));
if (res.length == 1)
return res[0].textContent;
else if (res.length == 0)
return undefined;
throw new Error("id is ambigous for this element!");
}
/**
* Get the version string of an entity.
*
* @param {HTMLElement} an Entity in HTML representation
* (div.caosdb-entity-panel)
* @return {string} the version
*/
var getEntityVersion = function (entity) {
return entity.getAttribute("data-version-id");
}
/**
* Get the id and, if present, the version of an entity.
*
* @param {HTMLElement} an Entity in HTML representation
* (div.caosdb-entity-panel)
* @return {string} <id>[@<version>]
*/
var getEntityIdVersion = function (entity) {
const id = getEntityID(entity);
const version = getEntityVersion(entity);
if (version) {
return `${id}@${version}`;
}
return id;
}
/**
* Take a date, a time and subseconds and format it into a LinkAhead compatible representation.
* @param date A date
* @param time A time
* @param subsec fractions of a seconds part of the time stamp; Must be empty
* string if not used
* @return A LinkAhead compatible representation.
*/
function input2caosdbDate(date, time, subsec) {
let res = date + "T" + time;
if (subsec!=""){
res = res +"." + subsec;
}
return res
}
/**
* Return true if the current user has the given permission for the given
* entity.
*
* @param {HTMLElement} entity
* @return {boolean}
*/
var hasEntityPermission = function (entity, permission) {
if (userHasRole("administration")) {
// administration is a special role. It has * permissions.
return true;
}
const permissions = getAllEntityPermissions(entity);
return permissions.indexOf(permission.toUpperCase()) > -1;
}
/**
* Get all permissions the current user has for this entity.
* @param {HTMLElement} entity
* @return {string[]} array of permissions.
*/
var getAllEntityPermissions = function (entity) {
const permissions = $(entity).find("[data-permission]").toArray().map(x => x.getAttribute("data-permission"));
return permissions;
}
/**
* Take a datetime from caosdb and return a date, a time and the subseconds
* suitable for html inputs.
*
* If the text contains only a date it is returned.
*
* @param text The string from LinkAhead.
* @return A list of strings for the input elements.
*/
function caosdb2InputDate(text) {
if (text.includes("T")) {
var spl = text.trim().split("T");
var subsec=""
if (spl[1].includes(".")) {
subsec = spl[1].split('.')[1]
}
return [spl[0], spl[1].substring(0, 8), subsec];
}
return [text];
}
/**
* Return the id of an href attribute.
* This is needed for retrieving an ID that is contained in a link to another entity.
*
* Some anker tags have a data-entity-id tag instead which is returned.
*
* @param el The element holding the link.
* @return A String holding the ID or undefined if no href attribute could be found.
*/
function getIDfromHREF(el) {
if (el.hasAttribute("href")) {
let attr = el["href"];
let idstr = attr.substring(attr.lastIndexOf("/") + 1);
// Currently the XSLT returns wrong links for missing IDs.
if (idstr.length == 0)
return undefined;
return idstr;
} else if (el.hasAttribute("data-entity-id")) {
return el.getAttribute("data-entity-id");
}
return undefined;
}
/**
* Return an entity heading attribute from an element.
* @param element The element holding the entity.
* @return The value of the entity heading attribute or undefined if it is not present.
*/
function getEntityHeadingAttribute(element, attribute_name) {
var res = element.getElementsByClassName("caosdb-entity-heading-attr");
for (var i = 0; i < res.length; i++) {
var subres = res[i].getElementsByClassName("caosdb-entity-heading-attr-name");
if (subres.length != 1) {
throw "Entity heading attribute does not have a name.";
}
if (subres[0].textContent == attribute_name + ":") {
return res[i].childNodes[1].textContent;
}
}
return undefined;
}
/**
* Return the entity attribute description from an element.
* @param element The element holding the entity.
* @return The value of the description or undefined if it is not present.
*/
function getEntityDescription(element) {
if ($(element).find('[data-entity-description]').length == 1) {
return $(element).find('[data-entity-description]')[0].dataset.entityDescription;
} else if (typeof $(element).find('.caosdb-f-entity-description').val() !== 'undefined') {
// This is needed for the edit mode to work properly:
return $(element).find('.caosdb-f-entity-description').val();
}
return getEntityHeadingAttribute(element, "description");
}
/**
* Return the parents of an entity.
* @param element The element holding the entity.
* @return A list of objects with name and id of the parents.
*/
function getParents(element) {
var res = element.getElementsByClassName("caosdb-parent-name");
var list = [];
for (var i = 0; i < res.length; i++) {
list.push({
id: getIDfromHREF(res[i]),
name: res[i].textContent
});
}
return list;
}
/**
* Find all elements that fulfill a condition.
* Don't traverse elements if except condition is matched.
*
* @param {HTMLElement} element - The start node.
* @param {function} condition - A filter callback which returns `true` for
* each element which should be included.
* @param {function} except - A filter callback which returns `true` for an
* element when the traversing should stop.
*
* @returns {HTMLElement[]} an array of HTMLElements
*/
function findElementByConditions(element, condition, except) {
let found = [];
let echild = element.children;
for (var i = 0; i < echild.length; i++) {
if (condition(echild[i])) {
found.push(echild[i]);
}
if (!except(echild[i])) {
found.push.apply(found, findElementByConditions(echild[i], condition, except));
}
}
return found;
}
function getEntityXML(ent_element) {
var xml = createEntityXML(
getEntityRole(ent_element),
getEntityName(ent_element),
getEntityID(ent_element),
getProperties(ent_element),
getParents(ent_element),
true,
getEntityDatatype(ent_element),
getEntityDescription(ent_element),
getEntityUnit(ent_element),
);
return xml;
}
function getPropertyName(element) {
var name_element = element.getElementsByClassName("caosdb-property-name");
if (name_element.length > 0) {
return name_element[0].textContent;
} else if ($(element).is("[data-property-name]")) {
return $(element).attr("data-property-name");
}
var ret = $(element).find("[data-property-name]").attr("data-property-name");
if (ret && ret.length > 0) {
return ret;
} else {
return undefined;
}
}
/**
* A JSON representation of an entities property.
*
* TODO description, importance
*
* @type {EntityProperty}
* @property {string} id
* @property {string} name
* @property {number} duplicateIndex - Number of properties in the entity with
* the same name.
* @property {HTMLElement} html - An HTML representation of the property.
* @property {string} datatype
* @property {boolean} reference - has a reference datatype?
* @property {string|string[]} value
* @property {string} unit
* @property {boolean} list - has a list datatype?
* @property {string} listDatatype - the datatype of the list elements
*/
/**
* Return a json property object.
*
* @param {HTMLElement} propertyelement - A HTMLElement representing the property.
* property element.
* @param {object} [names] - a map of names tracking the count of usage for
* each name (Default: undefined)
*
* @return {Property}
**/
function getPropertyFromElement(propertyelement, names = undefined) {
let property = {};
let valel = propertyelement.getElementsByClassName("caosdb-f-property-value")[0];
let dtel = propertyelement.getElementsByClassName("caosdb-property-datatype")[0];
let idel = propertyelement.getElementsByClassName("caosdb-property-id")[0];
let unitel = valel.getElementsByClassName("caosdb-unit")[0];
const descel = propertyelement.getElementsByClassName("caosdb-property-description")[0];
property.html = propertyelement;
// name
property.name = getPropertyName(propertyelement);
// description
property.description = descel ? descel.textContent : undefined;
// id
if (idel === undefined) {
property.id = undefined;
} else {
property.id = idel.textContent;
}
// unit
if (typeof unitel == "undefined") {
property.unit = undefined;
} else {
property.unit = unitel.textContent.trim();
}
if (dtel === undefined) {
property.datatype = undefined;
property.list = undefined;
property.reference = undefined;
property.value = "";
} else {
property.datatype = dtel.textContent;
var base_datatypes = ["TEXT", "INTEGER", "DOUBLE", "DATETIME", "BOOLEAN"];
if (property.datatype.substring(0, 4) == "LIST") {
property.list = true;
property.value = [];
var list_datatype = property.datatype.substring(5, property.datatype.length - 1);
property.reference = base_datatypes.filter(el => el == list_datatype).length == 0;
property.listDatatype = list_datatype;
} else {
property.list = false;
property.value = "";
property.reference = base_datatypes.filter(el => el == property.datatype).length == 0;
}
}
if (!property.list && valel.getElementsByClassName("caosdb-f-property-single-raw-value").length == 1) {
valel = valel.getElementsByClassName("caosdb-f-property-single-raw-value")[0];
}
var value_string = undefined;
if (valel && valel.textContent.length > 0) {
value_string = valel.textContent;
} else if (valel && valel.value && valel.value.length > 0) {
// this is the case when the valel is an input coming from edit_mode.
value_string = valel.value;
}
// Needed for multiple properties with the same name:
// It is not set when names is undefined.
if (!(names === undefined)) {
if (names.has(property.name)) {
names.set(property.name, names.get(property.name) + 1);
} else {
names.set(property.name, 0);
}
property.duplicateIndex = names.get(property.name);
}
if (typeof value_string !== "undefined") {
// This is set to true, when there is a reference or a list of references:
if (typeof property.reference === "undefined") {
property.reference = (valel.getElementsByClassName("caosdb-id").length > 0);
}
if (property.list) {
// list datatypes
let listel;
// list of anything but references
listel = findElementByConditions(valel, x => x.classList.contains("caosdb-f-property-single-raw-value"),
x => x.classList.contains("caosdb-preview-container"));
for (var j = 0; j < listel.length; j++) {
property.value.push(listel[j].textContent);
}
} else if (property.reference && valel.getElementsByTagName("a")[0]) {
// reference datatypes
property.value = getIDfromHREF(valel.getElementsByTagName("a")[0]);
} else {
// all other datatypes
property.value = value_string;
}
}
return property;
}
/**
* Get all the properties from an entity.
*
* @param {HTMLElement} element The element holding the entity.
*
* @return {HTMLElement[]} a list of properties in HTML representation.
*/
function getPropertyElements(element) {
return findElementByConditions(element,
x => x.classList.contains("caosdb-f-entity-property"),
x => x.classList.contains("caosdb-preview-container"));
}
/**
* Return JavaScript object representations of the properties of an entity.
*
* @param {HTMLElement} element - The element holding the entity.
* @return {object[]} A list of objects for the properties according to the
* following specification.
*
*
* Specification of properties:
* prop = {
* name: ... // String, name of the property
* id: ... // String, id of the property
* value: ... // value of the property
* datatype: ... // full datatype, e.g. INTEGER or LIST<Uboot>
* duplicateIndex: ... // Integer starting from 0 and increasing for multiple properties
* reference: ... // Is this holding an ID of a reference? (boolean)
* list: ... // Is this a list? (boolean)
* }
*
*
*/
function getProperties(element) {
var res = getPropertyElements(element);
var list = [];
var names = new Map();
for (var i = 0; i < res.length; i++) {
let property = getPropertyFromElement(res[i], names);
list.push(property);
}
return list;
}
/**
* Construct XPath expression from selectors.
*
* Used by getPropertyValues.
*
* @param {String[][]} selectors
* @return {String[]} XPath expressions.
*/
var _constructXpaths = function (selectors) {
const xpaths = [];
for (let sel of selectors) {
var expr = "Property";
if (sel[0] == "id") {
expr = "";
}
for (let i = 0; i < sel.length; i++) {
const segment = sel[i];
if (segment == "id") {
expr += `@id`;
} else if (segment) {
expr += `[@name='${segment}']`;
}
if (i+1 < sel.length) {
expr += "//Property"
}
}
xpaths.push(expr);
}
return xpaths;
}
/**
* Return a table where each row represents an entity and each column a property.
*
* This also works for entities from select queries, where the properties
* are deeply nested, e.g. when each entity references a "Geo Location"
* record which have latitude and longitude properties:
*
* `getPropertyValues(entities, [["Geo Location", "latitude"], ["Geo Location", "longitude"]])`
*
* When the entitieshave normal non-list references to the "Geo Location" the
* result looks like this:
*
* `[[ "50", "-39"], ...]`
*
* When the entities have a LIST of thre Geo Locations the result looks like
* this:
*
* `[[[ "50", "51", "52"], [ "-39", "-38", "-37" ]], ...]`.
*
* Use empty strings for selector elements when the property name is irrelevant:
*
* `getPropertyValues(entities, [["", "latitude"], ["", "longitude"]])`
*
* Limitations:
*
* 1. Currently, this implementation assumes that properties (and subproperties
* for that matter) have unique names, entity-wide and do have a LIST
* datatype.
*
* 2. It only handles one of the many special cases, which is "id". Other
* special cases ("name", "description", "unit", etc.) are to be added when
* needed.
*
* @param {XMLElement[]} entities
* @param {String[][]} selectors
* @return {String[][]} A table of the property values for each entity (index
* order is `[row][column]`). Each row is an entity, each column is a value
* (or an array of values, when the entity has list properties).
*/
var getPropertyValues = function (entities, selectors) {
// @review Florian Spreckelsen 2022-05-06
const entity_iter = entities.evaluate("/Response/Record", entities);
const table = [];
const xpaths = _constructXpaths(selectors)
var current_entity = entity_iter.iterateNext();
while (current_entity) {
const row = [];
for (let expr of xpaths) {
const property_iter = entities.evaluate(expr, current_entity);
var property = property_iter.iterateNext();
if (typeof property !== "undefined" && property !== null) {
// handle lists and single values
var values = [];
while (property !== null) {
values.push(property.textContent.trim());
property = property_iter.iterateNext();
}
if(values.length < 2) {
// wasn't a list
values = values[0];
}
row.push(values);
} else {
row.push(undefined)
}
}
table.push(row);
current_entity = entity_iter.iterateNext();
}
return table;
}
/**
* Sets a property with some basic type checking.
*
* @param valueelement The dom element where the text content is to be set.
* @param property The new property.
* @param propold The old property belonging to valueelement.
*/
function setPropertySafe(valueelement, property, propold) {
const serverstring = connection.getBasePath() + "Entity/";
if (propold.list) {
if (property.value.length === undefined) {
throw ("New property must be a list.");
}
// Currently problems, when list is set to [] and afterwards to something different.
if (propold.reference) {
var finalstring;
if (property.value.length == 0) {
finalstring = "";
} else {
finalstring = '';
for (var i = 0; i < property.value.length; i++) {
finalstring += '<a class="btn btn-secondary btn-sm caosdb-resolvable-reference" href="' + serverstring + property.value[i] + '"><span class="caosdb-id">' + property.value[i] + '</span><span class="caosdb-resolve-reference-target" /></a>';
}
}
valueelement.getElementsByClassName("caosdb-value-list")[0].getElementsByClassName("caosdb-overflow-content")[0].innerHTML = finalstring;
} else {
throw ("Not Implemented: Lists with no references.");
}
} else if (propold.reference) {
var llist = valueelement.getElementsByTagName("a");
if (llist.length > 0) {
var ael = llist[0];
ael.setAttribute("href", serverstring + property.value);
ael.innerHTML = '<span class="caosdb-id">' + property.value + '</span><span class="caosdb-resolve-reference-target" />';
} else {
finalstring = '<a class="btn btn-secondary btn-sm caosdb-resolvable-reference" href="' + serverstring + property.value + '"><span class="caosdb-id">' + property.value + '</span><span class="caosdb-resolve-reference-target" /></a>';
valueelement.innerHTML = finalstring;
preview.init();
}
} else {
valueelement.innerHTML = "<span class='caosdb-f-property-single-raw-value caosdb-f-property-text-value caosdb-v-property-text-value'>" + property.value + "</span>";
}
}
/**
* Set a property in the dom model.
* @author Alexander Schlemmer
* @param element The document element of the record.
* @param property The new property as an object.
* @return The number of properties that were set.
*
* If multiple properties with the same name exist and the property
* to be set has not duplicateIndex, all properties will be set to that value.
*/
function setProperty(element, property) {
var elementp = element.getElementsByClassName("caosdb-properties")[0];
var res = elementp.getElementsByClassName("list-group-item");
var dindex = property.duplicateIndex;
var counter = 0;
for (var i = 0; i < res.length; i++) {
if (res[i].classList.contains("caosdb-properties-heading")) continue;
let propold = getPropertyFromElement(res[i]);
if (propold.name === property.name) {
if (dindex > 0) {
dindex--;
continue;
}
setPropertySafe(res[i].getElementsByClassName("caosdb-f-property-value")[0],
property,
propold);
counter++;
}
}
return counter;
}
/**
* Get a property value by name from an Entity in HTML representation.
*
* @param {HTMLElement} element - The element holding the entity.
* @param {string} property_name - The name of the property.
* @param {boolean} [case_sensitive=True] - If true search for property names
* case-sensitively. Otherwise neglect the case ("A" and "a" are
* equivalent).
* @returns {string} The value of the the property with property_name or `undefined` when this property is not available for this entity.
*/
function getProperty(element, property_name, case_sensitive = true) {
var props;
if (case_sensitive) {
props = getProperties(element).filter(el => el.name == property_name);
} else {
props = getProperties(element).filter(el => el.name.toLowerCase() == property_name.toLowerCase());
}
if (props.length == 0) {
return undefined;
}
return props[0].value;
}
/**
* Helper function for setting an id and or a name if contained in parent.
* @param parentElement The element which is to recieve the attributes.
* @param parent The object possibly containing an id and or a name.
*/
function _setDescriptionNameID(parentElement, parent) {
if (typeof parent.id !== 'undefined' && parent.id !== '') {
parentElement.setAttribute("id", parent.id);
}
if (typeof parent.name !== 'undefined' && parent.name !== '') {
parentElement.setAttribute("name", parent.name);
}
if (typeof parent.description !== 'undefined' && parent.description !== '') {
parentElement.setAttribute("description", parent.description);
}
}
/**
* Append a parent node to an XML document.
* @see getParents
* @param doc A document for the XML.
* @param element The element to append to.
* @param parent An object containing a name and or an id.
*/
function appendParent(doc, element, parent) {
var parentElement = document.createElementNS(undefined, "Parent");
_setDescriptionNameID(parentElement, parent);
element.appendChild(parentElement);
}
/**
* Append a text node with name name and value value to element element.
* @param doc A document for the XML.
* @param element
* @param name
* @param value
*/
function appendValueNode(doc, element, name, value) {
let el = document.createElementNS(undefined, name);
let valel = document.createTextNode(value);
el.appendChild(valel);
element.appendChild(el);
}
/**
* Append a property node to an XML document.
* @see getProperties
* @param doc An document for the XML.
* @param element The element to append to.
* @param property An object specifying a property.
*/
function appendProperty(doc, element, property, append_datatype = false) {
var propertyElement = document.createElementNS(undefined, "Property");
_setDescriptionNameID(propertyElement, property);
if (append_datatype && typeof property.datatype !== "undefined") {
propertyElement.setAttribute("datatype", property.datatype);
}
if (typeof property.unit !== 'undefined') {
propertyElement.setAttribute("unit", property.unit);
}
if (!(property.value === undefined)) {
if (("list" in property && property.list) || property.value instanceof Array) {
if (property.value instanceof Array) {
for (var i = 0; i < property.value.length; i++) {
appendValueNode(doc, propertyElement, "Value", property.value[i]);
}
} else {
appendValueNode(doc, propertyElement, "Value", property.value);
}
} else {
let valel = document.createTextNode(property.value);
propertyElement.appendChild(valel);
}
}
element.appendChild(propertyElement);
}
/**
* Return a new Document.
*
* Helper function.
*
* @param {string} root - the new root element.
* @returns {Document} the new document.
*/
function _createDocument(root) {
return document.implementation.createDocument(null, root, null);
}
/**
* Create an XML for an entity.
* This function uses the object notation.
* @see getProperties
* @see getParents
* @param role Record, RecordType, Property or File (in case of files the three file arguments must be used!)
* @param name The name of the entity. Can be undefined.
* @param id The id of the entity. Can be undefined.
* @param properties A list of properties.
* @param parents A list of parents.
* @param description A description for this entity.
* @return {Document|DocumentFragment} - An xml document holding the newly
* created entity.
*
*/
function createEntityXML(role, name, id, properties, parents,
append_datatypes = false, datatype = undefined, description = undefined,
unit = undefined,
file_path = undefined, file_checksum = undefined, file_size = undefined) {
var doc = _createDocument(role);
var nelnode = doc.children[0];
_setDescriptionNameID(nelnode, {
name: name,
id: id,
description: description,
});
if (typeof datatype !== 'undefined' && datatype.length > 0) {
$(nelnode).attr("datatype", datatype);
}
if (typeof unit !== 'undefined' && unit.length > 0) {
$(nelnode).attr("unit", unit);
}
if (!(parents === undefined)) {
for (var i = 0; i < parents.length; i++) {
appendParent(doc, nelnode, parents[i]);
}
}
if (!(properties === undefined)) {
for (var i = 0; i < properties.length; i++) {
appendProperty(doc, nelnode, properties[i], append_datatypes);
}
}
if (role.toLowerCase() == "file") {
/*
File path, checksum and size are needed for File entities.
An error is raised when these arguments are not set.
*/
if (file_path === undefined || file_checksum === undefined || file_size === undefined) {
throw "Path, checksum and size must not be undefined in case of file entities.";
}
$(nelnode).attr("path", file_path);
$(nelnode).attr("checksum", file_checksum);
$(nelnode).attr("size", file_size);
}
return doc;
}
/**
* Create an XML for a file entity.
* This is a convenience function for creating XML from file entities.
* This function uses the object notation.
* @see getProperties
* @see getParents
* @param name The name of the entity. Can be undefined.
* @param id The id of the entity. Can be undefined.
* @param parents A list of parents.
* @param file_path The path of the file in the LinkAhead file system.
* @param file_checksum The checksum of the file.
* @param file_size The size of the file in bytes.
* @param description A description for this entity.
* @return {Document|DocumentFragment} - An xml document holding the newly
* created entity.
*
*/
function createFileXML(name, id, parents,
file_path, file_checksum, file_size,
description = undefined) {
return createEntityXML("File", name, id, {}, parents,
false, undefined, description, undefined,
file_path, file_checksum, file_size);
}
/**
* Helper function to wrap xml documents into another node which could e.g. be
* Update, Response, Delete.
*
* @param {string} root - The name of the newly created document root node.
* @param {Document[]|XMLDocumentFragment[]} xmls The xml documents.
* @return {Document} A new xml document.
*/
function wrapXML(root, xmls) {
xmls = caosdb_utils.assert_array(xmls, "param `xmls`", true);
caosdb_utils.assert_string(root, "param `root`");
var doc = _createDocument(root);
for (var i = 0; i < xmls.length; i++) {
doc.firstElementChild.appendChild(xmls[i].firstElementChild);
}
return doc;
}
/**
* Convert this xml document into an update.
* @param {(Document[]|DocumentFragment[])} xmls - Array of xml documents.
* @return {(Document|DocumentFragment)} A new xml document.
*/
function createUpdate(xmls) {
return wrapXML("Request", xmls);
}
/**
* Concat and convert these xml documents into an insert request body for LinkAhead server.
*
* @param {(Document[]|DocumentFragment[])} xmls - Array of xml documents.
* @return {(Document|DocumentFragment)} A new xml document.
*/
function createInsert(xmls) {
return wrapXML("Request", xmls);
}
/**
* Convert this xml document into a response.
*
* @param {(Document[]|DocumentFragment[])} xmls - Array of xml documents.
* @return {(Document|DocumentFragment)} A new xml document.
*/
function createResponse(xmls) {
return wrapXML("Response", xmls);
}
/**
* Retrieve an entity by using an id or a name.
*
* TODO merge with connection.retrieveEntityById
*
* @param id The id of the entity. Can be undefined when name is used.
* @param name The name of the entity. Can be undefined when id is used.
* @return The element holding that entity.
*/
async function retrieve(id = undefined, name = undefined) {
var retstr = id;
if (id === undefined) {
retstr = name;
}
let entities = await connection.get("Entity/" + retstr);
return transformation.transformEntities(entities);
}
/**
* Query the database for querytext.
* @param querytext The search query.
* @return An array of entities.
*/
async function query(querytext) {
let entities = await connection.get("Entity/?query=" + querytext);
return transformation.transformEntities(entities);
}
/**
* Retrive one entity given by ID id
* and transform them into single entity objects.
* @param id The id.
* @return An array of entities.
*/
async function retrieve_dragged_property(id) {
let entities = await connection.get("Entity/" + id);
return transformation.transformProperty(entities);
}
/**
* Update an entity using its xml representation.
* @param xml The xml of the entity which will be automatically wrapped with an Update.
*/
async function update(xml) {
var wrapped = createUpdate(xml);
return await transaction.updateEntitiesXml(wrapped);
}
/**
* Restore an old version of an entity using an xml representation.
* First, the old version is retrieved and the current version is set to the
* old one.
* @param versionid The version id (e.g. 123@abbabbaeff23322) of the version of
* the entity which shall be restored.
*/
async function restore_old_version(versionid){
// retrieve entity
var ent = await transaction.retrieveEntityById(versionid);
if (ent === undefined){
throw new Error(`Entity with version id ${versionid} could not be retrieved.`);
}
// remove unwanted tags (Version and Permissions)
ent.getElementsByTagName("Version")[0].remove();
var permissions = ent.getElementsByTagName("Permissions");
for (let i = permissions.length-1; i >=0 ; i--) {
permissions[i].remove();
}
// use XML to update entity/restore old version
const doc = _createDocument("Request");
doc.firstElementChild.appendChild(ent);
reps = await transaction.updateEntitiesXml(doc);
if (reps.getElementsByTagName("Error").length>0) {
throw new Error(`Could not restore the Entity to the version ${versionid}.`);
}
}
/**
* Insert an entity in xml representation.
*
* @param {(Document[]|DocumentFragment[])} xml - xml of the entities which
* will be automatically wrapped with an Insert.
* @returns {Document} server response.
*/
async function insert(xml) {
var wrapped = createInsert(xml);
return await transaction.insertEntitiesXml(wrapped);
}