Source: ui/slider/SliderHeader.js

import { UiElement } from "../UiElement.js";
import "./SliderHeader.css";

export { SliderHeader };

/**
 * Helper class for creating a header for the sliders with an operator Dropdown
 * 
 * options = {
 *     title: string,
 *     operators: string[],
 *     defaultOperator: string,
 *     useInput: boolean,
 *     inputPattern: string,
 *     inputlength: number,
 *     inputType: string
 * }
 * 
 * @author rhess <robin.hess@awi.de>
 * @memberof vef.ui.slider
 */
class SliderHeader extends UiElement {
    constructor(target, options) {
        super(target, {
            "select": [],
            "input": [],
            "change": [],
            "arrow_up": [],
            "arrow_down": [],
            "arrow_up_shift": [],
            "arrow_down_shift": [],
            "arrow_up_shift_ctrl": [],
            "arrow_down_shift_ctrl": [],
            "pos1": [],
            "end": []
        });

        this.activeOperator_ = null
        this.useInput_ = options.useInput;
        this.inputType_ = options.inputType;
        this.inputLength_ = options.inputLength;

        this.initElement_(options.title || "");
        this.setOperators(options.operators || ["=", "<", "≤", ">", "≥", "-"]);
        this.select(options.defaultOperator || this.operators_[0]);

        if (this.useInput_) this.initInputEvents_();
        if (options.inputPattern) this.setPattern(options.inputPattern);
    }

    /**
     * @param {string} title
     * @private 
     */
    initElement_(title) {
        this.setClass("slider-header");
        this.setHtml(`
            <div class="header-title">${title}:</div>
            <${this.useInput_ ? ("input type='" + (this.inputType_ || "text") + "' required") : "div"} 
              ${this.inputLength_ ? (" maxlength='" + this.inputLength_ + "'") : ""}
              class="value-left"></${this.useInput_ ? "input" : "div"}>
            <div class="operator-dropdown">
                <div class="drop-down-title"></div>
                <div class="drop-down-list"></div>
            </div>
            <${this.useInput_ ? ("input type='" + (this.inputType_ || "text") + "' required") : "div"}
              ${this.inputLength_ ? (" maxlength='" + this.inputLength_ + "'") : ""}
              class="value-right"></${this.useInput_ ? "input" : "div"}>
        `);
    }

    fireChange_(inputType, e) {
        if (e.currentTarget.checkValidity()) {
            this.fire("change", {
                "type": inputType,
                "value": e.currentTarget.value
            });
        }
    }

    fireInput_(inputType, e) {
        this.fire("input", {
            "type": inputType,
            "value": e.currentTarget.value
        });
    }

    /**
     * Init the input events
     * @private 
     */
    initInputEvents_() {
        if (this.useInput_) {

            // generator function for keypress event
            const _keydown = (type) => {
                return (e) => {
                    if (( e.ctrlKey && e.shiftKey && e.code == "ArrowUp")) {
                        e.preventDefault();
                        this.fire("arrow_up_shift_ctrl", type);
                    }else if ((e.ctrlKey && e.shiftKey && e.code == "ArrowDown")) {
                        e.preventDefault();
                        this.fire("arrow_down_shift_ctrl", type);
                    }else if ((e.shiftKey && e.code == "ArrowUp")) {
                        e.preventDefault();
                        this.fire("arrow_up_shift", type);
                    }else if ((e.shiftKey && e.code == "ArrowDown")) {
                        e.preventDefault();
                        this.fire("arrow_down_shift", type);
                    }else if ((e.code === "Home")) {
                        e.preventDefault();
                        this.fire("pos1", type);
                    }else if ((e.code === "End")) {
                        e.preventDefault();
                        this.fire("end", type);
                    }else if (e.code == "ArrowUp") {
                        e.preventDefault();
                        this.fire("arrow_up", type);
                    } else if ((e.code == "ArrowDown")) {
                        e.preventDefault();
                        this.fire("arrow_down", type);
                    }
                }
            };

            const begin = this.query(".value-left");
            begin.addEventListener("input", e => this.fireInput_("begin", e));
            begin.addEventListener("change", e => this.fireChange_("begin", e));
            begin.addEventListener("keydown", _keydown("begin"));

            const end = this.query(".value-right");
            end.addEventListener("input", e => this.fireInput_("end", e));
            end.addEventListener("change", e => this.fireChange_("end", e));
            end.addEventListener("keydown", _keydown("end"));

        }
    }

    /**
     * get the currently selected operator
     * 
     * @returns {string}
     */
    getOperator() {
        return this.activeOperator_;
    }

    /**
     * select an operator
     * 
     * @param {string} operator 
     */
    select(operator) {
        const dropdown = this.query(".operator-dropdown");
        const items = dropdown.querySelectorAll(".list-item");

        for (let i = 0; i < items.length; ++i) {
            let item = items[i];
            if (item.dataset.value == operator) {
                item.style.display = "none";
            } else {
                item.style.display = "block";
            }
        }

        dropdown.querySelector(".drop-down-title").innerText = operator;
        this.activeOperator_ = operator;

        this.fire("select", operator);
    }

    /**
     * Set regex for string validation in the input
     * 
     * @param {string} regex 
     */
    setPattern(regex) {
        if (this.useInput_) {
            this.query(".value-left").pattern = regex;
            this.query(".value-right").pattern = regex;
        }
    }

    /**
     * populate the operator dropdown
     * 
     * @param {string[]} operators 
     */
    setOperators(operators) {
        this.operators_ = operators;

        const tooltips = {
            "-": "time range",
            "=": "exact match",
            "<": "less than",
            "≤": "less than or equal to",
            ">": "greater than",
            "≥": "greater than or equal to"
        };

        const list = this.query(".operator-dropdown .drop-down-list");
        list.innerHTML = "";

        for (let i = 0; i < operators.length; ++i) {
            const operator = operators[i];
            if (tooltips[operator]) {
                const item = document.createElement("div");
                item.innerText = operator;
                item.title = tooltips[operator];
                item.dataset.value = operator;
                item.classList.add("list-item");
                item.addEventListener("click", (e) => {
                    e.preventDefault();
                    this.select(operator);
                });
                list.appendChild(item);
            }
        }
    }

    /**
     * Set a single value
     * 
     * @param {string} type begin or end
     * @param {*} value 
     */
    setValue(type, value) {
        const selector = (type == "begin") ? "left" : "right";
        const el = this.query(".value-" + selector);
        const isSet = (value !== null) && (value !== undefined);

        if (el.nodeName == "INPUT") {
            el.value = (isSet) ? value : "";
            el.disabled = !isSet;
        } else {
            el.innerText = (isSet) ? value : "";
        }
    }

    /**
     * set the values in the header
     * 
     * @param {object} val {begin, end} 
     */
    setValues(val) {
        if ([">", "<", "="].includes(this.activeOperator_)) {
            this.setValue("begin", null);
            this.setValue("end", val.begin);
        } else {
            this.setValue("begin", val.begin);
            this.setValue("end", val.end);
        }
    }
}