Skip to content

Commit

Permalink
feat(TransitionHook): Pass in transition to HookMatchCriteria (#255)
Browse files Browse the repository at this point in the history
* Pass in transition to HookMatchCriteria
* Updated docs & state types
  • Loading branch information
jutaz authored and christopherthielen committed Oct 18, 2018
1 parent 713a0d0 commit 926705e
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 18 deletions.
1 change: 1 addition & 0 deletions src/common/common.ts
Expand Up @@ -32,6 +32,7 @@ export interface TypedMap<T> {
[key: string]: T;
}
export type Predicate<X> = (x?: X) => boolean;
export type PredicateBinary<X, Y> = (x?: X, y?: Y) => boolean;
/**
* An ng1-style injectable
*
Expand Down
12 changes: 8 additions & 4 deletions src/transition/hookBuilder.ts
Expand Up @@ -60,7 +60,7 @@ export class HookBuilder {
const treeChanges = transition.treeChanges();

// Find all the matching registered hooks for a given hook type
const matchingHooks = this.getMatchingHooks(hookType, treeChanges);
const matchingHooks = this.getMatchingHooks(hookType, treeChanges, transition);
if (!matchingHooks) return [];

const baseHookOptions = <TransitionHookOptions>{
Expand All @@ -70,7 +70,7 @@ export class HookBuilder {

const makeTransitionHooks = (hook: RegisteredHook) => {
// Fetch the Nodes that caused this hook to match.
const matches: IMatchingNodes = hook.matches(treeChanges);
const matches: IMatchingNodes = hook.matches(treeChanges, transition);
// Select the PathNode[] that will be used as TransitionHook context objects
const matchingNodes: PathNode[] = matches[hookType.criteriaMatchPath.name];

Expand Down Expand Up @@ -108,7 +108,11 @@ export class HookBuilder {
*
* @returns an array of matched [[RegisteredHook]]s
*/
public getMatchingHooks(hookType: TransitionEventType, treeChanges: TreeChanges): RegisteredHook[] {
public getMatchingHooks(
hookType: TransitionEventType,
treeChanges: TreeChanges,
transition: Transition
): RegisteredHook[] {
const isCreate = hookType.hookPhase === TransitionHookPhase.CREATE;

// Instance and Global hook registries
Expand All @@ -119,7 +123,7 @@ export class HookBuilder {
.map((reg: IHookRegistry) => reg.getHooks(hookType.name)) // Get named hooks from registries
.filter(assertPredicate(isArray, `broken event named: ${hookType.name}`)) // Sanity check
.reduce(unnestR, []) // Un-nest RegisteredHook[][] to RegisteredHook[] array
.filter(hook => hook.matches(treeChanges)); // Only those satisfying matchCriteria
.filter(hook => hook.matches(treeChanges, transition)); // Only those satisfying matchCriteria
}
}

Expand Down
17 changes: 9 additions & 8 deletions src/transition/hookRegistry.ts
Expand Up @@ -18,6 +18,7 @@ import {
IMatchingNodes,
HookFn,
} from './interface';
import { Transition } from './transition';
import { StateObject } from '../state/stateObject';
import { TransitionEventType } from './transitionEventType';
import { TransitionService } from './transitionService';
Expand All @@ -35,7 +36,7 @@ import { TransitionService } from './transitionService';
* - If a function, matchState calls the function with the state and returns true if the function's result is truthy.
* @returns {boolean}
*/
export function matchState(state: StateObject, criterion: HookMatchCriterion) {
export function matchState(state: StateObject, criterion: HookMatchCriterion, transition: Transition) {
const toMatch = isString(criterion) ? [criterion] : criterion;

function matchGlobs(_state: StateObject) {
Expand All @@ -51,7 +52,7 @@ export function matchState(state: StateObject, criterion: HookMatchCriterion) {
}

const matchFn = <any>(isFunction(toMatch) ? toMatch : matchGlobs);
return !!matchFn(state);
return !!matchFn(state, transition);
}

/**
Expand Down Expand Up @@ -93,9 +94,9 @@ export class RegisteredHook {
* with `entering: (state) => true` which only matches when a state is actually
* being entered.
*/
private _matchingNodes(nodes: PathNode[], criterion: HookMatchCriterion): PathNode[] {
private _matchingNodes(nodes: PathNode[], criterion: HookMatchCriterion, transition: Transition): PathNode[] {
if (criterion === true) return nodes;
const matching = nodes.filter(node => matchState(node.state, criterion));
const matching = nodes.filter(node => matchState(node.state, criterion, transition));
return matching.length ? matching : null;
}

Expand Down Expand Up @@ -132,7 +133,7 @@ export class RegisteredHook {
* };
* ```
*/
private _getMatchingNodes(treeChanges: TreeChanges): IMatchingNodes {
private _getMatchingNodes(treeChanges: TreeChanges, transition: Transition): IMatchingNodes {
const criteria = extend(this._getDefaultMatchCriteria(), this.matchCriteria);
const paths: PathType[] = values(this.tranSvc._pluginapi._getPathTypes());

Expand All @@ -144,7 +145,7 @@ export class RegisteredHook {
const path = treeChanges[pathtype.name] || [];
const nodes: PathNode[] = isStateHook ? path : [tail(path)];

mn[pathtype.name] = this._matchingNodes(nodes, criteria[pathtype.name]);
mn[pathtype.name] = this._matchingNodes(nodes, criteria[pathtype.name], transition);
return mn;
},
{} as IMatchingNodes
Expand All @@ -157,8 +158,8 @@ export class RegisteredHook {
* @returns an IMatchingNodes object, or null. If an IMatchingNodes object is returned, its values
* are the matching [[PathNode]]s for each [[HookMatchCriterion]] (to, from, exiting, retained, entering)
*/
matches(treeChanges: TreeChanges): IMatchingNodes {
const matches = this._getMatchingNodes(treeChanges);
matches(treeChanges: TreeChanges, transition: Transition): IMatchingNodes {
const matches = this._getMatchingNodes(treeChanges, transition);

// Check if all the criteria matched the TreeChanges object
const allMatched = values(matches).every(identity);
Expand Down
16 changes: 12 additions & 4 deletions src/transition/interface.ts
@@ -1,6 +1,6 @@
/** @publicapi @module transition */ /** */
import { StateDeclaration } from '../state/interface';
import { Predicate } from '../common/common';
import { PredicateBinary } from '../common/common';

import { Transition } from './transition';
import { StateObject } from '../state/stateObject';
Expand Down Expand Up @@ -716,8 +716,8 @@ export interface IHookRegistry {
getHooks(hookName: string): RegisteredHook[];
}

/** A predicate type which tests if a [[StateObject]] passes some test. Returns a boolean. */
export type IStateMatch = Predicate<StateObject>;
/** A predicate type which tests if a [[StateObject]] and [[Transition]] passes some test. Returns a boolean. */
export type IStateMatch = PredicateBinary<StateObject, Transition>;

/**
* This object is used to configure whether or not a Transition Hook is invoked for a particular transition,
Expand Down Expand Up @@ -765,6 +765,14 @@ export type IStateMatch = Predicate<StateObject>;
* }
* }
* ```
* #### Example:
* ```js
* // This will match when route is just entered (initial load) or when the state is hard-refreshed
* // by specifying `{refresh: true}` as transition options.
* var match = {
* from: (state, transition) => state.self.name === '' || transition.options().reload
* }
* ```
*
* #### Example:
* ```js
Expand Down Expand Up @@ -826,7 +834,7 @@ export interface PathType {
*
* A [[Glob]] string that matches the name of a state.
*
* Or, a function with the signature `function(state) { return matches; }`
* Or, a function with the signature `function(state, transition) { return matches; }`
* which should return a boolean to indicate if a state matches.
*
* Or, `true` to always match
Expand Down
4 changes: 2 additions & 2 deletions src/transition/transition.ts
Expand Up @@ -247,8 +247,8 @@ export class Transition implements IHookRegistry {
return this.is({ to: compare.$to().name, from: compare.$from().name });
}
return !(
(compare.to && !matchState(this.$to(), compare.to)) ||
(compare.from && !matchState(this.$from(), compare.from))
(compare.to && !matchState(this.$to(), compare.to, this)) ||
(compare.from && !matchState(this.$from(), compare.from, this))
);
}

Expand Down

0 comments on commit 926705e

Please sign in to comment.