Source: map/importer/ui/DwsInput.js

import { generateTable } from "../../../utils/template/functions/common.js";
import { UiElement } from "../../../ui/UiElement.js";
import { roundNumber } from "../../../utils/utils.js";
import { Window } from "../../../ui/Window.js";
import { DWS_DEFAULT_URL } from "../../../data/DwsLoader.js";
import "./DwsInput.css";

export { DwsInput }

/**
 * HTML input for searching URNs in the DWS
 *
 * events
 * - select_urn: fired when a URN is selected from the List
 *
 * @memberof vef.map.importer.ui
 * 
 */
class DwsInput extends UiElement {

    static dropdowns = [];

    /**
     * options = {
     *   dwsUrl: string,
     *   closeable: boolean,
     *   staticQuery: string
     * }
     * 
     * @param {HTMLElement | string} target target element
     * @param {object} options 
     */
    constructor(target, options = {}) {
        super(target, {
            "select_urn": []
        });

        this.options = Object.assign({
            dwsUrl: null,
            staticQuery: "",
            closeable: true
        }, options || {});

        this.cache_ = null;
        this.activeDevice = null;
        this.staticQuery = this.options.staticQuery;

        this.initHTMLElement_(this.options.closeable);

        DwsInput.dropdowns.push(this);
    }

    /**
     * Initialize the HTML structure of the Ui
     *
     * @private
     */
    initHTMLElement_(closeable = true) {
        const element = this.getElement();
        element.classList.add("dws-input");
        if (!closeable) element.classList.add("not-closeable");
        element.innerHTML = `
            <i class="fas fa-times"><!--
         --></i><input spellcheck="false" placeholder="Search for Sensor URNs" class="query" type="text"/><!--
         --><div class="search-results"></div>
        `;

        element.querySelector(".fa-times").addEventListener("click", () => this.hide());
        element.addEventListener("click", (e) => e.stopPropagation());

        const input = element.querySelector(".query");
        input.addEventListener("focus", () => this.showSearchResults_());

        // init search events
        let searchTimeout = null;
        input.addEventListener("input", () => {
            // abort any running fetches
            if (this.abortController_) this.abortController_.abort();
            clearTimeout(searchTimeout);
            searchTimeout = setTimeout(() => {
                this.search_();
                searchTimeout = null;
            }, 750);
        });
        input.addEventListener("keydown", (e) => {
            if (e.key == "Enter") {
                if (this.abortController_) this.abortController_.abort();
                clearTimeout(searchTimeout);
                this.search_();
            }
        });

        this.infoWindow = new Window(this.getElement(), { open: false });
        this.infoWindow.getElement().classList.add("pointer-left");

        // close info window when leaving
        element.querySelector(".search-results").addEventListener("mouseleave", () => this.infoWindow.close());
    }

    dispose() {
        DwsInput.dropdowns = DwsInput.dropdowns.filter(dropdown => dropdown != this);
        super.dispose();
    }

    /**
     * Display the search result list.
     *
     * @private
     */
    showSearchResults_() {
        const element = this.getElement();
        element.classList.add("active");

        const callback = () => {
            document.removeEventListener("click", callback);
            this.hideSearchResults_();
        }

        document.addEventListener("click", callback);

        this.search_();

        // close other dropdowns
        DwsInput.dropdowns.forEach(dropdown => {
            if (dropdown != this) dropdown.hideSearchResults_();
        });
    }

    /**
     * Hide the search result list
     *
     * @private
     */
    hideSearchResults_() {
        const element = this.getElement();
        element.classList.remove("active");
    }

    /**
     * Execute the search. takes the
     * search query from the text input
     *
     * @private
     */
    search_() {
        const q = this.getInputValue_();

        this.abortController_ = new AbortController();

        const url = this.options.dwsUrl || DWS_DEFAULT_URL;
        fetch(url + "/search", {
            signal: this.abortController_.signal,
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                hits: 20,
                query: ((q) ? ("(id:/.*" + q + ".*/^20 )") : "*:*") + " AND (metadata.source:NRT)" + this.staticQuery,
                sorts: ["metadata.code:asc"],
                indices: [],
                offset: 0,
                queryFacets: {},
                types: []
            })
        })
            .then(respone => {
                this.abortController_ = null;
                return respone.json()
            })
            .then(json => {
                this.cache_ = json.records;
            })
            .catch(error => {
                this.cache_ = [];
            })
            .finally(() => {
                this.populateList_(this.cache_);
            })
    }

    /**
     * Populate the search result list with content.
     * Gets called by this.search_()
     *
     * @param {object[]} devices
     *
     * @private
     */
    populateList_(devices) {
        const element = this.getElement();
        const results = element.querySelector(".search-results");

        while (results.firstChild) {
            results.removeChild(results.lastChild);
        }

        for (let i = 0; i < devices.length; ++i) {
            let device = devices[i];

            const result = document.createElement("div");
            result.classList.add("search-result");
            result.innerHTML = `
                <i class="layer-type fas fa-database"></i>
                <span class="layer-title">${device.metadata.code}</span>
                <i id="result-icon" class="add-layer fas fa-plus-circle"></i>
            `;

            result.addEventListener("click", (e) => {
                this.selectUrn_(device, e);
            });

            result.addEventListener("mouseover", () => this.showInfoBox_(device, result, results));

            results.appendChild(result);
        }
    }

    /**
     * Creates an infoBox for the DWS layers
     *
     * @param {object} device
     * @param {HTMLElement} result
     * @param {HTMLElement} results
     *
     * @private
     */
    showInfoBox_(device, result, results) {
        let rect = result.getBoundingClientRect();
        let outerRect = results.getBoundingClientRect();

        const lastDate = new Date(device.metadata.lastDate);
        const difference = new Date().getTime() - lastDate.getTime();
        const days = roundNumber(difference / 86400000, 1);

        const properties = {
            "Last Active": days + " days ago",
            "Model": device.metadata.model,
            "Parameter": device.parameters[0].united
        };

        this.infoWindow.setOptions({
            top: rect.top + "px",
            left: outerRect.left + outerRect.width + 15 + "px",
            width: "300px",
            height: "unset",
            open: true,
            title: device.metadata.code,
            content: generateTable(properties)
        });
    }

    getInputValue_() {
        return this.getElement().querySelector(".query").value.trim();
    }

    setInputValue_(value) {
        this.getElement().querySelector(".query").value = value;
    }

    /**
     * Create an instance of the Layer class
     * and fire the event "select_urn"
     *
     * @param {object} device
     * @param {object} pointerEvent
     */
    selectUrn_(device, pointerEvent) {
        this.setInputValue_(device.metadata.code);

        this.activeDevice = device;
        this.hideSearchResults_();
        this.fire("select_urn", { device: device, pointerEvent: pointerEvent });
    }

    deselect_() {
        this.setInputValue_("");
        this.activeDevice = null;
    }

    /**
     * Show the DwsInput
     */
    show() {
        const element = this.getElement();
        element.style.display = "flex";
        element.querySelector(".query").value = "";
        element.querySelector(".query").focus();
    }

    /**
     * Hide the DwsInput
     */
    hide() {
        this.getElement().style.display = "none";
    }
}