ARCS logo.js Augmented Reality Component System

Source: component.js

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


/** 
 * Defines main traits of components in a namespace regrouping important methods
 * 
 * @namespace 
 */
ARCS.Component = {
    /** Error message */
    SourceIsNotComponent : {message : "Source is not a component"},
    /** Error message */
    UndefinedSignal : {message : "Signal is not defined"},
    /** Error message */
    UndefinedSlot : {message : "Slot is not defined"},
    /**
     * 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 mehtod 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.
     */
    create : function (name, sltList, sgnList) {
        if (name.prototype === undefined) {
            console.error("Cannot create such a component");
            return 0;
        }

        name.prototype.slots = [];
        name.prototype.signals = {};
        name.slotList = function () {
            return name.prototype.slots;
        };
        name.prototype.slotList = function () {
            return name.prototype.slots;
        };
        name.prototype.signalList = function () {
            var res = [], i;
            for (i in name.prototype.signals) {
                res.push(i);
            }
            return res;
        };
        name.signalList = function () {
            return name.prototype.signalList();
        };
        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);
            }
        };
        name.slot = function (slot, func) {
            var i;
            if (slot instanceof Array) {
                for (i = 0; i < slot.length; i++) {
                    name.prototype.slots.push(slot[i]);
                }
            } else {
                name.prototype.slots.push(slot);
                if (func !== undefined) {
                    name.prototype[slot] = func;
                }
            }
        };
        name.signal = function (signal) {
            var i;
            if (signal instanceof Array) {
                for (i = 0; i < signal.length; i++) {
                    name.prototype.signals[signal[i]] = 1;
                }
            } else {
                name.prototype.signals[signal] = 1;
            }
        };

        // 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
     */
    check : function (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
     */
    connect : function (source, signal, destination, slt) {
        var orig, p;
        // here we can perform various checks.
        if (source.signals === undefined) {
            throw ARCS.Component.SourceIsNotComponent;
        }
        if (source.signals[signal] === undefined) {
            throw ARCS.Component.UndefinedSignal;
        }
        if (destination[slt] === undefined) {
            throw ARCS.Component.UndefinedSlot;
        }
        // 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
     */
    disconnect : function (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
     */
    invoke : function (destination, slt, value) {
        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
     */
    config : function (component, obj) {
        if (typeof component.config === 'function') {
            component.config(obj);
        }
    }
};