diff --git a/FEATURES.md b/FEATURES.md index 6eba48b47ab..91c8a673601 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -5,6 +5,27 @@ for a detailed explanation. ## Feature Flags +* `ember-application-instance-initializers` + + Splits apart initializers into two phases: + + * Boot-time Initializers that receive a registry, and use it to set up + code structures + * Instance Initializers that receive an application instance, and use + it to set up application state per run of the application. + + In FastBoot, each request will have its own run of the application, + and will only need to run the instance initializers. + + In the future, tests will also be able to use this differentiation to + run just the instance initializers per-test. + + With this change, `App.initializer` becomes a "boot-time" initializer, + and issues a deprecation warning if instances are accessed. + + Apps should migrate any initializers that require instances to the new + `App.instanceInitializer` API. + * `ember-application-initializer-context` Sets the context of the initializer function to its object instead of the diff --git a/features.json b/features.json index 8b5573546b4..572628786bf 100644 --- a/features.json +++ b/features.json @@ -15,6 +15,7 @@ "ember-testing-checkbox-helpers": null, "ember-metal-stream": null, "ember-htmlbars-each-with-index": true, + "ember-application-instance-initializers": null, "ember-application-initializer-context": null }, "debugStatements": [ diff --git a/packages/container/lib/registry.js b/packages/container/lib/registry.js index 1bef0bb348c..8356bb04a49 100644 --- a/packages/container/lib/registry.js +++ b/packages/container/lib/registry.js @@ -136,13 +136,21 @@ Registry.prototype = { lookup: function(fullName, options) { Ember.assert('Create a container on the registry (with `registry.container()`) before calling `lookup`.', this._defaultContainer); - Ember.deprecate('`lookup` should not be called on a Registry. Call `lookup` directly on an associated Container instead.'); + + if (Ember.FEATURES.isEnabled('ember-application-instance-initializers')) { + Ember.deprecate('`lookup` was called on a Registry. The `initializer` API no longer receives a container, and you should use an `instanceInitializer` to look up objects from the container.', { url: "http://emberjs.com/guides/deprecations#toc_deprecate-access-to-instances-in-initializers" }); + } + return this._defaultContainer.lookup(fullName, options); }, lookupFactory: function(fullName) { Ember.assert('Create a container on the registry (with `registry.container()`) before calling `lookupFactory`.', this._defaultContainer); - Ember.deprecate('`lookupFactory` should not be called on a Registry. Call `lookupFactory` directly on an associated Container instead.'); + + if (Ember.FEATURES.isEnabled('ember-application-instance-initializers')) { + Ember.deprecate('`lookupFactory` was called on a Registry. The `initializer` API no longer receives a container, and you should use an `instanceInitializer` to look up objects from the container.', { url: "http://emberjs.com/guides/deprecations#toc_deprecate-access-to-instances-in-initializers" }); + } + return this._defaultContainer.lookupFactory(fullName); }, diff --git a/packages/ember-application/lib/system/application-instance.js b/packages/ember-application/lib/system/application-instance.js new file mode 100644 index 00000000000..a5b4659d497 --- /dev/null +++ b/packages/ember-application/lib/system/application-instance.js @@ -0,0 +1,113 @@ +/** +@module ember +@submodule ember-application +@private +*/ + +import EmberObject from "ember-runtime/system/object"; +import run from "ember-metal/run_loop"; + +/** + The `ApplicationInstance` encapsulates all of the stateful aspects of a + running `Application`. + + At a high-level, we break application boot into two distinct phases: + + * Definition time, where all of the classes, templates, and other + dependencies are loaded (typically in the browser). + * Run time, where we begin executing the application once everything + has loaded. + + Definition time can be expensive and only needs to happen once since it is + an idempotent operation. For example, between test runs and FastBoot + requests, the application stays the same. It is only the state that we want + to reset. + + That state is what the `ApplicationInstance` manages: it is responsible for + creating the container that contains all application state, and disposing of + it once the particular test run or FastBoot request has finished. +*/ + +export default EmberObject.extend({ + /** + The application instance's container. The container stores all of the + instance-specific state for this application run. + + @property {Ember.Container} container + */ + container: null, + + /** + The application's registry. The registry contains the classes, templates, + and other code that makes up the application. + + @property {Ember.Registry} registry + */ + registry: null, + + /** + The DOM events for which the event dispatcher should listen. + + By default, the application's `Ember.EventDispatcher` listens + for a set of standard DOM events, such as `mousedown` and + `keyup`, and delegates them to your application's `Ember.View` + instances. + + @private + @property {Object} customEvents + */ + customEvents: null, + + /** + The root DOM element of the Application as an element or a + [jQuery-compatible selector + string](http://api.jquery.com/category/selectors/). + + @private + @property {String|DOMElement} rootElement + */ + rootElement: null, + + init: function() { + this._super.apply(this, arguments); + this.container = this.registry.container(); + }, + + /** + @private + */ + startRouting: function(isModuleBasedResolver) { + var router = this.container.lookup('router:main'); + if (!router) { return; } + + router.startRouting(isModuleBasedResolver); + }, + + /** + @private + */ + handleURL: function(url) { + var router = this.container.lookup('router:main'); + + return router.handleURL(url); + }, + + /** + @private + */ + setupEventDispatcher: function() { + var dispatcher = this.container.lookup('event_dispatcher:main'); + + dispatcher.setup(this.customEvents, this.rootElement); + + return dispatcher; + }, + + /** + @private + */ + willDestroy: function() { + this._super.apply(this, arguments); + run(this.container, 'destroy'); + } +}); diff --git a/packages/ember-application/lib/system/application.js b/packages/ember-application/lib/system/application.js index 2b9e2e30356..a8303d6554f 100644 --- a/packages/ember-application/lib/system/application.js +++ b/packages/ember-application/lib/system/application.js @@ -31,6 +31,7 @@ import HistoryLocation from "ember-routing/location/history_location"; import AutoLocation from "ember-routing/location/auto_location"; import NoneLocation from "ember-routing/location/none_location"; import BucketCache from "ember-routing/system/cache"; +import ApplicationInstance from "ember-application/system/application-instance"; import ContainerDebugAdapter from "ember-extension-support/container_debug_adapter"; @@ -253,6 +254,8 @@ var Application = Namespace.extend(DeferredMixin, { customEvents: null, init: function() { + this._super(); + // Start off the number of deferrals at 1. This will be // decremented by the Application's own `initialize` method. this._readinessDeferrals = 1; @@ -261,42 +264,20 @@ var Application = Namespace.extend(DeferredMixin, { this.$ = jQuery; } + // Create subclass of Ember.Router for this Application instance. + // This is to ensure that someone reopening `App.Router` does not + // tamper with the default `Ember.Router`. + // 2.0TODO: Can we move this into a globals-mode-only library? + this.Router = Router.extend(); this.buildRegistry(); - this.buildContainer(); - this.Router = this.defaultRouter(); + // TODO:(tomdale+wycats) Move to session creation phase + this.buildInstance(); - this._super(); + registerLibraries(); + logLibraryVersions(); this.scheduleInitialize(); - - if (!librariesRegistered) { - librariesRegistered = true; - - if (environment.hasDOM) { - Ember.libraries.registerCoreLibrary('jQuery', jQuery().jquery); - } - } - - if (Ember.LOG_VERSION) { - // we only need to see this once per Application#init - Ember.LOG_VERSION = false; - var libs = Ember.libraries._registry; - - var nameLengths = EnumerableUtils.map(libs, function(item) { - return get(item, 'name.length'); - }); - - var maxNameLength = Math.max.apply(this, nameLengths); - - Ember.debug('-------------------------------'); - for (var i = 0, l = libs.length; i < l; i++) { - var lib = libs[i]; - var spaces = new Array(maxNameLength - lib.name.length + 1).join(' '); - Ember.debug([lib.name, spaces, ' : ', lib.version].join('')); - } - Ember.debug('-------------------------------'); - } }, /** @@ -316,46 +297,20 @@ var Application = Namespace.extend(DeferredMixin, { Create a container for the current application's registry. @private - @method buildContainer + @method buildInstance @return {Ember.Container} the configured container */ - buildContainer: function() { - var container = this.__container__ = this.__registry__.container(); - - return container; - }, - - /** - If the application has not opted out of routing and has not explicitly - defined a router, supply a default router for the application author - to configure. - - This allows application developers to do: - - ```javascript - var App = Ember.Application.create(); - - App.Router.map(function() { - this.resource('posts'); + buildInstance: function() { + var instance = this.__instance__ = ApplicationInstance.create({ + customEvents: get(this, 'customEvents'), + rootElement: get(this, 'rootElement'), + registry: this.__registry__ }); - ``` - @private - @method defaultRouter - @return {Ember.Router} the default router - */ + // TODO2.0: Legacy support for App.__container__ + this.__container__ = instance.container; - defaultRouter: function() { - if (this.Router === false) { return; } - var container = this.__container__; - var registry = this.__registry__; - - if (this.Router) { - registry.unregister('router:main'); - registry.register('router:main', this.Router); - } - - return container.lookupFactory('router:main'); + return instance; }, /** @@ -573,18 +528,9 @@ var Application = Namespace.extend(DeferredMixin, { _initialize: function() { if (this.isDestroyed) { return; } - // At this point, the App.Router must already be assigned - if (this.Router) { - var registry = this.__registry__; - var container = this.__container__; - - registry.unregister('router:main'); - registry.register('router:main', this.Router); - - container.reset('router:main'); - } + this.runInitializers(this.__registry__); + this.runInstanceInitializers(this.__instance__); - this.runInitializers(); runLoadHooks('application', this); // At this point, any initializers or load hooks that would have wanted @@ -664,15 +610,14 @@ var Application = Namespace.extend(DeferredMixin, { @method reset **/ reset: function() { + var instance = this.__instance__; + this._readinessDeferrals = 1; function handleReset() { - var router = this.__container__.lookup('router:main'); - router.reset(); - - run(this.__container__, 'destroy'); + run(instance, 'destroy'); - this.buildContainer(); + this.buildInstance(); run.schedule('actions', this, '_initialize'); } @@ -684,12 +629,31 @@ var Application = Namespace.extend(DeferredMixin, { @private @method runInitializers */ - runInitializers: function() { - var initializersByName = get(this.constructor, 'initializers'); + runInitializers: function(registry) { + var App = this; + this._runInitializer('initializers', function(name, initializer) { + Ember.assert("No application initializer named '" + name + "'", !!initializer); + + if (Ember.FEATURES.isEnabled("ember-application-initializer-context")) { + initializer.initialize(registry, App); + } else { + var ref = initializer.initialize; + ref(registry, App); + } + }); + }, + + runInstanceInitializers: function(instance) { + this._runInitializer('instanceInitializers', function(name, initializer) { + Ember.assert("No instance initializer named '" + name + "'", !!initializer); + initializer.initialize(instance); + }); + }, + + _runInitializer: function(bucketName, cb) { + var initializersByName = get(this.constructor, bucketName); var initializers = props(initializersByName); - var registry = this.__registry__; var graph = new DAG(); - var namespace = this; var initializer; for (var i = 0; i < initializers.length; i++) { @@ -698,15 +662,7 @@ var Application = Namespace.extend(DeferredMixin, { } graph.topsort(function (vertex) { - var initializer = vertex.value; - Ember.assert("No application initializer named '" + vertex.name + "'", !!initializer); - - if (Ember.FEATURES.isEnabled("ember-application-initializer-context")) { - initializer.initialize(registry, namespace); - } else { - var ref = initializer.initialize; - ref(registry, namespace); - } + cb(vertex.name, vertex.value); }); }, @@ -716,7 +672,7 @@ var Application = Namespace.extend(DeferredMixin, { */ didBecomeReady: function() { if (environment.hasDOM) { - this.setupEventDispatcher(); + this.__instance__.setupEventDispatcher(); } this.ready(); // user hook @@ -731,23 +687,6 @@ var Application = Namespace.extend(DeferredMixin, { this.resolve(this); }, - /** - Setup up the event dispatcher to receive events on the - application's `rootElement` with any registered - `customEvents`. - - @private - @method setupEventDispatcher - */ - setupEventDispatcher: function() { - var customEvents = get(this, 'customEvents'); - var rootElement = get(this, 'rootElement'); - var dispatcher = this.__container__.lookup('event_dispatcher:main'); - - set(this, 'eventDispatcher', dispatcher); - dispatcher.setup(customEvents, rootElement); - }, - /** If the application has a router, use it to route to the current URL, and trigger a new call to `route` whenever the URL changes. @@ -757,18 +696,12 @@ var Application = Namespace.extend(DeferredMixin, { @property router {Ember.Router} */ startRouting: function() { - var router = this.__container__.lookup('router:main'); - if (!router) { return; } - - var moduleBasedResolver = this.Resolver && this.Resolver.moduleBasedResolver; - - router.startRouting(moduleBasedResolver); + var isModuleBasedResolver = this.Resolver && this.Resolver.moduleBasedResolver; + this.__instance__.startRouting(isModuleBasedResolver); }, handleURL: function(url) { - var router = this.__container__.lookup('router:main'); - - router.handleURL(url); + return this.__instance__.handleURL(url); }, /** @@ -795,12 +728,10 @@ var Application = Namespace.extend(DeferredMixin, { */ Resolver: null, + // This method must be moved to the application instance object willDestroy: function() { Ember.BOOTED = false; - // Ensure deactivation of routes before objects are destroyed - this.__container__.lookup('router:main').reset(); - - this.__container__.destroy(); + this.__instance__.destroy(); }, initializer: function(options) { @@ -819,8 +750,21 @@ var Application = Namespace.extend(DeferredMixin, { } }); +if (Ember.FEATURES.isEnabled('ember-application-instance-initializers')) { + Application.reopen({ + instanceInitializer: function(options) { + this.constructor.instanceInitializer(options); + } + }); + + Application.reopenClass({ + instanceInitializer: buildInitializerMethod('instanceInitializers', 'instance initializer') + }); +} + Application.reopenClass({ initializers: create(null), + instanceInitializers: create(null), /** Initializer receives an object which has the following attributes: @@ -945,23 +889,7 @@ Application.reopenClass({ @method initializer @param initializer {Object} */ - initializer: function(initializer) { - // If this is the first initializer being added to a subclass, we are going to reopen the class - // to make sure we have a new `initializers` object, which extends from the parent class' using - // prototypal inheritance. Without this, attempting to add initializers to the subclass would - // pollute the parent class as well as other subclasses. - if (this.superclass.initializers !== undefined && this.superclass.initializers === this.initializers) { - this.reopenClass({ - initializers: create(this.initializers) - }); - } - - Ember.assert("The initializer '" + initializer.name + "' has already been registered", !this.initializers[initializer.name]); - Ember.assert("An initializer cannot be registered without an initialize function", canInvoke(initializer, 'initialize')); - Ember.assert("An initializer cannot be registered without a name property", initializer.name !== undefined); - - this.initializers[initializer.name] = initializer; - }, + initializer: buildInitializerMethod('initializers', 'initializer'), /** This creates a registry with the default Ember naming conventions. @@ -1016,7 +944,6 @@ Application.reopenClass({ registry.register('route:basic', Route, { instantiate: false }); registry.register('event_dispatcher:main', EventDispatcher); - registry.register('router:main', Router); registry.injection('router:main', 'namespace', 'application:main'); registry.register('location:auto', AutoLocation); @@ -1097,4 +1024,56 @@ function resolverFor(namespace) { return resolve; } +function registerLibraries() { + if (!librariesRegistered) { + librariesRegistered = true; + + if (environment.hasDOM) { + Ember.libraries.registerCoreLibrary('jQuery', jQuery().jquery); + } + } +} + +function logLibraryVersions() { + if (Ember.LOG_VERSION) { + // we only need to see this once per Application#init + Ember.LOG_VERSION = false; + var libs = Ember.libraries._registry; + + var nameLengths = EnumerableUtils.map(libs, function(item) { + return get(item, 'name.length'); + }); + + var maxNameLength = Math.max.apply(this, nameLengths); + + Ember.debug('-------------------------------'); + for (var i = 0, l = libs.length; i < l; i++) { + var lib = libs[i]; + var spaces = new Array(maxNameLength - lib.name.length + 1).join(' '); + Ember.debug([lib.name, spaces, ' : ', lib.version].join('')); + } + Ember.debug('-------------------------------'); + } +} + +function buildInitializerMethod(bucketName, humanName) { + return function(initializer) { + // If this is the first initializer being added to a subclass, we are going to reopen the class + // to make sure we have a new `initializers` object, which extends from the parent class' using + // prototypal inheritance. Without this, attempting to add initializers to the subclass would + // pollute the parent class as well as other subclasses. + if (this.superclass[bucketName] !== undefined && this.superclass[bucketName] === this[bucketName]) { + var attrs = {}; + attrs[bucketName] = create(this[bucketName]); + this.reopenClass(attrs); + } + + Ember.assert("The " + humanName + " '" + initializer.name + "' has already been registered", !this[bucketName][initializer.name]); + Ember.assert("An " + humanName + " cannot be registered without an initialize function", canInvoke(initializer, 'initialize')); + Ember.assert("An " + humanName + " cannot be registered without a name property", initializer.name !== undefined); + + this[bucketName][initializer.name] = initializer; + }; +} + export default Application; diff --git a/packages/ember-application/lib/system/resolver.js b/packages/ember-application/lib/system/resolver.js index 12ef81df87a..d0dc03af025 100644 --- a/packages/ember-application/lib/system/resolver.js +++ b/packages/ember-application/lib/system/resolver.js @@ -169,9 +169,7 @@ export default EmberObject.extend({ resolved = this[resolveMethodName](parsedName); } - if (!resolved) { - resolved = this.resolveOther(parsedName); - } + resolved = resolved || this.resolveOther(parsedName); if (parsedName.root && parsedName.root.LOG_RESOLVER) { this._logLookup(resolved, parsedName); @@ -179,6 +177,7 @@ export default EmberObject.extend({ return resolved; }, + /** Convert the string name of the form 'type:name' to a Javascript object with the parsed aspects of the name @@ -214,13 +213,15 @@ export default EmberObject.extend({ ' namespace, but the namespace could not be found', root); } + var resolveMethodName = fullNameWithoutType === 'main' ? 'Main' : classify(type); + return { fullName: fullName, type: type, fullNameWithoutType: fullNameWithoutType, name: name, root: root, - resolveMethodName: 'resolve' + classify(type) + resolveMethodName: 'resolve' + resolveMethodName }; }, @@ -253,6 +254,7 @@ export default EmberObject.extend({ makeToString: function(factory, fullName) { return factory.toString(); }, + /** Given a parseName object (output from `parseName`), apply the conventions expected by `Ember.Router` @@ -368,6 +370,11 @@ export default EmberObject.extend({ if (factory) { return factory; } }, + resolveMain: function(parsedName) { + var className = classify(parsedName.type); + return get(parsedName.root, className); + }, + /** @method _logLookup @param {Boolean} found diff --git a/packages/ember-application/tests/system/application_test.js b/packages/ember-application/tests/system/application_test.js index 4e429c1d033..ec0e70feefb 100644 --- a/packages/ember-application/tests/system/application_test.js +++ b/packages/ember-application/tests/system/application_test.js @@ -263,7 +263,7 @@ test("can resolve custom router", function() { var CustomRouter = Router.extend(); var CustomResolver = DefaultResolver.extend({ - resolveOther: function(parsedName) { + resolveMain: function(parsedName) { if (parsedName.type === "router") { return CustomRouter; } else { diff --git a/packages/ember-application/tests/system/dependency_injection/default_resolver_test.js b/packages/ember-application/tests/system/dependency_injection/default_resolver_test.js index 09d0b222198..9a45e0b1995 100644 --- a/packages/ember-application/tests/system/dependency_injection/default_resolver_test.js +++ b/packages/ember-application/tests/system/dependency_injection/default_resolver_test.js @@ -75,6 +75,12 @@ test("the default resolver resolves models on the namespace", function() { detectEqual(application.Post, locator.lookupFactory('model:post'), "looks up Post model on application"); }); +test("the default resolver resolves *:main on the namespace", function() { + application.FooBar = EmberObject.extend({}); + + detectEqual(application.FooBar, locator.lookupFactory('foo-bar:main'), "looks up FooBar type without name on application"); +}); + test("the default resolver resolves helpers", function() { expect(2); diff --git a/packages/ember-application/tests/system/initializers_test.js b/packages/ember-application/tests/system/initializers_test.js index 1fc3410682d..cc73f3c6e3d 100644 --- a/packages/ember-application/tests/system/initializers_test.js +++ b/packages/ember-application/tests/system/initializers_test.js @@ -2,6 +2,7 @@ import run from "ember-metal/run_loop"; import Application from "ember-application/system/application"; import { indexOf } from "ember-metal/array"; import jQuery from "ember-views/system/jquery"; +import Registry from "container/registry"; var app; @@ -33,6 +34,25 @@ test("initializers require proper 'name' and 'initialize' properties", function( }); +test("initializers are passed a registry and App", function() { + var MyApplication = Application.extend(); + + MyApplication.initializer({ + name: 'initializer', + initialize: function(registry, App) { + ok(registry instanceof Registry, "initialize is passed a registry"); + ok(App instanceof Application, "initialize is passed an Application"); + } + }); + + run(function() { + app = MyApplication.create({ + router: false, + rootElement: '#qunit-fixture' + }); + }); +}); + test("initializers can be registered in a specified order", function() { var order = []; var MyApplication = Application.extend(); @@ -94,6 +114,69 @@ test("initializers can be registered in a specified order", function() { deepEqual(order, ['first', 'second', 'third', 'fourth', 'fifth', 'sixth']); }); +test("initializers can be registered in a specified order as an array", function() { + var order = []; + var MyApplication = Application.extend(); + + + MyApplication.initializer({ + name: 'third', + initialize: function(registry) { + order.push('third'); + } + }); + + MyApplication.initializer({ + name: 'second', + after: 'first', + before: ['third', 'fourth'], + initialize: function(registry) { + order.push('second'); + } + }); + + MyApplication.initializer({ + name: 'fourth', + after: ['second', 'third'], + initialize: function(registry) { + order.push('fourth'); + } + }); + + MyApplication.initializer({ + name: 'fifth', + after: 'fourth', + before: 'sixth', + initialize: function(registry) { + order.push('fifth'); + } + }); + + MyApplication.initializer({ + name: 'first', + before: ['second'], + initialize: function(registry) { + order.push('first'); + } + }); + + MyApplication.initializer({ + name: 'sixth', + initialize: function(registry) { + order.push('sixth'); + } + }); + + run(function() { + app = MyApplication.create({ + router: false, + rootElement: '#qunit-fixture' + }); + }); + + deepEqual(order, ['first', 'second', 'third', 'fourth', 'fifth', 'sixth']); +}); + test("initializers can have multiple dependencies", function () { var order = []; var a = { diff --git a/packages/ember-application/tests/system/instance_initializers_test.js b/packages/ember-application/tests/system/instance_initializers_test.js new file mode 100644 index 00000000000..7ede4f2307c --- /dev/null +++ b/packages/ember-application/tests/system/instance_initializers_test.js @@ -0,0 +1,347 @@ +import run from "ember-metal/run_loop"; +import Application from "ember-application/system/application"; +import ApplicationInstance from "ember-application/system/application-instance"; +import { indexOf } from "ember-metal/array"; +import jQuery from "ember-views/system/jquery"; + +var app; + +if (Ember.FEATURES.isEnabled('ember-application-instance-initializers')) { + QUnit.module("Ember.Application instance initializers", { + setup: function() { + }, + + teardown: function() { + if (app) { + run(function() { app.destroy(); }); + } + } + }); + + test("initializers require proper 'name' and 'initialize' properties", function() { + var MyApplication = Application.extend(); + + expectAssertion(function() { + run(function() { + MyApplication.instanceInitializer({ name: 'initializer' }); + }); + }); + + expectAssertion(function() { + run(function() { + MyApplication.instanceInitializer({ initialize: Ember.K }); + }); + }); + + }); + + test("initializers are passed an app instance", function() { + var MyApplication = Application.extend(); + + MyApplication.instanceInitializer({ + name: 'initializer', + initialize: function(instance) { + ok(instance instanceof ApplicationInstance, "initialize is passed an application instance"); + } + }); + + run(function() { + app = MyApplication.create({ + router: false, + rootElement: '#qunit-fixture' + }); + }); + }); + + test("initializers can be registered in a specified order", function() { + var order = []; + var MyApplication = Application.extend(); + MyApplication.instanceInitializer({ + name: 'fourth', + after: 'third', + initialize: function(registry) { + order.push('fourth'); + } + }); + + MyApplication.instanceInitializer({ + name: 'second', + after: 'first', + before: 'third', + initialize: function(registry) { + order.push('second'); + } + }); + + MyApplication.instanceInitializer({ + name: 'fifth', + after: 'fourth', + before: 'sixth', + initialize: function(registry) { + order.push('fifth'); + } + }); + + MyApplication.instanceInitializer({ + name: 'first', + before: 'second', + initialize: function(registry) { + order.push('first'); + } + }); + + MyApplication.instanceInitializer({ + name: 'third', + initialize: function(registry) { + order.push('third'); + } + }); + + MyApplication.instanceInitializer({ + name: 'sixth', + initialize: function(registry) { + order.push('sixth'); + } + }); + + run(function() { + app = MyApplication.create({ + router: false, + rootElement: '#qunit-fixture' + }); + }); + + deepEqual(order, ['first', 'second', 'third', 'fourth', 'fifth', 'sixth']); + }); + + test("initializers can be registered in a specified order as an array", function() { + var order = []; + var MyApplication = Application.extend(); + + + MyApplication.instanceInitializer({ + name: 'third', + initialize: function(registry) { + order.push('third'); + } + }); + + MyApplication.instanceInitializer({ + name: 'second', + after: 'first', + before: ['third', 'fourth'], + initialize: function(registry) { + order.push('second'); + } + }); + + MyApplication.instanceInitializer({ + name: 'fourth', + after: ['second', 'third'], + initialize: function(registry) { + order.push('fourth'); + } + }); + + MyApplication.instanceInitializer({ + name: 'fifth', + after: 'fourth', + before: 'sixth', + initialize: function(registry) { + order.push('fifth'); + } + }); + + MyApplication.instanceInitializer({ + name: 'first', + before: ['second'], + initialize: function(registry) { + order.push('first'); + } + }); + + MyApplication.instanceInitializer({ + name: 'sixth', + initialize: function(registry) { + order.push('sixth'); + } + }); + + run(function() { + app = MyApplication.create({ + router: false, + rootElement: '#qunit-fixture' + }); + }); + + deepEqual(order, ['first', 'second', 'third', 'fourth', 'fifth', 'sixth']); + }); + + test("initializers can have multiple dependencies", function () { + var order = []; + var a = { + name: "a", + before: "b", + initialize: function(registry) { + order.push('a'); + } + }; + var b = { + name: "b", + initialize: function(registry) { + order.push('b'); + } + }; + var c = { + name: "c", + after: "b", + initialize: function(registry) { + order.push('c'); + } + }; + var afterB = { + name: "after b", + after: "b", + initialize: function(registry) { + order.push("after b"); + } + }; + var afterC = { + name: "after c", + after: "c", + initialize: function(registry) { + order.push("after c"); + } + }; + + Application.instanceInitializer(b); + Application.instanceInitializer(a); + Application.instanceInitializer(afterC); + Application.instanceInitializer(afterB); + Application.instanceInitializer(c); + + run(function() { + app = Application.create({ + router: false, + rootElement: '#qunit-fixture' + }); + }); + + ok(indexOf.call(order, a.name) < indexOf.call(order, b.name), 'a < b'); + ok(indexOf.call(order, b.name) < indexOf.call(order, c.name), 'b < c'); + ok(indexOf.call(order, b.name) < indexOf.call(order, afterB.name), 'b < afterB'); + ok(indexOf.call(order, c.name) < indexOf.call(order, afterC.name), 'c < afterC'); + }); + + test("initializers set on Application subclasses should not be shared between apps", function() { + var firstInitializerRunCount = 0; + var secondInitializerRunCount = 0; + var FirstApp = Application.extend(); + FirstApp.instanceInitializer({ + name: 'first', + initialize: function(registry) { + firstInitializerRunCount++; + } + }); + var SecondApp = Application.extend(); + SecondApp.instanceInitializer({ + name: 'second', + initialize: function(registry) { + secondInitializerRunCount++; + } + }); + jQuery('#qunit-fixture').html('
'); + run(function() { + FirstApp.create({ + router: false, + rootElement: '#qunit-fixture #first' + }); + }); + equal(firstInitializerRunCount, 1, 'first initializer only was run'); + equal(secondInitializerRunCount, 0, 'first initializer only was run'); + run(function() { + SecondApp.create({ + router: false, + rootElement: '#qunit-fixture #second' + }); + }); + equal(firstInitializerRunCount, 1, 'second initializer only was run'); + equal(secondInitializerRunCount, 1, 'second initializer only was run'); + }); + + test("initializers are concatenated", function() { + var firstInitializerRunCount = 0; + var secondInitializerRunCount = 0; + var FirstApp = Application.extend(); + FirstApp.instanceInitializer({ + name: 'first', + initialize: function(registry) { + firstInitializerRunCount++; + } + }); + + var SecondApp = FirstApp.extend(); + SecondApp.instanceInitializer({ + name: 'second', + initialize: function(registry) { + secondInitializerRunCount++; + } + }); + + jQuery('#qunit-fixture').html(''); + run(function() { + FirstApp.create({ + router: false, + rootElement: '#qunit-fixture #first' + }); + }); + equal(firstInitializerRunCount, 1, 'first initializer only was run when base class created'); + equal(secondInitializerRunCount, 0, 'first initializer only was run when base class created'); + firstInitializerRunCount = 0; + run(function() { + SecondApp.create({ + router: false, + rootElement: '#qunit-fixture #second' + }); + }); + equal(firstInitializerRunCount, 1, 'first initializer was run when subclass created'); + equal(secondInitializerRunCount, 1, 'second initializers was run when subclass created'); + }); + + test("initializers are per-app", function() { + expect(0); + var FirstApp = Application.extend(); + FirstApp.instanceInitializer({ + name: 'shouldNotCollide', + initialize: function(registry) {} + }); + + var SecondApp = Application.extend(); + SecondApp.instanceInitializer({ + name: 'shouldNotCollide', + initialize: function(registry) {} + }); + }); + + if (Ember.FEATURES.isEnabled("ember-application-initializer-context")) { + test("initializers should be executed in their own context", function() { + expect(1); + var MyApplication = Application.extend(); + + MyApplication.instanceInitializer({ + name: 'coolBabeInitializer', + myProperty: 'coolBabe', + initialize: function(registry, application) { + equal(this.myProperty, 'coolBabe', 'should have access to its own context'); + } + }); + + run(function() { + app = MyApplication.create({ + router: false, + rootElement: '#qunit-fixture' + }); + }); + }); + } +} diff --git a/packages/ember-routing/lib/system/router.js b/packages/ember-routing/lib/system/router.js index 2627e8747fd..652ba47f8f9 100644 --- a/packages/ember-routing/lib/system/router.js +++ b/packages/ember-routing/lib/system/router.js @@ -297,6 +297,11 @@ var EmberRouter = EmberObject.extend(Evented, { } }, + willDestroy: function() { + this._super.apply(this, arguments); + this.reset(); + }, + _lookupActiveView: function(templateName) { var active = this._activeViews[templateName]; return active && active[0]; diff --git a/packages/ember/tests/application_lifecycle.js b/packages/ember/tests/application_lifecycle.js index ab23c2312a3..9ccd91c72e8 100644 --- a/packages/ember/tests/application_lifecycle.js +++ b/packages/ember/tests/application_lifecycle.js @@ -9,7 +9,7 @@ QUnit.module("Application Lifecycle", { rootElement: '#qunit-fixture' }); - App.Router.extend({ + App.Router = App.Router.extend({ location: 'none' });