domUI/settingsUI.js

const CONTAINER_PROPS = {
    className: "club-crawler-settings-container",
    style: {
        width: "100%",
        color: "white",
    }
}

const DROP_DOWN_PROPS = {
    className: "club-crawler-settings-drop-down",
    style: {
        cursor: "pointer",
    }
}

const INPUT_CONTAINER = {
    className: "club-crawler-settings-drop-down-area",
    style: {
        display: "none"
    }
}

/**
 * @classdesc Presents an expandable menu(s) for the user to change game settings. Creates a new instance of SettingsUI for each nested non-input nested object (category) inside the object which it models and creates inputs for each [inputConfig]{@link ClubCrawler.Types.SettingsInputConfig} object which it finds nested in its modeled object.
 * @memberof ClubCrawler.DOMUserInterface
 */
class SettingsUI {
    /**
     * @param {HTMLElement} element - The container element.
     */
    constructor(element) {
        /** @member {HTMLElement} - The container element. */
        this.element = element;
        Object.assign(this.element, CONTAINER_PROPS);
        Object.assign(this.element.style, CONTAINER_PROPS.style);
        /** @member {Object} - The object whose properties to display as inputs and categories. */
        this.settingsObject = null;
        /** @member {HTMLElement} - The drop down button with the category title. When clicked, it will toggle showing of [SettingsUI.inputContainer]{@link ClubCrawler.DOMUserInterface.SettingsUI#inputContainer}. */
        this.dropDownElement = document.createElement('div');
        Object.assign(this.dropDownElement, DROP_DOWN_PROPS);
        Object.assign(this.dropDownElement.style, DROP_DOWN_PROPS.style);
        this.element.appendChild(this.dropDownElement);
        /** @member {HTMLElement} - The element containing the options, which will toggle visibility when [DebugUI.dropDownElement]{@link ClubCrawler.DOMUserInterface.SettingsUI#dropDownElement} is clicked. */
        this.inputContainer = document.createElement('div');
        Object.assign(this.inputContainer, INPUT_CONTAINER);
        Object.assign(this.inputContainer.style, INPUT_CONTAINER.style);
        this.element.appendChild(this.inputContainer);
        /** @member {Array} - The child settingsUIs and InputComponents created */
        this.children = [];
        /** @member {ClubCrawler.UserInterface.DOMUIManager} - The ui manager */
        this.uiManager = null;

        this.addDropDownListener();

    }


    /**
     * Adds a click listener to [SettingsUI.dropDownElement]{@link ClubCrawler.DOMUserInterface.SettingsUI#dropDownElement}
     */
    addDropDownListener() {
        let dropDownArea = this.inputContainer;
        this.dropDownElement.addEventListener('click', (ev)=> {
            switch(dropDownArea.style.display) {
                case "none":
                    dropDownArea.style.display = "block";
                    break;
                default:
                    dropDownArea.style.display = "none";
                    break;
            }
        })
    }

    /**
     * Creates an input component for each settings in the settings object. Creates a new SettingsUI for each non input (category) in the settings object.
     * @param {Object} settingsObject - The object to load and create interfaces for.
     * @param {string} [name="settings"] - The name for the drop down button.
     */
    loadSettingsObject(settingsObject, name="settings") {
        this.settingsObject = settingsObject;
        
        this.dropDownElement.innerHTML = name + "⬇";

        let keys = Object.keys(settingsObject);
        for(let key of keys) {
            testValue = settingsObject[key];
            if(typeof testValue == "object") {
                let newElement = document.createElement('div');
                if(testValue.name) {
                    let inputObject = new InputComponent(newElement, testValue);
                    inputObject.inputElement.addEventListener('change', (ev)=> {
                        inputObject.validate();
                    });
                    this.children.push(inputObject);

                }
                else {
                    let subObjectSettingsUI = new SettingsUI(newElement);
                    this.children.push(subObjectSettingsUI);
                    subObjectSettingsUI.loadSettingsObject(testValue, key);

                }
                this.inputContainer.appendChild(newElement);
            }
        }

    }

    /** load the UI manager into this and all children objects
     * @param {ClubCrawler.DOMUserInterface.DOMUIManager} - The manager
     */
    loadManager(uiManager) {
        for(let child of this.children) {
            if(child.loadManager) {
                child.loadManager(uiManager);
            }
        }
        this.uiManager = uiManager;
    }
}

const INPUT_COMPONENT = {
    className: "club-crawler-input",
    style: {

    }}
const INPUT_NAME = {
    className: "club-crawler-input-name",
    style: {
        width: "25%"
    }}
const INPUT_BOOLEAN = {
    className: "club-crawler-input-bool",
    type:"checkbox",
    style: {

    }}
const INPUT_NUMBER = {
    className: "club-crawler-input-number", 
    // avoid type:number, because of buggy browsers
    style: { 
        width:"10%"
    }}
const INPUT_STRING = {
    className: "club-crawler-input-string",
    style: {
        width:"25%",

    }}

/**
 * 
 * A component for allowing user selection
 * @private
 */
class InputComponent {
    /**
     * @param {HTMLElement} element - The container element
     * @param {string} name - The name to display
     * @param {string} value - The starting value
     */
    constructor(element, inputSettingsObject) { 
        /** @property {HTMLElement} - The container */
        this.element = element;
        /** @property {ClubCrawler.Types.InputConfig} - The input configuration object this models */
        this.config = inputSettingsObject;
        Object.assign(this.element, INPUT_COMPONENT);
        Object.assign(this.element.style, INPUT_COMPONENT.style);
        /** @property {HTMLElement} - The element showing the name of this control */
        this.nameElement = document.createElement('span');
        Object.assign(this.nameElement, INPUT_NAME);
        Object.assign(this.nameElement, INPUT_NAME.style);
        this.nameElement.innerHTML = "➡" + this.config.name;
        this.element.appendChild(this.nameElement);
        /** @property {HTMLElement} - The input element */
        this.inputElement = document.createElement('input');
        switch(this.config.type) {
            case "boolean": {
                Object.assign(this.inputElement, INPUT_BOOLEAN);
                Object.assign(this.inputElement.style, INPUT_BOOLEAN.style);
                this.inputElement.checked = this.config.value;
                break;
            }
            case "number": {
                Object.assign(this.inputElement, INPUT_NUMBER);
                Object.assign(this.inputElement.style, INPUT_NUMBER.style);
                this.inputElement.value = this.config.value;
                break;
            }
            default: {
                Object.assign(this.inputElement, INPUT_STRING);
                Object.assign(this.inputElement.style, INPUT_STRING.style);
                this.inputElement.value = this.config.value;
                break;
            }
        }
        this.element.appendChild(this.inputElement);

    }

    /** validates input and sets this.value to the input*/
    validate() {
        if(this.config.type == "number") {
            let inputVal = this.inputElement.value;
            try {
                this.config.value = parseFloat(this.inputElement.value);
                this.inputElement.value = this.config.value;
            }
            catch(e) {
                this.inputElement.value = this.config.value;
            }
            if(this.config.value > this.config.max) {
                this.config.value = this.config.max;
                this.inputElement.value = this.config.value;
            }
            if(this.config.value < this.config.min) {
                this.config.value = this.config.min;
                this.inputElement.value = this.config.value;
            }            
        }
        else if(this.config.type == "boolean") {
            this.config.value = this.inputElement.checked;
        }
        else { // string
            this.config.value = this.inputElement.value;
        }
    }
}

module.exports = SettingsUI;