import { createElement, createLinks } from "../../utils/utils.js";
/**
* A collection of metadata table utils
*
* @author rhess <robin.hess@awi.de>
*/
// private functions
/**
* private method for checking if a feature has valid information
* @param {string} v
* @memberof vef.ui.table
* @private
*/
function isSet_(v) {
if ((v != null) && (v != undefined) && (v != 'null') && (v != 'undefined') && (v.length || Number.isFinite(v) || (typeof v == "boolean"))) {
return true;
} else {
return false;
}
}
// public exports
/**
* Helper method for creating a single html table row,
*
* @param {string} k key
* @param {string} v value
* @param {boolean} showEmpty show empty properties if true
* @memberof vef.ui.table
*/
function createTableRow(k, v, showEmpty) {
let toolTip = v;
if (typeof v === 'string') {
v = createLinks(v.trim());
// dirty check for indicators of html markup -> don't show tooltip
toolTip = (toolTip.startsWith("<") && toolTip.endsWith(">")) ? "" : toolTip.trim();
if (toolTip.length > 0) toolTip = toolTip.replaceAll("'", "'");
}
if (showEmpty || isSet_(v)) {
return `<tr><td title='${k}'>${k}</td><td title='${toolTip}'>${v}</td></tr>`;
} else {
return '';
}
}
/**
* @param {HTMLElement} container table row or container element with table rows
*
* Add a copy button to an existing HTMLElement table additions
* @memberof vef.ui.table
*/
export function addCopyButton(container) {
// only initialize if the feature is available
if (!navigator.clipboard) return;
// find table rows
let tableRows = [];
if (container.nodeName.toLowerCase() == "tr") {
tableRows.push(container);
} else {
tableRows = container.querySelectorAll("tr");
}
for (let i = 0; i < tableRows.length; ++i) {
if (tableRows[i].classList.contains("no-copy")) continue;
addButtonToTableRow(tableRows[i], ["far", "fa-copy"], "Copy Value", "Copied", (keyCell, valueCell) => {
const value = valueCell.innerText.trim();
navigator.clipboard.writeText(value);
});
}
}
/**
* @param {HTMLElement} container table row or container element with table rows
*
* Add an expansion button to an existing HTMLElement table additions
* @memberof vef.ui.table
*/
export function addExpansionButton(container) {
// find table rows
let tableRows = [];
if (container.nodeName.toLowerCase() == "tr") {
tableRows.push(container);
} else {
tableRows = container.querySelectorAll("tr");
}
for (let i = 0; i < tableRows.length; ++i) {
if (!tableRows[i].getAttribute("data-button-expansion")) continue;
const buttonClassOpen = "fa-chevron-down";
const buttonClassClose = "fa-chevron-right";
addButtonToTableRow(tableRows[i], ["fas", buttonClassClose], "Expand", "", (keyCell, valueCell) => {
let buttonContainer = keyCell.querySelector(".tr-buttons a")
const shouldBeExpanded = valueCell.toggleAttribute("data-button-state-expanded");
if (shouldBeExpanded) {
valueCell.style.whiteSpace = "pre-wrap";
buttonContainer.classList.add(buttonClassOpen);
buttonContainer.classList.remove(buttonClassClose);
}
else {
valueCell.style.whiteSpace = "nowrap";
buttonContainer.classList.add(buttonClassClose);
buttonContainer.classList.remove(buttonClassOpen);
}
});
}
}
/**
* Helper method to add a button to a table row in the metadata table
* Buttons will be added from right to left
*
* @param {HTMLElement} tr
* @param {string[]} classes
* @param {string} title
* @param {string} message
* @param {function} callback callback(keyCell, ValueCell);
*
* @memberof vef.ui.table
*/
export function addButtonToTableRow(tr, classes, title, message, callback) {
const keyCell = tr.querySelector("td:first-child");
const valueCell = tr.querySelector("td:last-child");
let buttonContainer = keyCell.querySelector(".tr-buttons");
if (!buttonContainer) {
buttonContainer = document.createElement("span");
buttonContainer.classList.add("tr-buttons");
keyCell.appendChild(buttonContainer);
}
const a = document.createElement("a");
a.href = "#";
a.title = title || "";
classes.push("tr-button");
for (let i = 0; i < classes.length; ++i) {
a.classList.add(classes[i]);
}
a.addEventListener("click", e => {
e.preventDefault();
// remove old message span if available
const oldMsgSpan = valueCell.querySelector(".tr-message");
if (oldMsgSpan) oldMsgSpan.remove();
// apply callback before adding the message
callback(keyCell, valueCell);
if (message) {
const msgSpan = document.createElement("span");
msgSpan.classList.add("tr-message");
msgSpan.innerText = message;
valueCell.appendChild(msgSpan);
setTimeout(() => msgSpan.remove(), 3000);
}
});
buttonContainer.appendChild(a);
}
/**
* Function for initializing editing for a metadataTable
*
* @memberof vef.ui.table
* @param {HTMLElement} html
* @param {object} data
* @param {string[]} excludedKeys
* @returns {HTMLElement} wrapper
*/
export function initAnnotations(container, data, excludedKeys = []) {
const createAnnotationRow = (k, v) => {
const row = document.createElement("tr");
row.classList.add("annotation-row");
row.innerHTML = `<td><input spellcheck="false" type="text" value="${k || ''}"/></td><td><input type="text" value="${v || ''}"/></td></tr>`
const inputKey = row.querySelector("td:first-child input");
const inputValue = row.querySelector("td:last-child input");
const clearRow = e => {
document.body.removeEventListener("click", clickListener);
inputKey.removeEventListener("keypress", keyEvent);
inputValue.removeEventListener("keypress", keyEvent);
row.remove();
};
const apply = () => {
const key = inputKey.value.trim();
const value = inputValue.value.trim();
if (key.length && !excludedKeys.includes(key) && (!(key in data) || (k == key))) {
if ((typeof k == "string") && k.length && (k != key) && (k in data)) delete data[k];
data[key] = value;
const newRow = createElement(createTableRow(key, value, true));
addCopyButton(newRow);
row.insertAdjacentElement("afterend", newRow);
clearRow();
} if (key.length == 0) {
if ((typeof k == "string") && k.length && (k != key) && (k in data)) delete data[k];
clearRow();
} else {
inputKey.classList.add("error");
}
};
const clickListener = (e) => {
if ((e.target == inputKey) || (e.target == inputValue)) {
e.preventDefault();
e.stopPropagation();
} else {
apply();
}
};
const keyEvent = e => {
if (e.which == 13) apply();
};
setTimeout(() => {
inputKey.addEventListener("keypress", keyEvent);
inputValue.addEventListener("keypress", keyEvent);
document.body.addEventListener("click", clickListener);
}, 0);
return row;
};
const table = container.querySelector("tbody");
table.addEventListener("click", e => {
e.stopPropagation();
let tr = e.target;
while ((tr.nodeName.toLowerCase() != "tr") && table.contains(tr.parentElement)) {
if (tr.nodeName.toLowerCase() == "a") return;
tr = tr.parentElement;
}
if ((tr.nodeName.toLowerCase() == "tr") && !tr.classList.contains("annotation-row")) {
const k = tr.querySelector("td:first-child").innerText.trim();
if (excludedKeys.includes(k)) return;
const v = tr.querySelector("td:last-child").innerText.trim();
const row = createAnnotationRow(k, v);
tr.insertAdjacentElement("afterend", row);
tr.remove();
}
}, false);
const addButton = document.createElement("a");
addButton.classList.add("popup-link");
addButton.innerHTML = "<i class='fas fa-plus'></i> add property";
addButton.href = "#";
addButton.style.marginTop = "0px";
addButton.addEventListener("click", e => {
e.preventDefault();
const row = createAnnotationRow();
table.appendChild(row);
});
container.appendChild(addButton);
return container
}