UNPKG

ui-router

Version:

State-based routing for Javascript

171 lines (143 loc) 7.5 kB
/** @module path */ /** for typedoc */ import {extend, find, pick, omit, tail, mergeR, map, values} from "../common/common"; import {prop, propEq, not, curry} from "../common/hof"; import {RawParams} from "../params/interface"; import {TreeChanges} from "../transition/interface"; import {State, TargetState} from "../state/module"; import {Node} from "../path/node"; import {ResolveContext, Resolvable, ResolveInjector} from "../resolve/module"; import {Transition} from "../transition/module"; import {ViewService} from "../view/view"; /** * This class contains functions which convert TargetStates, Nodes and paths from one type to another. */ export class PathFactory { constructor() { } /** Given a Node[], create an TargetState */ static makeTargetState(path: Node[]): TargetState { let state = tail(path).state; return new TargetState(state, state, path.map(prop("paramValues")).reduce(mergeR, {})); } static buildPath(targetState: TargetState) { let toParams = targetState.params(); return targetState.$state().path.map(state => new Node(state).applyRawParams(toParams)); } /** Given a fromPath: Node[] and a TargetState, builds a toPath: Node[] */ static buildToPath(fromPath: Node[], targetState: TargetState): Node[] { let toPath: Node[] = PathFactory.buildPath(targetState); if (targetState.options().inherit) { return PathFactory.inheritParams(fromPath, toPath, Object.keys(targetState.params())); } return toPath; } static applyViewConfigs($view: ViewService, path: Node[]) { return path.map(node => extend(node, { views: values(node.state.views || {}).map(view => $view.createViewConfig(node, view))}) ); } /** * Given a fromPath and a toPath, returns a new to path which inherits parameters from the fromPath * * For a parameter in a node to be inherited from the from path: * - The toPath's node must have a matching node in the fromPath (by state). * - The parameter name must not be found in the toKeys parameter array. * * Note: the keys provided in toKeys are intended to be those param keys explicitly specified by some * caller, for instance, $state.transitionTo(..., toParams). If a key was found in toParams, * it is not inherited from the fromPath. */ static inheritParams(fromPath: Node[], toPath: Node[], toKeys: string[] = []): Node[] { function nodeParamVals(path: Node[], state: State): RawParams { let node: Node = find(path, propEq('state', state)); return extend({}, node && node.paramValues); } /** * Given an Node "toNode", return a new Node with param values inherited from the * matching node in fromPath. Only inherit keys that aren't found in "toKeys" from the node in "fromPath"" */ let makeInheritedParamsNode = curry(function(_fromPath: Node[], _toKeys: string[], toNode: Node): Node { // All param values for the node (may include default key/vals, when key was not found in toParams) let toParamVals = extend({}, toNode && toNode.paramValues); // limited to only those keys found in toParams let incomingParamVals = pick(toParamVals, _toKeys); toParamVals = omit(toParamVals, _toKeys); let fromParamVals = nodeParamVals(_fromPath, toNode.state) || {}; // extend toParamVals with any fromParamVals, then override any of those those with incomingParamVals let ownParamVals: RawParams = extend(toParamVals, fromParamVals, incomingParamVals); return new Node(toNode.state).applyRawParams(ownParamVals); }); // The param keys specified by the incoming toParams return <Node[]> toPath.map(makeInheritedParamsNode(fromPath, toKeys)); } /** * Given a path, upgrades the path to a Node[]. Each node is assigned a ResolveContext * and ParamValues object which is bound to the whole path, but closes over the subpath from root to the node. * The views are also added to the node. */ static bindTransNodesToPath(resolvePath: Node[]): Node[] { let resolveContext = new ResolveContext(resolvePath); // let paramValues = new ParamValues(resolvePath); // Attach bound resolveContext and paramValues to each node // Attach views to each node resolvePath.forEach((node: Node) => { node.resolveContext = resolveContext.isolateRootTo(node.state); node.resolveInjector = new ResolveInjector(node.resolveContext, node.state); node.resolves['$stateParams'] = new Resolvable("$stateParams", () => node.paramValues, node.paramValues); }); return resolvePath; } /** * Computes the tree changes (entering, exiting) between a fromPath and toPath. */ static treeChanges(fromPath: Node[], toPath: Node[], reloadState: State): TreeChanges { let keep = 0, max = Math.min(fromPath.length, toPath.length); const staticParams = (state) => state.parameters({ inherit: false }).filter(not(prop('dynamic'))).map(prop('id')); const nodesMatch = (node1: Node, node2: Node) => node1.equals(node2, staticParams(node1.state)); while (keep < max && fromPath[keep].state !== reloadState && nodesMatch(fromPath[keep], toPath[keep])) { keep++; } /** Given a retained node, return a new node which uses the to node's param values */ function applyToParams(retainedNode: Node, idx: number): Node { let cloned = Node.clone(retainedNode); cloned.paramValues = toPath[idx].paramValues; return cloned; } let from: Node[], retained: Node[], exiting: Node[], entering: Node[], to: Node[]; // intermediate vars let retainedWithToParams: Node[], enteringResolvePath: Node[], toResolvePath: Node[]; from = fromPath; retained = from.slice(0, keep); exiting = from.slice(keep); // Create a new retained path (with shallow copies of nodes) which have the params of the toPath mapped retainedWithToParams = retained.map(applyToParams); enteringResolvePath = toPath.slice(keep); // "toResolvePath" is "retainedWithToParams" concat "enteringResolvePath". toResolvePath = (retainedWithToParams).concat(enteringResolvePath); // "to: is "toResolvePath" with ParamValues/ResolveContext added to each node and bound to the path context to = PathFactory.bindTransNodesToPath(toResolvePath); // "entering" is the tail of "to" entering = to.slice(keep); return { from, to, retained, exiting, entering }; } static bindTransitionResolve(treeChanges: TreeChanges, transition: Transition) { let rootNode = treeChanges.to[0]; rootNode.resolves['$transition$'] = new Resolvable('$transition$', () => transition, transition); } /** * Find a subpath of a path that stops at the node for a given state * * Given an array of nodes, returns a subset of the array starting from the first node, up to the * node whose state matches `stateName` * * @param path a path of [[Node]]s * @param state the [[State]] to stop at */ static subPath(path: Node[], state): Node[] { let node = find(path, _node => _node.state === state); let elementIdx = path.indexOf(node); if (elementIdx === -1) throw new Error("The path does not contain the state: " + state); return path.slice(0, elementIdx + 1); } /** Gets the raw parameter values from a path */ static paramValues = (path: Node[]) => path.reduce((acc, node) => extend(acc, node.paramValues), {}); }