diff --git a/karma.conf.js b/karma.conf.js index e697edbe..326dec4b 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -43,7 +43,7 @@ module.exports = function (karma) { module: { loaders: [ - { test: /\.ts$/, loader: "awesome-typescript-loader?declaration=false&tsconfig=test/tsconfig.json" } + { test: /\.ts$/, loader: "awesome-typescript-loader?declaration=false&configFileName=test/tsconfig.json" } ] }, diff --git a/package.json b/package.json index 4b76c6b8..eed06095 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "devDependencies": { "@types/jasmine": "^2.2.34", "@types/jquery": "^1.10.31", - "awesome-typescript-loader": "^2.2.4", + "awesome-typescript-loader": "3.0.0-beta.10", "conventional-changelog": "^1.1.0", "conventional-changelog-cli": "^1.1.1", "conventional-changelog-ui-router-core": "^1.3.0", @@ -73,7 +73,7 @@ "shelljs": "^0.7.0", "shx": "^0.1.4", "tslint": "=2.5.0", - "typescript": "rc", + "typescript": "^2.1.4", "webpack": "^1.13.3" } } diff --git a/src/router.ts b/src/router.ts index ff72eb0a..aab13918 100644 --- a/src/router.ts +++ b/src/router.ts @@ -77,7 +77,7 @@ export class UIRouter { } constructor() { - this.viewService.rootContext(this.stateRegistry.root()); + this.viewService._pluginapi._rootViewContext(this.stateRegistry.root()); this.globals.$current = this.stateRegistry.root(); this.globals.current = this.globals.$current.self; diff --git a/src/view/view.ts b/src/view/view.ts index dcf0b22b..a58f6ee6 100644 --- a/src/view/view.ts +++ b/src/view/view.ts @@ -10,22 +10,49 @@ import {_ViewDeclaration} from "../state/interface"; export type ViewConfigFactory = (path: PathNode[], decl: _ViewDeclaration) => ViewConfig|ViewConfig[]; +export interface ViewServicePluginAPI { + _rootViewContext(context?: ViewContext): ViewContext; + _viewConfigFactory(viewType: string, factory: ViewConfigFactory); + _registeredUIViews(): ActiveUIView[]; + _activeViewConfigs(): ViewConfig[]; +} + /** * The View service + * + * This service pairs existing `ui-view` components (which live in the DOM) + * with view configs (from the state declaration objects: [[StateDeclaration.views]]). + * + * - After a successful Transition, the views from the newly entered states are activated via [[activateViewConfig]]. + * The views from exited states are deactivated via [[deactivateViewConfig]]. + * (See: the [[registerActivateViews]] Transition Hook) + * + * - As `ui-view` components pop in and out of existence, they register themselves using [[registerUIView]]. + * + * - When the [[sync]] function is called, the registered `ui-view`(s) ([[UIViewConfig]] + * are configured with the matching `ViewConfig`(s) ([[ActiveUIView]]). + * */ export class ViewService { - private uiViews: ActiveUIView[] = []; - private viewConfigs: ViewConfig[] = []; + private _uiViews: ActiveUIView[] = []; + private _viewConfigs: ViewConfig[] = []; private _rootContext: ViewContext; private _viewConfigFactories: { [key: string]: ViewConfigFactory } = {}; constructor() { } - rootContext(context?: ViewContext): ViewContext { + public _pluginapi: ViewServicePluginAPI = { + _rootViewContext: this._rootViewContext.bind(this), + _viewConfigFactory: this._viewConfigFactory.bind(this), + _registeredUIViews: () => this._uiViews, + _activeViewConfigs: () => this._viewConfigs, + }; + + private _rootViewContext(context?: ViewContext): ViewContext { return this._rootContext = context || this._rootContext; }; - viewConfigFactory(viewType: string, factory: ViewConfigFactory) { + private _viewConfigFactory(viewType: string, factory: ViewConfigFactory) { this._viewConfigFactories[viewType] = factory; } @@ -37,23 +64,26 @@ export class ViewService { } /** - * De-registers a ViewConfig. + * Deactivates a ViewConfig. + * + * This function deactivates a `ViewConfig`. + * After calling [[sync]], it will un-pair from any `ui-view` with which it is currently paired. * * @param viewConfig The ViewConfig view to deregister. */ deactivateViewConfig(viewConfig: ViewConfig) { trace.traceViewServiceEvent("<- Removing", viewConfig); - removeFrom(this.viewConfigs, viewConfig); - }; + removeFrom(this._viewConfigs, viewConfig); + } activateViewConfig(viewConfig: ViewConfig) { trace.traceViewServiceEvent("-> Registering", viewConfig); - this.viewConfigs.push(viewConfig); - }; + this._viewConfigs.push(viewConfig); + } - sync = () => { + sync() { let uiViewsByFqn: TypedMap = - this.uiViews.map(uiv => [uiv.fqn, uiv]).reduce(applyPairs, {}); + this._uiViews.map(uiv => [uiv.fqn, uiv]).reduce(applyPairs, {}); /** * Given a ui-view and a ViewConfig, determines if they "match". @@ -148,7 +178,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(matches(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 @@ -161,25 +191,31 @@ export class ViewService { const configureUIView = ([uiView, viewConfig]) => { // If a parent ui-view is reconfigured, it could destroy child ui-views. // Before configuring a child ui-view, make sure it's still in the active uiViews array. - if (this.uiViews.indexOf(uiView) !== -1) + if (this._uiViews.indexOf(uiView) !== -1) uiView.configUpdated(viewConfig); }; - this.uiViews.sort(depthCompare(uiViewDepth, 1)).map(matchingConfigPair).forEach(configureUIView); + this._uiViews.sort(depthCompare(uiViewDepth, 1)).map(matchingConfigPair).forEach(configureUIView); }; /** - * Allows a `ui-view` element to register its canonical name with a callback that allows it to - * be updated with a template, controller, and local variables. + * Registers a `ui-view` component + * + * When a `ui-view` component is created, it uses this method to register itself. + * After registration the [[sync]] method is used to ensure all `ui-view` are configured with the proper [[ViewConfig]]. + * + * Note: the `ui-view` component uses the `ViewConfig` to determine what view should be loaded inside the `ui-view`, + * and what the view's state context is. + * + * Note: There is no corresponding `deregisterUIView`. + * A `ui-view` should hang on to the return value of `registerUIView` and invoke it to deregister itself. * - * @param {String} name The fully-qualified name of the `ui-view` object being registered. - * @param {Function} configUpdatedCallback A callback that receives updates to the content & configuration - * of the view. - * @return {Function} Returns a de-registration function used when the view is destroyed. + * @param uiView The metadata for a UIView + * @return a de-registration function used when the view is destroyed. */ registerUIView(uiView: ActiveUIView) { trace.traceViewServiceUIViewEvent("-> Registering", uiView); - let uiViews = this.uiViews; + let uiViews = this._uiViews; const fqnMatches = uiv => uiv.fqn === uiView.fqn; if (uiViews.filter(fqnMatches).length) trace.traceViewServiceUIViewEvent("!!!! duplicate uiView named:", uiView); @@ -204,7 +240,7 @@ export class ViewService { * @return {Array} Returns an array of fully-qualified view names. */ available() { - return this.uiViews.map(prop("fqn")); + return this._uiViews.map(prop("fqn")); } /** @@ -213,7 +249,7 @@ export class ViewService { * @return {Array} Returns an array of fully-qualified view names. */ active() { - return this.uiViews.filter(prop("$config")).map(prop("name")); + return this._uiViews.filter(prop("$config")).map(prop("name")); } /**