Source: ui/table/Utils.js

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("'", "&#39;");
    }

    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
}