rx/presenters

View on GitHub
views/mdc/assets/js/components/base-component.js

Summary

Maintainability
A
2 hrs
Test Coverage
import {VErrors} from './events/errors';

export class VBaseComponent {
    constructor(element, mdcComponent) {
        this.root = getRootNode(element);
        this.element = element;
        this.element.vComponent = this;
        this.mdcComponent = mdcComponent;
        this.element.classList.add('v-component');
    }

    destroy() {
        // Work in progress - this will ultimately be cleaning up other items like events, etc. (more to come)
        if(this.mdcComponent && this.mdcComponent.destroy) {
            this.mdcComponent.destroy();
        }
    }

    validate(formData) {
        return true;
    }

    onShow() {
    }

    onHide() {
    }

    // Invoked after event handlers have been initialized.
    afterInit() {
    }

    parentComponent(selector) {
        if (!this.element.parentElement) {
            return null;
        }
        const element = this.element.parentElement.closest(selector);

        if (!(element && element.vComponent)) {
            return null;
        }

        return element.vComponent;
    }

    // Event actions results bubble up to their containers
    actionsStarted(vEvent) {
        const ev = new Event('V:actionsStarted', {
            bubbles: true,
            cancelable: false,
            detail: vEvent,
            composed: true,
        });
        this.element.dispatchEvent(ev);
    }

    // Event actions results bubble up to their containers
    actionsHalted(vEvent) {
        const ev = new Event('V:actionsHalted', {
            bubbles: true,
            cancelable: false,
            detail: vEvent,
            composed: true,
        });
        this.element.dispatchEvent(ev);
    }

    actionsSucceeded(vEvent) {
        const ev = new CustomEvent('V:actionsSucceeded', {
            bubbles: true,
            cancelable: false,
            detail: vEvent,
            composed: true,
        });
        this.element.dispatchEvent(ev);
    }

    actionsFinished(vEvent) {
        const ev = new CustomEvent('V:actionsFinished', {
            bubbles: true,
            cancelable: false,
            detail: vEvent,
            composed: true,
        });
        this.element.dispatchEvent(ev);
    }

    hasHandlers() {
        return this.eventsHandler && Object.keys(this.eventsHandler).length > 0;
    }

    clearErrors() {
        new VErrors(this.root).clearErrors();
    }

    respondTo(method) {
        return typeof this[method] === 'function';
    }

    is(name) {
        return this.constructor.name === name;
    }
}

function getCandidateElements(root, selector) {
    const elements = Array.from(root.querySelectorAll(selector));

    if (root && typeof root.matches === 'function' && root.matches(selector)) {
        elements.unshift(root);
    }

    return elements;
}

// `fn` is a unary function accepting a HTMLElement and returning an instance of
// VBaseComponent.
export function hookupComponentsManually(root, selector, fn) {
    const elements = getCandidateElements(root, selector);

    for (const element of elements) {
        if (element.mdcComponent || element.vComponent) {
            continue;
        }

        fn(element);
    }
}

export function hookupComponents(root, selector, VoomClass, MDCClass) {
    const ctor = componentFactory(VoomClass, MDCClass);
    hookupComponentsManually(root, selector, ctor);
}

// Returns a function capable of constructing a Voom component.
function componentFactory(VoomClass, MDCClass) {
    return (element) => new VoomClass(
        element,
        typeof MDCClass === 'function' ? new MDCClass(element) : null
    );
}

export function unhookupComponents(root, selector) {
    const elements = getCandidateElements(root, selector);

    for (const element of elements) {
        if (element.vComponent) {
            element.vComponent.destroy();
        }
    }
}

// Retrieve the element's owning document or shadow root.
export function getRootNode(node) {
    if (!node.parentNode || isShadowRoot(node)) {
        return node;
    }

    return getRootNode(node.parentNode);
}

function isShadowRoot(node) {
    return node.constructor.name === 'ShadowRoot';
}