Skip to content

Commit

Permalink
refactor(path): Codified criteria match paths and their scopes
Browse files Browse the repository at this point in the history
refactor(Event): renamed getTransitionEventType to _getEvent
feat(transition): Create
  • Loading branch information
christopherthielen committed Dec 9, 2016
1 parent 0dc2c19 commit 2673406
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 96 deletions.
6 changes: 3 additions & 3 deletions src/transition/hookBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 => {
Expand All @@ -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 <HookTuple> { hook, node, transitionHook };
});
Expand Down
49 changes: 26 additions & 23 deletions src/transition/hookRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/** @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 {
TransitionStateHookFn, TransitionHookFn, TransitionHookPhase, TransitionHookScope, IHookRegistry
} from "./interface"; // has or is using

import {
HookRegOptions, HookMatchCriteria, IHookRegistration, TreeChanges,
HookRegOptions, HookMatchCriteria, TreeChanges,
HookMatchCriterion, IMatchingNodes, HookFn
} from "./interface";
import {Glob} from "../common/glob";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<any[]>(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);
}

/**
Expand All @@ -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);
}
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/transition/transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
11 changes: 4 additions & 7 deletions src/transition/transitionEventType.ts
Original file line number Diff line number Diff line change
@@ -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`.
Expand All @@ -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,
Expand Down
161 changes: 100 additions & 61 deletions src/transition/transitionService.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.
*
Expand Down Expand Up @@ -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 = <TransitionServicePluginAPI> bindFunctions(this, {}, this, [
'_definePath',
'_defineEvent',
'_getPaths',
'_getEvents',
'getHooks',
]);

/**
* This object has hook de-registration functions for the built-in hooks.
Expand All @@ -128,8 +141,11 @@ export class TransitionService implements IHookRegistry, Disposable {
constructor(private _router: UIRouter) {
this.$view = _router.viewService;
this._deregisterHookFns = <any> {};
this.registerTransitionHookTypes();
this.registerTransitionHooks();

this._defineDefaultPaths();
this._defineDefaultEvents();

this._registerDefaultTransitionHooks();
}

/** @internalapi */
Expand Down Expand Up @@ -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 = <TransitionServicePluginAPI> 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();
Expand All @@ -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
Expand All @@ -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[];
}
Loading

0 comments on commit 2673406

Please sign in to comment.