From 5447d279c7a6f226f115e2a1678bd8037709b1ca Mon Sep 17 00:00:00 2001 From: Chad Hietala Date: Tue, 2 Oct 2018 16:49:29 -0400 Subject: [PATCH] add docs and more deprecations --- .../-internals/routing/lib/services/router.ts | 83 ++++++- .../-internals/routing/lib/system/route.ts | 40 ++- .../-internals/routing/lib/system/router.ts | 229 +++++++++--------- .../router_service_test/events_test.js | 88 ++++++- 4 files changed, 313 insertions(+), 127 deletions(-) diff --git a/packages/@ember/-internals/routing/lib/services/router.ts b/packages/@ember/-internals/routing/lib/services/router.ts index d58308a1fcd..869d8c5ba99 100644 --- a/packages/@ember/-internals/routing/lib/services/router.ts +++ b/packages/@ember/-internals/routing/lib/services/router.ts @@ -38,6 +38,79 @@ import { extractRouteArgs, resemblesURL, shallowEqual } from '../utils'; export default class RouterService extends Service { _router!: EmberRouter; + init() { + this._super(...arguments); + this._router.on('routeWillChange', (transition: Transition) => { + this.trigger('routeWillChange', transition); + }); + + this._router.on('routeDidChange', (transition: Transition) => { + this.trigger('routeDidChange', transition); + }); + } + + /** + The `routeWillChange` event is fired at the beginning of any + attempted transition with a `Transition` object as the sole + argument. This action can be used for aborting, redirecting, + or decorating the transition from the currently active routes. + + A good example is preventing navigation when a form is + half-filled out: + + ```app/routes/contact-form.js + import {inject as service} from '@ember/service'; + + export default Route.extend({ + router: service('router'), + init() { + this._super(...arguments); + this.router.on('routeWillUpdate', (transition) => { + if (!transition.to.find(route => route.name === this.routeName)) { + alert("Please save or cancel your changes."); + transition.abort(); + } + }) + } + }); + ``` + + The `routeWillChange` event fires whenever a new route is chosen as the desired target of a transition. This includes `transitionTo`, `replaceWith`, all redirection for any reason including error handling, and abort. Aborting implies changing the desired target back to where you already were. Once a transition has completed, `routeDidChange` fires. + + @event routeWillChange + @param {Transition} transition + @public + */ + + /** + The `routeDidChange` event only fires once a transition has settled. + This includes aborts and error substates. Like the `routeWillChange` event + it recieves a Transition as the sole argument. + + A good example is sending some analytics when the route has transitioned: + + ```app/routes/contact-form.js + import {inject as service} from '@ember/service'; + + export default Route.extend({ + router: service('router'), + init() { + this._super(...arguments); + this.router.on('routeDidUpdate', (transition) => { + ga.send('pageView', { + current: transition.to.name, + from: transition.from.name + }); + }) + } + }); + ``` + + @event routeDidChange + @param {Transition} transition + @public + */ + /** Transition the application into another route. The route may be either a single route or route path: @@ -151,16 +224,6 @@ export default class RouterService extends Service { } RouterService.reopen(Evented, { - init() { - this._super(...arguments); - this._router.on('routeWillChange', (transition: Transition) => { - this.trigger('routeWillChange', transition); - }); - - this._router.on('routeDidChange', (transition: Transition) => { - this.trigger('routeDidChange', transition); - }); - }, /** Name of the current route. diff --git a/packages/@ember/-internals/routing/lib/system/route.ts b/packages/@ember/-internals/routing/lib/system/route.ts index 42d77d3a9f9..fdce44eb433 100644 --- a/packages/@ember/-internals/routing/lib/system/route.ts +++ b/packages/@ember/-internals/routing/lib/system/route.ts @@ -7,8 +7,9 @@ import { Object as EmberObject, typeOf, } from '@ember/-internals/runtime'; +import { EMBER_ROUTING_ROUTER_SERVICE } from '@ember/canary-features'; import { assert, deprecate, info, isTesting } from '@ember/debug'; -import { ROUTER_ROUTER } from '@ember/deprecated-features'; +import { ROUTER_EVENTS, ROUTER_ROUTER } from '@ember/deprecated-features'; import { assign } from '@ember/polyfills'; import { once } from '@ember/runloop'; import { classify } from '@ember/string'; @@ -330,7 +331,7 @@ class Route extends EmberObject implements IRoute { @private @method _internalReset - @since 1.7.0 + @since 3.6.0 */ _internalReset(isExiting: boolean, transition: Transition) { let controller = this.controller; @@ -2511,4 +2512,39 @@ Route.reopen(ActionHandler, Evented, { }, }); +export let ROUTER_EVENT_DEPRECATIONS: any; +if (EMBER_ROUTING_ROUTER_SERVICE && ROUTER_EVENTS) { + ROUTER_EVENT_DEPRECATIONS = { + on(name: string) { + this._super(...arguments); + let hasDidTransition = name === 'didTransition'; + let hasWillTransition = name === 'willTransition'; + + if (hasDidTransition) { + deprecate( + 'You attempted to listen to the "didTransition" event which is deprecated. Please inject the router service and listen to the "routeDidChange" event.', + false, + { + id: 'deprecate-router-events', + until: '4.0.0', + } + ); + } + + if (hasWillTransition) { + deprecate( + 'You attempted to listen to the "willTransition" event which is deprecated. Please inject the router service and listen to the "routeWillChange" event.', + false, + { + id: 'deprecate-router-events', + until: '4.0.0', + } + ); + } + }, + }; + + Route.reopen(ROUTER_EVENT_DEPRECATIONS); +} + export default Route; diff --git a/packages/@ember/-internals/routing/lib/system/router.ts b/packages/@ember/-internals/routing/lib/system/router.ts index 157bc107307..574b9a38b8e 100644 --- a/packages/@ember/-internals/routing/lib/system/router.ts +++ b/packages/@ember/-internals/routing/lib/system/router.ts @@ -11,7 +11,12 @@ import { DEBUG } from '@glimmer/env'; import EmberLocation, { EmberLocation as IEmberLocation } from '../location/api'; import { calculateCacheKey, extractRouteArgs, getActiveTargetName, resemblesURL } from '../utils'; import EmberRouterDSL from './dsl'; -import Route, { defaultSerialize, hasDefaultSerialize, RenderOptions } from './route'; +import Route, { + defaultSerialize, + hasDefaultSerialize, + RenderOptions, + ROUTER_EVENT_DEPRECATIONS, +} from './route'; import RouterState from './router_state'; /** @module @ember/routing @@ -28,6 +33,46 @@ import Router, { } from 'router_js'; import { EngineRouteInfo } from './engines'; +function defaultDidTransition(this: EmberRouter, infos: PrivateRouteInfo[]) { + updatePaths(this); + + this._cancelSlowTransitionTimer(); + + this.notifyPropertyChange('url'); + this.set('currentState', this.targetState); + + // Put this in the runloop so url will be accurate. Seems + // less surprising than didTransition being out of sync. + once(this, this.trigger, 'didTransition'); + + if (DEBUG) { + if (get(this, 'namespace').LOG_TRANSITIONS) { + // eslint-disable-next-line no-console + console.log(`Transitioned into '${EmberRouter._routePath(infos)}'`); + } + } +} + +function defaultWillTransition( + this: EmberRouter, + oldInfos: PrivateRouteInfo[], + newInfos: PrivateRouteInfo[], + transition: Transition +) { + once(this, this.trigger, 'willTransition', transition); + + if (DEBUG) { + if (get(this, 'namespace').LOG_TRANSITIONS) { + // eslint-disable-next-line no-console + console.log( + `Preparing to transition from '${EmberRouter._routePath( + oldInfos + )}' to '${EmberRouter._routePath(newInfos)}'` + ); + } + } +} + if (HANDLER_INFOS) { Object.defineProperty(InternalRouteInfo.prototype, 'handler', { get() { @@ -239,6 +284,18 @@ class EmberRouter extends EmberObject { } didTransition(infos: PrivateRouteInfo[]) { + if (EMBER_ROUTING_ROUTER_SERVICE && ROUTER_EVENTS) { + if (router.didTransition !== defaultDidTransition) { + deprecate( + 'You attempted to override the "didTransition" method which is deprecated. Please inject the router service and listen to the "routeDidChange" event.', + false, + { + id: 'deprecate-router-events', + until: '4.0.0', + } + ); + } + } router.didTransition(infos); } @@ -247,6 +304,18 @@ class EmberRouter extends EmberObject { newInfos: PrivateRouteInfo[], transition: Transition ) { + if (EMBER_ROUTING_ROUTER_SERVICE && ROUTER_EVENTS) { + if (router.willTransition !== defaultWillTransition) { + deprecate( + 'You attempted to override the "willTransition" method which is deprecated. Please inject the router service and listen to the "routeWillChange" event.', + false, + { + id: 'deprecate-router-events', + until: '4.0.0', + } + ); + } + } router.willTransition(oldInfos, newInfos, transition); } @@ -438,57 +507,6 @@ class EmberRouter extends EmberObject { return true; } - /** - Handles updating the paths and notifying any listeners of the URL - change. - - Triggers the router level `didTransition` hook. - - For example, to notify google analytics when the route changes, - you could use this hook. (Note: requires also including GA scripts, etc.) - - ```javascript - import config from './config/environment'; - import EmberRouter from '@ember/routing/router'; - - let Router = EmberRouter.extend({ - location: config.locationType, - - didTransition: function() { - this._super(...arguments); - - return ga('send', 'pageview', { - 'page': this.get('url'), - 'title': this.get('url') - }); - } - }); - ``` - - @method didTransition - @public - @since 1.2.0 - */ - didTransition(infos: PrivateRouteInfo[]) { - updatePaths(this); - - this._cancelSlowTransitionTimer(); - - this.notifyPropertyChange('url'); - this.set('currentState', this.targetState); - - // Put this in the runloop so url will be accurate. Seems - // less surprising than didTransition being out of sync. - once(this, this.trigger, 'didTransition'); - - if (DEBUG) { - if (get(this, 'namespace').LOG_TRANSITIONS) { - // eslint-disable-next-line no-console - console.log(`Transitioned into '${EmberRouter._routePath(infos)}'`); - } - } - } - _setOutlets() { // This is triggered async during Route#willDestroy. // If the router is also being destroyed we do not want to @@ -547,35 +565,6 @@ class EmberRouter extends EmberObject { } } - /** - Handles notifying any listeners of an impending URL - change. - - Triggers the router level `willTransition` hook. - - @method willTransition - @public - @since 1.11.0 - */ - willTransition( - oldInfos: PrivateRouteInfo[], - newInfos: PrivateRouteInfo[], - transition: Transition - ) { - once(this, this.trigger, 'willTransition', transition); - - if (DEBUG) { - if (get(this, 'namespace').LOG_TRANSITIONS) { - // eslint-disable-next-line no-console - console.log( - `Preparing to transition from '${EmberRouter._routePath( - oldInfos - )}' to '${EmberRouter._routePath(newInfos)}'` - ); - } - } - } - handleURL(url: string) { // 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. @@ -1789,6 +1778,50 @@ function representEmptyRoute( } EmberRouter.reopen(Evented, { + /** + Handles updating the paths and notifying any listeners of the URL + change. + + Triggers the router level `didTransition` hook. + + For example, to notify google analytics when the route changes, + you could use this hook. (Note: requires also including GA scripts, etc.) + + ```javascript + import config from './config/environment'; + import EmberRouter from '@ember/routing/router'; + + let Router = EmberRouter.extend({ + location: config.locationType, + + didTransition: function() { + this._super(...arguments); + + return ga('send', 'pageview', { + 'page': this.get('url'), + 'title': this.get('url') + }); + } + }); + ``` + + @method didTransition + @public + @since 1.2.0 + */ + didTransition: defaultDidTransition, + + /** + Handles notifying any listeners of an impending URL + change. + + Triggers the router level `willTransition` hook. + + @method willTransition + @public + @since 1.11.0 + */ + willTransition: defaultWillTransition, /** Represents the URL of the root of the application, often '/'. This prefix is assumed on all routes defined on this router. @@ -1832,37 +1865,7 @@ EmberRouter.reopen(Evented, { }), }); -if (EMBER_ROUTING_ROUTER_SERVICE) { - if (ROUTER_EVENTS) { - EmberRouter.reopen({ - on(name: string) { - this._super(...arguments); - let hasDidTransition = name === 'didTransition'; - let hasWillTransition = name === 'willTransition'; - - if (hasDidTransition) { - deprecate( - 'You attempted to listen to the "didTransition" event which is deprecated. Please inject the router service and listen to the "routeDidChange" event.', - false, - { - id: 'deprecate-router-events', - until: '3.9.0', - } - ); - } - - if (hasWillTransition) { - deprecate( - 'You attempted to listen to the "willTransition" event which is deprecated. Please inject the router service and listen to the "routeWillChange" event.', - false, - { - id: 'deprecate-router-events', - until: '3.9.0', - } - ); - } - }, - }); - } +if (EMBER_ROUTING_ROUTER_SERVICE && ROUTER_EVENTS) { + EmberRouter.reopen(ROUTER_EVENT_DEPRECATIONS); } export default EmberRouter; diff --git a/packages/ember/tests/routing/router_service_test/events_test.js b/packages/ember/tests/routing/router_service_test/events_test.js index d2434178a2c..f3d1f2f7586 100644 --- a/packages/ember/tests/routing/router_service_test/events_test.js +++ b/packages/ember/tests/routing/router_service_test/events_test.js @@ -635,7 +635,7 @@ if (EMBER_ROUTING_ROUTER_SERVICE) { moduleFor( 'Router Service - deprecated events', class extends RouterTestCase { - '@test willTransition is deprecated'() { + '@test willTransition events are deprecated'() { return this.visit('/').then(() => { expectDeprecation(() => { this.routerService['_router'].on('willTransition', () => {}); @@ -643,7 +643,52 @@ if (EMBER_ROUTING_ROUTER_SERVICE) { }); } - '@test didTransition is deprecated'() { + '@test willTransition events are deprecated on routes'() { + this.add( + 'route:application', + Route.extend({ + init() { + this._super(...arguments); + this.on('willTransition', () => {}); + }, + }) + ); + expectDeprecation(() => { + return this.visit('/'); + }, 'You attempted to listen to the "willTransition" event which is deprecated. Please inject the router service and listen to the "routeWillChange" event.'); + } + + '@test didTransition events are deprecated on routes'() { + this.add( + 'route:application', + Route.extend({ + init() { + this._super(...arguments); + this.on('didTransition', () => {}); + }, + }) + ); + expectDeprecation(() => { + return this.visit('/'); + }, 'You attempted to listen to the "didTransition" event which is deprecated. Please inject the router service and listen to the "routeDidChange" event.'); + } + + '@test other events are not deprecated on routes'() { + this.add( + 'route:application', + Route.extend({ + init() { + this._super(...arguments); + this.on('fixx', () => {}); + }, + }) + ); + expectNoDeprecation(() => { + return this.visit('/'); + }); + } + + '@test didTransition events are deprecated'() { return this.visit('/').then(() => { expectDeprecation(() => { this.routerService['_router'].on('didTransition', () => {}); @@ -660,4 +705,43 @@ if (EMBER_ROUTING_ROUTER_SERVICE) { } } ); + + moduleFor( + 'Router Service: deprecated willTransition hook', + class extends RouterTestCase { + get routerOptions() { + return { + willTransition() { + this._super(...arguments); + // Overrides + }, + }; + } + + '@test willTransition hook is deprecated'() { + expectDeprecation(() => { + return this.visit('/'); + }, 'You attempted to override the "willTransition" method which is deprecated. Please inject the router service and listen to the "routeWillChange" event.'); + } + } + ); + moduleFor( + 'Router Service: deprecated didTransition hook', + class extends RouterTestCase { + get routerOptions() { + return { + didTransition() { + this._super(...arguments); + // Overrides + }, + }; + } + + '@test didTransition hook is deprecated'() { + expectDeprecation(() => { + return this.visit('/'); + }, 'You attempted to override the "didTransition" method which is deprecated. Please inject the router service and listen to the "routeDidChange" event.'); + } + } + ); }