Source: D:/Projects/MoaJs/src/moa.js

/**
 * Created with WebStorm.
 * Project: MoaJs
 * User: Sergii Danilov
 * Date: 10/31/13
 * Time: 6:10 PM
 */
/*global define:true, module:true*/
/**
 * @module Moa
 *
 * @desc MoaJs micro library for easiest implementation of prototype inheritance,
 * closure for base prototype, mixins, static methods and mixins,
 * simple declaration for singleton behavior of type in JavaScript.
 * MoaJs contains IoC container for resolving declared types as
 * field or constructor injection to instance
 */
(function () {
    "use strict";
    if (!Object.create) {
        Object.create = (function () {
            function F() {}
            return function (o) {
                if (arguments.length !== 1) {
                    throw new Error('Object.create implementation only accepts one parameter.');
                }
                F.prototype = o;
                return new F();
            };
        }());
    }
    var undef,
        map = {},
        mixins = {},
        extend = function (target, source) {
            var prop;
            if (source) {
                for (prop in source) {
                    if (source.hasOwnProperty(prop)) {
                        target[prop] = source[prop];
                    }
                }
                //Some Object methods are not enumerable on Internet Explorer
                target.toString = source.toString;
                target.valueOf = source.valueOf;
                target.toLocaleString = source.toLocaleString;
            }
            return target;
        },
        fastExtend = function (target, source) {
            var prop;
            for (prop in source) {
                target[prop] = source[prop];
            }
            return target;
        },
        throwWrongParamsErr = function (method, param) {
            var msg = 'Wrong parameters in ' + method;
            if (param) {
                msg = 'Wrong parameter ' + param + ' in ' + method;
            }
            throw new Error(msg, 'Moa');
        },
        throwWrongType = function (obj, extendType, isMixin) {
            var type = 'Type ';
            if (obj === undef) {
                if (isMixin === true) {
                    type = 'Mixin type ';
                }
                throw new Error(type + extendType + ' not found', 'Moa');
            }
        },
        addMixins = function ($proto, $mixin) {
            var prop,
                value,
                MixFn;
            for (prop in $mixin) {
                value = $mixin[prop];
                MixFn = mixins[value];
                throwWrongType(MixFn, value, true);
                MixFn.call($proto);
                $proto[prop] = new MixFn();
            }
            return $proto;
        },
        build = function (type, base, definition) {
            var basetype,
                $staticMixin,
                $single = definition.$single,
                $static = definition.$static,
                $mixin = definition.$mixin,
                $ctor = definition.$ctor,
                $di = definition.$di,
                $base = {};
            if ($ctor !== undef) {
                delete definition.$ctor;
            } else {
                $ctor = function () {};
            }
            delete definition.$single;
            delete definition.$static;
            delete definition.$mixin;
            delete definition.$extend;
            if ($static !== undef) {
                $staticMixin = $static.$mixin;
                if ($staticMixin !== undef) {
                    delete $static.$mixin;
                    addMixins($ctor, $staticMixin);
                }
                extend($ctor, $static);
            }
            if ($mixin !== undef) {
                definition = extend(addMixins({}, $mixin), definition);
            }
            if (base !== undef) {
                basetype = base.$type;
                definition = extend(Object.create(base.$ctor.prototype), definition);
            }
            definition.getType = function () {
                return type;
            };
            extend($base, definition);
            $ctor.prototype = definition;
            $ctor.prototype.constructor = $ctor;
            if ($single !== undef && $single === true) {
                (function () {
                    var instance = new $ctor();
                    $ctor = function () {
                        return instance;
                    };
                    $ctor.getInstance = function () {
                        return instance;
                    };
                }());
            }
            $base.$ctor = $ctor;
            return {
                $type: type,
                $basetype: basetype,
                $mixin: $mixin,
                $di: $di,
                $ctor: $ctor,
                $base: $base
            };
        },
        resolveDeclaration = function (type, diConfiguration, owner) {
            var configurationProperty, configurationValue, configurationValueType,
                typeObj, propFlag = false;
            diConfiguration = diConfiguration || {};
            if (!diConfiguration.$current) {
                diConfiguration.$current = type;
            }
            for (configurationProperty in diConfiguration) {
                configurationValue = diConfiguration[configurationProperty];
                configurationValueType = typeof configurationValue;
                switch (configurationProperty) {
                case '$current':
                    switch (configurationValueType) {
                    case 'string':
                        configurationValue = {
                            type: configurationValue,
                            instance: 'item',
                            lifestyle: 'transient'
                        };
                        break;
                    case 'object':
                        configurationValue.type = type;
                        if (!configurationValue.instance) {
                            configurationValue.instance = 'item';
                        }
                        if (!configurationValue.lifestyle && configurationValue.instance !== 'ctor') {
                            configurationValue.lifestyle = 'transient';
                        }
                        break;
                    default:
                    }
                    break;
                case '$ctor':
                    configurationValue = resolveDeclaration(type, configurationValue, configurationProperty);
                    delete configurationValue.$current;
                    break;
                case '$proto':
                    configurationValue = resolveDeclaration(type, configurationValue, configurationProperty);
                    delete configurationValue.$current;
                    break;
                case '$prop':
                    configurationValue = resolveDeclaration(type, configurationValue, configurationProperty);
                    delete configurationValue.$current;
                    break;
                default:
                    propFlag = true;
                    switch (configurationValueType) {
                    case 'string':
                        typeObj = map[configurationValue];
                        if (typeObj) {
                            if (owner === '$proto') {
                                configurationValue = fastExtend({}, typeObj.$di.$current);
                                configurationValue.lifestyle = 'singleton';
                            } else {
                                configurationValue = typeObj.$di.$current;
                            }
                        }
                        break;
                    case 'object':
                        if (configurationValue.type) {
                            if (configurationValue.instance === 'ctor') {
                                delete configurationValue.lifestyle;
                            } else {
                                if (!configurationValue.instance) {
                                    configurationValue.instance = 'item';
                                }
                                if (!configurationValue.lifestyle) {
                                    configurationValue.lifestyle = 'transient';
                                }
                            }
                        }
                        break;
                    default:
                    }
                }
                if (owner || !propFlag) {
                    diConfiguration[configurationProperty] = configurationValue;
                } else {
                    propFlag = false;
                    if (!diConfiguration.$prop) {
                        diConfiguration.$prop = {};
                    }
                    diConfiguration.$prop[configurationProperty] = configurationValue;
                    delete diConfiguration[configurationProperty];
                }
            }
            return diConfiguration;
        },
        Moa = {
            /**
             * Clear all defined types and mixins
             * @method clear
             */
            clear: function () {
                var clearObj = function (obj) {
                    var prop;
                    for (prop in obj) {
                        delete obj[prop];
                    }
                };
                clearObj(map);
                clearObj(mixins);
            },
            /**
             * Declaration configuration for type
             * @typedef {object} DeclarationConf
             * @property {function} [$ctor] - constructor of type
             * @property {string} [$extend] - inheritable type name
             * @property {DiConf} [$di] - configuration for IoC container
             * @property {object} [$mixin] - literal with mixins declaration
             * @property {object} [$static] - literal with properties and function that applied to constructor
             * @property {boolean} [$single] - setup type as singleton
             */
            /**
             * Declaration configuration for type
             * @typedef {function} DeclarationFn
             * @param {object} $base - prototype of inheritable type with $base.$ctor - constructor of inheritable type
             * @return {DeclarationConf} object that uses $base closure for access to inheritable type implementation constructor and methods
             */
            /**
             * Define new or inherited type
             * @method define
             * @param {string} type - name of type
             * @param {(DeclarationConf|DeclarationFn)} [definition] - see {@link DeclarationConf} or {@link DeclarationFn}.
             * If it is null - delete declared object
             * @return {function} constructor of defined object type
             *
             * @example <caption>Declaration without <code>$base</code> closure</caption>
             * var constructor = Moa.define('baseObj', {
             *      $ctor: function (name) {
             *              this.name = name;
             *          },
             *      getName: function() {
             *          return this.name;
             *      }
             *  });
             *
             * @example <caption>Declaration inheritance and <code>$base</code> closure</caption>
             * var constructor = Moa.define('child', function ($base) {
             *     // $base - containe reference to prototype of 'baseObj'
             *     return {
             *         $extend: 'baseObj',
             *         $ctor: function (name, age) {
             *             this.age = age;
             *             $base.$ctor.call(this, name);
             *         },
             *         getAge: function () {
             *             return this.age;
             *         }
             *     };
             * });
             *
             * @example <caption>Delete type declaration</caption>
             * Moa.define('base', {});     // new type declaration
             * Moa.define('base', null);   // delete type declaration
             *
             * @example <caption>Declaration <code>$base</code> closure</caption>
             * var childItem,
             *     base = Moa.define('base', function ($base) {
             *        // $base - undefined
             *        return {
             *            $ctor: function (name) {
             *                this.name = name;
             *            },
             *            getName: function() {
             *                return this.name;
             *            }
             *        };
             *     }),
             *     child = Moa.define('child', function ($base) {
             *        // $base - reference to 'base' type
             *        return {
             *            $extend: 'base',
             *            $ctor: function (name, age) {
             *                this.age = age;
             *                $base.$ctor.call(this, name);
             *            },
             *            // override base implementation
             *            getName: function() {
             *                return 'Child: ' + $base.getName.call(this);
             *            },
             *            getAge: function () {
             *                return this.age;
             *            }
             *        };
             *    });
             *
             * @example <caption>Using instance</caption>
             * childItem = new child('Pet', 7);
             * childItem.getName(); // 'Child: Pet'
             * childItem.getAge();  // 7
             *
             * @example <caption>Declaration static methods</caption>
             * var baseCtor, item,
             *     strMix = function () {
             *         this.add = function () {
             *             return (this.a.toString() + this.b.toString());
             *         };
             *     }
             *     base = {
             *         $ctor: function () {
             *         },
             *         $static: {
             *             // Also you can declare static mixins in usual way
             *             $mixin: {
             *                 str: 'strMix'
             *             },
             *             getMsg: function () {
             *                 return 'Static!';
             *             },
             *             a: 15,
             *             b: 17
             *         }
             *     };
             * Moa.mixin('strMix', strMix);
             * Moa.define('base', base);
             *
             * @example <caption>Using static methods</caption>
             * baseCtor = Moa.define('base');
             * baseCtor.getMsg(); // 'Static!' - static method
             * baseCtor.add(); // '15' + '17' => '1517' - static mixin
             * Ctor.str.add.call(Ctor); // '1517'
             *
             * @example <caption>Declaration singleton</caption>
             * var itemA, itemB, ItemC,
             *     singeltonConstructor = Moa.define('singleExample', {
             *         $single: true,
             *         $ctor: function () {
             *             this.name = 'Moa';
             *         },
             *         getName: function () {
             *             return this.name;
             *         }
             *     })
             *
             * @example <caption>Using singleton</caption>
             * // Unfortunately it can not have constructor parameters
             * itemA = new singeltonConstructor();
             * itemB = singeltonConstructor();
             * itemC = singeltonConstructor.getInstance();
             * // itemA equal itemB equal itemC
             */
            define: function (type, definition) {
                var mapObj, baseType, base,
                    len = arguments.length;
                switch (len) {
                case 1:
                    mapObj = map[type];
                    throwWrongType(mapObj, type);
                    break;
                case 2:
                    switch (typeof definition) {
                    case 'function':
                        baseType = definition().$extend;
                        if (baseType !== undef) {
                            base = map[baseType];
                            throwWrongType(base, baseType);
                            mapObj = build(type, base, definition(base.$base));
                        } else {
                            mapObj = build(type, undef, definition(undef));
                        }
                        break;
                    case 'object':
                        if (definition !== null) {
                            baseType = definition.$extend;
                            if (baseType !== undef) {
                                base = map[baseType];
                                throwWrongType(base, baseType);
                            }
                            mapObj = build(type, base, definition);
                        } else {
                            delete map[type];
                            return undef;
                        }
                        break;
                    default:
                        throwWrongParamsErr('define', 'definition');
                    }
                    map[type] = mapObj;
                    map[type].$di = resolveDeclaration(type, mapObj.$di);
                    break;
                default:
                    throwWrongParamsErr('define');
                }
                return mapObj.$ctor;
            },
            /**
             * Declaration of dependency injection behavior
             * @typedef {object} InjectionConf
             * @property {string} type - name of type for injection.
             * Not available for $current in {@link DiConf}
             * @property {string} instance - Injected instance.
             * Values: 'item' or 'ctor'. Default value: 'item'.
             * @property {string} lifestyle - Life style for 'item' instance. Not used for 'ctor'.
             * Values: 'transient' or 'singleton'. Default value: 'transient'.
             */
            /**
             * Configuration of dependency injection. Used as $di parameter in type declaration.
             * @typedef {object} DiConf
             * @property {object} [$current] - set default injection behavior for declared type
             * @property {object} [$ctor] - literal declare types that inject to constructor
             * @property {object} [$prop] - literal declare types that inject to instance properties
             * @property {object} [$proto] - literal declare types that inject to prototype of instance properties.
             * BE CAREFUL! It resolved one time after use 'resolve' method and override exist properties and methods in prototype.
             * Resolved properties and methods available in prototype of constructor type for all places where constructor uses ('define' method for example).
             * @property {*} - properties that injected as instance properties. All string values try to resolve as declared types
             * @example
             * {
             *     $ctor: {
             *         fieldA: {
             *             type: 'typeA',
             *             instance: 'item',
             *             lifestyle: 'transient'
             *         },
             *         field: 'typeB'   // try to resolve as 'typeB' otherwise use as a string
             *     },
             *     $prop: {
             *         propA: 'typeA'   // resolved like fieldA to instance field
             *     },
             *     $proto: {
             *         protoProp: {     // resolve constructor of 'typeB' to instance prototype
             *             type: 'typeB',
             *             instance: 'ctor' // if instance is 'item' it has lifestyle as 'singleton'
             *         }
             *     },
             *     propC: {             // resolve the same instance of 'typeC' for every instance in $prop literal
             *         type: 'typeA',
             *         instance: 'item',
             *         lifestyle: 'singleton'
             *     },
             *     prop: 2315           // copy number field to resolved instance
             * }
             */
            /**
             * Resolve new instance of type with field and constructor injection.
             * Resolving logic based on $di configuration of type declaration.
             * @method resolve
             * @param {string} type - name of type
             * @param {object} [paramsObj] - constructor parameter for resolved type
             * @return {object} instance of type
             */
            resolve: function (type, paramsObj) {
                var item,
                    //depthRecursion = 64, cntRecursion = 0,
                    mapObj = map[type],
                    len = arguments.length,
                    fnResolveListConf = function (target, config, fnResolveObjConf) {
                        var prop, propValue;
                        for (prop in config) {
                            propValue = config[prop];
                            if (typeof propValue === 'object') {
                                target[prop] = fnResolveObjConf(propValue, fnResolveListConf);
                            } else {
                                target[prop] = propValue;
                            }
                        }
                        return target;
                    },
                    createItem = function (declaration, obj, fnResolveObjConf, cParams) {
                        var item, conf, proto;
                        cParams = cParams || {};
                        conf = declaration.$ctor;
                        if (conf) {
                            cParams = fnResolveListConf(cParams, conf, fnResolveObjConf);
                        }
                        conf = declaration.$proto;
                        if (conf) {
                            if (!conf.resolved) {
                                proto = fnResolveListConf({}, conf, fnResolveObjConf);
                                fastExtend(obj.$ctor.prototype, proto);
                                conf.resolved = true;
                            }
                        }
                        item = new obj.$ctor(cParams);
                        conf = declaration.$prop;
                        if (conf) {
                            item = fnResolveListConf(item, conf, fnResolveObjConf);
                        }
                        return item;
                    },
                    fnResolveObjConf = function (declaration, ctorParams) {
                        var resolvedObj,
                            current = declaration.$current;
                        /*==========================================================
                        if you have problem with IoC, just uncomment 3 rows bellow
                        and second row in 'resolve' function
                        =========================================================*/
//                        cntRecursion += 1;
//                        if (cntRecursion > depthRecursion) {
//                            throw new Error('Loop of recursion', 'moa');
//                        }
                        if (current) {
                            resolvedObj = map[current.type];
                        } else {
                            current = declaration;
                            resolvedObj = map[current.type];
                            if (resolvedObj) {
                                declaration = resolvedObj.$di;
                            } else {
                                return declaration;
                            }
                        }
                        switch (current.instance) {
                        case 'item':
                            switch (current.lifestyle) {
                            case 'transient':
                                item = createItem(declaration, resolvedObj, fnResolveObjConf, ctorParams);
                                break;
                            case 'singleton':
                                if (!current.item) {
                                    current.item = createItem(declaration, resolvedObj, fnResolveObjConf, ctorParams);
                                }
                                item = current.item;
                                break;
                            default:
                                throwWrongParamsErr('resolve', type + '::$di::$current::lifestyle');
                            }
                            break;
                        case 'ctor':
                            item = resolvedObj.$ctor;
                            break;
                        default:
                            throwWrongParamsErr('resolve', type + '::$di::$current::instance');
                        }
                        return item;
                    };
                if (len !== 1 && len !== 2) {
                    throwWrongParamsErr('resolve');
                }
                throwWrongType(mapObj, type);
                item = fnResolveObjConf(mapObj.$di, paramsObj);
                return item;
            },
            /**
             * Declare mixin
             * @method mixin
             * @param {string} mixType - name of mixin type
             * @param {function} definition - implementation of behavior for mixin.
             * If it is null - delete declared mixin
             *
             * @example <caption>Declaration mixin</caption>
             * var numMix = function () {
             *      this.add = function () {
             *          return (this.a + this.b);
             *      };
             *      this.sub = function () {
             *         return (this.a - this.b);
             *      };
             *      this.mul = function () {
             *          return (this.a * this.b);
             *      };
             * };
             * Moa.mixin('numMix', numMix);
             *
             * @example <caption>Using mixin</caption>
             * var item, Ctor,
             *     base = {
             *     $ctor: function (a, b) {
             *         this.a = a;
             *         this.b = b;
             *     },
             *     $mixin: {
             *         nummix: 'numMix'
             *     },
             *     mul: function () {
             *         return 'a*b=' + this.nummix.mul.call(this);
             *     }
             * };
             * Ctor = Moa.define('base', base);
             * item = new Ctor(3, 4);
             * item.add(); // 7
             * item.mul(); // 'a*b=12'
             *
             * @example <caption>Multiple mixins example</caption>
             * var Ctor, item,
             *     base = {
             *         $ctor: function (a, b) {
             *             this.a = a;
             *             this.b = b;
             *         },
             *         $mixin: {
             *             num: 'numMix',
             *             str: 'strMix'
             *         }
             *     },
             *     numMix = function () {
             *         this.add = function () {
             *             return (this.a + this.b);
             *         };
             *     },
             *     strMix = function () {
             *         this.add = function () {
             *             return (this.a.toString() + this.b.toString());
             *         };
             *     };
             * Moa.mixin('numMix', numMix);
             * Moa.mixin('strMix', strMix);
             * Ctor = Moa.define('base', base);
             * item = new Ctor(10, 12);
             * item.add(); // '1012'
             * item.num.add.call(item); // 22
             * item.str.add.call(item); //'1012'
             *
             * @example <caption>Delete mixin declaration</caption>
             * Moa.mixin('mix', function () {});    // new mixin declaration
             * Moa.mixin('mix', null);    // delete mixin declaration
             *
             * @example <caption>Static mixin declaration</caption>
             * var base = {
             *     $mixin: {
             *         num: 'numMix'
             *     },
             *     $static: {
             *         $mixin: {
             *             str: 'strMix'
             *         }
             *     }
             * }
             */
            mixin: function (mixType, definition) {
                if (definition !== null) {
                    if (typeof definition !== 'function') {
                        throwWrongParamsErr('mixin', 'definition');
                    }
                    mixins[mixType] = definition;
                } else {
                    delete mixins[mixType];
                }
            },
            /**
             * Get all available types and mixins
             * @method getRegistry
             * @return {object} object with arrays declared types and mixins
             * @example
             * {
             *   type: ['type1', 'type2', ...],
             *   mixin: ['mixin1', 'mixin2', ...]
             * }
             */
            getRegistry: function () {
                var iterate = function (obj) {
                        var prop, arr = [];
                        for (prop in obj) {
                            arr.push(prop);
                        }
                        return arr;
                    };
                return {
                    type: iterate(map),
                    mixin: iterate(mixins)
                };
            },
            /**
             * Get internal information about type
             * @method getTypeInfo
             * @param {string} type - name of type
             * @return {object} object with information about type,
             * base type, applied mixins and configuration for dependency injection
             * @example
             * {
             *      $type: 'child',
             *      $basetype: 'base',
             *      $mixin: {
             *          mixA: 'mixinA',
             *          mixB: 'mixinB'
             *      },
             *      $di: {
             *          $current: {
             *              type: 'child',
             *              instance: 'item',
             *              lifestyle: 'transient'
             *          },
             *          $prop: {
             *              a: {
             *                  type: 'base',
             *                  instance: 'item',
             *                  lifestyle: 'transient'
             *              },
             *              b: 'child'
             *          }
             *      }
             *  }
             */
            getTypeInfo: function (type) {
                var result,
                    mapObj = map[type];
                throwWrongType(mapObj, type);
                result = extend({}, mapObj);
                delete result.$ctor;
                delete result.$base;
                delete result.$di;
                result.$di = JSON.parse(JSON.stringify(mapObj.$di));
                return result;
            }
        };
    // Return as AMD module or attach to head object
    if (typeof define !== 'undefined') {
        define([], function () { return Moa; });
    } else if (typeof window !== 'undefined') {
        window.Moa = Moa;
    } else {
        module.exports = Moa;
    }
}());
Copyright © 2013-2014 Sergii Danilov. All rights reserved.
Documentation generated by JSDoc 3.2.2 on Wed Apr 09 2014 00:21:18 GMT+0100 (IST) using the DocStrap template.