ARCS logo.js Augmented Reality Component System

Source: component.js

/******************************************************************************
 * Component implementation
 * ***************************************************************************/


/** 
 * Defines main traits of components in a namespace regrouping important methods
 * 
 * @class Component 
 */

class Component {

    /** Error message */
    //const SourceIsNotComponent = {message : "Source is not a component"};
    /** Error message */
    //const UndefinedSignal = {message : "Signal is not defined"};
    /** Error message */
    //const UndefinedSlot = {message : "Slot is not defined"};

    //slots = [];
    //signals = {};

    constructor(sltList, sgnList) {
        this.slots = [];
        this.signals = {};
        if (sltList !== undefined) {
            this.slot(sltList);
        }

        if (sgnList !== undefined) {
            this.signal(sgnList);
        }
    }

    slotList() {
        return this.slots;
    }

    signalList() {
        return Object.keys(this.signals);
    }

    emit(signal) {
        let slt, func, obj;
        let args = Array.prototype.slice.call(arguments,1);
        for (slt in this.signals[signal]) {
            func = this.signals[signal][slt].func;
            obj = this.signals[signal][slt].obj;
            func.apply(obj, args);
        }
    }
     
    slot(slot, func) {
        let self = this;
        if (slot instanceof Array) {
            slot.forEach(s => {
                if (!self.slots.includes(s)) 
                    self.slot(s);
            });
        } else {
            if (!this.slots.includes(slot)) {
                this.slots.push(slot);
            }
            if (func!== undefined)
               this[slot]= func;
        }
    }

    signal(signal) {
        let self = this;
        if (signal instanceof Array) {
            signal.forEach(s => {self.signals[s] = [];});
        } else {
            this.signals[signal] = [];
        }
    }


    /**
     * External constructor: give component traits to any constructor.
     * 
     * Component traits are the following: 
     * <ul>
     * <li>Slot functions listed in an array;</li>
     * <li>A signal list described in an array;</li>
     * <li>A method returning the slot list;</li>
     * <li>A method returnung the signal list;</li>
     * <li>An emit method, to trigger signals by their names;</li>
     * <li>A slot method to cast an internal method to a slot;</li>
     * <li>A signal method to register a possible signal.</li>
     * </ul>
     * @param name {string} Class name to transform to a component
     * @param sltList {string[]} names of functions designated as slots, may be empty.
     * @param sgnList {string[]} names of functions designated as signals, may be empty.
     */
     static create(name, sltList, sgnList) {
        if (name.prototype === undefined) {
            console.error("Cannot create such a component");
            return 0;
        }

        name.prototype.slots = [];
        name.prototype.signals = {};
        /**
         * Gives the list of global slot names for a given type of component
         * @returns {Array.String} list of global slot names
         * @function slotList
         * @memberof Component
         * @static
         */
        name.slotList = function () {
            return name.prototype.slots;
        };
        /**
         * Gives the list of local slot names for a given type of component
         * @returns {Array.String} list of global slot names
         * @function Component#slotList
         */
        name.prototype.slotList = function () {
            return this.slots;
        };

        /**
         * Gives the list of global slot names for a given type of component
         * @returns {Array.String} list of global slot names
         * @function slotList
         * @memberof Component
         * @static
         */
        name.prototype.signalList = function () {
            return Object.keys(this.signals);
        };
        /**
         * Gives the list of local slot names for a given type of component
         * @returns {Array.String} list of global slot names
         * @function Component#slotList
         */
        name.signalList = function () {
            return name.prototype.signalList();
        };
        
        
        /**
         * Emits a signal 
         * @param signal {String} name of the signal to emit
         * @param {...*} [params] zero or more params to pass to the signal 
         * @function Component#emit
         */
        name.prototype.emit = function (signal) {
            var slt, func, obj;
            var args = Array.prototype.slice.call(arguments,1);
            for (slt in this.signals[signal]) {
                func = this.signals[signal][slt].func;
                obj = this.signals[signal][slt].obj;
                func.apply(obj, args);
            }
        };
        
        /**
         * Creates a slot that is only owned by this component instance. 
         * You can see an example of such use in component {@link StateMachine}
         * Please notice, that when this method is called, the list of global
         * slots will be separated from the local one. Newer global slots will
         * then not be added to the component instance.
         * In case the slot is already existing, a call to this function will
         * replace the slot.
         * @param slot {String} name of the slot to create
         * @param func {Function} callback function that will be used as a slot
         * @function Component#slot
         */
        name.prototype.slot = function(slot, func) {
            if (!this.hasOwnProperty('slots')) {
                this.slots = name.prototype.slots.map(s => s);
            }
            if (!this.slots.includes(slot)) {
              this.slots.push(slot);
            }
            this[slot]= func;
        };
        
        /**
         * Creates a signal that is only owned by this component instance. 
         * Please notice, that when this method is called, the list of global
         * signals will be separated from the local one. Newer global signals will
         * then not be added to the component instance.
         * @param slot {String} name of the signal to create
         * @function Component#signal
         */
        name.prototype.signal = function(signal) {
            if (!this.hasOwnProperty('signals')) {
                this.signals = {};
                Object.assign(this.signals, name.prototype.signals);
            }            
            this.signals[signal]= [];            
        };

        
        /**
         * Adds one or more global slots. This method has two different 
         * behaviours. The first one is equivalent to {@link Component#slot}
         * except the defined slot is global. The second one tags some methods
         * of the components as slots just by giving their names.
         * 
         * @param slot {String|Array.String} name or names of the slots to create
         * @param [func] {Function} callback function that will be used as a slot
         * @function slot
         * @memberof Component
         * @static
         */        
        name.slot = function (slot, func) {
            var i;
            if (slot instanceof Array) {
                for (i = 0; i < slot.length; i++) {
                    if (!name.prototype.slots.includes(slot[i])) {
                      name.prototype.slots.push(slot[i]);
                    }
                }
            } else {
                if (!name.prototype.slots.includes(slot)) {
                  name.prototype.slots.push(slot);
                }
                if (func !== undefined) {
                    name.prototype[slot] = func;
                }
            }
        };

        /**
         * Adds one or more global signals. 
         * @param signal {String|Array.String} name or names of the signals to create
         * @function signal
         * @memberof Component
         * @static
         */        
        name.signal = function (signal) {
            var i;
            if (signal instanceof Array) {
                for (i = 0; i < signal.length; i++) {
                    name.prototype.signals[signal[i]] = [];
                }
            } else {
                name.prototype.signals[signal] = [];
            }
        };

        // code for returning component, and or completing its definition
        if (sltList !== undefined) {
            name.slot(sltList);
        }

        if (sgnList !== undefined) {
            name.signal(sgnList);
        }
        return name;
    }
    /** 
     * Checks if the given prototype has traits of a component
     * @param name {string} name of the prototype
     * @TODO we need to check if this is used and refactor this method
     */
    static check(name) {
        if (name.prototype === undefined) {
            return false;
        }
        if (name.prototype.signals === undefined ||
                name.prototype.slots === undefined) {
            return false;
        }
        return true;
    }
    /**
     * Connects two different components by using their signal and slots
     * @param source {object} component sending data
     * @param signal {string} name of the signal to connect
     * @param destination {object} component receiving data
     * @param slt {string} name of the slot to connect
     */
    static connect(source, signal, destination, slt) {
        var orig, p;
        // here we can perform various checks.
        if (source.signals === undefined) {
            throw Component.SourceIsNotComponent;
        }
        if (source.signals[signal] === undefined) {
            let e = Object.assign({}, Component.UndefinedSignal);
            e.message = e.message + ": \"" + signal + "\"";            
            throw e;
        }
        if (destination[slt] === undefined) {
            let e = Object.assign({}, Component.UndefinedSlot);
            e.message = e.message + ": \"" + slt + "\"";
            throw e;
        }
        // we must also check if the signals dispose of their own implementation
        if (!source.hasOwnProperty('signals')) {
            // otherwise, we should clone it so that each component dispose of its 
            // own signal copy.
            orig = source.signals;
            source.signals = {};
            for (p in orig) {
                source.signals[p] = [];
            }
        }
        source.signals[signal].push({obj: destination, func: destination[slt]});
    }
    /**
     * Diconnects a signal/slot connection between two components
     * @param source {object} component sending data
     * @param signal {string} name of the signal to connect
     * @param destination {object} component receiving data
     * @param slt {string} name of the slot to connect
     */
    static disconnect(source, signal, destination, slt) {
        var i;
        for (i = 0; i < source.signals[signal].length; i++) {
            if (source.signals[signal][i].obj === destination) {
                if (source.signals[signal][i].func === destination[slt]) {
                    source.signals[signal].splice(i, 1);
                    i--;
                }
            }
        }
    }
    /**
     * Invokes a specific slot of a given component
     * @param destination {object} component upon which invocation is performed
     * @param slt {string} name of the slot to invoke
     * @param value {mixed} value to input
     */
    static invoke(destination, slt, value) {
        if (destination[slt] === undefined) {
            throw Component.UndefinedSlot;            
        }
                
        var func = destination[slt];
        func.apply(destination, value);
    }
    /** 
     * Specific hook that can be called when initializing a component
     * @param component {object} prototype of the component
     * @param obj {object} the actual object
     */
    static config(component, obj) {
        if (typeof component.config === 'function') {
            component.config(obj);
        }
    }
};

    /** Error message */
Component.SourceIsNotComponent = {message : "Source is not a component"};
    /** Error message */
Component.UndefinedSignal = {message : "Signal is not defined"};
    /** Error message */
Component.UndefinedSlot = {message : "Slot is not defined"};



export default Component;