diff --git a/src/transition/hookBuilder.ts b/src/transition/hookBuilder.ts index fd343385..cd9ed0e5 100644 --- a/src/transition/hookBuilder.ts +++ b/src/transition/hookBuilder.ts @@ -54,7 +54,7 @@ export class HookBuilder { } buildHooksForPhase(phase: TransitionHookPhase): TransitionHook[] { - return this.$transitions._pluginapi.getTransitionEventTypes(phase) + return this.$transitions._pluginapi._getEvents(phase) .map(type => this.buildHooks(type)) .reduce(unnestR, []) .filter(identity); @@ -78,7 +78,7 @@ export class HookBuilder { // Fetch the Nodes that caused this hook to match. let matches: IMatchingNodes = hook.matches(this.treeChanges); // Select the PathNode[] that will be used as TransitionHook context objects - let matchingNodes: PathNode[] = matches[hookType.criteriaMatchPath]; + let matchingNodes: PathNode[] = matches[hookType.criteriaMatchPath.name]; // Return an array of HookTuples return matchingNodes.map(node => { @@ -87,7 +87,7 @@ export class HookBuilder { traceData: { hookType: hookType.name, context: node} }, this.baseHookOptions); - let state = hookType.hookScope === TransitionHookScope.STATE ? node.state : null; + let state = hookType.criteriaMatchPath.scope === TransitionHookScope.STATE ? node.state : null; let transitionHook = new TransitionHook(this.transition, state, hook, _options); return { hook, node, transitionHook }; }); diff --git a/src/transition/hookRegistry.ts b/src/transition/hookRegistry.ts index 8197e769..3c1e521d 100644 --- a/src/transition/hookRegistry.ts +++ b/src/transition/hookRegistry.ts @@ -1,5 +1,5 @@ /** @coreapi @module transition */ /** for typedoc */ -import { extend, removeFrom, allTrueR, tail, uniqR, pushTo, equals, values, identity } from "../common/common"; +import { extend, removeFrom, tail, values, identity, map } from "../common/common"; import {isString, isFunction} from "../common/predicates"; import {PathNode} from "../path/node"; import { @@ -7,7 +7,7 @@ import { } from "./interface"; // has or is using import { - HookRegOptions, HookMatchCriteria, IHookRegistration, TreeChanges, + HookRegOptions, HookMatchCriteria, TreeChanges, HookMatchCriterion, IMatchingNodes, HookFn } from "./interface"; import {Glob} from "../common/glob"; @@ -51,7 +51,7 @@ export function matchState(state: State, criterion: HookMatchCriterion) { * @hidden * The registration data for a registered transition hook */ -export class RegisteredHook implements RegisteredHook { +export class RegisteredHook { matchCriteria: HookMatchCriteria; priority: number; bind: any; @@ -93,21 +93,7 @@ export class RegisteredHook implements RegisteredHook { * { to: true, from: true, entering: true, exiting: true, retained: true } */ private _getDefaultMatchCriteria(): HookMatchCriteria { - return this.tranSvc._pluginapi.getTransitionEventTypes() - .map(type => type.criteriaMatchPath) - .reduce(uniqR, []) - .reduce((acc, path) => (acc[path] = true, acc), {}); - } - - /** - * For all the criteria match paths in all TransitionHookTypes, - * return an object where: keys are pathname, vals are TransitionHookScope - */ - private _getPathScopes(): { [key: string]: TransitionHookScope } { - return this.tranSvc._pluginapi.getTransitionEventTypes().reduce((paths, type) => { - paths[type.criteriaMatchPath] = type.hookScope; - return paths - }, {}); + return map(this.tranSvc._pluginapi._getPaths(), () => true); } /** @@ -122,15 +108,15 @@ export class RegisteredHook implements RegisteredHook { * }; */ private _getMatchingNodes(treeChanges: TreeChanges): IMatchingNodes { - let pathScopes: { [key: string]: TransitionHookScope } = this._getPathScopes(); + let paths: PathType[] = values(this.tranSvc._pluginapi._getPaths()); - return Object.keys(pathScopes).reduce((mn: IMatchingNodes, pathName: string) => { + return paths.reduce((mn: IMatchingNodes, path: PathType) => { // STATE scope criteria matches against every node in the path. // TRANSITION scope criteria matches against only the last node in the path - let isStateHook = pathScopes[pathName] === TransitionHookScope.STATE; - let nodes: PathNode[] = isStateHook ? treeChanges[pathName] : [tail(treeChanges[pathName])]; + let name = path.name, isStateHook = path.scope === TransitionHookScope.STATE; + let nodes: PathNode[] = isStateHook ? treeChanges[name] : [tail(treeChanges[name])]; - mn[pathName] = this._matchingNodes(nodes, this.matchCriteria[pathName]); + mn[name] = this._matchingNodes(nodes, this.matchCriteria[name]); return mn; }, {} as IMatchingNodes); } @@ -155,6 +141,23 @@ export interface RegisteredHooks { [key: string]: RegisteredHook[]; } +/** @hidden */ +export interface PathTypes { + [key: string]: PathType + + to: PathType + from: PathType + exiting: PathType + retained: PathType + entering: PathType +} + +/** @hidden */ +export interface PathType { + name: string; + scope: TransitionHookScope; +} + /** @hidden Return a registration function of the requested type. */ export function makeEvent(registry: IHookRegistry, transitionService: TransitionService, eventType: TransitionEventType) { // Create the object which holds the registered transition hooks. diff --git a/src/transition/transition.ts b/src/transition/transition.ts index 8a4f59d7..8a525fe9 100644 --- a/src/transition/transition.ts +++ b/src/transition/transition.ts @@ -116,7 +116,7 @@ export class Transition implements IHookRegistry { * (which can then be used to register hooks) */ private createTransitionHookRegFns() { - this.router.transitionService._pluginapi.getTransitionEventTypes() + this.router.transitionService._pluginapi._getEvents() .filter(type => type.hookPhase !== TransitionHookPhase.CREATE) .forEach(type => makeEvent(this, this.router.transitionService, type)); } diff --git a/src/transition/transitionEventType.ts b/src/transition/transitionEventType.ts index 0078bc4f..ca002295 100644 --- a/src/transition/transitionEventType.ts +++ b/src/transition/transitionEventType.ts @@ -1,8 +1,6 @@ -import {TransitionHookScope, TransitionHookPhase} from "./interface"; -import {PathNode} from "../path/node"; -import {Transition} from "./transition"; -import {isString} from "../common/predicates"; -import {GetErrorHandler, GetResultHandler, TransitionHook} from "./transitionHook"; +import { TransitionHookPhase } from "./interface"; +import { GetErrorHandler, GetResultHandler, TransitionHook } from "./transitionHook"; +import { PathType } from "./hookRegistry"; /** * This class defines a type of hook, such as `onBefore` or `onEnter`. * Plugins can define custom hook types, such as sticky states does for `onInactive`. @@ -14,9 +12,8 @@ export class TransitionEventType { constructor(public name: string, public hookPhase: TransitionHookPhase, - public hookScope: TransitionHookScope, public hookOrder: number, - public criteriaMatchPath: string, + public criteriaMatchPath: PathType, public reverseSort: boolean = false, public getResultHandler: GetResultHandler = TransitionHook.HANDLE_RESULT, public getErrorHandler: GetErrorHandler = TransitionHook.REJECT_ERROR, diff --git a/src/transition/transitionService.ts b/src/transition/transitionService.ts index ba73f13a..6e4e9459 100644 --- a/src/transition/transitionService.ts +++ b/src/transition/transitionService.ts @@ -1,31 +1,25 @@ /** @coreapi @module transition */ /** for typedoc */ import { - IHookRegistry, TransitionOptions, TransitionHookScope, TransitionHookPhase, - TransitionCreateHookFn + IHookRegistry, TransitionOptions, TransitionHookScope, TransitionHookPhase, TransitionCreateHookFn, + HookMatchCriteria, HookRegOptions } from "./interface"; - -import { - HookMatchCriteria, HookRegOptions, TransitionStateHookFn, TransitionHookFn -} from "./interface"; // has or is using - -import {Transition} from "./transition"; -import {RegisteredHooks, makeEvent, RegisteredHook} from "./hookRegistry"; -import {TargetState} from "../state/targetState"; -import {PathNode} from "../path/node"; -import {ViewService} from "../view/view"; -import {UIRouter} from "../router"; - -import {registerEagerResolvePath, registerLazyResolveState} from "../hooks/resolve"; -import {registerLoadEnteringViews, registerActivateViews} from "../hooks/views"; -import {registerUpdateUrl} from "../hooks/url"; -import {registerRedirectToHook} from "../hooks/redirectTo"; -import {registerOnExitHook, registerOnRetainHook, registerOnEnterHook} from "../hooks/onEnterExitRetain"; -import {registerLazyLoadHook} from "../hooks/lazyLoad"; -import {TransitionEventType} from "./transitionEventType"; +import { Transition } from "./transition"; +import { RegisteredHooks, makeEvent, RegisteredHook, PathTypes, PathType } from "./hookRegistry"; +import { TargetState } from "../state/targetState"; +import { PathNode } from "../path/node"; +import { ViewService } from "../view/view"; +import { UIRouter } from "../router"; +import { registerEagerResolvePath, registerLazyResolveState } from "../hooks/resolve"; +import { registerLoadEnteringViews, registerActivateViews } from "../hooks/views"; +import { registerUpdateUrl } from "../hooks/url"; +import { registerRedirectToHook } from "../hooks/redirectTo"; +import { registerOnExitHook, registerOnRetainHook, registerOnEnterHook } from "../hooks/onEnterExitRetain"; +import { registerLazyLoadHook } from "../hooks/lazyLoad"; +import { TransitionEventType } from "./transitionEventType"; import { TransitionHook, GetResultHandler, GetErrorHandler } from "./transitionHook"; -import {isDefined} from "../common/predicates"; +import { isDefined } from "../common/predicates"; import { removeFrom, values, bindFunctions } from "../common/common"; -import { Disposable } from "../interface"; +import { Disposable } from "../interface"; // has or is using /** * The default [[Transition]] options. @@ -45,6 +39,15 @@ export let defaultTransOpts: TransitionOptions = { source : "unknown" }; + +export interface TransitionServicePluginAPI { + _definePath(name: string, hookScope: TransitionHookScope); + _getPaths(): PathTypes; + _defineEvent(hookType: TransitionEventType): void; + _getEvents(phase?: TransitionHookPhase): TransitionEventType[]; + getHooks(hookName: string): RegisteredHook[]; +} + /** * This class provides services related to Transitions. * @@ -104,7 +107,17 @@ export class TransitionService implements IHookRegistry, Disposable { /** @hidden The transition hook types, such as `onEnter`, `onStart`, etc */ private _eventTypes: TransitionEventType[] = []; /** @hidden The registered transition hooks */ - _registeredHooks: RegisteredHooks = { }; + _registeredHooks = { } as RegisteredHooks; + /** @hidden The paths on a criteria object */ + private _criteriaPaths = { } as PathTypes; + + _pluginapi = bindFunctions(this, {}, this, [ + '_definePath', + '_defineEvent', + '_getPaths', + '_getEvents', + 'getHooks', + ]); /** * This object has hook de-registration functions for the built-in hooks. @@ -128,8 +141,11 @@ export class TransitionService implements IHookRegistry, Disposable { constructor(private _router: UIRouter) { this.$view = _router.viewService; this._deregisterHookFns = {}; - this.registerTransitionHookTypes(); - this.registerTransitionHooks(); + + this._defineDefaultPaths(); + this._defineDefaultEvents(); + + this._registerDefaultTransitionHooks(); } /** @internalapi */ @@ -157,59 +173,62 @@ export class TransitionService implements IHookRegistry, Disposable { } /** @hidden */ - private registerTransitionHookTypes() { - const Scope = TransitionHookScope; + private _defineDefaultEvents() { const Phase = TransitionHookPhase; const TH = TransitionHook; + const paths = this._criteriaPaths; - this.defineEvent("onCreate", Phase.CREATE, Scope.TRANSITION, 0, "to", false, TH.IGNORE_RESULT, TH.THROW_ERROR, false); + this._defineEvent("onCreate", Phase.CREATE, 0, paths.to, false, TH.IGNORE_RESULT, TH.THROW_ERROR, false); - this.defineEvent("onBefore", Phase.BEFORE, Scope.TRANSITION, 0, "to", false, TH.HANDLE_RESULT); + this._defineEvent("onBefore", Phase.BEFORE, 0, paths.to, false, TH.HANDLE_RESULT); - this.defineEvent("onStart", Phase.ASYNC, Scope.TRANSITION, 0, "to"); - this.defineEvent("onExit", Phase.ASYNC, Scope.STATE, 100, "exiting", true); - this.defineEvent("onRetain", Phase.ASYNC, Scope.STATE, 200, "retained"); - this.defineEvent("onEnter", Phase.ASYNC, Scope.STATE, 300, "entering"); - this.defineEvent("onFinish", Phase.ASYNC, Scope.TRANSITION, 400, "to"); + this._defineEvent("onStart", Phase.ASYNC, 0, paths.to); + this._defineEvent("onExit", Phase.ASYNC, 100, paths.exiting, true); + this._defineEvent("onRetain", Phase.ASYNC, 200, paths.retained); + this._defineEvent("onEnter", Phase.ASYNC, 300, paths.entering); + this._defineEvent("onFinish", Phase.ASYNC, 400, paths.to); - this.defineEvent("onSuccess", Phase.SUCCESS, Scope.TRANSITION, 0, "to", false, TH.IGNORE_RESULT, TH.LOG_ERROR, false); - this.defineEvent("onError", Phase.ERROR, Scope.TRANSITION, 0, "to", false, TH.IGNORE_RESULT, TH.LOG_ERROR, false); + this._defineEvent("onSuccess", Phase.SUCCESS, 0, paths.to, false, TH.IGNORE_RESULT, TH.LOG_ERROR, false); + this._defineEvent("onError", Phase.ERROR, 0, paths.to, false, TH.IGNORE_RESULT, TH.LOG_ERROR, false); } - _pluginapi = bindFunctions(this, {}, this, [ - 'registerTransitionHookType', - 'getTransitionEventTypes', - 'getHooks', - ]); + /** @hidden */ + private _defineDefaultPaths() { + const { STATE, TRANSITION } = TransitionHookScope; + + this._definePath("to", TRANSITION); + this._definePath("from", TRANSITION); + this._definePath("exiting", STATE); + this._definePath("retained", STATE); + this._definePath("entering", STATE); + } /** * Defines a transition hook type and returns a transition hook registration * function (which can then be used to register hooks of this type). * @internalapi */ - defineEvent(name: string, - hookPhase: TransitionHookPhase, - hookScope: TransitionHookScope, - hookOrder: number, - criteriaMatchPath: string, - reverseSort: boolean = false, - getResultHandler: GetResultHandler = TransitionHook.HANDLE_RESULT, - getErrorHandler: GetErrorHandler = TransitionHook.REJECT_ERROR, - rejectIfSuperseded: boolean = true) + _defineEvent(name: string, + hookPhase: TransitionHookPhase, + hookOrder: number, + criteriaMatchPath: PathType, + reverseSort: boolean = false, + getResultHandler: GetResultHandler = TransitionHook.HANDLE_RESULT, + getErrorHandler: GetErrorHandler = TransitionHook.REJECT_ERROR, + rejectIfSuperseded: boolean = true) { - let eventType = new TransitionEventType(name, hookPhase, hookScope, hookOrder, criteriaMatchPath, reverseSort, getResultHandler, getErrorHandler, rejectIfSuperseded); + let eventType = new TransitionEventType(name, hookPhase, hookOrder, criteriaMatchPath, reverseSort, getResultHandler, getErrorHandler, rejectIfSuperseded); this._eventTypes.push(eventType); makeEvent(this, this, eventType); }; - /** * @hidden * Returns the known event types, such as `onBefore` * If a phase argument is provided, returns only events for the given phase. */ - private getTransitionEventTypes(phase?: TransitionHookPhase): TransitionEventType[] { + private _getEvents(phase?: TransitionHookPhase): TransitionEventType[] { let transitionHookTypes = isDefined(phase) ? this._eventTypes.filter(type => type.hookPhase === phase) : this._eventTypes.slice(); @@ -220,13 +239,39 @@ export class TransitionService implements IHookRegistry, Disposable { }) } + /** + * Adds a Path to be used as a criterion against a TreeChanges path + * + * For example: the `exiting` path in [[HookMatchCriteria]] is a STATE scoped path. + * It was defined by calling `defineTreeChangesCriterion('exiting', TransitionHookScope.STATE)` + * Each state in the exiting path is checked against the criteria and returned as part of the match. + * + * Another example: the `to` path in [[HookMatchCriteria]] is a TRANSITION scoped path. + * It was defined by calling `defineTreeChangesCriterion('to', TransitionHookScope.TRANSITION)` + * Only the tail of the `to` path is checked against the criteria and returned as part of the match. + * + * @internalapi + */ + private _definePath(name: string, hookScope: TransitionHookScope) { + this._criteriaPaths[name] = { name, scope: hookScope }; + } + + /** + * Gets a Path definition used as a criterion against a TreeChanges path + * + * @internalapi + */ + private _getPaths(): PathTypes { + return this._criteriaPaths; + } + /** @hidden */ public getHooks(hookName: string): RegisteredHook[] { return this._registeredHooks[hookName]; } /** @hidden */ - private registerTransitionHooks() { + private _registerDefaultTransitionHooks() { let fns = this._deregisterHookFns; // Wire up redirectTo hook @@ -252,9 +297,3 @@ export class TransitionService implements IHookRegistry, Disposable { fns.lazyLoad = registerLazyLoadHook(this); } } - -export interface TransitionServicePluginAPI { - registerTransitionHookType(hookType: TransitionEventType): void; - getTransitionEventTypes(phase?: TransitionHookPhase): TransitionEventType[]; - getHooks(hookName: string): RegisteredHook[]; -} \ No newline at end of file diff --git a/test/hookBuilderSpec.ts b/test/hookBuilderSpec.ts index 94392ab6..ef7c50ae 100644 --- a/test/hookBuilderSpec.ts +++ b/test/hookBuilderSpec.ts @@ -163,7 +163,7 @@ describe('HookBuilder:', function() { describe('should have the correct state context', function() { const hookTypeByName = name => - $trans._pluginapi.getTransitionEventTypes().filter(type => type.name === name)[0]; + $trans._pluginapi._getEvents().filter(type => type.name === name)[0]; const context = hook => hook.stateContext && hook.stateContext.name;