Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Another checkpoint, using Container now
- Loading branch information
Showing
24 changed files
with
534 additions
and
174 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
|
@@ -34,3 +34,4 @@ tmp*.gem | ||
tmp.bpm | tmp.bpm | ||
tmp.spade | tmp.spade | ||
tests/source | tests/source | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,138 @@ | |||
var get = Ember.get, set = Ember.set; | |||
|
|||
function Container() { | |||
this.registry = {}; | |||
this.cache = {}; | |||
this.typeInjections = {}; | |||
this.injections = {}; | |||
this.options = {}; | |||
this.typeOptions = {}; | |||
} | |||
|
|||
Container.prototype = { | |||
set: function(object, key, value) { | |||
object[key] = value; | |||
}, | |||
|
|||
register: function(type, name, factory, options) { | |||
this.registry[type + ":" + name] = factory; | |||
this.options[type + ":" + name] = options || {}; | |||
}, | |||
|
|||
resolve: function(fullName) { | |||
if (this.registry.hasOwnProperty(fullName)) { | |||
return this.registry[fullName]; | |||
} | |||
}, | |||
|
|||
lookup: function(fullName) { | |||
if (this.cache.hasOwnProperty(fullName)) { | |||
return this.cache[fullName]; | |||
} | |||
|
|||
var value = instantiate(this, fullName); | |||
|
|||
if (!value) { return; } | |||
|
|||
if (isSingleton(this, fullName)) { | |||
this.cache[fullName] = value; | |||
} | |||
|
|||
return value; | |||
}, | |||
|
|||
optionsForType: function(type, options) { | |||
this.typeOptions[type] = options; | |||
}, | |||
|
|||
typeInjection: function(type, property, fullName) { | |||
var injections = this.typeInjections[type] = this.typeInjections[type] || []; | |||
injections.push({ property: property, fullName: fullName }); | |||
}, | |||
|
|||
injection: function(factoryName, property, injectionName) { | |||
var injections = this.injections[factoryName] = this.injections[factoryName] || []; | |||
injections.push({ property: property, fullName: injectionName }); | |||
}, | |||
|
|||
destroy: function() { | |||
eachDestroyable(this, function(item) { | |||
item.isDestroying = true; | |||
}); | |||
|
|||
eachDestroyable(this, function(item) { | |||
item.destroy(); | |||
}); | |||
|
|||
this.isDestroyed = true; | |||
} | |||
}; | |||
|
|||
function isSingleton(container, fullName) { | |||
var singleton = option(container, fullName, 'singleton'); | |||
|
|||
return singleton !== false; | |||
} | |||
|
|||
function applyInjections(container, value, injections) { | |||
if (!injections) { return; } | |||
|
|||
var injection, lookup; | |||
|
|||
for (var i=0, l=injections.length; i<l; i++) { | |||
injection = injections[i]; | |||
lookup = container.lookup(injection.fullName); | |||
container.set(value, injection.property, lookup); | |||
} | |||
} | |||
|
|||
function option(container, fullName, optionName) { | |||
var options = container.options[fullName]; | |||
|
|||
if (options && options[optionName] !== undefined) { | |||
return options[optionName]; | |||
} | |||
|
|||
var type = fullName.split(":")[0]; | |||
options = container.typeOptions[type]; | |||
|
|||
if (options) { | |||
return options[optionName]; | |||
} | |||
} | |||
|
|||
function instantiate(container, fullName) { | |||
var splitName = fullName.split(":"), | |||
type = splitName[0], name = splitName[1], | |||
value; | |||
|
|||
var factory = container.resolve(fullName); | |||
|
|||
if (option(container, fullName, 'instantiate') === false) { | |||
return factory; | |||
} | |||
|
|||
if (factory) { | |||
value = factory.create({ container: container }); | |||
|
|||
var injections = []; | |||
injections = injections.concat(container.typeInjections[type] || []); | |||
injections = injections.concat(container.injections[fullName] || []); | |||
|
|||
applyInjections(container, value, injections); | |||
|
|||
return value; | |||
} | |||
} | |||
|
|||
function eachDestroyable(container, callback) { | |||
var cache = container.cache; | |||
|
|||
for (var prop in cache) { | |||
if (!cache.hasOwnProperty(prop)) { continue; } | |||
if (option(container, prop, 'instantiate') === false) { continue; } | |||
callback(cache[prop]); | |||
} | |||
} | |||
|
|||
Ember.Container = Container; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,30 @@ | |||
{ | |||
"name": "container", | |||
"summary": "A lightweight dependency injection container", | |||
"description": "A lightweight library that provides a pluggable dependency injection container designed for use in Ember.js", | |||
"homepage": "https://github.com/emberjs/ember.js", | |||
"authors": ["Yehuda Katz", "Tom Dale"], | |||
"version": "1.0.0-pre.2", | |||
|
|||
"dependencies": { | |||
"spade": "~> 1.0.0" | |||
}, | |||
|
|||
"directories": { | |||
"lib": "lib" | |||
}, | |||
|
|||
"bpm:build": { | |||
"bpm_libs.js": { | |||
"files": ["lib"], | |||
"modes": "*" | |||
}, | |||
|
|||
"handlebars/bpm_tests.js": { | |||
"files": ["tests"], | |||
"modes": ["debug"] | |||
} | |||
} | |||
} | |||
|
|||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,181 @@ | |||
var get = Ember.get; | |||
|
|||
module("Container"); | |||
|
|||
function factory() { | |||
var Klass = function(container) { | |||
this.container = container; | |||
}; | |||
|
|||
Klass.prototype.destroy = function() { | |||
this.isDestroyed = true; | |||
}; | |||
|
|||
Klass.create = function(options) { | |||
return new Klass(options.container); | |||
}; | |||
|
|||
return Klass; | |||
} | |||
|
|||
test("A registered factory returns the same instance each time", function() { | |||
var container = new Ember.Container(); | |||
var PostController = factory(); | |||
|
|||
container.register('controller', 'post', PostController); | |||
|
|||
var postController = container.lookup('controller:post'); | |||
|
|||
ok(postController instanceof PostController, "The lookup is an instance of the factory"); | |||
|
|||
equal(postController, container.lookup('controller:post')); | |||
}); | |||
|
|||
test("A container lookup has access to the container", function() { | |||
var container = new Ember.Container(); | |||
var PostController = factory(); | |||
|
|||
container.register('controller', 'post', PostController); | |||
|
|||
var postController = container.lookup('controller:post'); | |||
|
|||
equal(postController.container, container); | |||
}); | |||
|
|||
test("A factory type with a registered injection receives the injection", function() { | |||
var container = new Ember.Container(); | |||
var PostController = factory(); | |||
var Store = factory(); | |||
|
|||
container.register('controller', 'post', PostController); | |||
container.register('store', 'main', Store); | |||
|
|||
container.typeInjection('controller', 'store', 'store:main'); | |||
|
|||
var postController = container.lookup('controller:post'); | |||
var store = container.lookup('store:main'); | |||
|
|||
equal(postController.store, store); | |||
}); | |||
|
|||
test("An individual factory with a registered injection receives the injection", function() { | |||
var container = new Ember.Container(); | |||
var PostController = factory(); | |||
var Store = factory(); | |||
|
|||
container.register('controller', 'post', PostController); | |||
container.register('store', 'main', Store); | |||
|
|||
container.injection('controller:post', 'store', 'store:main'); | |||
|
|||
var postController = container.lookup('controller:post'); | |||
var store = container.lookup('store:main'); | |||
|
|||
equal(postController.store, store); | |||
}); | |||
|
|||
test("A factory with both type and individual injections", function() { | |||
var container = new Ember.Container(); | |||
var PostController = factory(); | |||
var Store = factory(); | |||
var Router = factory(); | |||
|
|||
container.register('controller', 'post', PostController); | |||
container.register('store', 'main', Store); | |||
container.register('router', 'main', Router); | |||
|
|||
container.injection('controller:post', 'store', 'store:main'); | |||
container.typeInjection('controller', 'router', 'router:main'); | |||
|
|||
var postController = container.lookup('controller:post'); | |||
var store = container.lookup('store:main'); | |||
var router = container.lookup('router:main'); | |||
|
|||
equal(postController.store, store); | |||
equal(postController.router, router); | |||
}); | |||
|
|||
test("A non-singleton factory is never cached", function() { | |||
var container = new Ember.Container(); | |||
var PostView = factory(); | |||
|
|||
container.register('view', 'post', PostView, { singleton: false }); | |||
|
|||
var postView1 = container.lookup('view:post'); | |||
var postView2 = container.lookup('view:post'); | |||
|
|||
ok(postView1 !== postView2, "Non-singletons are not cached"); | |||
}); | |||
|
|||
test("A non-instantiated property is not instantiated", function() { | |||
var container = new Ember.Container(); | |||
|
|||
var template = function() {}; | |||
container.register('template', 'foo', template, { instantiate: false }); | |||
equal(container.lookup('template:foo'), template); | |||
}); | |||
|
|||
test("A failed lookup returns undefined", function() { | |||
var container = new Ember.Container(); | |||
|
|||
equal(container.lookup("doesnot:exist"), undefined); | |||
}); | |||
|
|||
test("Destroying the container destroys any cached singletons", function() { | |||
var container = new Ember.Container(); | |||
var PostController = factory(); | |||
var PostView = factory(); | |||
var template = function() {}; | |||
|
|||
container.register('controller', 'post', PostController); | |||
container.register('view', 'post', PostView, { singleton: false }); | |||
container.register('template', 'post', template, { instantiate: false }); | |||
|
|||
container.injection('controller:post', 'postView', 'view:post'); | |||
|
|||
var postController = container.lookup('controller:post'); | |||
var postView = postController.postView; | |||
|
|||
ok(postView instanceof PostView, "The non-singleton was injected"); | |||
|
|||
container.destroy(); | |||
|
|||
ok(postController.isDestroyed, "Singletons are destroyed"); | |||
ok(!postView.isDestroyed, "Non-singletons are not destroyed"); | |||
}); | |||
|
|||
test("The container can take a hook to resolve factories lazily", function() { | |||
var container = new Ember.Container(); | |||
var PostController = factory(); | |||
|
|||
container.resolve = function(fullName) { | |||
if (fullName === 'controller:post') { | |||
return PostController; | |||
} | |||
}; | |||
|
|||
var postController = container.lookup('controller:post'); | |||
|
|||
ok(postController instanceof PostController, "The correct factory was provided"); | |||
}); | |||
|
|||
test("The container can get options that should be applied to all factories for a given type", function() { | |||
var container = new Ember.Container(); | |||
var PostView = factory(); | |||
|
|||
container.resolve = function(fullName) { | |||
if (fullName === 'view:post') { | |||
return PostView; | |||
} | |||
}; | |||
|
|||
container.optionsForType('view', { singleton: false }); | |||
|
|||
var postView1 = container.lookup('view:post'); | |||
var postView2 = container.lookup('view:post'); | |||
|
|||
ok(postView1 instanceof PostView, "The correct factory was provided"); | |||
ok(postView2 instanceof PostView, "The correct factory was provided"); | |||
|
|||
ok(postView1 !== postView2, "The two lookups are different"); | |||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -1,2 +1,3 @@ | |||
require('container'); | |||
require('ember-application/system/dag'); | require('ember-application/system/dag'); | ||
require('ember-application/system/application'); | require('ember-application/system/application'); |
Oops, something went wrong.