UNPKG

plus.container

Version:

Dependency Injection Container - plus.container

441 lines (359 loc) 11.7 kB
// class Container function Container() { this._new(); } // Util Container.extend = function (dest, src) { for (var i in src) dest[i] = src[i]; }; Container.create = function () { return new Container(); }; // class Container Container.extend(Container.prototype, { _new: function () { this._resolved = new Container.Hash(); this._register = new Container.Hash(); this._dependencies = new Container.Hash(); this._tags = new Container.Hash(); this._parentGetter = null; this._accesor = new Container.Accessor(); this._properties = new Container.Hash(); }, add: function (name, definition, dependencies) { return this.register(name, definition, dependencies); }, provide: function (name, definition, dependencies) { if (Container.isFunction(definition)) { definition.$injectMap = dependencies || {}; } return this.register(name, definition, ['container']); }, register: function (name, definition, dependencies) { // clean up this.remove(name); this._defineProperty(name); this._defineParentGetter(definition); if (Container.isFunction(definition)) { this._register.set(name, definition); this._dependencies.set(name, dependencies || definition.$inject || []); this._tags.set(name, definition.$tags || []); } else { this.set(name, definition); } // to chain return this; }, get: function (name) { // return self if (name == 'container') return this; // use accessor if (this._accesor.isPath(name)) return this._accesor.get(this, name); // if resolved return if (this._resolved.has(name)) return this._resolved.get(name); // if not registered return null if (!this._register.has(name)) { if (Container.isFunction(this._parentGetter)) { return this._parentGetter(name); } return null; } // resolve this.set(name, this.create(name)); return this.get(name); }, set: function (name, definition) { if (this._accesor.isPath(name)) return this._accesor.set(this, name, definition); this._resolved.set(name, definition); }, create: function (name) { if (!this._register.has(name)) return null; // get _class var _class = this._register.get(name); // null if not a function if (!Container.isFunction(_class)) return null; if (_class.$injectMap) { return this._createMapInjected(name); } else { return this._createArrayInjected(name); } }, remove: function (name) { this._register.remove(name); this._resolved.remove(name); this._dependencies.remove(name); this._tags.remove(name); }, find: function (include, exclude) { var self = this; var result = []; this._tags.each(function (tags, name) { var found = true; Container.each(include || [], function (name) { if (tags.indexOf(name) < 0) found = false; }); Container.each(exclude || [], function (name) { if (tags.indexOf(name) >= 0) found = false; }); if (found) result.push(self.get(name)); }); return result; }, merge: function (container) { if (container instanceof Container) { this._resolved.merge(container._resolved); this._register.merge(container._register); this._dependencies.merge(container._dependencies); this._tags.merge(container._tags); } }, load: function (options) { this.merge(Container.load(options)); }, _defineProperty: function (name) { var container = this; var hasSupport = Container.isFunction(Object.defineProperty); var isNotDefined = !container._properties.has(name); var isNotOwnMethod = !container[name] || true; if (hasSupport && isNotDefined && isNotOwnMethod) { container._properties.set(name, name); Object.defineProperty(container, name, { get: function () { return container.get(name); } }); } }, _getPropertyContainer: function (map) { map = map || {}; var container = this; var wrapper = {}; var bind = function (name) { if (Container.isFunction(Object.defineProperty)) { Object.defineProperty(wrapper, name, { get: function () { var mappedName = map[name] || name; return container.get(mappedName); } }); } }; /// @@@ reafactor var Hash = Container.Hash; var props = (new Hash()).merge(new Hash(map)).merge(container._properties); props.each(function (value, name) { bind(name); }); return wrapper; }, _createMapInjected: function (name) { var _class = this._register.get(name); var map = _class.$injectMap || {}; var wrapper = this._getPropertyContainer(map); var args = [wrapper]; // make creator with args var creator = Container.makeCreator(_class, args); // create return creator(); }, _createArrayInjected: function (name) { // get class var _class = this._register.get(name); // get names var $inject = this._dependencies.has(name) ? this._dependencies.get(name) : []; // args collection var args = []; // collect args for (var i = 0; i < $inject.length; i++) { args.push(this.get($inject[i])); } // make creator with args var creator = Container.makeCreator(_class, args); // create return new creator(); }, _setParent: function (parent) { var container = this; this._parentGetter = function (name) { return parent.get(name); }; parent._properties.each(function (name) { container._defineProperty(name) }); }, _defineParentGetter: function (childContainer) { if (Container.isContainer(childContainer)) { childContainer._setParent(this); } } }); // class Hash Container.Hash = function (hash) { this._new(hash); }; Container.extend(Container.Hash.prototype, { _new: function (hash) { this.hash = hash || {}; }, get: function (name) { return this.has(name) ? this.hash[name] : null; }, set: function (name, value) { this.hash[name] = value; }, has: function (name) { return name in this.hash; }, remove: function (name) { return this.has(name) && delete this.hash[name]; }, each: function (fn) { for (var i in this.hash) fn(this.hash[i], i); }, keys: function () { var keys = []; this.each(function (value, key) { keys.push(key); }); return keys; }, merge: function (hash) { if (hash instanceof Container.Hash) Container.extend(this.hash, hash.hash); return this; } }); // Tools Container.extend(Container, { isFunction: function (value) { return value instanceof Function; }, isClass: function (value) { return typeof value === 'function' && /^\s*class\s+/.test(value.toString()); }, isArray: function (value) { return Object.prototype.toString.call(value) === '[object Array]'; }, isPureObject: function (value) { return Container.isObject(value) && !Container.isArray(value) && !Container.isFunction(value); }, isObject: function (value) { return value instanceof Object; }, isContainer: function (value) { return value instanceof Container; }, each: function (hash, fn) { if (Container.isArray(hash)) { for (var i = 0; i < hash.length; i++) fn(hash[i], i); } else { new Container.Hash(hash).each(fn); } }, makeCreator: function (_class, _args) { if (Container.isClass(_class)) { var bind = function () { var a = _args .map(function (value, idx) { return '_args[' + idx + ']'; }) .join(', '); return eval('new _class(' + a + ')'); }; return bind; } else { function bind() { return _class.apply(this, _args); } bind.prototype = _class.prototype; return bind; } }, bind: function (_class, args) { function bind() { return _class.apply(this, args); } bind.prototype = _class.prototype; return bind; } }); // Accessor Container.Accessor = function () { this.separator = '/'; this.getters = [function (context, name) { var method = 'get'; if (Container.isFunction(context[method])) { return context[method].call(context, name); } }, function (context, name) { if (context[name]) { return context[name]; } }]; this.setters = [function (context, name, value) { var method = 'set'; if (Container.isFunction(context[method])) { context[method].call(context, name, value); return true; } }, function (context, name, value) { context[name] = value; return true; }]; this._new(); }; Container.extend(Container.Accessor.prototype, { _new: function () { }, isPath: function (name) { return ('' + name).indexOf(this.separator) >= 0; }, get: function (context, name) { if (Container.isObject(context)) { var names = Container.isArray(name) ? name.slice() : name.split(this.separator); if (names.length) { var name = names.shift(); var result = undefined; for (var i = 0; i < this.getters.length; i++) { var getter = this.getters[i]; result = getter(context, name); if (result !== undefined) break; } if (Container.isObject(result) && names.length) return this.get(result, names); return result; } } return undefined; }, set: function (context, name, value) { if (Container.isObject(context)) { var names = Container.isArray(name) ? name.slice() : name.split(this.separator); var name = names.pop(); var result = this.get(context, names); var done = false; if (Container.isObject(result)) { Container.each(this.setters, function (setter) { if (!done && setter(result, name, value)) { done = true; } }); } else { // can not set } } } }); module.exports = Container;