From 25f444603b660976c00eb4cb4b7be31d1da01f86 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Sun, 16 Dec 2012 17:47:23 -0800 Subject: [PATCH] Another checkpoint, using Container now --- .gitignore | 1 + .jshintrc | 1 + packages/container/lib/main.js | 138 +++++++++++++ packages/container/package.json | 30 +++ packages/container/tests/container_test.js | 181 ++++++++++++++++++ packages/ember-application/lib/system.js | 1 + .../lib/system/application.js | 113 +++++------ packages/ember-application/package.json | 1 + .../tests/system/application_test.js | 19 +- .../tests/system/injections_test.js | 9 +- .../tests/system/readiness_test.js | 9 +- packages/ember-metal/lib/observer.js | 2 +- packages/ember-routing/lib/system.js | 1 + packages/ember-routing/lib/system/route.js | 49 ++--- packages/ember-routing/lib/system/router.js | 43 ++--- packages/ember-routing/package.json | 3 +- .../tests/helpers/link_to_test.js | 24 +-- .../tests/integration/basic_test.js | 45 +++-- .../ember-runtime/lib/system/core_object.js | 6 +- packages/ember-views/lib/system/controller.js | 1 + .../ember-views/lib/views/container_view.js | 2 +- packages/ember-views/package.json | 1 + .../tests/views/container_view_test.js | 5 +- tests/index.html | 23 ++- 24 files changed, 534 insertions(+), 174 deletions(-) create mode 100644 packages/container/lib/main.js create mode 100644 packages/container/package.json create mode 100644 packages/container/tests/container_test.js diff --git a/.gitignore b/.gitignore index 85f6a23c01f..d84b61f07e7 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ tmp*.gem tmp.bpm tmp.spade tests/source +node_modules diff --git a/.jshintrc b/.jshintrc index 76da3cae409..cb7a44ef5bd 100644 --- a/.jshintrc +++ b/.jshintrc @@ -19,6 +19,7 @@ "testBoth", "testWithDefault", "raises", + "throws", "deepEqual", "start", "stop", diff --git a/packages/container/lib/main.js b/packages/container/lib/main.js new file mode 100644 index 00000000000..9decdbc7fc5 --- /dev/null +++ b/packages/container/lib/main.js @@ -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 1.0.0" + }, + + "directories": { + "lib": "lib" + }, + + "bpm:build": { + "bpm_libs.js": { + "files": ["lib"], + "modes": "*" + }, + + "handlebars/bpm_tests.js": { + "files": ["tests"], + "modes": ["debug"] + } + } +} + + diff --git a/packages/container/tests/container_test.js b/packages/container/tests/container_test.js new file mode 100644 index 00000000000..16dc91dc7f3 --- /dev/null +++ b/packages/container/tests/container_test.js @@ -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"); +}); diff --git a/packages/ember-application/lib/system.js b/packages/ember-application/lib/system.js index 9ba8839f163..6f10f092e2f 100644 --- a/packages/ember-application/lib/system.js +++ b/packages/ember-application/lib/system.js @@ -1,2 +1,3 @@ +require('container'); require('ember-application/system/dag'); require('ember-application/system/application'); diff --git a/packages/ember-application/lib/system/application.js b/packages/ember-application/lib/system/application.js index 83b0f26383d..f4032fb5227 100644 --- a/packages/ember-application/lib/system/application.js +++ b/packages/ember-application/lib/system/application.js @@ -3,7 +3,9 @@ @submodule ember-application */ -var get = Ember.get, set = Ember.set; +var get = Ember.get, set = Ember.set, classify = Ember.String.classify; + +Ember.Container.set = Ember.set; /** An instance of `Ember.Application` is the starting point for every Ember @@ -189,7 +191,7 @@ var get = Ember.get, set = Ember.set; @namespace Ember @extends Ember.Namespace */ -Ember.Application = Ember.Namespace.extend( +var Application = Ember.Application = Ember.Namespace.extend( /** @scope Ember.Application.prototype */{ /** @@ -267,11 +269,10 @@ Ember.Application = Ember.Namespace.extend( init: function() { if (!this.$) { this.$ = Ember.$; } - this._super(); + var container = this.container = Application.buildContainer(this); + container.register('view', 'application', Ember.View.extend()); - if (this.Router === undefined && this.router === undefined) { - this.Router = Ember.Router.extend(); - } + this._super(); this.createEventDispatcher(); @@ -345,13 +346,15 @@ Ember.Application = Ember.Namespace.extend( @method initialize @param router {Ember.Router} */ - initialize: function(router) { + initialize: function() { Ember.assert("Application initialize may only be called once", !this.isInitialized); Ember.assert("Application not destroyed", !this.isDestroyed); - router = this.setupRouter(router); + var Router = this.Router; + if (!Router && this.router === undefined) { Router = Ember.Router.extend(); } + this.container.register('router', 'main', Router); - this.runInjections(router); + this.runInjections(); Ember.runLoadHooks('application', this); @@ -365,8 +368,9 @@ Ember.Application = Ember.Namespace.extend( }, /** @private */ - runInjections: function(router) { - var injections = get(this.constructor, 'injections'), + runInjections: function() { + var router = this.container.lookup('router:main'), + injections = get(this.constructor, 'injections'), graph = new Ember.DAG(), namespace = this, properties, i, injection; @@ -385,31 +389,11 @@ Ember.Application = Ember.Namespace.extend( }); }, - /** @private */ - setupRouter: function(router) { - if (!router && Ember.Router.detect(this.Router)) { - router = this.Router.create(); - this._createdRouter = router; - } - - if (router) { - // By default, the router's namespace is the current application. - // - // This allows it to find model classes when a state has a - // route like `/posts/:post_id`. In that case, it would first - // convert `post_id` into `Post`, and then look it up on its - // namespace. - set(router, 'namespace', this); - } - - return router; - }, - /** @private */ didBecomeReady: function() { var eventDispatcher = get(this, 'eventDispatcher'), customEvents = get(this, 'customEvents'), - router = this._createdRouter; + router = this.container.lookup('router:main'); eventDispatcher.setup(customEvents); @@ -426,33 +410,8 @@ Ember.Application = Ember.Namespace.extend( createApplicationView: function () { var rootElement = get(this, 'rootElement'), - router = this._createdRouter, - applicationViewOptions = {}, - applicationViewClass = this.ApplicationView, - applicationTemplate = Ember.TEMPLATES.application, - applicationController, applicationView; - - // don't do anything unless there is an ApplicationView or application template - if (!applicationViewClass && !applicationTemplate) return; - - if (router) { - applicationController = get(router, 'applicationController'); - if (applicationController) { - applicationViewOptions.controller = applicationController; - } - } - - if (applicationTemplate) { - applicationViewOptions.template = applicationTemplate; - } - - if (!applicationViewClass) { - applicationViewClass = Ember.View; - } - - applicationView = applicationViewClass.create(applicationViewOptions); - - this._createdApplicationView = applicationView; + router = this.container.lookup('router:main'), + applicationView = this.container.lookup('view:application'); if (router) { router._activeViews.application = applicationView; @@ -491,7 +450,7 @@ Ember.Application = Ember.Namespace.extend( willDestroy: function() { get(this, 'eventDispatcher').destroy(); - if (this._createdRouter) { this._createdRouter.destroy(); } + this.container.destroy(); if (this._createdApplicationView) { this._createdApplicationView.destroy(); } }, @@ -511,9 +470,43 @@ Ember.Application.reopenClass({ Ember.assert("An injection cannot be registered without an injection function", Ember.canInvoke(injection, 'injection')); injections.push(injection); + }, + + buildContainer: function(namespace) { + var container = new Ember.Container(); + container.set = Ember.set; + container.resolve = resolveFor(namespace); + container.optionsForType('view', { singleton: false }); + container.optionsForType('template', { instantiate: false }); + container.register('application', 'main', namespace, { instantiate: false }); + container.injection('router:main', 'namespace', 'application:main'); + container.typeInjection('controller', 'target', 'router:main'); + + container.injection('view:application', 'controller', 'controller:application'); + container.injection('view:application', 'defaultTemplate', 'template:application'); + + return container; } }); +function resolveFor(namespace) { + return function(fullName) { + var nameParts = fullName.split(":"), + type = nameParts[0], name = nameParts[1]; + + if (type === 'template' && Ember.TEMPLATES[name]) { + return Ember.TEMPLATES[name]; + } + + var className = classify(name) + classify(type); + var factory = get(namespace, className); + + if (factory) { return factory; } + + return Ember.Container.prototype.resolve.call(this, fullName); + }; +} + Ember.Application.registerInjection({ name: 'controllers', injection: function(app, router, property) { diff --git a/packages/ember-application/package.json b/packages/ember-application/package.json index 4f1ba4764f7..98612064cbf 100644 --- a/packages/ember-application/package.json +++ b/packages/ember-application/package.json @@ -9,6 +9,7 @@ "ember-views": "1.0.0-pre.2", "ember-states": "1.0.0-pre.2", "ember-routing": "1.0.0-pre.2" + "container": "1.0.0-pre.2" }, "directories": { diff --git a/packages/ember-application/tests/system/application_test.js b/packages/ember-application/tests/system/application_test.js index bb06f3e73aa..b752bcf0e73 100644 --- a/packages/ember-application/tests/system/application_test.js +++ b/packages/ember-application/tests/system/application_test.js @@ -62,11 +62,11 @@ test("you cannot make two default applications without a rootElement error", fun }); Ember.run(function() { - application = Ember.Application.create().initialize(); + application = Ember.Application.create({ router: false }).initialize(); }); raises(function() { Ember.run(function() { - Ember.Application.create().initialize(); + Ember.Application.create({ router: false }).initialize(); }); }, Error); }); @@ -94,6 +94,7 @@ var app; module("Ember.Application initialization", { teardown: function() { + Ember.TEMPLATES = {}; Ember.run(function(){ app.destroy(); }); } }); @@ -112,8 +113,8 @@ test('initialized application go to initial route', function() { match("/").to("index"); }); - Ember.TEMPLATES.application = Ember.Handlebars.compile( - "{{outlet}}" + app.container.register('template', 'application', + Ember.Handlebars.compile("{{outlet}}") ); Ember.TEMPLATES.index = Ember.Handlebars.compile( @@ -149,8 +150,9 @@ test("initialize application via initialize call", function() { app.initialize(); }); - equal(app._createdRouter instanceof Ember.Router, true, "Router was set from initialize call"); - equal(app._createdRouter.location instanceof Ember.NoneLocation, true, "Location was set from location implementation name"); + var router = app.container.lookup('router:main'); + equal(router instanceof Ember.Router, true, "Router was set from initialize call"); + equal(router.location instanceof Ember.NoneLocation, true, "Location was set from location implementation name"); }); test("initialize application with stateManager via initialize call from Router class", function() { @@ -178,7 +180,8 @@ test("initialize application with stateManager via initialize call from Router c app.initialize(); }); - equal(app._createdRouter instanceof Ember.Router, true, "Router was set from initialize call"); + var router = app.container.lookup('router:main'); + equal(router instanceof Ember.Router, true, "Router was set from initialize call"); equal(Ember.$("#qunit-fixture h1").text(), "Hello!"); }); @@ -213,6 +216,7 @@ test("ApplicationView is inserted into the page", function() { test("Application initialized twice raises error", function() { Ember.run(function() { app = Ember.Application.create({ + router: false, rootElement: '#qunit-fixture' }).initialize(); }); @@ -228,6 +232,7 @@ test("Minimal Application initialized with just an application template", functi Ember.$('#qunit-fixture').html(''); Ember.run(function () { app = Ember.Application.create({ + router: false, rootElement: '#qunit-fixture' }).initialize(); }); diff --git a/packages/ember-application/tests/system/injections_test.js b/packages/ember-application/tests/system/injections_test.js index da371d332ea..7df0533c99a 100644 --- a/packages/ember-application/tests/system/injections_test.js +++ b/packages/ember-application/tests/system/injections_test.js @@ -61,10 +61,11 @@ test("injections can be registered in a specified order", function() { Ember.run(function() { app = Ember.Application.create({ + router: false, order: order, rootElement: '#qunit-fixture' }); - app.initialize(Ember.Object.create()); + app.initialize(); }); deepEqual(order, ['first', 'second', 'third', 'fourth', 'fifth']); @@ -118,10 +119,11 @@ test("injections can have multiple dependencies", function () { Ember.run(function() { app = Ember.Application.create({ + router: false, order: order, rootElement: '#qunit-fixture' }); - app.initialize(Ember.Object.create()); + app.initialize(); }); ok(indexOf.call(order, a.name) < indexOf.call(order, b.name), 'a < b'); @@ -156,9 +158,10 @@ test("injections are passed properties created from previous injections", functi Ember.run(function() { app = Ember.Application.create({ + router: false, rootElement: '#qunit-fixture' }); - app.initialize(Ember.Object.create()); + app.initialize(); }); ok(secondInjectionWasPassedProperty, "second injections wasn't passed the property created in the first"); diff --git a/packages/ember-application/tests/system/readiness_test.js b/packages/ember-application/tests/system/readiness_test.js index 6c610029e55..4e00b3376d8 100644 --- a/packages/ember-application/tests/system/readiness_test.js +++ b/packages/ember-application/tests/system/readiness_test.js @@ -61,7 +61,7 @@ test("Ember.Application's ready event is called right away if jQuery is already jQuery.isReady = true; Ember.run(function() { - application = Application.create().initialize(); + application = Application.create({ router: false }).initialize(); }); equal(readyWasCalled, 1, "ready was called"); @@ -75,7 +75,7 @@ test("Ember.Application's ready event is called right away if jQuery is already test("Ember.Application's ready event is called after the document becomes ready", function() { Ember.run(function() { - application = Application.create().initialize(); + application = Application.create({ router: false }).initialize(); }); equal(readyWasCalled, 0, "ready wasn't called yet"); @@ -90,6 +90,7 @@ test("Ember.Application's ready event is called after the document becomes ready test("Ember.Application's ready event is called after the document becomes ready without initialize if autoinit is set", function() { Ember.run(function() { application = Application.create({ + router: false, autoinit: true }); }); @@ -105,7 +106,7 @@ test("Ember.Application's ready event is called after the document becomes ready test("Ember.Application's ready event can be deferred by other components", function() { Ember.run(function() { - application = Application.create(); + application = Application.create({ router: false }); }); application.deferReadiness(); @@ -133,7 +134,7 @@ test("Ember.Application's ready event can be deferred by other components", func jQuery.isReady = true; Ember.run(function() { - application = Application.create(); + application = Application.create({ router: false }); }); application.deferReadiness(); diff --git a/packages/ember-metal/lib/observer.js b/packages/ember-metal/lib/observer.js index e947713d539..7f45862cc97 100644 --- a/packages/ember-metal/lib/observer.js +++ b/packages/ember-metal/lib/observer.js @@ -66,7 +66,7 @@ ObserverSet.prototype.flush = function() { for (i=0, len=observers.length; i < len; ++i) { observer = observers[i]; sender = observer.sender; - if (sender.isDestroyed) { continue; } + if (sender.isDestroying || sender.isDestroyed) { continue; } Ember.sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners); } }; diff --git a/packages/ember-routing/lib/system.js b/packages/ember-routing/lib/system.js index d956ac8bf34..ea29cc63254 100644 --- a/packages/ember-routing/lib/system.js +++ b/packages/ember-routing/lib/system.js @@ -1,2 +1,3 @@ +require('container'); require('ember-routing/system/router'); require('ember-routing/system/route'); diff --git a/packages/ember-routing/lib/system/route.js b/packages/ember-routing/lib/system/route.js index aaa75765a9f..7de5a858538 100644 --- a/packages/ember-routing/lib/system/route.js +++ b/packages/ember-routing/lib/system/route.js @@ -5,7 +5,6 @@ var DefaultView = Ember.View.extend(Ember._Metamorph); Ember.Route = Ember.Object.extend({ init: function() { var router = this.router; - this._container = router._container; this._activeViews = router._activeViews; this.namespace = router.namespace; }, @@ -16,16 +15,23 @@ Ember.Route = Ember.Object.extend({ This hook is the entry point for router.js */ setup: function(context) { + var container = this.router.container; + var templateName = this.templateName, - controller = this.lookup('controller', templateName, function() { - if (context && context.isSCArray) { - return Ember.ArrayController.create({ content: context }); - } else if (context) { - return Ember.ObjectController.create({ content: context }); - } else { - return Ember.Controller.create(); - } - }); + controller = container.lookup('controller:' + templateName); + + if (!controller) { + if (context && context.isSCArray) { + controller = Ember.ArrayController.extend({ content: context }); + } else if (context) { + controller = Ember.ObjectController.extend({ content: context }); + } else { + controller = Ember.Controller.extend(); + } + + container.register('controller', templateName, controller); + controller = container.lookup('controller:' + templateName); + } this.setupControllers(controller, context); this.renderTemplates(context); @@ -71,7 +77,7 @@ Ember.Route = Ember.Object.extend({ }, controller: function(name) { - return this.lookup('controller', name); + return this.router.container.lookup('controller:' + name); }, renderTemplates: function(context) { @@ -80,12 +86,11 @@ Ember.Route = Ember.Object.extend({ render: function(name, options) { var templateName = this.templateName, + container = this.router.container, className = classify(templateName), - viewClassName = className + "View", - viewClass = this.namespace[viewClassName] || DefaultView; + view = container.lookup('view:' + templateName) || DefaultView.create(); - var view = this._activeViews[templateName] = - viewClass.create({ templateName: templateName }); + set(view, 'template', container.lookup('template:' + templateName)); options = options || {}; var into = options.into || 'application'; @@ -93,24 +98,12 @@ Ember.Route = Ember.Object.extend({ var controller = options.controller || templateName; if (typeof controller === 'string') { - controller = this.lookup('controller', controller); + controller = container.lookup('controller:' + controller); } - set(controller, 'target', this.router); set(view, 'controller', controller); var parentView = this._activeViews[into]; parentView.connectOutlet(outlet, view); - }, - - lookup: function(kind, name, callback) { - var object = this._container[kind][name]; - - if (!object && callback) { - object = callback.call(this); - this._container[kind][name] = object; - } - - return object; } }); diff --git a/packages/ember-routing/lib/system/router.js b/packages/ember-routing/lib/system/router.js index 92be63f3a14..a37f5a713f1 100644 --- a/packages/ember-routing/lib/system/router.js +++ b/packages/ember-routing/lib/system/router.js @@ -3,6 +3,18 @@ var get = Ember.get, set = Ember.set, classify = Ember.String.classify; var DefaultView = Ember.View.extend(Ember._Metamorph); +function setupLocation(router) { + var location = get(router, 'location'), + rootURL = get(router, 'rootURL'); + + if ('string' === typeof location) { + set(router, 'location', Ember.Location.create({ + implementation: location, + rootURL: rootURL + })); + } +} + Ember.Router = Ember.Object.extend({ location: 'hash', @@ -10,25 +22,9 @@ Ember.Router = Ember.Object.extend({ var router = this.router = new Router(), self = this, handlers = {}; - // This is a temporary implementation. Apps should go through - // the `lookup` API and not try to use this data structure - // directly. - var container = this._container = { - view: {}, - controller: {} - }; - var activeViews = this._activeViews = {}; - var location = get(this, 'location'), - rootURL = get(this, 'rootURL'); - - if ('string' === typeof location) { - location = set(this, 'location', Ember.Location.create({ - implementation: location, - rootURL: rootURL - })); - } + setupLocation(this); Ember.assert("You must call " + this.constructor.toString() + ".map() before your application is initialized", this.constructor.callback); router.map(this.constructor.callback); @@ -36,17 +32,18 @@ Ember.Router = Ember.Object.extend({ startRouting: function() { var router = this.router, - location = get(this, 'location'); + location = get(this, 'location'), + container = this.container; - router.getHandler = getHandlerFunction(this, this._container, this._activeViews); + router.getHandler = getHandlerFunction(this, this._activeViews); router.updateURL = function() { location.setURL.apply(location, arguments); }; - if (!this._container.view.application) { - this._container.view.application = DefaultView.create({ + if (!container.lookup('view:application')) { + container.register('view', 'application', DefaultView.create({ templateName: 'application' - }); + })); } router.handleURL(location.getURL()); @@ -76,7 +73,7 @@ Ember.Router = Ember.Object.extend({ } }); -function getHandlerFunction(router, container, activeViews) { +function getHandlerFunction(router, activeViews) { var handlers = {}, namespace = get(router, 'namespace'); return function(name) { diff --git a/packages/ember-routing/package.json b/packages/ember-routing/package.json index 957e2bf0efd..0ca446fb83f 100644 --- a/packages/ember-routing/package.json +++ b/packages/ember-routing/package.json @@ -12,7 +12,8 @@ "dependencies": { "spade": "~> 1.0", - "ember-views": "1.0.0-pre.2" + "ember-views": "1.0.0-pre.2", + "container": "1.0.0-pre.2" }, "dependencies:development": { diff --git a/packages/ember-routing/tests/helpers/link_to_test.js b/packages/ember-routing/tests/helpers/link_to_test.js index 66fd617f45f..dc6125c875f 100644 --- a/packages/ember-routing/tests/helpers/link_to_test.js +++ b/packages/ember-routing/tests/helpers/link_to_test.js @@ -1,10 +1,10 @@ -var Router, App, AppView, templates, router, eventDispatcher; +require('ember-application'); + +var Router, App, AppView, templates, router, eventDispatcher, container; var get = Ember.get, set = Ember.set; function bootApplication() { - router = Router.create({ - location: 'none' - }); + router = container.lookup('router:main'); Ember.run(function() { router._activeViews.application = AppView.create().appendTo('#qunit-fixture'); @@ -18,20 +18,24 @@ module("The {{linkTo}} helper", { App = Ember.Namespace.create(); App.toString = function() { return "App"; }; + container = Ember.Application.buildContainer(App); + Ember.TEMPLATES.app = Ember.Handlebars.compile("{{outlet}}"); Ember.TEMPLATES.home = Ember.Handlebars.compile("

Home

{{#linkTo about id='about-link'}}About{{/linkTo}}"); Ember.TEMPLATES.about = Ember.Handlebars.compile("

About

{{#linkTo home id='home-link'}}Home{{/linkTo}}"); Ember.TEMPLATES.item = Ember.Handlebars.compile("

Item

{{name}}

{{#linkTo home id='home-link'}}Home{{/linkTo}}"); - AppView = Ember.View.extend({ - template: Ember.TEMPLATES.app + Router = Ember.Router.extend({ + location: 'none' }); - Router = Ember.Router.extend({ - namespace: App, - templates: Ember.TEMPLATES + AppView = Ember.View.extend({ + templateName: 'app' }); + container.register('view', 'app'); + container.register('router', 'main', Router); + eventDispatcher = Ember.EventDispatcher.create(); eventDispatcher.setup(); }); @@ -56,8 +60,6 @@ test("The {{linkTo}} helper moves into the named route", function() { equal(Ember.$('h3:contains(Home)', '#qunit-fixture').length, 1, "The home template was rendered"); - console.log(Ember.$('#qunit-fixture')[0]); - Ember.run(function() { Ember.$('a', '#qunit-fixture').click(); }); diff --git a/packages/ember-routing/tests/integration/basic_test.js b/packages/ember-routing/tests/integration/basic_test.js index 94687167a91..25fef086301 100644 --- a/packages/ember-routing/tests/integration/basic_test.js +++ b/packages/ember-routing/tests/integration/basic_test.js @@ -1,10 +1,8 @@ -var Router, App, AppView, templates, router; +var Router, App, AppView, templates, router, container; var get = Ember.get, set = Ember.set; function bootApplication() { - router = Router.create({ - location: 'none' - }); + router = container.lookup('router:main'); Ember.run(function() { router._activeViews.application = AppView.create().appendTo('#qunit-fixture'); @@ -18,6 +16,9 @@ module("Basic Routing", { App = Ember.Namespace.create(); App.toString = function() { return "App"; }; + + container = Ember.Application.buildContainer(App); + App.LoadingRoute = Ember.Route.extend({ }); @@ -25,14 +26,16 @@ module("Basic Routing", { Ember.TEMPLATES.home = Ember.Handlebars.compile("

Hours

"); Ember.TEMPLATES.homepage = Ember.TEMPLATES.home; + Router = Ember.Router.extend({ + location: 'none' + }); + AppView = Ember.View.extend({ template: Ember.TEMPLATES.app }); - Router = Ember.Router.extend({ - namespace: App, - templates: Ember.TEMPLATES - }); + container.register('view', 'app'); + container.register('router', 'main', Router); }); } }); @@ -95,7 +98,7 @@ test("The Homepage with a `setupControllers` hook", function() { bootApplication(); - router._container.controller.home = Ember.Controller.create(); + container.register('controller', 'home', Ember.Controller.extend()); Ember.run(function() { router.handleURL("/"); @@ -125,7 +128,7 @@ test("The Homepage with a `setupControllers` hook modifying other controllers", bootApplication(); - router._container.controller.home = Ember.Controller.create(); + container.register('controller', 'home', Ember.Controller.extend()); Ember.run(function() { router.handleURL("/"); @@ -159,7 +162,7 @@ test("The Homepage getting its controller context via model", function() { bootApplication(); - router._container.controller.home = Ember.Controller.create(); + container.register('controller', 'home', Ember.Controller.extend()); Ember.run(function() { router.handleURL("/"); @@ -192,7 +195,7 @@ test("The Specials Page getting its controller context by deserializing the para bootApplication(); - router._container.controller.special = Ember.Controller.create(); + container.register('controller', 'special', Ember.Controller.extend()); Ember.run(function() { router.handleURL("/specials/1"); @@ -226,7 +229,7 @@ test("The Specials Page defaults to looking models up via `find`", function() { bootApplication(); - router._container.controller.special = Ember.Controller.create(); + container.register('controller', 'special', Ember.Controller.extend()); Ember.run(function() { router.handleURL("/specials/1"); @@ -271,7 +274,7 @@ test("The Special Page returning a promise puts the app into a loading state unt bootApplication(); - router._container.controller.special = Ember.Controller.create(); + container.register('controller', 'special', Ember.Controller.extend()); Ember.run(function() { router.handleURL("/specials/1"); @@ -331,7 +334,7 @@ test("Moving from one page to another triggers the correct callbacks", function( bootApplication(); - router._container.controller.special = Ember.Controller.create(); + container.register('controller', 'special', Ember.Controller.extend()); Ember.run(function() { router.handleURL("/"); @@ -414,7 +417,7 @@ test("Nested callbacks are not exited when moving to siblings", function() { bootApplication(); }); - router._container.controller.special = Ember.Controller.create(); + container.register('controller', 'special', Ember.Controller.extend()); equal(Ember.$('h3', '#qunit-fixture').text(), "Home", "The app is now in the initial state"); equal(rootSetup, 1, "The root setup was triggered"); @@ -462,8 +465,10 @@ asyncTest("Events are triggered on the current state", function() { bootApplication(); - var controller = router._container.controller.home = Ember.Controller.create(); - controller.target = router; + container.register('controller', 'home', Ember.Controller.extend()); + + //var controller = router._container.controller.home = Ember.Controller.create(); + //controller.target = router; Ember.run(function() { router.handleURL("/"); @@ -506,8 +511,8 @@ asyncTest("Events are triggered on the current state", function() { bootApplication(); - var controller = router._container.controller.home = Ember.Controller.create(); - controller.target = router; + //var controller = router._container.controller.home = Ember.Controller.create(); + //controller.target = router; Ember.run(function() { router.handleURL("/"); diff --git a/packages/ember-runtime/lib/system/core_object.js b/packages/ember-runtime/lib/system/core_object.js index 207c31390ed..6d88e58132e 100644 --- a/packages/ember-runtime/lib/system/core_object.js +++ b/packages/ember-runtime/lib/system/core_object.js @@ -194,7 +194,6 @@ CoreObject.PrototypeMixin = Mixin.create({ if (this.willDestroy) { this.willDestroy(); } - set(this, 'isDestroyed', true); schedule('destroy', this, this._scheduledDestroy); return this; }, @@ -209,6 +208,8 @@ CoreObject.PrototypeMixin = Mixin.create({ */ _scheduledDestroy: function() { destroy(this); + set(this, 'isDestroyed', true); + if (this.didDestroy) { this.didDestroy(); } }, @@ -407,6 +408,3 @@ ClassMixin.apply(CoreObject); @namespace Ember */ Ember.CoreObject = CoreObject; - - - diff --git a/packages/ember-views/lib/system/controller.js b/packages/ember-views/lib/system/controller.js index b1db2d24218..081fac4cf69 100644 --- a/packages/ember-views/lib/system/controller.js +++ b/packages/ember-views/lib/system/controller.js @@ -20,6 +20,7 @@ Ember.ControllerMixin.reopen({ controllers: null, namespace: null, view: null, + container: null, /** Convenience method to connect controllers. This method makes other controllers diff --git a/packages/ember-views/lib/views/container_view.js b/packages/ember-views/lib/views/container_view.js index 142a6ffae84..36915ab0242 100644 --- a/packages/ember-views/lib/views/container_view.js +++ b/packages/ember-views/lib/views/container_view.js @@ -386,8 +386,8 @@ Ember.ContainerView = Ember.View.extend({ currentView = get(this, 'currentView'); if (currentView) { - childViews.removeObject(currentView); currentView.destroy(); + childViews.removeObject(currentView); } }, 'currentView'), diff --git a/packages/ember-views/package.json b/packages/ember-views/package.json index 1f66273fe44..820c01ea92b 100644 --- a/packages/ember-views/package.json +++ b/packages/ember-views/package.json @@ -14,6 +14,7 @@ "spade": "~> 1.0", "jquery": "~> 1.7", "ember-runtime": "1.0.0-pre.2" + "container": "1.0.0-pre.2" }, "dependencies:development": { diff --git a/packages/ember-views/tests/views/container_view_test.js b/packages/ember-views/tests/views/container_view_test.js index c42e0775167..2010413405a 100644 --- a/packages/ember-views/tests/views/container_view_test.js +++ b/packages/ember-views/tests/views/container_view_test.js @@ -90,7 +90,10 @@ test("should set the parentView property on views that are added to the child vi equal(get(thirdView, 'parentView'), container, "sets the parent view of the third view"); equal(get(fourthView, 'parentView'), container, "sets the parent view of the fourth view"); - childViews.replace(2, 2); + Ember.run(function() { + childViews.replace(2, 2); + }); + equal(get(view, 'parentView'), container, "doesn't change non-removed view"); equal(get(thirdView, 'parentView'), container, "doesn't change non-removed view"); equal(get(secondView, 'parentView'), null, "clears the parent view of the third view"); diff --git a/tests/index.html b/tests/index.html index caa245e5f92..e768ddb49a0 100644 --- a/tests/index.html +++ b/tests/index.html @@ -14,17 +14,20 @@ if (origOpts && origOpts.setup) { opts.setup = origOpts.setup; } opts.teardown = function() { if (origOpts && origOpts.teardown) { origOpts.teardown(); } - if (Ember.run.currentRunLoop) { - ok(false, "Should not be in a run loop at end of test"); - while (Ember.run.currentRunLoop) { - Ember.run.end(); + + if (Ember && Ember.run) { + if (Ember.run.currentRunLoop) { + ok(false, "Should not be in a run loop at end of test"); + while (Ember.run.currentRunLoop) { + Ember.run.end(); + } + } + if (Ember.run.hasScheduledTimers()) { + // Use `ok` so we get full description. + // Gate inside of `if` so that we don't mess up `expects` counts + ok(false, "Ember run should not have scheduled timers at end of test"); + Ember.run.cancelTimers(); } - } - if (Ember.run.hasScheduledTimers()) { - // Use `ok` so we get full description. - // Gate inside of `if` so that we don't mess up `expects` counts - ok(false, "Ember run should not have scheduled timers at end of test"); - Ember.run.cancelTimers(); } } return originalModule(name, opts);