Source: map/filters/ui/CheckboxFilter.js

import { FilterUi } from "./FilterUi.js";
import { CheckboxGroup } from "../../../ui/CheckboxGroup.js"
import { FilterSettings } from "../FilterSettings.js";
import { EditableTable } from "../../../ui/table/EditableTable.js";

import "./CheckboxFilter.css";

export { CheckboxFilter };

/**
 * A class that defines the UI for the CheckboxGroup Attribute Filter
 * 
 * @author sjaswal <shahzeib.jaswal@awi.de>
 * @author rhess <robin.hess@awi.de> 
 *
 * @memberof vef.map.filters.ui
 */
class CheckboxFilter extends FilterUi {

    /**
     * @param {HTMLElement | string} target
     * @param {object} options filter specific options
     * @param {LayerManager} layers Used for included/excluded layers
     */
    constructor(target, options, layers) {

        // apply default options
        options = Object.assign({
            title: "Checkbox Filter",
            allowShowNullValues: true,
            showNullValues: true,
            multiColumn: true,
            sortFields: true,
            fields: [],
            uniqueValueSource: null
        }, options || {});

        // fallback for old wps option
        if (options.wps) {
            options.uniqueValueSource = options.wps;
            delete options.wps;
        }

        super(target, options, layers);

        this.searchMode_ = false;

        this.addSearchBar(this.element_.children[1]);
        this.getElement().classList.add("checkbox-filter");
        this.validateFields_(options.fields);

        this.settingsClass_ = CheckboxFilterSettings;
        this.checkBoxGroup_ = this.initCheckboxGroup_({
            nodes: [],
            singleSelect: options.singleSelect,
            multiColumn: options.multiColumn
        });

        this.addToolButtons_();
        this.initShowNullButton_();
        this.addTool("fas fa-search search-button", (enabled) => this.enableSearch_(enabled), "Searching for Checkbox", true);
        this.reloadOptions_();
    }

    async getfields_() {
        let fields = this.options_.fields.slice(0);

        if (!this.options_.uniqueValueSource) return fields;
        const layer = this.layers_.getLayerById(this.options_.uniqueValueSource.layer);
        if (!layer) return fields;
        const values = await layer.getUniqueValues(this.options_.uniqueValueSource.column);
        if (values.length == 0) return fields;

        // merge cache and fields and remove duplicates
        for (let i = 0; i < values.length; ++i) {
            const value = values[i];
            if (!fields.find(field => (field.column == this.options_.uniqueValueSource.column) && (field.operator == "eq") && (field.values == value))) {
                fields.push({
                    column: this.options_.uniqueValueSource.column,
                    operator: "eq",
                    values: value,
                    title: value
                });
            }
        }

        return fields;
    }

    /**
     * Initiates the Checkbox Group for Attribute Filter
     * 
     * @param {object} options 
     */
    initCheckboxGroup_(options) {
        const group = new CheckboxGroup(this.getContentContainer(), options);

        group.on("changed", (e) => {
            this.fire("change", this);
        });

        return group;
    }

    /**
     * Validates the fields
     */
    validateFields_(fields) {
        for (let i = 0; i < fields.length; ++i) {
            const field = fields[i];
            if (!field.type) field.type = "string";
            if (!field.operator) field.operator = "eq";
        }
    }

    /**
     * @private
     */
    addToolButtons_() {
        this.addTool("vef vef-deselect-all", () => this.checkBoxGroup_.deselectAll(), "Deselect All");
        if (!this.options_.singleSelect) this.addTool("vef vef-select-all", () => this.checkBoxGroup_.selectAll(), "Select All");
    }

    /**
     * Returns the active values in the Attribute Filter
     * 
     * @returns values
     */
    getActiveValues() {
        const selectedItems = this.checkBoxGroup_.getSelected();
        const filterValues = [];

        for (let i = 0; i < selectedItems.length; ++i) {
            const item = selectedItems[i];
            const column = item.config.column;
            const operator = item.config.operator;
            let values = item.config.values;

            if (!Array.isArray(values)) values = [values];
            for (let i = 0; i < values.length; ++i) {
                filterValues.push([operator, column, values[i]]);
            }

        }

        return filterValues;
    }

    /**
     * Returns the Active Filter Values
     * 
     * @override
     * @returns {object} Filter Object
     */
    getActiveFilter() {
        const activeAttributeFilter = this.getActiveValues();

        let activeFilter = {
            type: "attribute",
            excludedLayers: this.getExcludedLayers(),
            values: []
        };

        const columns = []

        for (let i = 0; i < activeAttributeFilter.length; ++i) {
            const activeItem = activeAttributeFilter[i];

            if (activeItem.length < 2) continue;
            const operator = activeItem[0];
            const column = activeItem[1];

            if (!columns.includes(column)) columns.push(column);

            if (operator == "null") {
                activeFilter.values.push([operator, column]);
            } else if (["eq", "gt", "gteq", "lt", "lteq", "like"].includes(operator) && activeItem.length == 3) {
                activeFilter.values.push([operator, column, activeItem[2]]);
            }
        }

        if (this.options_.showNullValues) {
            columns.forEach(column => {
                activeFilter.values.push(["null", column]);
            });
        }

        return activeFilter;
    }

    /**
     * Get the filter's options-object
     */
    getOptions() {
        const options = Object.assign({}, this.options_);

        return options;
    }

    /**
     * method to reload options after applying settings
     */
    reloadOptions_() {
        this.setTitle(this.options_.title);
        this.toggleToolVisibility("btn-power", this.options_.deactivatable);
        this.toggleToolVisibility("btn-show-null", this.options_.allowShowNullValues);
        this.getfields_().then(fields => {
            if (this.options_.sortFields) fields.sort((a, b) => a.title.localeCompare(b.title));
            this.checkBoxGroup_.setNodes(fields);
            this.fire("change", this);
        });
    }

    addSearchBar(element) {
        if (element.classList[0] != "sidebar-element-content") return;
        const input = document.createElement('div');
        input.classList.add("search_checkbox_area")
        input.innerHTML = '<input class="search_checkbox" type="text" placeholder="Search ...">'
        element.prepend(input)
        input.addEventListener("keyup", (e) => {
            this.searching(e.target.value);
        })
        this.enableSearch_(this.searchMode_)
    }

    searching(search_word) {
        //set
        const arr = this.checkBoxGroup_.nodes;
        search_word = search_word.toLowerCase();

        //set to zero
        arr.forEach(element => {
            element.element.style.display = "none";
        });

        //searching
        arr.forEach(element => {
            if (element.config.column.toLowerCase().includes(search_word) || element.config.operator.toLowerCase().includes(search_word) || element.config.title.toLowerCase().includes(search_word) || element.config.values.toLowerCase().includes(search_word)) {
                element.element.style.display = "flex";
            }
        });

    }
    enableSearch_(enabled) {
        const a = this.content_.querySelector(".search_checkbox_area")

        if (enabled) {
            a.style.display = "flex";
            console.log(this)
        } else {
            a.style.display = "none";
        }

        this.searchMode_ = enabled;
    }
}

/**
 * A class that defines the UI for the CheckboxGroup Attribute Filter
 * 
 * @memberof vef.map.filters.ui
 * @private
 */
export class CheckboxFilterSettings extends FilterSettings {

    htmlExtension = `
        <div>
            <div class="table-container"></div>

            <label><input type="checkbox" name="unique-values-enabled"/> Load unique values from a layer</label>
            <div class="unique-values-wrapper">
                <div class="unique-values-info"><i class="fas fa-file-import" style="color:var(--primary-color);"></i> Drag & Drop
a Layer here</div>
                <div class="unique-values-inputs">
                    <label>Layer</label><span class="unique-layer-name"/></span><br>
                    <label>Column</label><select class="unique-column"></select>
                </div>
            </div>

            <label><input type="checkbox" name="allow-show-null-values"/> Show "include NULL values" Button</label>
        </div>
    `;

    uniqueValuesLayer = null;

    /**
     * @param {FilterUi} filter
     */
    constructor(filter) {
        super(filter);
        this.query("form").insertAdjacentHTML("beforeend", this.htmlExtension);
        this.table = new EditableTable(this.query(".table-container"), [
            {
                key: "title",
                name: "Title"
            },
            {
                key: "column",
                name: "Column"
            },
            {
                key: "operator",
                name: "Operator"
            },
            {
                key: "values",
                name: "Value"
            }
        ], filter.options_.fields);
        this.setTitleEditable();

        // Unique Values checkbox
        const uniqueValuesCheckbox = this.query("input[name='unique-values-enabled']");
        uniqueValuesCheckbox.addEventListener("change", () => {
            this.query(".unique-values-wrapper").style.display = (uniqueValuesCheckbox.checked) ? "block" : "none";
        });

        this.query(".unique-values-wrapper").addEventListener("dragover", e => e.preventDefault());
        this.query(".unique-values-wrapper").addEventListener('drop', e => {
            e.preventDefault();
            e.stopPropagation();

            const layer = this.filter.layers_.getLayerById(e.dataTransfer.getData("layer_id"));
            if (layer) this.setUniqueValuesLayer(layer);
        });
    }

    setUniqueValuesLayer(layer) {
        this.uniqueValuesLayer = layer;
        this.query(".unique-values-wrapper .unique-layer-name").innerText = layer.title;
        this.query(".unique-values-wrapper .unique-values-inputs").style.display = "block";
        this.query(".unique-column").innerHTML = "";
        const columns = layer.getAttributeNames();

        let option = document.createElement("option");
        option.value = "";
        option.innerText = "-- Please select a column --";
        this.query(".unique-column").appendChild(option);

        for (let i = 0; i < columns.length; ++i) {
            const column = columns[i];
            option = document.createElement("option");
            option.value = column;
            option.innerText = column;
            this.query(".unique-column").appendChild(option);
        }

        this.query(".unique-column").value = "";
    }

    /**
     * Get the currently configured filter options
     */
    getFilterOptions() {
        const options = super.getFilterOptions();

        options.allowShowNullValues = this.query("form input[name='allow-show-null-values']").checked;
        options.fields = this.table.getValues();

        if (this.query("input[name='unique-values-enabled']").checked && this.uniqueValuesLayer) {
            options.uniqueValueSource = {
                layer: this.uniqueValuesLayer.uniqueId,
                column: this.query(".unique-column").value
            };
        } else {
            options.uniqueValueSource = null;
        }

        return options;
    }

    /**
     * Update Ui based on the filters current options
     */
    updateFilterOptions() {
        super.updateFilterOptions();
        const options = this.filter.options_;
        // clear function
        this.table.clearTable();
        this.table.Row(options.fields);
        this.query("form input[name='allow-show-null-values']").checked = options.allowShowNullValues;

        if (options.uniqueValueSource) {
            const layer = this.filter.layers_.getLayerById(options.uniqueValueSource.layer);
            if (layer) {
                this.query("input[name='unique-values-enabled']").checked = true;
                this.setUniqueValuesLayer(layer);
                this.query(".unique-column").value = options.uniqueValueSource.column;
                return;
            }
        }

        this.query("input[name='unique-values-enabled']").checked = false;
        this.query(".unique-values-wrapper").style.display = "none";
        this.query(".unique-values-wrapper .unique-values-inputs").style.display = "none";
    }

}