diff --git a/FEATURES.md b/FEATURES.md index 91c8a673601..0621759dc1b 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -204,3 +204,18 @@ for a detailed explanation. ``` Added in [#10160](https://github.com/emberjs/ember.js/pull/10160) + +* `ember-router-willtransition` + + + Adds `willTransition` hook to `Ember.Router`. For example: + + ```js + Ember.Router.extend({ + onBeforeTransition: function(transition) { + //doSomething + }.on('willTransition') + }); + ``` + + Added in [#10274](https://github.com/emberjs/ember.js/pull/10274) \ No newline at end of file diff --git a/bower.json b/bower.json index 160cf951a56..2e513440063 100644 --- a/bower.json +++ b/bower.json @@ -8,7 +8,7 @@ "devDependencies": { "backburner": "https://github.com/ebryn/backburner.js.git#f4bd6a2df221240ed36d140f0c53c036a7ecacad", "rsvp": "https://github.com/tildeio/rsvp.js.git#3.0.14", - "router.js": "https://github.com/tildeio/router.js.git#a1ffd97dc66a6d9d4e8dd89a72c1c4e21a3328c5", + "router.js": "https://github.com/tildeio/router.js.git#72eb0d336c1c3b7ad3965198b330fe148d935efe", "dag-map": "https://github.com/krisselden/dag-map.git#e307363256fe918f426e5a646cb5f5062d3245be", "ember-dev": "https://github.com/emberjs/ember-dev.git#1c30a1666273ab2a9b134a42bad28c774f9ecdfc" } diff --git a/features.json b/features.json index 572628786bf..9040f020a1a 100644 --- a/features.json +++ b/features.json @@ -16,7 +16,8 @@ "ember-metal-stream": null, "ember-htmlbars-each-with-index": true, "ember-application-instance-initializers": null, - "ember-application-initializer-context": null + "ember-application-initializer-context": null, + "ember-router-willtransition": true }, "debugStatements": [ "Ember.warn", diff --git a/packages/ember-htmlbars/lib/helpers/view.js b/packages/ember-htmlbars/lib/helpers/view.js index b31f6b5a7ef..47a6ff549ca 100644 --- a/packages/ember-htmlbars/lib/helpers/view.js +++ b/packages/ember-htmlbars/lib/helpers/view.js @@ -4,6 +4,7 @@ */ import Ember from "ember-metal/core"; // Ember.warn, Ember.assert +import { read } from "ember-metal/streams/utils"; import { readViewFactory } from "ember-views/streams/utils"; import View from "ember-views/views/view"; import mergeViewBindings from "ember-htmlbars/system/merge-view-bindings"; @@ -190,7 +191,7 @@ export function viewHelper(params, hash, options, env) { params.length <= 2 ); - var container = this.container || this._keywords.view.value().container; + var container = this.container || read(this._keywords.view).container; var viewClassOrInstance; if (params.length === 0) { if (container) { diff --git a/packages/ember-routing/lib/system/router.js b/packages/ember-routing/lib/system/router.js index 652ba47f8f9..c4144cf84c5 100644 --- a/packages/ember-routing/lib/system/router.js +++ b/packages/ember-routing/lib/system/router.js @@ -188,6 +188,24 @@ var EmberRouter = EmberObject.extend(Evented, { } }, + /** + Handles notifying any listeners of an impending URL + change. + + Triggers the router level `willTransition` hook. + + @method willTransition + @private + @since 1.11.0 + */ + willTransition: function(oldInfos, newInfos, transition) { + run.once(this, this.trigger, 'willTransition', transition); + + if (get(this, 'namespace').LOG_TRANSITIONS) { + Ember.Logger.log("Preparing to transition from '" + EmberRouter._routePath(oldInfos) + "' to '" + EmberRouter._routePath(newInfos) + "'"); + } + }, + handleURL: function(url) { // Until we have an ember-idiomatic way of accessing #hashes, we need to // remove it because router.js doesn't know how to handle it. @@ -419,6 +437,12 @@ var EmberRouter = EmberObject.extend(Evented, { router.didTransition = function(infos) { emberRouter.didTransition(infos); }; + + if (Ember.FEATURES.isEnabled('ember-router-willtransition')) { + router.willTransition = function(oldInfos, newInfos, transition) { + emberRouter.willTransition(oldInfos, newInfos, transition); + }; + } }, _serializeQueryParams: function(targetRouteName, queryParams) { diff --git a/packages/ember-views/lib/views/component.js b/packages/ember-views/lib/views/component.js index 6cee1408b88..f822e2498c5 100644 --- a/packages/ember-views/lib/views/component.js +++ b/packages/ember-views/lib/views/component.js @@ -113,6 +113,7 @@ var Component = View.extend(TargetActionSupport, ComponentTemplateDeprecation, { init: function() { this._super(); + this._keywords.view = this; set(this, 'context', this); set(this, 'controller', this); }, @@ -158,9 +159,7 @@ var Component = View.extend(TargetActionSupport, ComponentTemplateDeprecation, { */ templateName: null, - _setupKeywords: function() { - this._keywords.view.setSource(this); - }, + _setupKeywords: function() {}, _yield: function(context, options, morph, blockArguments) { var view = options.data.view; diff --git a/packages/ember-views/lib/views/view.js b/packages/ember-views/lib/views/view.js index da14507c40a..e021f9addc8 100644 --- a/packages/ember-views/lib/views/view.js +++ b/packages/ember-views/lib/views/view.js @@ -24,7 +24,6 @@ import { observer, beforeObserver } from "ember-metal/mixin"; -import SimpleStream from "ember-metal/streams/simple"; import KeyStream from "ember-views/streams/key_stream"; import StreamBinding from "ember-metal/streams/stream_binding"; import ContextStream from "ember-views/streams/context_stream"; @@ -1070,7 +1069,7 @@ var View = CoreView.extend({ if (contextView) { var parentKeywords = contextView._keywords; - keywords.view.setSource(this.isVirtual ? parentKeywords.view : this); + keywords.view = this.isVirtual ? parentKeywords.view : this; for (var name in parentKeywords) { if (keywords[name]) { @@ -1080,7 +1079,7 @@ var View = CoreView.extend({ keywords[name] = parentKeywords[name]; } } else { - keywords.view.setSource(this.isVirtual ? null : this); + keywords.view = this.isVirtual ? null : this; } }, @@ -1753,8 +1752,8 @@ var View = CoreView.extend({ if (!this._keywords) { this._keywords = create(null); } - this._keywords.view = new SimpleStream(); this._keywords._view = this; + this._keywords.view = undefined; this._keywords.controller = new KeyStream(this, 'controller'); this._setupKeywords(); diff --git a/packages/ember-views/tests/views/container_view_test.js b/packages/ember-views/tests/views/container_view_test.js index fdbd76fbf20..f07e62e9c19 100644 --- a/packages/ember-views/tests/views/container_view_test.js +++ b/packages/ember-views/tests/views/container_view_test.js @@ -2,6 +2,7 @@ import { get } from "ember-metal/property_get"; import { set } from "ember-metal/property_set"; import run from "ember-metal/run_loop"; import { computed } from "ember-metal/computed"; +import { read } from "ember-metal/streams/utils"; import Controller from "ember-runtime/controllers/controller"; import jQuery from "ember-views/system/jquery"; import View from "ember-views/views/view"; @@ -300,8 +301,8 @@ test("if a ContainerView starts with a currentView, it is rendered as a child vi equal(container.objectAt(0), mainView, "should have the currentView as the only child view"); equal(mainView.get('parentView'), container, "parentView is setup"); equal(context, container.get('context'), 'context preserved'); - equal(mainView._keywords.controller.value(), controller, 'controller keyword is setup'); - equal(mainView._keywords.view.value(), mainView, 'view keyword is setup'); + equal(read(mainView._keywords.controller), controller, 'controller keyword is setup'); + equal(read(mainView._keywords.view), mainView, 'view keyword is setup'); }); test("if a ContainerView is created with a currentView, it is rendered as a child view", function() { @@ -329,8 +330,8 @@ test("if a ContainerView is created with a currentView, it is rendered as a chil equal(container.objectAt(0), mainView, "should have the currentView as the only child view"); equal(mainView.get('parentView'), container, "parentView is setup"); equal(context, container.get('context'), 'context preserved'); - equal(mainView._keywords.controller.value(), controller, 'controller keyword is setup'); - equal(mainView._keywords.view.value(), mainView, 'view keyword is setup'); + equal(read(mainView._keywords.controller), controller, 'controller keyword is setup'); + equal(read(mainView._keywords.view), mainView, 'view keyword is setup'); }); test("if a ContainerView starts with no currentView and then one is set, the ContainerView is updated", function() { @@ -364,8 +365,8 @@ test("if a ContainerView starts with no currentView and then one is set, the Con equal(container.objectAt(0), mainView, "should have the currentView as the only child view"); equal(mainView.get('parentView'), container, "parentView is setup"); equal(context, container.get('context'), 'context preserved'); - equal(mainView._keywords.controller.value(), controller, 'controller keyword is setup'); - equal(mainView._keywords.view.value(), mainView, 'view keyword is setup'); + equal(read(mainView._keywords.controller), controller, 'controller keyword is setup'); + equal(read(mainView._keywords.view), mainView, 'view keyword is setup'); }); test("if a ContainerView starts with a currentView and then is set to null, the ContainerView is updated", function() { @@ -394,8 +395,8 @@ test("if a ContainerView starts with a currentView and then is set to null, the equal(container.objectAt(0), mainView, "should have the currentView as the only child view"); equal(mainView.get('parentView'), container, "parentView is setup"); equal(context, container.get('context'), 'context preserved'); - equal(mainView._keywords.controller.value(), controller, 'controller keyword is setup'); - equal(mainView._keywords.view.value(), mainView, 'view keyword is setup'); + equal(read(mainView._keywords.controller), controller, 'controller keyword is setup'); + equal(read(mainView._keywords.view), mainView, 'view keyword is setup'); run(function() { set(container, 'currentView', null); @@ -431,8 +432,8 @@ test("if a ContainerView starts with a currentView and then is set to null, the equal(container.objectAt(0), mainView, "should have the currentView as the only child view"); equal(mainView.get('parentView'), container, "parentView is setup"); equal(context, container.get('context'), 'context preserved'); - equal(mainView._keywords.controller.value(), controller, 'controller keyword is setup'); - equal(mainView._keywords.view.value(), mainView, 'view keyword is setup'); + equal(read(mainView._keywords.controller), controller, 'controller keyword is setup'); + equal(read(mainView._keywords.view), mainView, 'view keyword is setup'); run(function() { set(container, 'currentView', null); diff --git a/packages/ember/tests/routing/basic_test.js b/packages/ember/tests/routing/basic_test.js index b2961af3a06..15526994703 100644 --- a/packages/ember/tests/routing/basic_test.js +++ b/packages/ember/tests/routing/basic_test.js @@ -2738,6 +2738,54 @@ test("Route silently fails when cleaning an outlet from an inactive view", funct Ember.run(function() { router.send('hideModal'); }); }); +if (Ember.FEATURES.isEnabled('ember-router-willtransition')) { + test("Router `willTransition` hook passes in cancellable transition", function() { + // Should hit willTransition 3 times, once for the initial route, and then 2 more times + // for the two handleURL calls below + expect(3); + + Router.map(function() { + this.route("nork"); + this.route("about"); + }); + + Router.reopen({ + init: function() { + this._super(); + this.on('willTransition', this.testWillTransitionHook); + }, + testWillTransitionHook: function(transition, url) { + ok(true, "willTransition was called " + url); + transition.abort(); + } + }); + + App.LoadingRoute = Ember.Route.extend({ + activate: function() { + ok(false, "LoadingRoute was not entered"); + } + }); + + App.NorkRoute = Ember.Route.extend({ + activate: function() { + ok(false, "NorkRoute was not entered"); + } + }); + + App.AboutRoute = Ember.Route.extend({ + activate: function() { + ok(false, "AboutRoute was not entered"); + } + }); + + bootApplication(); + + // Attempted transitions out of index should abort. + Ember.run(router, 'handleURL', '/nork'); + Ember.run(router, 'handleURL', '/about'); + }); +} + test("Aborting/redirecting the transition in `willTransition` prevents LoadingRoute from being entered", function() { expect(8);