palra/breadboard

View on GitHub
lib/breadboard.js

Summary

Maintainability
C
7 hrs
Test Coverage
var EventEmitter = require('events').EventEmitter,
    util = require('util');

/**
 * Creates a new IoC Container
 *
 * @constructor
 * @augments EventEmitter
 */
function Breadboard() {
    this.components = {};
    this._toLoad = {};
    this.setMaxListeners(0);

    Breadboard.Component.prototype._breadboard = this;

    return;
}

util.inherits(Breadboard, EventEmitter);

/**
 * Registers a new component to the container. It doesn't load it, ie it doesn't
 * loads its dependencies, only add it to the array of objects waiting to be loaded.
 * @param {string}             name      The name of the component
 * @param {Component|Function} component The component, or a function returning the component
 *
 * @throws {Error} Argument `name` must be a string
 * @throws {Error} Argument `component` must be a Component or a function
 */
Breadboard.prototype.add = function(name, component) {
    if (!_.isString(name))
        throw new Error('Invalid name');
    else {
        if (component instanceof Breadboard.Component) {
            component._meta.name = name;
            this._toLoad[name] = component;
        } else if (_.isFunction(component)) {
            this._toLoad[name] = component;
        } else {
            throw new Error("Not a valid component");
        }
    }
};

/**
 * Removes a loaded component or a registered component
 * @param  {string} name The name of the component you want to remove
 * @return {Boolean}     Is the component successfully removed ?
 */
Breadboard.prototype.remove = function(name) {
    if (!_.isString(name))
        return false;
    else {
        var ret = false;
        if (this._toLoad.hasOwnProperty(name)) {
            delete this._toLoad[name];
            ret = true;
        }
        if (this.components.hasOwnProperty(name)) {
            delete this.components[name];
            ret = true;
        }

        return ret;
    }
};

/**
 * Returns a component identified by his name. Lazy loads it if the component
 * was added with a function in Breadboard#add
 * @param  {string}         component The component name
 * @return {Component|void}           The component or nothing, if it was not found
 */
Breadboard.prototype.get = function(component) {
    if (_.isString(component)) {
        return this.components[component];
    } else {
        return;
    }
};

/**
 * Does the container has this component ?
 * @param  {string}  component The name of the component you are looking for
 * @return {Boolean}           Is the component addded ?
 */
Breadboard.prototype.has = function(component) {
    if (typeof component == 'string')
        return (this.components[component] !== undefined);
    else
        return false;
};


/**
 * Load dependencies of each components
 *
 * @throws {Error} if a component is not a valid component
 * @fires Breadboard#components:loaded
 */
Breadboard.prototype.load = function() {

    /**
     * Load dependencies of a component
     *
     * @private
     */
    function _loadDependencies() {
        for (var name in this._meta.dependencies) {
            if (this._meta.dependencies.hasOwnProperty(name)) {
                if (name != this._meta.name) {
                    if (this._breadboard.has(name)) {
                        this._meta.dependencies[name] =
                            this._breadboard.get(name);
                    } else {
                        throw new Error("No component named " + name);
                    }
                } else {
                    throw new Error("Can't add yourself as a dependency");
                }
            }
        }

        /**
         * Event fired when dependencies are loaded
         *
         * @event Component#dependencies:loaded
         * @type {Object.<string, Component>}
         */
        this.emit('dependencies:loaded', this._meta.dependencies);
    }

    var loadDependencies = [];
    for (var name in this._toLoad) {
        var comp = this._toLoad[name];
        if (this._toLoad.hasOwnProperty(name)) {
            var added = false;
            if (_.isFunction(comp)) {
                var component = comp();
                if (component instanceof Breadboard.Component) {
                    this.components[name] = component;
                    delete this._toLoad[name];
                    added = true;
                } else {
                    throw new Error(name + "'s lazy load function does not return a Component");
                }
            } else {
                this.components[name] = comp;
                delete this._toLoad[name];
                added = true;
            }
            if (added) {

                /**
                 * Event fired when a component was finished to be loaded.
                 * Different of components:loaded event.
                 *
                 * @event Breadboard#component:loaded
                 * @type {Component}
                 */
                this.emit('component:loaded', this.components[name]);
                loadDependencies.push(name);
            }
        }
    }

    for (var i in loadDependencies) {
        if (this.components.hasOwnProperty(loadDependencies[i])) {
            _loadDependencies.call(this.components[loadDependencies[i]]);
        }
    }

    /**
     * Event fired when components are all loaded
     *
     * @event Breadboard#components:loaded
     * @type {Object.<string, Component>}
     */
    this.emit('components:loaded', this.components);
};

Breadboard.Component = require('./component');

module.exports = Breadboard;