API Docs for: 0.1.4
Show:

File: src/Module.js

/**
 * @module Mimeo
 */

/**
 * Modules are the primary interface to mimeo. On a module, you can define
 * injectables. Each injectable definition will return the current module,
 * allowing you to chain injectable definitions.
 *
 * Injectables consist of three parts: A name, a list of dependencies and an
 * executable. The dependencies are names of other injectables that will be
 * passed to the executable.
 *
 * There are two ways of defining an injectable. The first is an array notation
 * where the last entry in the array is the executable. The other is an
 * executable that has the special properties $name and $inject.
 *
 * Here is an example of the array-style. Two factories A and B are defined,
 * with B having a dependency on A:
 *
 *      mimeo.module('example', [])
 *          .factory('A', [() => {}])
 *          .factory('B', ['B', (b) => {}])
 *
 * And here's how the same example would look like with the executable style:
 *
 *      function A() {}
 *      A.$name = 'A';
 *      A.$inject = [];
 *
 *      function B() {}
 *      B.$name = 'B';
 *      B.$inject = ['A'];
 *
 *      mimeo.module('example', [])
 *          .factory(A)
 *          .factory(B);
 *
 * The executable-style makes it very easy to separate out your code from the
 * mimeo bindings. In the example, function A and B can be used independent of
 * mimeo. This is great of unit-testing your code, as you can import the
 * executables into your test suite without having to worry about mimeo.
 *
 * @class Module
 * @constructor
 */
function Module(injectables, name, dependencies) {
    var module = this;

    var toRun = [];

    this.$name = name;
    this.$inject = dependencies;

    function prepareInjectable(name, parameters) {
        if (injectables.has(name)) {
            throw new Error('Injectable "' + name + '" already exists');
        }

        var injectable;

        if (parameters instanceof Function) {
            injectable = parameters;
            if (!injectable.$inject) {
                injectable.$inject = [];
            }
        } else {
            var dependencies = parameters.slice(0, -1);
            injectable = parameters.slice(-1)[0];
            injectable.$inject = dependencies;
        }

        injectable.$name = name;

        return injectable;
    }

    function addInjectable(name, parameters) {
        injectables.add(prepareInjectable(name, parameters));

        return module;
    }

    this.executeRun = function executeRun() {
        toRun.forEach(function(injectableName) {
            injectables.get(injectableName)();
        });
    };

    /*
     * I don't like the wrapper and auto-generated name, but for now I can't
     * come up with a better solution. The problem is that the run-function
     * needs to work with the injection system (since it can have other
     * injectables injected), and the whole system isn't designed to deal with
     * unnamed things.
     *
     * In fact, I feel that an injection system that can handle unnamed items
     * would be wrong. How would you identify what to inject? Having names for
     * injectables (or at least IDs) is a core aspect of an injection system.
     *
     * So this would have to live outside of it. But that means having it's own
     * "make sure all these injectables exist" system. Then we could just get
     * the named injectables the run-function needs and call the run-function
     * with those.
     *
     * I can't think of a good way to de-duplicated that dependency resolution
     * system though, so there'd be one for all named injectables and one for
     * the run-functions.
     *
     * I don't plan on having other unnamed injectables, so I feel that effort
     * would be wasted. Hence the "hack" here with an auto-generated name and
     * a wrapper that executes the run-function with pass-through arguments.
     */
    /**
     * Defines an injectable that will be run after modules are instantiated.
     *
     * @method run
     * @for Module
     * @chainable
     * @param {Array|Function} Injectable definition
     * @return {Module}
     */
    this.run = function(parameters) {
        var name = module.$name + '-run.' + toRun.length;
        toRun.push(name);

        var provider = function providerRun() {
            var args = arguments;
            return function() {
                if (parameters instanceof Function) {
                    return parameters.apply(parameters, args);
                } else {
                    var lastEntry = parameters.slice(-1)[0];
                    return lastEntry.apply(lastEntry, args);
                }
            }
        };

        if (parameters instanceof Function) {
            provider.$inject = parameters.$inject;
        } else {
            provider.$inject = parameters.slice(0, -1);
        }

        return addInjectable(name, provider);
    };

    /**
     * Use factories for anything that doesn't create output
     *
     * @method factory
     * @for Module
     * @chainable
     * @param {Array|Function} Injectable definition
     * @return {Module}
     */
    this.factory = addInjectable;

    /**
     * Components are meant to produce some output, regardless of what rendering
     * technique you use
     *
     * @method component
     * @for Module
     * @chainable
     * @param {Array|Function} Injectable definition
     * @return {Module}
     */
    this.component = addInjectable;

    /**
     * Values are different from factories and components in that there's no
     * executable. It's just a name and a value.
     *
     * @example
     *      mimeo.module('example', [])
     *          .value('name', 'value')
     *
     * @method value
     * @for Module
     * @chainable
     * @param {string} name Name of value
     * @param {*} value Value you want available for injection
     * @return {Module}
     */
    this.value = function(name, value) {
        return addInjectable(name, function() {
            return value;
        });
    }
}

module.exports = Module;