Skip to content

Commit

Permalink
fix(ViewHooks): Fix problem with injecting uiCanExit
Browse files Browse the repository at this point in the history
feat(TransitionHook): Allow a transition hook's `this` to be bound

Added `{bind: any}` to the HookRegOptions

Closes #2661
  • Loading branch information
christopherthielen committed Mar 30, 2016
1 parent d000919 commit 76ab22d
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 43 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"jsdoc": "git://github.com/jsdoc3/jsdoc.git#v3.2.2",
"karma": "~0.12.0",
"karma-chrome-launcher": "~0.1.0",
"karma-jasmine": "~0.3.6",
"karma-jasmine": "^0.3.8",
"karma-phantomjs-launcher": "~0.1.0",
"karma-script-launcher": "~0.1.0",
"karma-systemjs": "^0.7.2",
Expand Down
9 changes: 6 additions & 3 deletions src/ng1/viewDirective.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {Transition} from "../transition/transition";
import {Node} from "../path/node";
import {Param} from "../params/param";
import {kebobString} from "../common/strings";
import {HookRegOptions} from "../transition/interface";

export type UIViewData = {
$cfg: Ng1ViewConfig;
Expand Down Expand Up @@ -384,6 +385,7 @@ function registerControllerCallbacks($transitions: TransitionService, controller
// Call $onInit() ASAP
if (isFunction(controllerInstance.$onInit)) controllerInstance.$onInit();

var hookOptions: HookRegOptions = { bind: controllerInstance };
// Add component-level hook for onParamsChange
if (isFunction(controllerInstance.uiOnParamsChanged)) {
// Fire callback on any successful transition
Expand Down Expand Up @@ -412,18 +414,19 @@ function registerControllerCallbacks($transitions: TransitionService, controller
controllerInstance.uiOnParamsChanged(filter(toParams, (val, key) => changedKeys.indexOf(key) !== -1), $transition$);
}
};
$scope.$on('$destroy', $transitions.onSuccess({}, ['$transition$', paramsUpdated]));
$scope.$on('$destroy', $transitions.onSuccess({}, ['$transition$', paramsUpdated]), hookOptions);

// Fire callback on any IGNORED transition
let onDynamic = ($error$, $transition$) => {
if ($error$.type === RejectType.IGNORED) paramsUpdated($transition$);
};
$scope.$on('$destroy', $transitions.onError({}, ['$error$', '$transition$', onDynamic]));
$scope.$on('$destroy', $transitions.onError({}, ['$error$', '$transition$', onDynamic]), hookOptions);
}

// Add component-level hook for uiCanExit
if (isFunction(controllerInstance.uiCanExit)) {
$scope.$on('$destroy', $transitions.onBefore({exiting: cfg.node.state.name}, controllerInstance.uiCanExit.bind(controllerInstance)));
var criteria = {exiting: cfg.node.state.name};
$scope.$on('$destroy', $transitions.onBefore(criteria, controllerInstance.uiCanExit, hookOptions));
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/ng1/viewsBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ export function ng1ViewsBuilder(state: State) {
}

// for ng 1.2 style, process the scope: { input: "=foo" } object
const scopeBindings = bindingsObj => Object.keys(bindingsObj)
const scopeBindings = bindingsObj => Object.keys(bindingsObj || {})
.map(key => [key, /^[=<](.*)/.exec(bindingsObj[key])])
.filter(tuple => isDefined(tuple[1]))
.map(tuple => tuple[1][1] || tuple[0]);

// for ng 1.3+ bindToController or 1.5 component style, process a $$bindings object
const bindToCtrlBindings = bindingsObj => Object.keys(bindingsObj)
const bindToCtrlBindings = bindingsObj => Object.keys(bindingsObj || {})
.filter(key => !!/[=<]/.exec(bindingsObj[key].mode))
.map(key => bindingsObj[key].attrName);

Expand Down
2 changes: 1 addition & 1 deletion src/resolve/resolveContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export class ResolveContext {
let resolvables = this.getResolvablesForFn(fn);
trace.tracePathElementInvoke(tail(this._path), fn, Object.keys(resolvables), extend({when: "Now "}, options));
let resolvedLocals = map(resolvables, prop("data"));
return services.$injector.invoke(<Function> fn, null, extend({}, locals, resolvedLocals));
return services.$injector.invoke(<Function> fn, options.bind || null, extend({}, locals, resolvedLocals));
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/transition/hookBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ export class HookBuilder {
getOnRetainHooks = () => this._buildNodeHooks("onRetain", "retained", tupleSort(), (node) => ({ $state$: node.state }));
getOnEnterHooks = () => this._buildNodeHooks("onEnter", "entering", tupleSort(), (node) => ({ $state$: node.state }));
getOnFinishHooks = () => this._buildNodeHooks("onFinish", "to", tupleSort(), (node) => ({ $treeChanges$: this.treeChanges }));
getOnSuccessHooks = () => this._buildNodeHooks("onSuccess", "to", tupleSort(), undefined, {async: false, rejectIfSuperseded: false});
getOnErrorHooks = () => this._buildNodeHooks("onError", "to", tupleSort(), undefined, {async: false, rejectIfSuperseded: false});
getOnSuccessHooks = () => this._buildNodeHooks("onSuccess", "to", tupleSort(), undefined, { async: false, rejectIfSuperseded: false });
getOnErrorHooks = () => this._buildNodeHooks("onError", "to", tupleSort(), undefined, { async: false, rejectIfSuperseded: false });

asyncHooks() {
let onStartHooks = this.getOnStartHooks();
Expand Down Expand Up @@ -79,7 +79,7 @@ export class HookBuilder {
matchingNodesProp: string,
sortHooksFn: (l: HookTuple, r: HookTuple) => number,
getLocals: (node: Node) => any = (node) => ({}),
options: TransitionHookOptions = {}): TransitionHook[] {
options?: TransitionHookOptions): TransitionHook[] {

// Find all the matching registered hooks for a given hook type
let matchingHooks = this._matchingHooks(hookType, this.treeChanges);
Expand All @@ -93,7 +93,7 @@ export class HookBuilder {

// Return an array of HookTuples
return nodes.map(node => {
let _options = extend({ traceData: { hookType, context: node} }, this.baseHookOptions, options);
let _options = extend({ bind: hook.bind, traceData: { hookType, context: node} }, this.baseHookOptions, options);
let transitionHook = new TransitionHook(hook.callback, getLocals(node), node.resolveContext, _options);
return <HookTuple> { hook, node, transitionHook };
});
Expand Down
14 changes: 8 additions & 6 deletions src/transition/hookRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {isString, isFunction} from "../common/predicates";
import {val} from "../common/hof";
import {Node} from "../path/node";

import {IMatchCriteria, IStateMatch, IEventHook, IHookRegistry, IHookRegistration, TreeChanges, MatchCriterion, IMatchingNodes} from "./interface";
import {HookRegOptions, HookMatchCriteria, IStateMatch, IEventHook, IHookRegistry, IHookRegistration, TreeChanges, HookMatchCriterion, IMatchingNodes} from "./interface";
import {Glob} from "../common/glob";
import {State} from "../state/stateObject";

Expand All @@ -18,7 +18,7 @@ import {State} from "../state/stateObject";
* - 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: State, criterion: MatchCriterion) {
export function matchState(state: State, criterion: HookMatchCriterion) {
let toMatch = isString(criterion) ? [criterion] : criterion;

function matchGlobs(_state) {
Expand All @@ -40,16 +40,18 @@ export function matchState(state: State, criterion: MatchCriterion) {

export class EventHook implements IEventHook {
callback: IInjectable;
matchCriteria: IMatchCriteria;
matchCriteria: HookMatchCriteria;
priority: number;
bind: any;

constructor(matchCriteria: IMatchCriteria, callback: IInjectable, options: { priority?: number } = <any>{}) {
constructor(matchCriteria: HookMatchCriteria, callback: IInjectable, options: HookRegOptions = <any>{}) {
this.callback = callback;
this.matchCriteria = extend({ to: true, from: true, exiting: true, retained: true, entering: true }, matchCriteria);
this.priority = options.priority || 0;
this.bind = options.bind || null;
}

private static _matchingNodes(nodes: Node[], criterion: MatchCriterion): Node[] {
private static _matchingNodes(nodes: Node[], criterion: HookMatchCriterion): Node[] {
if (criterion === true) return nodes;
let matching = nodes.filter(node => matchState(node.state, criterion));
return matching.length ? matching : null;
Expand All @@ -59,7 +61,7 @@ export class EventHook implements IEventHook {
* Determines if this hook's [[matchCriteria]] match the given [[TreeChanges]]
*
* @returns an IMatchingNodes object, or null. If an IMatchingNodes object is returned, its values
* are the matching [[Node]]s for each [[MatchCriterion]] (to, from, exiting, retained, entering)
* are the matching [[Node]]s for each [[HookMatchCriterion]] (to, from, exiting, retained, entering)
*/
matches(treeChanges: TreeChanges): IMatchingNodes {
let mc = this.matchCriteria, _matchingNodes = EventHook._matchingNodes;
Expand Down
67 changes: 45 additions & 22 deletions src/transition/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export interface TransitionHookOptions {
hookType ?: string;
target ?: any;
traceData ?: any;
bind ?: any;
}

/**
Expand Down Expand Up @@ -151,7 +152,27 @@ export interface ITransitionService extends IHookRegistry {
}

export type IHookGetter = (hookName: string) => IEventHook[];
export type IHookRegistration = (matchCriteria: IMatchCriteria, callback: IInjectable, options?) => Function;
export type IHookRegistration = (matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions) => Function;

/**
* These options may be provided when registering a Transition Hook (such as `onStart`)
*/
export interface HookRegOptions {
/**
* Sets the priority of the registered hook
*
* Hooks of the same type (onBefore, onStart, etc) are invoked in priority order. A hook with a higher priority
* is invoked before a hook with a lower priority.
*
* The default hook priority is 0
*/
priority?: number;

/**
* Specifies what `this` is bound to during hook invocation.
*/
bind?: any;
}

/**
* This interface specifies the api for registering Transition Hooks. Both the
Expand Down Expand Up @@ -255,7 +276,7 @@ export interface IHookRegistry {
* @param callback the hook function which will be injected and invoked.
* @returns a function which deregisters the hook.
*/
onBefore(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
onBefore(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;

/**
* Registers a callback function as an `onStart` Transition Hook.
Expand Down Expand Up @@ -325,7 +346,7 @@ export interface IHookRegistry {
* @param callback the hook function which will be injected and invoked.
* @returns a function which deregisters the hook.
*/
onStart(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
onStart(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;

/**
* Registers a callback function as an `onEnter` Transition+State Hook.
Expand Down Expand Up @@ -394,7 +415,7 @@ export interface IHookRegistry {
* @param callback the hook function which will be injected and invoked.
* @returns a function which deregisters the hook.
*/
onEnter(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
onEnter(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;

/**
* Registers a callback function as an `onRetain` Transition+State Hook.
Expand Down Expand Up @@ -432,7 +453,7 @@ export interface IHookRegistry {
* @param callback the hook function which will be injected and invoked.
* @returns a function which deregisters the hook.
*/
onRetain(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
onRetain(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;

/**
* Registers a callback function as an `onExit` Transition+State Hook.
Expand Down Expand Up @@ -471,7 +492,7 @@ export interface IHookRegistry {
* @param callback the hook function which will be injected and invoked.
* @returns a function which deregisters the hook.
*/
onExit(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
onExit(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;

/**
* Registers a callback function as an `onFinish` Transition Hook.
Expand Down Expand Up @@ -504,7 +525,7 @@ export interface IHookRegistry {
* @param callback the hook function which will be injected and invoked.
* @returns a function which deregisters the hook.
*/
onFinish(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
onFinish(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;

/**
* Registers a callback function as an `onSuccess` Transition Hook.
Expand All @@ -531,7 +552,7 @@ export interface IHookRegistry {
* @param callback the hook function which will be injected and invoked.
* @returns a function which deregisters the hook.
*/
onSuccess(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
onSuccess(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;

/**
* Registers a callback function as an `onError` Transition Hook.
Expand All @@ -557,7 +578,7 @@ export interface IHookRegistry {
* @param callback the hook function which will be injected and invoked.
* @returns a function which deregisters the hook.
*/
onError(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
onError(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;

/**
* Returns all the registered hooks of a given `hookName` type
Expand All @@ -571,13 +592,14 @@ export interface IHookRegistry {
getHooks(hookName: string): IEventHook[];
}

/** A predicate type which takes a [[State]] and returns a boolean */
export type IStateMatch = Predicate<State>
/**
* This object is used to configure whether or not a Transition Hook is invoked for a particular transition,
* based on the Transition's "to state" and "from state".
*
* Each property (`to`, `from`, `exiting`, `retained`, and `entering`) can be state globs, a function that takes a
* state, or a boolean (see [[MatchCriterion]])
* state, or a boolean (see [[HookMatchCriterion]])
*
* All properties are optional. If any property is omitted, it is replaced with the value `true`, and always matches.
*
Expand Down Expand Up @@ -631,17 +653,17 @@ export type IStateMatch = Predicate<State>
* }
* ```
*/
export interface IMatchCriteria {
/** A [[MatchCriterion]] to match the destination state */
to?: MatchCriterion;
/** A [[MatchCriterion]] to match the original (from) state */
from?: MatchCriterion;
/** A [[MatchCriterion]] to match any state that would be exiting */
exiting?: MatchCriterion;
/** A [[MatchCriterion]] to match any state that would be retained */
retained?: MatchCriterion;
/** A [[MatchCriterion]] to match any state that would be entering */
entering?: MatchCriterion;
export interface HookMatchCriteria {
/** A [[HookMatchCriterion]] to match the destination state */
to?: HookMatchCriterion;
/** A [[HookMatchCriterion]] to match the original (from) state */
from?: HookMatchCriterion;
/** A [[HookMatchCriterion]] to match any state that would be exiting */
exiting?: HookMatchCriterion;
/** A [[HookMatchCriterion]] to match any state that would be retained */
retained?: HookMatchCriterion;
/** A [[HookMatchCriterion]] to match any state that would be entering */
entering?: HookMatchCriterion;
}

export interface IMatchingNodes {
Expand All @@ -658,10 +680,11 @@ export interface IMatchingNodes {
* which should return a boolean to indicate if a state matches.
* Or, `true` to match anything
*/
export type MatchCriterion = (string|IStateMatch|boolean)
export type HookMatchCriterion = (string|IStateMatch|boolean)

export interface IEventHook {
callback: IInjectable;
priority: number;
bind: any;
matches: (treeChanges: TreeChanges) => IMatchingNodes;
}
5 changes: 3 additions & 2 deletions src/transition/transitionHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import {ResolveContext} from "../resolve/module";

let REJECT = new RejectFactory();

let defaultOptions = {
let defaultOptions: TransitionHookOptions = {
async: true,
rejectIfSuperseded: true,
current: noop,
transition: null,
traceData: {}
traceData: {},
bind: null
};

export class TransitionHook {
Expand Down
2 changes: 1 addition & 1 deletion test/hookBuilderSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('HookBuilder:', function() {
});


describe('MatchCriteria', function() {
describe('HookMatchCriteria', function() {

describe('.to', function() {
it("should match a transition with same to state", function() {
Expand Down
2 changes: 1 addition & 1 deletion test/transitionSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ describe('transition', function () {
});
});

describe('Transition MatchCriterion', function() {
xdescribe('Transition HookMatchCriterion', function() {
it("should", function() {

})
Expand Down
Loading

0 comments on commit 76ab22d

Please sign in to comment.