Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exposing Global Variables #4

Open
MarketingPip opened this issue May 10, 2024 · 1 comment
Open

Exposing Global Variables #4

MarketingPip opened this issue May 10, 2024 · 1 comment

Comments

@MarketingPip
Copy link

You need to make this a module. Plus encapsulate your variables. As this code is almost usable to developers until you do both.

@MarketingPip
Copy link
Author

All variables have been encapsulated in this code below:

class Contextify {
    constructor(buttons, theme, container) {
        this.contextEvent = 'contextmenu';
        this.keyPressEvent = 'keydown';
        this.mouseClickEvent = 'mousedown';
        this.mouseEnterEvent = 'mouseenter';
        this.mouseLeaveEvent = 'mouseleave';

        this.clickAttribute = 'click';
        this.childAttribute = 'child';
        this.typeAttribute = 'type';
        this.iconAttribute = 'icon';
        this.textAttribute = 'text';
        this.hotkeyAttribute = 'hotkey';
        this.colourAttribute = 'colour';
        this.textColourAttribute = 'textcolour';
        this.enabledAttribute = 'enabled';

        this.childTimeout = 200;

        this.container = (typeof container === 'undefined') ? document.body : container;
        this.parent = null;
        this.active = false;
        this.focused = false;
        this.root = true;
        this.buttons = buttons;
        this.children = [];
        this.hotkey = [];
        this.pressed = [];
        this.keypressFunc = null;
        this.theme = theme;

        this.assignTheme(theme);
        this.constructEvents();
        this.register();
    }

    assignTheme(theme) {
        if (this.link !== undefined) {
            document.getElementsByTagName('head')[0].removeChild(this.link);
        }
        this.link = document.createElement('link');
        this.link.rel = 'stylesheet';
        this.link.type = 'text/css';
        this.link.href = 'themes/contextify-' + theme + '.css';
        document.getElementsByTagName('HEAD')[0].appendChild(this.link);
    }

    constructEvents() {
        this.container.addEventListener(this.contextEvent, this.handleContextMenu.bind(this));
        this.container.addEventListener(this.mouseClickEvent, this.handleClick.bind(this));
    }

    handleContextMenu(event) {
        event.preventDefault();
        if (this.root && event.target !== this.menu) {
            for (const child of this.children) {
                if (event.target === child.menu)
                    return;
            }
            this.show(event.clientX, event.clientY);
        }
    }

    handleClick(event) {
        event.preventDefault();
        if (this.root) {
            if (this.menu !== undefined) {
                if (!this.menuEntryMember(event.target)) {
                    this.hide(false);
                }
            }
        }
    }

    mouseLeave(event) {
        this.leaveTimeout = setTimeout(() => {
            this.focused = false;
            if (!this.root) {
                if (this.hasActiveChild() && !this.getActiveChild().focused) {
                    this.hide(false);
                } else if (!this.hasActiveChild()) {
                    this.hide(false);
                    if (!this.parent.root && !this.parent.focused)
                        this.parent.hide(false);
                }
            } else {
                if (this.hasActiveChild()) {
                    const child = this.getActiveChild();
                    if (!child.focused)
                        child.hide(false);
                }
            }
        }, this.childTimeout);
    }

    mouseEnter(event) {
        this.focused = true;
        if (this.leaveTimeout !== undefined)
            clearTimeout(this.leaveTimeout);
    }

    constructMenu() {
        this.menu = document.createElement('div');
        this.menu.classList.add('context');
        for (const button of this.buttons)
            this.menu.appendChild(this.parseButtonData(button));
        this.container.appendChild(this.menu);
        this.menu.addEventListener(this.mouseLeaveEvent, this.mouseLeave.bind(this));
        this.menu.addEventListener(this.mouseEnterEvent, this.mouseEnter.bind(this));
    }

    parseButtonData(button) {
        const menuEntry = document.createElement('div');
        menuEntry.classList.add('menuEntry');

        const type = this.hasAttribute(button, this.typeAttribute) ? button[this.typeAttribute] : 'button';
        if ('separator' === type) {
            menuEntry.classList.add('separator');
            return menuEntry;
        }

        if (this.hasAttribute(button, this.childAttribute)) {
            this.buildChildMenu(menuEntry, button);
        } else {
            const closeSubMenu = event => {
                for (const child of this.children) {
                    if (child.active) {
                        child.hide(false);
                    }
                }
            }
            menuEntry.addEventListener(this.mouseEnterEvent, closeSubMenu);
        }

        menuEntry.classList.add('contextButton');

        if (this.hasAttribute(button, this.colourAttribute)) {
            if (this.validateColourCode(button[this.colourAttribute])) {
                let rgba = this.translateColourCodes(button[this.colourAttribute].toString());
                menuEntry.style.cssText = `background-color: ${rgba}`;
            }
        }

        if (this.hasAttribute(button, this.textColourAttribute)) {
            if (this.validateColourCode(button[this.textColourAttribute])) {
                let rgba = this.translateColourCodes(button[this.textColourAttribute].toString());
                menuEntry.style.cssText = `color: ${rgba}`;
            }
        }

        this.appendLabelAndIcon(button, menuEntry, button[this.textAttribute].toString());
        menuEntry.id = button[this.textAttribute];

        if (this.hasAttribute(button, this.enabledAttribute)) {
            menuEntry.classList.add(button[this.enabledAttribute] ? this.enabledAttribute : 'disabled');
        }

        if (this.hasAttribute(button, this.clickAttribute)) {
            if (typeof button[this.clickAttribute] === 'function') {
                this.assignClickHandler(button, menuEntry, this.parent, button[this.clickAttribute]);
            }
        }

        return menuEntry;
    }

    buildChildMenu(menuEntry, button) {
        const childMenu = new Contextify(button[this.childAttribute], this.theme, this.container);
        childMenu.parent = this;
        childMenu.root = false;
        const openSubMenu = event => {
            if (button[this.enabledAttribute] === undefined || button[this.enabledAttribute]) {
                let x = this.menu.offsetLeft + this.menu.clientWidth + menuEntry.offsetLeft;
                let y = this.menu.offsetTop + menuEntry.offsetTop;
                if (!childMenu.active) {
                    childMenu.show(x, y);
                }
                else childMenu.hide(false);
            }
        }

        this.children.push(childMenu);
        menuEntry.addEventListener(this.mouseEnterEvent, openSubMenu);
    }

    assignClickHandler(button, menuEntry, parent, func) {
        menuEntry.addEventListener(this.clickAttribute, function () {
            func({ handled: false, button: menuEntry, parent: parent });
        });

        if (this.hasAttribute(button, this.hotkeyAttribute)) {
            const hotkey = document.createElement('span');
            const hotkeyName = button[this.hotkeyAttribute];
            hotkey.classList.add(this.hotkeyAttribute);
            hotkey.innerText = hotkeyName;

            if (hotkeyName.includes(' + ')) {
                this.hotkey = hotkeyName.split(' + ');
            }

            this.keypressFunc = func;
            menuEntry.appendChild(hotkey);
        }
    }

    hasAttribute(button, attribute) {
        return button.hasOwnProperty(attribute);
    }

    validateColourCode(colour) {
        let splitColour = colour.split(':');
        for (let i = 0; i < 3; i++) {
            if (!/^(([0-1]?[0-9]?[0-9])|([2][0-4][0-9])|(25[0-5]))$/i.exec(splitColour[i])) {
                console.error("[Contextify]: Invalid rgba colour attribute format. Please use format: 255:255:255:1.0");
                return false;
            }
        }
        if (!/^(0(\.\d+)?|1(\.0+)?)$/i.exec(splitColour[3])) {
            console.error("[Contextify]: Invalid rgba colour attribute format. Please use format: 255:255:255:1.0");
            return false;
        }
        return true;
    }

    appendLabelAndIcon(button, parent, text) {
        if (this.hasAttribute(button, this.iconAttribute)) {
            if (this.validateFontAwesome()) {
                const icon = document.createElement('span');
                icon.classList.add('fa');
                icon.classList.add(button[this.iconAttribute]);
                icon.classList.add(this.iconAttribute);
                parent.appendChild(icon);
            }
            else console.warn('Warning: Use of icons requires fontawesome. Icons will not load.');
        }

        const label = document.createElement('span');
        label.classList.add('label');
        label.innerText = text === undefined ? '' : text;
        parent.appendChild(label);
    }

    show(x, y) {
        if (this.active) this.hide(false);
        this.constructMenu();
        this.setPosition(x, y);
        this.active = true;
    }

    hide(hideParent) {
        if (this.active) {
            this.active = false;
            this.hideChildren();
            this.container.removeChild(this.menu);

            if (hideParent && this.parent !== null && this.parent.active) {
                this.parent.hide(false);
            }
        }
    }

    hideChildren() {
        for (const child of this.children) {
            if (child.active) {
                child.hide(false);
            }
        }
    }

    hasActiveChild() {
        return this.getActiveChild() !== null;
    }

    getActiveChild() {
        if (this.children !== undefined) {
            for (const child of this.children) {
                if (child.active)
                    return child;
            }
        }
        return null;
    }

  setPosition(x, y) {
    const screenWidth = this.container.offsetWidth;
    const menuWidth = this.menu.offsetWidth;
    if (x + menuWidth > screenWidth) {
        // Touching right side, adjust position by subtracting 200
        this.menu.style.left = `${x - 200}px`;
    } else if (x < 0) {
        // Touching left side, adjust position by adding 200
        this.menu.style.left = `${x + 200}px`;
    } else {
        // Normal position
        this.menu.style.left = `${x}px`;
    }

    this.menu.style.top = `${y}px`;
}


    disableButton(button) {
        this.container.querySelector(button).classList.remove('enabled');
        this.container.querySelector(button).classList.add('disabled');
    }

    enableButton(button) {
        this.container.querySelector(button).classList.remove('disabled');
        this.container.querySelector(button).classList.add('enabled');
    }

    translateColourCodes(code) {
        let split = code.split(':');
        return 'rgba(' + split[0] + ',' + split[1] + ',' + split[2] + ',' + split[3] + ')';
    }

    menuEntryMember(obj) {
        return (obj?.classList?.contains('menuEntry') ||
            obj?.parentElement?.classList?.contains('menuEntry'));
    }

    validateFontAwesome() {
        var span = document.createElement('span');

        span.className = 'fa';
        span.style.display = 'none';
        document.body.insertBefore(span, document.body.firstChild);

        function css(element, property) {
            return window.getComputedStyle(element, null).getPropertyValue(property);
        }

        let cdnExists = css(span, 'font-family').toLowerCase() === 'fontawesome';
        let jsExists = window.FontAwesomeKitConfig !== undefined;
        document.body.removeChild(span);
        return cdnExists || jsExists;
    }

    register() {
        this.container.addEventListener(this.contextEvent, this.handleContextMenu.bind(this));
        this.container.addEventListener(this.mouseClickEvent, this.handleClick.bind(this));
    }

    deregister() {
        this.container.removeEventListener(this.contextEvent, this.handleContextMenu.bind(this));
        this.container.removeEventListener(this.mouseClickEvent, this.handleClick.bind(this));
    }

}

Will make a PR request shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant