Skip to content

Commit

Permalink
docs(View): document IInjectable and ViewService.matches
Browse files Browse the repository at this point in the history
  • Loading branch information
christopherthielen committed Jan 10, 2017
1 parent 32640b2 commit 92b34a1
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 77 deletions.
27 changes: 27 additions & 0 deletions src/common/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,33 @@ export const noop = () => <any> undefined;
export type Mapper<X, T> = (x: X, key?: (string|number)) => T;
export interface TypedMap<T> { [key: string]: T; }
export type Predicate<X> = (x?: X) => boolean;
/**
* An ng1-style injectable
*
* This could be a (non-minified) function such as:
* ```js
* function injectableFunction(SomeDependency) {
*
* }
* ```
*
* or an explicitly annotated function (minify safe)
* ```js
* injectableFunction.$inject = [ 'SomeDependency' ];
* function injectableFunction(SomeDependency) {
*
* }
* ```
*
* or an array style annotated function (minify safe)
* ```js
* ['SomeDependency', function injectableFunction(SomeDependency) {
*
* }];
* ```
*
* @publicapi
*/
export type IInjectable = (Function|any[]);

export interface Obj extends Object {
Expand Down
159 changes: 82 additions & 77 deletions src/view/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,86 +84,88 @@ export class ViewService {
this._viewConfigs.push(viewConfig);
}

sync() {
let uiViewsByFqn: TypedMap<ActiveUIView> =
this._uiViews.map(uiv => [uiv.fqn, uiv]).reduce(applyPairs, <any> {});
/**
* Given a ui-view and a ViewConfig, determines if they "match".
*
* A ui-view has a fully qualified name (fqn) and a context object. The fqn is built from its overall location in
* the DOM, describing its nesting relationship to any parent ui-view tags it is nested inside of.
*
* A ViewConfig has a target ui-view name and a context anchor. The ui-view name can be a simple name, or
* can be a segmented ui-view path, describing a portion of a ui-view fqn.
*
* In order for a ui-view to match ViewConfig, ui-view's $type must match the ViewConfig's $type
*
* If the ViewConfig's target ui-view name is a simple name (no dots), then a ui-view matches if:
* - the ui-view's name matches the ViewConfig's target name
* - the ui-view's context matches the ViewConfig's anchor
*
* If the ViewConfig's target ui-view name is a segmented name (with dots), then a ui-view matches if:
* - There exists a parent ui-view where:
* - the parent ui-view's name matches the first segment (index 0) of the ViewConfig's target name
* - the parent ui-view's context matches the ViewConfig's anchor
* - And the remaining segments (index 1..n) of the ViewConfig's target name match the tail of the ui-view's fqn
*
* Example:
*
* DOM:
* <ui-view> <!-- created in the root context (name: "") -->
* <ui-view name="foo"> <!-- created in the context named: "A" -->
* <ui-view> <!-- created in the context named: "A.B" -->
* <ui-view name="bar"> <!-- created in the context named: "A.B.C" -->
* </ui-view>
* </ui-view>
* </ui-view>
* </ui-view>
*
* uiViews: [
* { fqn: "$default", creationContext: { name: "" } },
* { fqn: "$default.foo", creationContext: { name: "A" } },
* { fqn: "$default.foo.$default", creationContext: { name: "A.B" } }
* { fqn: "$default.foo.$default.bar", creationContext: { name: "A.B.C" } }
* ]
*
* These four view configs all match the ui-view with the fqn: "$default.foo.$default.bar":
*
* - ViewConfig1: { uiViewName: "bar", uiViewContextAnchor: "A.B.C" }
* - ViewConfig2: { uiViewName: "$default.bar", uiViewContextAnchor: "A.B" }
* - ViewConfig3: { uiViewName: "foo.$default.bar", uiViewContextAnchor: "A" }
* - ViewConfig4: { uiViewName: "$default.foo.$default.bar", uiViewContextAnchor: "" }
*
* Using ViewConfig3 as an example, it matches the ui-view with fqn "$default.foo.$default.bar" because:
* - The ViewConfig's segmented target name is: [ "foo", "$default", "bar" ]
* - There exists a parent ui-view (which has fqn: "$default.foo") where:
* - the parent ui-view's name "foo" matches the first segment "foo" of the ViewConfig's target name
* - the parent ui-view's context "A" matches the ViewConfig's anchor context "A"
* - And the remaining segments [ "$default", "bar" ].join("."_ of the ViewConfig's target name match
* the tail of the ui-view's fqn "default.bar"
*
* @internalapi
*/
static matches = (uiViewsByFqn: TypedMap<ActiveUIView>, uiView: ActiveUIView) => (viewConfig: ViewConfig) => {
// Don't supply an ng1 ui-view with an ng2 ViewConfig, etc
if (uiView.$type !== viewConfig.viewDecl.$type) return false;

/**
* Given a ui-view and a ViewConfig, determines if they "match".
*
* A ui-view has a fully qualified name (fqn) and a context object. The fqn is built from its overall location in
* the DOM, describing its nesting relationship to any parent ui-view tags it is nested inside of.
*
* A ViewConfig has a target ui-view name and a context anchor. The ui-view name can be a simple name, or
* can be a segmented ui-view path, describing a portion of a ui-view fqn.
*
* In order for a ui-view to match ViewConfig, ui-view's $type must match the ViewConfig's $type
*
* If the ViewConfig's target ui-view name is a simple name (no dots), then a ui-view matches if:
* - the ui-view's name matches the ViewConfig's target name
* - the ui-view's context matches the ViewConfig's anchor
*
* If the ViewConfig's target ui-view name is a segmented name (with dots), then a ui-view matches if:
* - There exists a parent ui-view where:
* - the parent ui-view's name matches the first segment (index 0) of the ViewConfig's target name
* - the parent ui-view's context matches the ViewConfig's anchor
* - And the remaining segments (index 1..n) of the ViewConfig's target name match the tail of the ui-view's fqn
*
* Example:
*
* DOM:
* <div ui-view> <!-- created in the root context (name: "") -->
* <div ui-view="foo"> <!-- created in the context named: "A" -->
* <div ui-view> <!-- created in the context named: "A.B" -->
* <div ui-view="bar"> <!-- created in the context named: "A.B.C" -->
* </div>
* </div>
* </div>
* </div>
*
* uiViews: [
* { fqn: "$default", creationContext: { name: "" } },
* { fqn: "$default.foo", creationContext: { name: "A" } },
* { fqn: "$default.foo.$default", creationContext: { name: "A.B" } }
* { fqn: "$default.foo.$default.bar", creationContext: { name: "A.B.C" } }
* ]
*
* These four view configs all match the ui-view with the fqn: "$default.foo.$default.bar":
*
* - ViewConfig1: { uiViewName: "bar", uiViewContextAnchor: "A.B.C" }
* - ViewConfig2: { uiViewName: "$default.bar", uiViewContextAnchor: "A.B" }
* - ViewConfig3: { uiViewName: "foo.$default.bar", uiViewContextAnchor: "A" }
* - ViewConfig4: { uiViewName: "$default.foo.$default.bar", uiViewContextAnchor: "" }
*
* Using ViewConfig3 as an example, it matches the ui-view with fqn "$default.foo.$default.bar" because:
* - The ViewConfig's segmented target name is: [ "foo", "$default", "bar" ]
* - There exists a parent ui-view (which has fqn: "$default.foo") where:
* - the parent ui-view's name "foo" matches the first segment "foo" of the ViewConfig's target name
* - the parent ui-view's context "A" matches the ViewConfig's anchor context "A"
* - And the remaining segments [ "$default", "bar" ].join("."_ of the ViewConfig's target name match
* the tail of the ui-view's fqn "default.bar"
*/
const matches = (uiView: ActiveUIView) => (viewConfig: ViewConfig) => {
// Don't supply an ng1 ui-view with an ng2 ViewConfig, etc
if (uiView.$type !== viewConfig.viewDecl.$type) return false;
// Split names apart from both viewConfig and uiView into segments
let vc = viewConfig.viewDecl;
let vcSegments = vc.$uiViewName.split(".");
let uivSegments = uiView.fqn.split(".");

// Split names apart from both viewConfig and uiView into segments
let vc = viewConfig.viewDecl;
let vcSegments = vc.$uiViewName.split(".");
let uivSegments = uiView.fqn.split(".");
// Check if the tails of the segment arrays match. ex, these arrays' tails match:
// vc: ["foo", "bar"], uiv fqn: ["$default", "foo", "bar"]
if (!equals(vcSegments, uivSegments.slice(0 - vcSegments.length)))
return false;

// Check if the tails of the segment arrays match. ex, these arrays' tails match:
// vc: ["foo", "bar"], uiv fqn: ["$default", "foo", "bar"]
if (!equals(vcSegments, uivSegments.slice(0 - vcSegments.length)))
return false;
// Now check if the fqn ending at the first segment of the viewConfig matches the context:
// ["$default", "foo"].join(".") == "$default.foo", does the ui-view $default.foo context match?
let negOffset = (1 - vcSegments.length) || undefined;
let fqnToFirstSegment = uivSegments.slice(0, negOffset).join(".");
let uiViewContext = uiViewsByFqn[fqnToFirstSegment].creationContext;
return vc.$uiViewContextAnchor === (uiViewContext && uiViewContext.name);
};

// Now check if the fqn ending at the first segment of the viewConfig matches the context:
// ["$default", "foo"].join(".") == "$default.foo", does the ui-view $default.foo context match?
let negOffset = (1 - vcSegments.length) || undefined;
let fqnToFirstSegment = uivSegments.slice(0, negOffset).join(".");
let uiViewContext = uiViewsByFqn[fqnToFirstSegment].creationContext;
return vc.$uiViewContextAnchor === (uiViewContext && uiViewContext.name);
};
sync() {
let uiViewsByFqn: TypedMap<ActiveUIView> =
this._uiViews.map(uiv => [uiv.fqn, uiv]).reduce(applyPairs, <any> {});

// Return the number of dots in the fully qualified name
function uiViewDepth(uiView: ActiveUIView) {
Expand All @@ -181,7 +183,7 @@ export class ViewService {
const depthCompare = curry((depthFn, posNeg, left, right) => posNeg * (depthFn(left) - depthFn(right)));

const matchingConfigPair = (uiView: ActiveUIView) => {
let matchingConfigs = this._viewConfigs.filter(matches(uiView));
let matchingConfigs = this._viewConfigs.filter(ViewService.matches(uiViewsByFqn, uiView));
if (matchingConfigs.length > 1) {
// This is OK. Child states can target a ui-view that the parent state also targets (the child wins)
// Sort by depth and return the match from the deepest child
Expand Down Expand Up @@ -258,6 +260,9 @@ export class ViewService {
/**
* Normalizes a view's name from a state.views configuration block.
*
* This should be used by a framework implementation to calculate the values for
* [[_ViewDeclaration.$uiViewName]] and [[_ViewDeclaration.$uiViewContextAnchor]].
*
* @param context the context object (state declaration) that the view belongs to
* @param rawViewName the name of the view, as declared in the [[StateDeclaration.views]]
*
Expand Down

0 comments on commit 92b34a1

Please sign in to comment.