diff --git a/CHANGELOG.md b/CHANGELOG.md index 913d344..21f76fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +1.0.0-rc.22 / 2015-07-17 +================== + + * Added TypeScript Pull-Request + + 1.0.0-rc.21 / 2015-07-02 ================== diff --git a/app/index.js b/app/index.js index bb64625..f4a44b8 100644 --- a/app/index.js +++ b/app/index.js @@ -183,11 +183,15 @@ var HirschGenerator = yeoman.generators.Base.extend({ scaffoldFolders: function() { this.mkdir('src'); this.mkdir('src/app'); - this.mkdir('src/app/common/services'); - this.mkdir('src/app/common/directives'); - this.mkdir('src/app/common/templates'); this.mkdir('src/app/common/decorators'); + this.mkdir('src/app/common/directives'); this.mkdir('src/app/common/filters'); + this.mkdir('src/app/common/models'); + this.mkdir('src/app/common/services'); + this.mkdir('src/app/common/services/converters'); + this.mkdir('src/app/common/services/rest'); + this.mkdir('src/app/common/templates'); + this.mkdir('src/app/common/util'); this.mkdir('src/app/common/views'); this.mkdir('src/app/core'); this.mkdir('src/assets'); diff --git a/app/templates/_tsd.json b/app/templates/_tsd.json index ff041fb..8160dcd 100644 --- a/app/templates/_tsd.json +++ b/app/templates/_tsd.json @@ -23,9 +23,6 @@ "angularjs/angular-cookies.d.ts": { "commit": "c50b111ded0a3a18b87b5abffea3150d6aca94da" }, - "restangular/restangular.d.ts": { - "commit": "c50b111ded0a3a18b87b5abffea3150d6aca94da" - }, "jquery/jquery.d.ts": { "commit": "c50b111ded0a3a18b87b5abffea3150d6aca94da" }, diff --git a/app/templates/_tslint.json b/app/templates/_tslint.json index 630484d..008fbaa 100644 --- a/app/templates/_tslint.json +++ b/app/templates/_tslint.json @@ -7,7 +7,7 @@ "indent": [true, 2], "label-position": true, "label-undefined": true, - "max-line-length": [true, 140], + "max-line-length": [true, 200], "no-arg": true, "no-bitwise": true, "no-console": [true, diff --git a/app/templates/src/app-ts/common/util/lazy.ts b/app/templates/src/app-ts/common/util/lazy.ts new file mode 100644 index 0000000..d0c72f1 --- /dev/null +++ b/app/templates/src/app-ts/common/util/lazy.ts @@ -0,0 +1,24 @@ +/// + +module <%= prompts.prefix %>.common.util { + 'use strict'; + + export class Lazy { + private instance: T; + + constructor(private factory: () => T) { } + + get isCreated() { + return !!this.instance; + } + + get value() { + return this.instance || (this.instance = this.factory()); + } + + reset = () => { + this.instance = undefined; + return this; + } + } +} diff --git a/app/templates/src/app-ts/common/views/abstractController.ts b/app/templates/src/app-ts/common/views/abstractController.ts deleted file mode 100644 index 4867c36..0000000 --- a/app/templates/src/app-ts/common/views/abstractController.ts +++ /dev/null @@ -1,13 +0,0 @@ -/// - -module <%= prompts.prefix %>.common.views { - export class AbstractController { - constructor($state: ng.ui.IStateService) { - $state.current.onExit = this.dispose.bind(this); - } - - protected dispose() { - // nothing to do here - } - } -} diff --git a/app/templates/src/app-ts/core/router/router.config.ts b/app/templates/src/app-ts/core/router/router.config.ts index 1660c48..7fccb85 100644 --- a/app/templates/src/app-ts/core/router/router.config.ts +++ b/app/templates/src/app-ts/core/router/router.config.ts @@ -1,14 +1,61 @@ /// +/** + * Extension for 3rd party type definition file. + */ +declare module angular.ui { + interface IState { + includes?(name: string): void; + } +} + module <%= prompts.prefix %>.core.router { - var routerConfig = ($urlRouterProvider: ng.ui.IUrlRouterProvider) => { - $urlRouterProvider.otherwise('/home'); + // this variable is to prevent an issue with the default routes; + // if the initial request to the application is for an invalid route + // the default route rule triggers multiple times (once during a state + // transition). This causes the actual state transition to be superseded. + // By capturing a variable inside the module scope, we can make the + // default route handler ignore invalid route requests during routing + var stateChangeCountingSemaphore = 0; + var checkSemaphore = (fn: () => string) => stateChangeCountingSemaphore > 0 ? undefined : fn(); + + var routerConfig = ($urlRouterProvider: ng.ui.IUrlRouterProvider, $stateProvider: ng.ui.IStateProvider) => { + $urlRouterProvider.otherwise(() => checkSemaphore(() => `/home`)); + + // We decorate the 'includes' to extract all states that would match + // a $state.includes() test for each state, and extend the state + // object with a function that can be used to check whether a given + // state is included in the list. This is then used inside the router + // pipeline to check whether a given middleware is applicable for a + // routing request + $stateProvider.decorator('includes', (state, parent): any => { + var result = parent(state); + var includeNames = Object.keys(result); + + // state is actually a wrapper object, so we unwrap it + state = state['self']; + + state.includes = name => includeNames.some(s => s === name); + + return result; + }); + }; + + routerConfig.$inject = ['$urlRouterProvider', '$stateProvider']; + + var run = ($rootScope: ng.IRootScopeService, routerService: IRouterService) => { + $rootScope.$on('$stateChangeStart', (evt, ...rest: any[]) => { + stateChangeCountingSemaphore += 1; + rest.unshift(evt.preventDefault.bind(evt)); + routerService.startPipeline.apply(null, rest).finally(() => stateChangeCountingSemaphore -= 1); + }); }; - routerConfig.$inject = ['$urlRouterProvider']; + run.$inject = ['$rootScope', ID.RouterService]; angular .module(`${Namespace}.RouterConfig`, []) - .config(routerConfig); + .config(routerConfig) + .run(run); } diff --git a/app/templates/src/app-ts/core/router/router.constants.ts b/app/templates/src/app-ts/core/router/router.constants.ts deleted file mode 100644 index ab4a98d..0000000 --- a/app/templates/src/app-ts/core/router/router.constants.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// - -module <%= prompts.prefix %>.core.router { - - var getSecuredRoutes = () => [ - '/private/*' - ]; - - angular - .module(`${Namespace}.RouterConstants`, []) - .constant(ID.APP_ROUTER_PRIVATE_ROUTES, getSecuredRoutes()); -} diff --git a/app/templates/src/app-ts/core/router/router.middleware.auth.ts b/app/templates/src/app-ts/core/router/router.middleware.auth.ts new file mode 100644 index 0000000..5d744da --- /dev/null +++ b/app/templates/src/app-ts/core/router/router.middleware.auth.ts @@ -0,0 +1,31 @@ +/// + +module <%= prompts.prefix %>.core.router { + 'use strict'; + + var run = (logger: util.ILoggerFactory, $timeout: ng.ITimeoutService, routerService: IRouterService) => { + var log = logger('Auth middleware'); + + var authMiddleware: IRouterMiddleware = (actions, context) => { + if (context.toState.data && context.toState.data.requiresSession) { + // TODO: check if user has session + return $timeout(() => { ; }, 1000).then(() => { + log.debug('Redirecting to login page...'); + return actions.redirect('public.login'); + }); + } else { + return actions.next(); + } + }; + + routerService.addMiddleware(authMiddleware, 'auth'); + }; + + run.$inject = [util.ID.LoggerFactory, '$timeout', ID.RouterService]; + + angular + .module(`${Namespace}.RouterMiddlewareAuth`, [ + util.ID.LoggerFactory + ]) + .run(run); +} diff --git a/app/templates/src/app-ts/core/router/router.middleware.errorHandling.ts b/app/templates/src/app-ts/core/router/router.middleware.errorHandling.ts new file mode 100644 index 0000000..46ab483 --- /dev/null +++ b/app/templates/src/app-ts/core/router/router.middleware.errorHandling.ts @@ -0,0 +1,24 @@ +/// + +module <%= prompts.prefix %>.core.router { + 'use strict'; + + var run = ($q: ng.IQService, routerService: IRouterService, logger: util.ILoggerFactory) => { + var log = logger('ErrorHandling middleware'); + + var errorHandlingMiddleware: IRouterMiddleware = a => a.next().catch(err => { + log.error(err); + return $q.reject(err); + }); + + routerService.addMiddleware(errorHandlingMiddleware, 'errorHandling'); + }; + + run.$inject = ['$q', ID.RouterService, util.ID.LoggerFactory]; + + angular + .module(`${Namespace}.RouterMiddlewareErrorHandling`, [ + util.ID.LoggerFactory + ]) + .run(run); +} diff --git a/app/templates/src/app-ts/core/router/router.module.ts b/app/templates/src/app-ts/core/router/router.module.ts index 6f94876..e629682 100644 --- a/app/templates/src/app-ts/core/router/router.module.ts +++ b/app/templates/src/app-ts/core/router/router.module.ts @@ -6,8 +6,7 @@ module <%= prompts.prefix %>.core.router { export var Namespace = '<%= prompts.prefix %>.core.router'; export var ID = { - RouterService: `${Namespace}.RouterService`, - APP_ROUTER_PRIVATE_ROUTES: `${Namespace}.APP_ROUTER_PRIVATE_ROUTES` + RouterService: `${Namespace}.RouterService` }; angular @@ -15,10 +14,8 @@ module <%= prompts.prefix %>.core.router { 'ui.router', 'ui.router.router', 'ui.router.state', - - `${Namespace}.RouterConstants`, + `${Namespace}.RouterConfig`, - ID.RouterService, - `${Namespace}.Router` + ID.RouterService ]); } diff --git a/app/templates/src/app-ts/core/router/router.service.ts b/app/templates/src/app-ts/core/router/router.service.ts index 2cb410d..a2a8bf4 100644 --- a/app/templates/src/app-ts/core/router/router.service.ts +++ b/app/templates/src/app-ts/core/router/router.service.ts @@ -1,49 +1,259 @@ -/// +/// module <%= prompts.prefix %>.core.router { export interface IRouterService { - isInitialized: boolean; - initialize(): ng.IPromise; + /** + * Flag indicating whether the routing pipeline is currently active. + */ + isNavigating: boolean; + startPipeline(preventDef: Function, toState: ng.ui.IState, toParams: any, fromState: ng.ui.IState, fromParams: any): ng.IPromise; + + /** + * Add a middleware to the routing pipeline. Middlewares are executed in + * the order of registration. If the state parameter is used, the middleware + * will only be processed by the pipeline if the target state of the + * navigation is either a child state of or equal to the given state (depending + * on the given matching mode, defaults to include children). Only absolute + * state names are supported, i.e. no wildcards or other patterns are allowed. + */ + addMiddleware(middleware: IRouterMiddleware, name: string, state?: string, stateMatchingMode?: StateMatchingMode): IRouterService; } - class RouterService implements IRouterService { - private initializing = false; - private initialized = false; - private deferredInit: ng.IDeferred; - private log: util.Logger; - - static $inject = ['$q', util.ID.LoggerFactory]; - constructor($q: ng.IQService, loggerFactory: util.ILoggerFactory) { - this.log = loggerFactory('AppRouterService'); - this.deferredInit = $q.defer(); - this.deferredInit.promise.then(() => this.initialized = true); - } + /** + * A router middleware is a function that is executed as part of a pipeline. + * The middleware can perform exactly one of the following actions when executed + * (in addition to any logic the middleware itself contains): + * 1) Call the next middleware in the pipeline + * 2) Redirect to another state, thereby restarting the pipeline + * 3) Cancel the pipeline + * + * The middleware can also pass information to other middlewares that are executed + * after it by modifying the context object. Note that all objects in the context + * are mutable, but only the "toParams" object should be modified by the middleware. + */ + export interface IRouterMiddleware { + (actions: IRouterMiddlewareActions, context: IRouterMiddlewareContext): ng.IPromise; + } - get isInitialized() { - return this.initialized; - } + export interface IRouterMiddlewareActions { + /** + * Execute the next middleware in the pipeline. + */ + next(): ng.IPromise; + + /** + * Redirect to the given state, restarting the pipeline. + */ + redirect(to: string, params?: any): ng.IPromise; /** - * Here we will load some initial data for the application like the active event, but - * only at the first run. + * Cancel the pipeline. */ - initialize = (): ng.IPromise => { - if (this.initializing || this.initialized) { - return this.deferredInit.promise; + cancel(): ng.IPromise; + } + + export interface IRouterMiddlewareContext { + /** + * The target state of the routing pipeline. + */ + toState: ng.ui.IState; + + /** + * The parameters for the target state. This object can be used by middlewares to + * pass additional information to other middlewares or to the controller of the + * target state. + */ + toParams: any; + + /** + * The state that was active when the routing pipeline was started. + */ + fromState: ng.ui.IState; + fromParams: any; + } + + export enum StateMatchingMode { + Exact, + IncludeChildren + } + + interface IMiddlewareContainer { + name: string; + middleware: IRouterMiddleware; + state: string; + stateMatchingMode: StateMatchingMode; + } + + class RouterService implements IRouterService { + private log: util.ILogger; + private middlewares: IMiddlewareContainer[] = []; + private sync = false; + private current: { state: ng.ui.IState; params: any } = { state: {}, params: {} }; + private lastPipelineId = 0; + + isNavigating: boolean; + + static $inject = ['$state', '$q', util.ID.LoggerFactory]; + + constructor( + private $state: ng.ui.IStateService, + private $q: ng.IQService, + loggerFactory: util.ILoggerFactory + ) { + this.log = loggerFactory(this); + } + + startPipeline = (preventDefault: Function, toState: ng.ui.IState, toParams: any, fromState: ng.ui.IState, fromParams: any) => { + // if we are in the process of finishing pipeline processing, don't do anything + if (this.sync) { + return this.$q.reject('router is syncing'); } - this.initializing = true; + this.lastPipelineId += 1; + return this.startPipelineInternal(preventDefault, toState, toParams, fromState, fromParams, this.lastPipelineId); + } + + private startPipelineInternal = ( + preventDefault: Function, + toState: ng.ui.IState, + toParams: any, + fromState: ng.ui.IState, + fromParams: any, + pipelineId: number + ) => { + this.log.debug(`Starting routing pipeline [ID: ${pipelineId}; to: ${toState.name}; from: ${fromState.name}]...`); + this.isNavigating = true; + + preventDefault(); + + // by calling $state.go with notify set to false, we update the URL + // in the browser address bar without triggering navigation. This is + // necessary, since otherwise the preventDefault() call above would + // cause the old URL to be displayed + this.$state.go(toState.name, {}, { notify: false }); + var context = { toState, toParams, fromState, fromParams }; + + var redirect = (middlewareName: string) => (to: string, params: any = {}): ng.IPromise => { + this.log.debug(`Middleware '${middlewareName}' redirected to '${to}'!`); + + // this check is slightly different from $state.is(), since it + // takes into account all params instead of only the ones defined + // in the params definition of the state + if (to.indexOf(this.current.state.name) === 0 && angular.equals(this.current.params, params)) { + // if we got redirected to the last active state, we need to restore + // its URL + this.$state.go(to, params, { notify: false }); + return this.endPipeline(pipelineId, this.current.state, params).then(() => this.$q.reject('pipeline ended in source state')); + } else { + return this.startPipelineInternal(() => { ; }, this.$state.get(to), params, fromState, fromParams, pipelineId); + } + }; + + var cancel = (middlewareName: string) => (): ng.IPromise => { + this.log.debug(`Middleware '${middlewareName}' canceled navigation!`); + + // if navigation is canceled, we need to restore the URL of the + // original state + this.$state.go(fromState.name, fromParams, { notify: false }); + return this.endPipeline(pipelineId, fromState, fromParams).then(() => this.$q.reject('canceled')); + }; + + var next = (remaining: IMiddlewareContainer[]) => (): ng.IPromise => { + if (remaining.length === 0) { + var isInitialRequest = fromState.name === ''; + + // before we transition to the final state, we quickly return to the original + // state, but without triggering any navigation; however, on initial page load + // there is no previous state to return to, so skip the restore + var restore = () => this.$state.go(fromState.name, fromParams, { notify: false }); + var go = () => this.$state.go(context.toState.name, context.toParams); + + this.sync = true; + return (isInitialRequest ? go() : restore().then(go)) + .then(() => this.endPipeline(pipelineId, context.toState, context.toParams)) + .finally(() => this.sync = false) + .then(() => context); + } - // TODO: load initial data - this.log.info('loadInitialData'); - this.deferredInit.resolve(); + var container = remaining.splice(0, 1)[0]; + var executeMiddleware = + container.stateMatchingMode === StateMatchingMode.Exact + ? toState.name === container.state + : toState.includes(container.state); - return this.deferredInit.promise; + if (executeMiddleware) { + this.log.debug(`Executing middleware '${container.name}'...`); + + var guardActive = false; + var guard = (fn: Function) => (...rest: any[]) => { + if (guardActive) { + return this.$q.reject(new Error(`Middleware ${container.name} is trying to execute more than one action!`)); + } + + guardActive = true; + + if (pipelineId !== this.lastPipelineId) { + return this.cancelPipeline(pipelineId); + } + + return fn.apply(null, rest); + }; + + var actions = { + next: guard(next(remaining)), + cancel: guard(cancel(container.name)), + redirect: guard(redirect(container.name)) + }; + + return container.middleware(actions, context).then(() => { + if (!guardActive) { + return this.$q.reject(new Error(`Middleware ${container.name} did not execute any action!`)); + } + + return this.$q.when(); + }); + } else { + this.log.debug(`Skipping middleware '${container.name}'...`); + return next(remaining)(); + } + }; + + var clone = this.middlewares.slice(0); + return next(clone)(); + }; + + addMiddleware = ( + middleware: IRouterMiddleware, + name: string, + state: string = '', + stateMatchingMode = StateMatchingMode.IncludeChildren + ) => { + this.middlewares.push({ middleware, state, name, stateMatchingMode }); + return this; + }; + + private endPipeline = (pipelineId: number, state: ng.ui.IState, params: any) => { + this.log.debug(`Finished routing pipeline [ID: ${pipelineId}] in state '${state.name}'!`); + this.isNavigating = false; + this.current = { state, params }; + return this.$q.when(); + }; + + private cancelPipeline = (pipelineId: number) => { + this.log.debug(`Canceled routing pipeline [ID: ${pipelineId}]!`); + this.isNavigating = false; + return this.$q.when(); }; } angular - .module(ID.RouterService, []) + .module(ID.RouterService, [ + 'ui.router', + 'ui.router.router', + 'ui.router.state', + + util.ID.LoggerFactory + ]) .service(ID.RouterService, RouterService); } diff --git a/app/templates/src/app-ts/core/router/router.ts b/app/templates/src/app-ts/core/router/router.ts deleted file mode 100644 index 79f72d5..0000000 --- a/app/templates/src/app-ts/core/router/router.ts +++ /dev/null @@ -1,109 +0,0 @@ -/// - -/** - * Fix for 3rd party type definition file. - */ -declare module angular.ui { - interface IUrlRouterService { - listen(): void; - } -} - -module <%= prompts.prefix %>.core.router { - 'use strict'; - - angular - .module(`${Namespace}.Router`, []) - .run(AppRouter); - - AppRouter.$inject = [ - '$q', - '$rootScope', - '$urlRouter', - util.ID.LoggerFactory, - '$state', - ID.RouterService, - ID.APP_ROUTER_PRIVATE_ROUTES - ]; - - function AppRouter( - $q: ng.IQService, - $rootScope: ng.IRootScopeService, - $urlRouter: ng.ui.IUrlRouterService, - logger: util.ILoggerFactory, - $state: ng.ui.IStateService, - routerService: IRouterService, - privateRoutes: string[]) { - var log = logger('AppRouter'); - log.info('start'); - - $rootScope.$on('$locationChangeSuccess', onLocationChange); - - $urlRouter.listen(); - - function onLocationChange(event, toUrl, fromUrl) { - log.info('onLocationChange', { toUrl, fromUrl }); - - // Halt state change from even starting - event.preventDefault(); - - routerService.initialize() - .then(() => ensurePrivateRoute(toUrl)) - .then(() => hasValidSession()) - .catch(err => { - log.warn('catch', err); - if (err.redirct) { - log.info('redirect to login'); - $state.go('authLogin'); - } - - if (err.sync) { - log.info('Browser sync'); - $urlRouter.sync(); - } - }); - } - - function ensurePrivateRoute(toUrl: string) { - var deferred = $q.defer(); - toUrl = parseRoute(toUrl); - - var isPrivate = privateRoutes.some(r => doesUrlMatchPattern(r, toUrl)); - - if (isPrivate) { - deferred.resolve(); - } else { - deferred.reject({ - sync: true - }); - } - - return deferred.promise; - } - - function hasValidSession() { - var deferred = $q.defer(); - - // TODO: Check if the user has a valid session - if (true) { - deferred.resolve(); - } else { - deferred.reject({ - session: true, - redirct: true - }); - } - - return deferred.promise; - } - - function parseRoute(route: string) { - return route.split('#')[1] || route || ''; - } - - function doesUrlMatchPattern(pattern: string, route: string) { - var exp = new RegExp(pattern); - return exp.test(route); - } - } -} diff --git a/app/templates/src/app-ts/core/util/backend.ts b/app/templates/src/app-ts/core/util/backend.ts new file mode 100644 index 0000000..90991ea --- /dev/null +++ b/app/templates/src/app-ts/core/util/backend.ts @@ -0,0 +1,44 @@ +/// + +module <%= prompts.prefix %>.core.util { + 'use strict'; + + export interface IBackend { + url: string; + get(url: string, config?: ng.IRequestShortcutConfig): ng.IPromise; + delete(url: string, config?: ng.IRequestShortcutConfig): ng.IPromise; + post(url: string, data: any, config?: ng.IRequestShortcutConfig): ng.IPromise; + put(url: string, data: any, config?: ng.IRequestShortcutConfig): ng.IPromise; + } + + class Backend implements IBackend { + static $inject = ['$http', constants.ID.AppConfig]; + constructor(private $http: ng.IHttpService, private appConfig: constants.IAppConfig) { + } + + get = (url: string, config?: ng.IRequestShortcutConfig) => this.unwrap(this.$http.get(this.prefix(url), this.headers(config))); + delete = (url: string, config?: ng.IRequestShortcutConfig) => this.unwrap(this.$http.delete(this.prefix(url), this.headers(config))); + post = (url: string, data, config?: ng.IRequestShortcutConfig) => this.unwrap(this.$http.post(this.prefix(url), data, this.headers(config))); + put = (url: string, data, config?: ng.IRequestShortcutConfig) => this.unwrap(this.$http.put(this.prefix(url), data, this.headers(config))); + + get url() { return `${this.appConfig.BASE_URL}`; } + + private prefix = (url: string) => `${this.appConfig.BASE_URL}${url}`; + private headers = (config?: ng.IRequestShortcutConfig) => { + config = config || {}; + config.headers = config.headers || {}; + config.headers.Accept = 'application/json'; + return config; + }; + + private unwrap(p: ng.IHttpPromise) { + return p.then(resp => resp.data); + } + } + + angular + .module(ID.Backend, [ + constants.Namespace + ]) + .service(ID.Backend, Backend); +} diff --git a/app/templates/src/app-ts/core/util/logger.ts b/app/templates/src/app-ts/core/util/logger.ts index a579b9e..d6b35bf 100644 --- a/app/templates/src/app-ts/core/util/logger.ts +++ b/app/templates/src/app-ts/core/util/logger.ts @@ -1,31 +1,54 @@ /// +/** + * Extend global function interface with ES6 name property. + */ +interface Function { + name?: string; +} + module <%= prompts.prefix %>.core.util { 'use strict'; - var loggerService = ($log, _, moment, appConfig): ILoggerFactory => { - return name => new Logger($log, _, moment, appConfig, name); - }; - - loggerService.$inject = ['$log', constants.ID.lodash, constants.ID.moment, constants.ID.AppConfig]; - export interface ILoggerFactory { /** * Get the logger for the given name. */ - (name: string): Logger; + (name: string): ILogger; + + /** + * Get a logger for the given object. The name of the logger is inferred + * from the name of the constructor function if it is supported by the + * browser. + */ + (obj: Object): ILogger; + + /** + * Get a logger for the given function. The name of the logger is inferred + * from the name of the function if it is supported by the browser. + */ + (obj: Function): ILogger; + } + + export interface ILogger { + debug(text: string | Object | any[], object?: any); + info(text: string | Object | any[], object?: any); + warn(text: string | Object | any[], object?: any); + error(text: string | Object | any[], object?: any); } - // TODO: rewrite as angular decorator on top of $log export class Logger { constructor( private $log: angular.ILogService, - private _: _.LoDashStatic, - private moment: moment.MomentStatic, + private dateFormatter: (date: Date, format?: string) => string, private config: constants.IAppConfig, public name: string) { } + debug(text: string | Object | any[], object?: any) { + this.log('debug', text, object); + } + info(text: string | Object | any[], object?: any) { this.log('info', text, object); } @@ -38,29 +61,46 @@ module <%= prompts.prefix %>.core.util { this.log('error', text, object); } - private log(type: string, text: string | Object | any[], object?: any) { - if (this.config.environment !== 'production') { + private log = (type: string, text: string | Object | any[], object?: any) => { + if (this.config.ENVIRONMENT !== 'prod') { - if (this._.isObject(text) || this._.isArray(text)) { + if (angular.isObject(text) || angular.isArray(text)) { object = text; text = undefined; } text = text || ''; - - if (this._.isBoolean(object)) { + + if (object === true || object === false) { object = (object) ? 'YES' : 'NO'; } object = object || ''; var arrow = (text !== '' || object !== '') ? '=> ' : ''; - this.$log[type]('[' + this.moment().format('HH:mm:ss.ms') + ' - ' + this.name + '] ' + arrow + text, object); + this.$log[type](`[${this.dateFormatter(new Date(), 'HH:mm:ss.sss')} - ${this.name}] ${arrow}${text}`, object); } - } + }; } + var loggerService = ($log, $filter, appConfig: constants.IAppConfig): ILoggerFactory => (obj: string | Object | Function) => { + var name: string; + if (angular.isObject(obj) && obj.constructor && obj.constructor.name) { + name = obj.constructor.name; + } else if (angular.isFunction(obj) && (obj).name) { + name = (obj).name; + } else { + name = obj || ''; + } + + return new Logger($log, $filter('date'), appConfig, name); + }; + + loggerService.$inject = ['$log', '$filter', constants.ID.AppConfig]; + angular - .module(ID.LoggerFactory, []) + .module(ID.LoggerFactory, [ + constants.Namespace + ]) .factory(ID.LoggerFactory, loggerService); } diff --git a/app/templates/src/app-ts/core/util/util.module.ts b/app/templates/src/app-ts/core/util/util.module.ts index d77415f..ebbcdae 100644 --- a/app/templates/src/app-ts/core/util/util.module.ts +++ b/app/templates/src/app-ts/core/util/util.module.ts @@ -7,12 +7,14 @@ module <%= prompts.prefix %>.core.util { export var ID = { AppEvents: `${Namespace}.Events`, - LoggerFactory: `${Namespace}.Logger` + LoggerFactory: `${Namespace}.Logger`, + Backend: `${Namespace}.Backend` }; angular .module(Namespace, [ ID.AppEvents, - ID.LoggerFactory + ID.LoggerFactory, + ID.Backend ]); } diff --git a/app/templates/src/app-ts/home/views/home.ts b/app/templates/src/app-ts/home/views/home.ts index 485d693..4841184 100644 --- a/app/templates/src/app-ts/home/views/home.ts +++ b/app/templates/src/app-ts/home/views/home.ts @@ -24,17 +24,17 @@ module <%= prompts.prefix %>.home.views { title: string; } - class HomeController extends common.views.AbstractController implements IHomeController { + class HomeController implements IHomeController { private offs: Function[] = []; title = 'Hirsch says hi!'; - static $inject = ['$state', core.util.ID.AppEvents]; - constructor($state, events: core.util.IAppEvents) { - super($state); - + static $inject = ['$scope', core.util.ID.AppEvents]; + constructor($scope, events: core.util.IAppEvents) { this.offs.push(events.on('someEvent', this.onSomeEvent)); + $scope.$on('$destroy', () => this.dispose()); + this.activate(); } @@ -46,8 +46,7 @@ module <%= prompts.prefix %>.home.views { // run initialization logic }; - protected dispose() { - super.dispose(); + private dispose() { this.offs.forEach(off => off()); } } diff --git a/constant/index.js b/constant/index.js index 73fa21d..5fa4b9a 100644 --- a/constant/index.js +++ b/constant/index.js @@ -8,6 +8,7 @@ var Generator = module.exports = function Generator() { ScriptBase.apply(this, arguments); this.generatorName = 'constant'; this.dirName = 'constants'; + this.$namespace = this.dirName; }; util.inherits(Generator, ScriptBase); @@ -21,15 +22,19 @@ Generator.prototype.prompting = function () { this.modulePrompt(); }; +Generator.prototype.folderPrompting = function () { + this.folderPrompt(this.dirName); +}; + Generator.prototype.initComponents = function () { - this.readComponents(this.module, this.generatorName); + this.readComponents(this.module, this.dirName); }; Generator.prototype.createFiles = function createFiles() { this.appTemplate(this.generatorName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName)); this.testTemplate('unit', this.generatorName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName)); if (!this.env.options.typescript) { - this.appTemplate('sub.module', path.join(this.module, this.dirName, this.dirName + '.module')); + this.appTemplate('sub.module', path.join(this.module, this.dirName, path.basename(this.dirName) + '.module')); } }; diff --git a/directive/index.js b/directive/index.js index 2be329a..2fd0adb 100644 --- a/directive/index.js +++ b/directive/index.js @@ -8,6 +8,7 @@ var Generator = module.exports = function Generator() { ScriptBase.apply(this, arguments); this.generatorName = 'directive'; this.dirName = 'directives'; + this.$namespace = this.dirName; }; util.inherits(Generator, ScriptBase); @@ -20,6 +21,10 @@ Generator.prototype.prompting = function () { this.modulePrompt(); }; +Generator.prototype.folderPrompting = function () { + this.folderPrompt(this.dirName); +}; + Generator.prototype.options = function () { var done = this.async(); var prompts = [ @@ -54,7 +59,7 @@ Generator.prototype.options = function () { this.hasTemplate = props.hasTemplate; this.hasController = props.hasController; this.hasLinkFnc = props.hasLinkFnc; - var relModulePath = path.join(this.module, this.dirName, this.name + '.' + this.generatorName + '.html').toLowerCase(); + var relModulePath = path.join(this.module, this.dirName, this.name + '.' + this.generatorName + '.html'); var relAppPath = path.join(this.env.options.appPath, relModulePath).replace(/^src/, '').replace(/\\/g, '/').replace(/^\//, ''); this.templateUrl = relAppPath; done(); @@ -62,7 +67,7 @@ Generator.prototype.options = function () { }; Generator.prototype.initComponents = function () { - this.readComponents(this.module, this.generatorName, function () { + this.readComponents(this.module, this.dirName, function () { this.components = this.components.map(function (c) { return c.replace(/Directive$/, ''); }); @@ -72,9 +77,9 @@ Generator.prototype.initComponents = function () { Generator.prototype.createFiles = function createFiles() { this.appTemplate(this.generatorName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName)); if (this.env.options.typescript) { - this.appTemplate(this.dirName + '.module', path.join(this.module, this.dirName, this.dirName + '.module')); + this.appTemplate(this.generatorName + 's.module', path.join(this.module, this.dirName, path.basename(this.dirName) + '.module')); }else{ - this.appTemplate('sub.module', path.join(this.module, this.dirName, this.dirName + '.module')); + this.appTemplate('sub.module', path.join(this.module, this.dirName, path.basename(this.dirName) + '.module')); } if (this.hasTemplate) { this.htmlTemplate(this.generatorName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName)); diff --git a/filter/index.js b/filter/index.js index 8e6e282..155f4f3 100644 --- a/filter/index.js +++ b/filter/index.js @@ -8,6 +8,7 @@ var Generator = module.exports = function Generator() { ScriptBase.apply(this, arguments); this.generatorName = 'filter'; this.dirName = 'filters'; + this.$namespace = this.dirName; }; util.inherits(Generator, ScriptBase); @@ -21,16 +22,20 @@ Generator.prototype.prompting = function () { this.modulePrompt(); }; +Generator.prototype.folderPrompting = function () { + this.folderPrompt(this.dirName); +}; + Generator.prototype.initComponents = function () { - this.readComponents(this.module, this.generatorName); + this.readComponents(this.module, this.dirName); }; Generator.prototype.createFiles = function createFiles() { this.appTemplate(this.generatorName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName)); if (this.env.options.typescript) { - this.appTemplate(this.dirName + '.module', path.join(this.module, this.dirName, this.dirName + '.module')); + this.appTemplate(this.generatorName + 's.module', path.join(this.module, this.dirName, path.basename(this.dirName) + '.module')); }else{ - this.appTemplate('sub.module', path.join(this.module, this.dirName, this.dirName + '.module')); + this.appTemplate('sub.module', path.join(this.module, this.dirName, path.basename(this.dirName) + '.module')); } this.testTemplate('unit', this.generatorName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName)); }; diff --git a/package.json b/package.json index 916f9b6..447a4e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "generator-hirsch", - "version": "1.0.0-rc.21", + "version": "1.0.0-rc.22", "description": "Yeoman generator for large module-based AngularJS applications with the option to use typescript", "license": "MIT", "main": "app/index.js", diff --git a/script-base.js b/script-base.js index 271b255..8f1f070 100644 --- a/script-base.js +++ b/script-base.js @@ -38,6 +38,8 @@ var Generator = module.exports = function Generator() { this.prefix = packageJson.prefix; this.components = []; + this.typingNesting = ''; + this.env.options.srcPath = projectConfig.path.srcDir; this.env.options.appPath = path.join(projectConfig.path.srcDir, projectConfig.path.appDir); @@ -74,12 +76,14 @@ Generator.prototype.readModules = function (cb) { }.bind(this)); }; -Generator.prototype.readComponents = function (module, type, cb) { +Generator.prototype.readComponents = function (module, dirName, cb) { var done = this.async(); - hirschUtils.getComponentsFromFileStructure(this, module || 'common', type, function (components) { + hirschUtils.getComponentsFromFileStructure(this, module || 'common', dirName, function (components) { var self = this; _.forEach(components, function (item) { - self.components.push(item); + if (item !== self.classedName) { + self.components.push(item); + } }); if (cb) { cb(); @@ -110,6 +114,30 @@ Generator.prototype.modulePrompt = function () { }.bind(this)); }; +Generator.prototype.folderPrompt = function($default, cb) { + var done = this.async(); + var prompts = [ + { + name: 'folder', + message: 'In which folder should the file be placed?', + default: $default + } + ]; + + this.prompt(prompts, function(props) { + this.dirName = props.folder.replace(/(\/|\\)/g, '/').replace(/(^\/|\/$)/g, ''); + this.$namespace = this.dirName.replace(/\//g, '.'); + var levels = (this.$namespace.match(/\./g) || []).length; + while (levels--) { + this.typingNesting += '../'; + } + if (cb) { + cb(this.folder); + } + done(); + }.bind(this)); +} + Generator.prototype.appTemplate = function (src, dest) { yeoman.generators.Base.prototype.template.apply(this, [ src + this.scriptSuffix, @@ -121,9 +149,12 @@ Generator.prototype.appTemplate = function (src, dest) { Generator.prototype.testTemplate = function (type, src, dest) { type = type || 'unit'; + dest = path.join(this.env.options.testPath[type], dest); + dest += dest.indexOf('.spec') >= 0 ? '' : '.spec'; + dest += this.scriptSuffix; yeoman.generators.Base.prototype.template.apply(this, [ src + '.' + type + '.spec' + this.scriptSuffix, - path.join(this.env.options.testPath[type], dest) + this.scriptSuffix, + dest, this, { interpolate: /<%=([\s\S]+?)%>/g } ]); diff --git a/service/index.js b/service/index.js index f6de6bb..800df92 100644 --- a/service/index.js +++ b/service/index.js @@ -8,6 +8,7 @@ var Generator = module.exports = function Generator() { ScriptBase.apply(this, arguments); this.generatorName = 'service'; this.dirName = 'services'; + this.$namespace = this.dirName; }; util.inherits(Generator, ScriptBase); @@ -21,8 +22,12 @@ Generator.prototype.prompting = function () { this.modulePrompt(); }; +Generator.prototype.folderPrompting = function () { + this.folderPrompt(this.dirName); +}; + Generator.prototype.initComponents = function () { - this.readComponents(this.module, this.generatorName); + this.readComponents(this.module, this.dirName); }; Generator.prototype.promptForServiceType = function () { @@ -44,15 +49,15 @@ Generator.prototype.promptForServiceType = function () { }; Generator.prototype.createFiles = function createFiles() { - var templateName = (this.useFactory) ?'factory':'service'; + var templateName = (this.useFactory && !this.env.options.typescript) ?'factory':'service'; this.appTemplate(templateName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName)); this.testTemplate('unit', templateName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName)); if (this.env.options.typescript) { - this.appTemplate(this.dirName + '.module', path.join(this.module, this.dirName, this.dirName + '.module')); + this.appTemplate(this.generatorName + 's.module', path.join(this.module, this.dirName, path.basename(this.dirName) + '.module')); } else { - this.appTemplate('sub.module', path.join(this.module, this.dirName, this.dirName + '.module')); + this.appTemplate('sub.module', path.join(this.module, this.dirName, path.basename(this.dirName) + '.module')); } }; diff --git a/templates/typescript/directive.ts b/templates/typescript/directive.ts index 58eb5c5..abc48e3 100644 --- a/templates/typescript/directive.ts +++ b/templates/typescript/directive.ts @@ -1,6 +1,6 @@ -/// +/// -module <%= prefix %>.<%= module %>.directives { +module <%= prefix %>.<%= module %>.<%= $namespace %> { 'use strict'; class <%= classedName %>Directive implements angular.IDirective { diff --git a/templates/typescript/directive.unit.spec.ts b/templates/typescript/directive.unit.spec.ts index d2c0e09..7c3cfb9 100644 --- a/templates/typescript/directive.unit.spec.ts +++ b/templates/typescript/directive.unit.spec.ts @@ -1,6 +1,6 @@ -/// +/// -module <%= prefix %>.<%= module %>.directives.test { +module <%= prefix %>.<%= module %>.<%= $namespace %>.test { 'use strict'; describe(`Unit: ${Namespace}.<%= classedName %>Directive`, () => { diff --git a/templates/typescript/directives.module.ts b/templates/typescript/directives.module.ts index 420568a..8469478 100644 --- a/templates/typescript/directives.module.ts +++ b/templates/typescript/directives.module.ts @@ -1,9 +1,9 @@ -/// +/// -module <%= prefix %>.<%= module %>.directives { +module <%= prefix %>.<%= module %>.<%= $namespace %> { 'use strict'; - export var Namespace = '<%= prefix %>.<%= module %>.directives'; + export var Namespace = '<%= prefix %>.<%= module %>.<%= $namespace %>'; angular .module(Namespace, [<% for (var i = 0, l = components.length; i < l; i++) { %> diff --git a/templates/typescript/filter.ts b/templates/typescript/filter.ts index 7ffaa2f..e568aea 100644 --- a/templates/typescript/filter.ts +++ b/templates/typescript/filter.ts @@ -1,6 +1,6 @@ -/// +/// -module <%= prefix %>.<%= module %>.filters { +module <%= prefix %>.<%= module %>.<%= $namespace %> { 'use strict'; export interface I<%= classedName %>Filter { diff --git a/templates/typescript/filter.unit.spec.ts b/templates/typescript/filter.unit.spec.ts index fe31fa9..108ce8b 100644 --- a/templates/typescript/filter.unit.spec.ts +++ b/templates/typescript/filter.unit.spec.ts @@ -1,6 +1,6 @@ -/// +/// -module <%= prefix %>.<%= module %>.filters.test { +module <%= prefix %>.<%= module %>.<%= $namespace %>.test { 'use strict'; describe(`Unit: ${Namespace}.<%= classedName %>Filter`, () => { diff --git a/templates/typescript/filters.module.ts b/templates/typescript/filters.module.ts index 855c82a..17c3d2d 100644 --- a/templates/typescript/filters.module.ts +++ b/templates/typescript/filters.module.ts @@ -1,9 +1,9 @@ -/// +/// -module <%= prefix %>.<%= module %>.filters { +module <%= prefix %>.<%= module %>.<%= $namespace %> { 'use strict'; - export var Namespace = '<%= prefix %>.<%= module %>.filters'; + export var Namespace = '<%= prefix %>.<%= module %>.<%= $namespace %>'; angular .module(Namespace, [<% for (var i = 0, l = components.length; i < l; i++) { %> diff --git a/templates/typescript/module.midway.spec.ts b/templates/typescript/module.midway.spec.ts index 2af8dae..a8ac8bc 100644 --- a/templates/typescript/module.midway.spec.ts +++ b/templates/typescript/module.midway.spec.ts @@ -1,4 +1,4 @@ -/// +/// module <%= prefix %>.<%= cameledName %>.test { 'use strict'; diff --git a/templates/typescript/module.ts b/templates/typescript/module.ts index 1bb059e..1c65c00 100644 --- a/templates/typescript/module.ts +++ b/templates/typescript/module.ts @@ -1,4 +1,4 @@ -/// +/// module <%= prefix %>.<%= cameledName %> { 'use strict'; diff --git a/templates/typescript/service.ts b/templates/typescript/service.ts index d983b04..9695a2a 100644 --- a/templates/typescript/service.ts +++ b/templates/typescript/service.ts @@ -1,15 +1,16 @@ -/// +/// -module <%= prefix %>.<%= module %>.services { +module <%= prefix %>.<%= module %>.<%= $namespace %> { 'use strict'; - export interface I<%= classedName %>Service { + export interface I<%= classedName %> { method(param: string): string; } - class <%= classedName %>Service implements I<%= classedName %>Service { + class <%= classedName %> implements I<%= classedName %> { private field; + static $inject = []; constructor() { this.field = 'value'; } @@ -19,16 +20,16 @@ module <%= prefix %>.<%= module %>.services { } }<% if(useFactory) { %> - var factory = (): I<%= classedName %>Service => { + var factory = (): I<%= classedName %> => { // TODO: private initialization code // TODO: pass initialization parameters to class - return new <%= classedName %>Service(); + return new <%= classedName %>(); }; factory.$inject = [];<% } %> angular - .module(ID.<%= classedName %>Service, [])<% if(!useFactory) { %> - .service(ID.<%= classedName %>Service, <%= classedName %>Service)<% } %><% if(useFactory) { %> - .factory(ID.<%= classedName %>Service, factory)<% } %>; + .module(ID.<%= classedName %>, [])<% if(!useFactory) { %> + .service(ID.<%= classedName %>, <%= classedName %>)<% } %><% if(useFactory) { %> + .factory(ID.<%= classedName %>, factory)<% } %>; } diff --git a/templates/typescript/service.unit.spec.ts b/templates/typescript/service.unit.spec.ts index 24de629..ba1d805 100644 --- a/templates/typescript/service.unit.spec.ts +++ b/templates/typescript/service.unit.spec.ts @@ -1,14 +1,14 @@ -/// +/// -module <%= prefix %>.<%= module %>.services.test { +module <%= prefix %>.<%= module %>.<%= $namespace %>.test { 'use strict'; - describe(`Unit: ${Namespace}.<%= classedName %>Service`, () => { + describe(`Unit: ${Namespace}.<%= classedName %>`, () => { beforeEach(module(Namespace)); - var service: I<%= classedName %>Service; - beforeEach(angular.mock.inject([ID.<%= classedName %>Service, s => service = s])); + var service: I<%= classedName %>; + beforeEach(angular.mock.inject([ID.<%= classedName %>, s => service = s])); it('should contain a <%= classedName %> service', () => should.exist(service)); diff --git a/templates/typescript/services.module.ts b/templates/typescript/services.module.ts index 7a00352..e26281b 100644 --- a/templates/typescript/services.module.ts +++ b/templates/typescript/services.module.ts @@ -1,18 +1,18 @@ -/// +/// -module <%= prefix %>.<%= module %>.services { +module <%= prefix %>.<%= module %>.<%= $namespace %> { 'use strict'; - export var Namespace = '<%= prefix %>.<%= module %>.services'; + export var Namespace = '<%= prefix %>.<%= module %>.<%= $namespace %>'; export var ID = {<% for (var i = 0, l = components.length; i < l; i++) { %> <%= components[i] %>: `${Namespace}.<%= components[i] %>`, <% } %> - <%= classedName %>Service: `${Namespace}.<%= classedName %>Service` + <%= classedName %>: `${Namespace}.<%= classedName %>` }; angular .module(Namespace, [<% for (var i = 0, l = components.length; i < l; i++) { %> ID.<%= components[i] %>, <% } %> - ID.<%= classedName %>Service + ID.<%= classedName %> ]); } diff --git a/templates/typescript/view.ts b/templates/typescript/view.ts index fb979aa..828dffe 100644 --- a/templates/typescript/view.ts +++ b/templates/typescript/view.ts @@ -1,6 +1,6 @@ -/// +/// -module <%= prefix %>.<%= module %>.views { +module <%= prefix %>.<%= module %>.<%= $namespace %> { 'use strict'; var stateConfig = ($stateProvider: ng.ui.IStateProvider) => { @@ -22,25 +22,18 @@ module <%= prefix %>.<%= module %>.views { export interface I<%= classedName %>Controller { prop: string; - asyncProp: string[]; + asyncProp: ng.IPromise; method(param: string): string; action(): void; } - class <%= classedName %>Controller extends common.views.AbstractController implements I<%= classedName %>Controller { - private offs: Function[] = []; - + class <%= classedName %>Controller implements I<%= classedName %>Controller { prop: string; - asyncProp: string[]; + asyncProp: ng.IPromise; - static $inject = ['$state', core.util.ID.AppEvents]; - constructor($state, events: core.util.IAppEvents) { - super($state); - + static $inject = []; + constructor() { this.prop = ''; - this.asyncProp = []; - - this.offs.push(events.on('someEvent', this.onSomeEvent)); this.activate(); } @@ -53,25 +46,14 @@ module <%= prefix %>.<%= module %>.views { // TODO: perform some action }; - private onSomeEvent = (eventObj: any) => { - // TODO: handle event - }; - private activate = () => { // TODO: call some service to asynchronously return data - // this.someService.getData().then(data => this.asyncProp = data); + // this.asyncProp = this.someService.getData(); }; - - protected dispose() { - super.dispose(); - this.offs.forEach(off => off()); - } } angular - .module(`${Namespace}.<%= classedName %>`, [ - core.util.Namespace - ]) + .module(`${Namespace}.<%= classedName %>`, []) .config(stateConfig) .controller(ID.<%= classedName %>Controller, <%= classedName %>Controller); } diff --git a/templates/typescript/view.unit.spec.ts b/templates/typescript/view.unit.spec.ts index d0bb121..c607612 100644 --- a/templates/typescript/view.unit.spec.ts +++ b/templates/typescript/view.unit.spec.ts @@ -1,6 +1,6 @@ -/// +/// -module <%= prefix %>.<%= module %>.views.test { +module <%= prefix %>.<%= module %>.<%= $namespace %>.test { 'use strict'; describe(`Unit: ${Namespace}.<%= classedName %>Controller`, () => { diff --git a/templates/typescript/views.module.ts b/templates/typescript/views.module.ts index 5ff1402..88301ad 100644 --- a/templates/typescript/views.module.ts +++ b/templates/typescript/views.module.ts @@ -1,9 +1,9 @@ -/// +/// -module <%= prefix %>.<%= module %>.views { +module <%= prefix %>.<%= module %>.<%= $namespace %> { 'use strict'; - export var Namespace = '<%= prefix %>.<%= module %>.views'; + export var Namespace = '<%= prefix %>.<%= module %>.<%= $namespace %>'; angular .module(Namespace, [ diff --git a/util.js b/util.js index 610e6ae..6e0c92e 100644 --- a/util.js +++ b/util.js @@ -56,11 +56,14 @@ module.exports = { }); }, - getComponentsFromFileStructure: function (scope, module, type, cb) { - fs.readdir(scope.destinationPath(path.join(scope.env.options.appPath, module, type + 's')), function (err, files) { + getComponentsFromFileStructure: function (scope, module, dirName, cb) { + var fullDirName = scope.destinationPath(path.join(scope.env.options.appPath, module, dirName)); + fs.readdir(fullDirName, function (err, files) { if (files) { var components = _.uniq(files.filter(function(f) { return f.indexOf('.module') === -1; + }).filter(function(f) { + return !fs.statSync(path.join(fullDirName, f)).isDirectory(); }).map(function(f) { return f.replace(/\.(js|ts|html|js\.map)$/, ''); }).map(_s.classify)); diff --git a/view/index.js b/view/index.js index 5536fc1..73affc5 100644 --- a/view/index.js +++ b/view/index.js @@ -8,6 +8,7 @@ var Generator = module.exports = function Generator() { ScriptBase.apply(this, arguments); this.generatorName = 'view'; this.dirName = 'views'; + this.$namespace = this.dirName; }; util.inherits(Generator, ScriptBase); @@ -20,6 +21,10 @@ Generator.prototype.prompting = function () { this.modulePrompt(); }; +Generator.prototype.folderPrompting = function () { + this.folderPrompt(this.dirName); +}; + Generator.prototype.options = function () { var done = this.async(); var prompts = [ @@ -32,7 +37,7 @@ Generator.prototype.options = function () { this.prompt(prompts, function (props) { this.url = props.url; - var relModulePath = path.join(this.module, this.dirName, this.name + '.html').toLowerCase(); + var relModulePath = path.join(this.module, this.dirName, this.name + '.html'); var relAppPath = path.join(this.env.options.appPath, relModulePath).replace(/^src/, '').replace(/\\/g, '/').replace(/^\//, ''); this.templateUrl = relAppPath; done(); @@ -40,15 +45,15 @@ Generator.prototype.options = function () { }; Generator.prototype.initComponents = function () { - this.readComponents(this.module, this.generatorName); + this.readComponents(this.module, this.dirName); }; Generator.prototype.createFiles = function createFiles() { this.appTemplate(this.generatorName, path.join(this.module, this.dirName, this.name)); if (this.env.options.typescript) { - this.appTemplate(this.dirName + '.module', path.join(this.module, this.dirName, this.dirName + '.module')); + this.appTemplate(this.generatorName + 's.module', path.join(this.module, this.dirName, path.basename(this.dirName) + '.module')); }else{ - this.appTemplate('sub.module', path.join(this.module, this.dirName, this.dirName + '.module')); + this.appTemplate('sub.module', path.join(this.module, this.dirName, path.basename(this.dirName) + '.module')); } this.htmlTemplate(this.generatorName, path.join(this.module, this.dirName, this.name)); this.testTemplate('unit', this.generatorName, path.join(this.module, this.dirName, this.name + '.' + this.generatorName));