diff --git a/src/interface.ts b/src/interface.ts index 30052663..09886482 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -11,6 +11,7 @@ // Need to import or export at least one concrete something import {noop} from "./common/common"; +import {UIRouter} from "./router"; /** * An interface for getting values from dependency injection. @@ -81,3 +82,7 @@ export interface UIInjector { getNative(token: any): any; getNative(token: any): T; } + +export abstract class UIRouterPlugin { + abstract name(): string; +} \ No newline at end of file diff --git a/src/router.ts b/src/router.ts index 452a6cb4..e4876f3a 100644 --- a/src/router.ts +++ b/src/router.ts @@ -7,6 +7,7 @@ import {ViewService} from "./view/view"; import {StateRegistry} from "./state/stateRegistry"; import {StateService} from "./state/stateService"; import {UIRouterGlobals, Globals} from "./globals"; +import {UIRouterPlugin} from "./interface"; /** * The master class used to instantiate an instance of UI-Router. @@ -41,5 +42,65 @@ export class UIRouter { this.globals.$current = this.stateRegistry.root(); this.globals.current = this.globals.$current.self; } -} + private _plugins: { [key: string]: UIRouterPlugin } = {}; + + /** + * Adds a plugin to UI-Router + * + * This method adds a UI-Router Plugin. + * A plugin can enhance or change UI-Router behavior using any public API. + * + * #### Example: + * ```js + * import { MyCoolPlugin } from "ui-router-cool-plugin"; + * + * var plugin = router.addPlugin(MyCoolPlugin); + * ``` + * + * ### Plugin authoring + * + * A plugin is simply a class (or constructor function) which accepts a [[UIRouter]] instance and (optionally) an options object. + * + * The plugin can implement its functionality using any of the public APIs of [[UIRouter]]. + * For example, it may configure router options or add a Transition Hook. + * + * The plugin can then be published as a separate module. + * + * #### Example: + * ```js + * export class MyAuthPlugin { + * constructor(router: UIRouter, options: any) { + * let $transitions = router.transitionService; + * let $state = router.stateService; + * + * let authCriteria = { + * to: (state) => state.data && state.data.requiresAuth + * }; + * + * function authHook(transition: Transition) { + * let authService = transition.injector().get('AuthService'); + * if (!authService.isAuthenticated()) { + * return $state.target('login'); + * } + * } + * + * $transitions.onStart(authCriteria, authHook); + * } + * } + * ``` + * + * @param PluginClass a UI-Router Plugin class (or constructor function). + * @param options options to pass to the plugin + * @returns {T} + */ + addPlugin(PluginClass: { new(router: UIRouter, options?: any): T }, options: any = {}): T { + let pluginInstance = new PluginClass(this, options); + var pluginName = pluginInstance.name(); + return this._plugins[pluginName] = pluginInstance; + } + + getPlugin(pluginName: string): UIRouterPlugin { + return this._plugins[pluginName]; + } +} diff --git a/test/pluginSpec.ts b/test/pluginSpec.ts new file mode 100644 index 00000000..1d6ec9ee --- /dev/null +++ b/test/pluginSpec.ts @@ -0,0 +1,63 @@ +import { UIRouter, TransitionService, StateService } from "../src/index"; +import "../src/justjs"; +import { StateRegistry } from "../src/state/stateRegistry"; +import { UrlRouter } from "../src/url/urlRouter"; +import {UIRouterPlugin} from "../src/interface"; + +describe('plugin api', function () { + let router: UIRouter; + let $registry: StateRegistry; + let $transitions: TransitionService; + let $state: StateService; + let $urlRouter: UrlRouter; + + beforeEach(() => { + router = new UIRouter(); + $registry = router.stateRegistry; + $state = router.stateService; + $transitions = router.transitionService; + $urlRouter = router.urlRouter; + router.stateRegistry.stateQueue.autoFlush($state); + }); + + class FancyPlugin extends UIRouterPlugin { + constructor(public router: UIRouter) { + super(); + + } + name() { return "fancyplugin" } + } + + describe('initialization', () => { + it('should return an instance of the plugin', () => { + let plugin = router.addPlugin(FancyPlugin); + expect(plugin instanceof FancyPlugin).toBeTruthy(); + }); + + it('should pass the router instance to the plugin constructor', () => { + let pluginRouterInstance = undefined; + function Plugin(router) { + pluginRouterInstance = router; + this.name = () => "plugin"; + } + + router.addPlugin( Plugin); + expect(pluginRouterInstance).toBe(router); + }); + + it('should throw if the plugin constructor returns an object without name() getter', () => { + function Plugin(router) { + } + + expect(() => router.addPlugin( Plugin)).toThrow() + }); + }); + + describe('getPlugin', () => { + it('should return the plugin instance', () => { + router.addPlugin(FancyPlugin); + let plugin = router.getPlugin('fancyplugin'); + expect(plugin instanceof FancyPlugin).toBeTruthy(); + }); + }) +});