diff --git a/src/app/layout/directives/.git.keep b/src/app/layout/directives/.git.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/layout/directives/.git.keep diff --git a/src/app/layout/html-utils.spec.ts b/src/app/layout/html-utils.spec.ts new file mode 100644 index 0000000..3645407 --- /dev/null +++ b/src/app/layout/html-utils.spec.ts @@ -0,0 +1,41 @@ +import { +HtmlUtils +} from "./html-utils"; + +describe("HtmlUtils", () => { + let element: ng.IAugmentedJQuery; + + beforeEach(inject(($compile: ng.ICompileService, $rootScope: ng.IRootScopeService) => { + element = $compile("")($rootScope); + })); + + it("removes a css class matching some prefix from an HTML Element", () => { + element.addClass("noosfero-class1"); + element.addClass("noosfero-class2"); + element.addClass("another-class"); + + HtmlUtils.removeCssClassByPrefix(element[0], "noosfero-"); + + // we expect classes prefixed with 'noosfero-' to be removed + expect(element[0].classList).not.toContain("noosfero-class1"); + expect(element[0].classList).not.toContain("noosfero-class2"); + + // class not prefixed with 'noosfero-' should be there + expect(element[0].classList).toContain("another-class"); + }); + + it("removes a css class matching some suffix from an HTML Element", () => { + element.addClass("class-1-noosfero"); + element.addClass("class-2-noosfero"); + element.addClass("another-class"); + + HtmlUtils.removeCssClassBySuffix(element[0], "-noosfero"); + + // we expect classes prefixed with 'noosfero-' to be removed + expect(element[0].classList).not.toContain("class-1-noosfero"); + expect(element[0].classList).not.toContain("class-2-noosfero"); + + // class not suffixed with '-noosfero' should be there + expect(element[0].classList).toContain("another-class"); + }); +}); \ No newline at end of file diff --git a/src/app/layout/html-utils.ts b/src/app/layout/html-utils.ts new file mode 100644 index 0000000..9ead755 --- /dev/null +++ b/src/app/layout/html-utils.ts @@ -0,0 +1,18 @@ +export namespace HtmlUtils { + + /** + * Remove All Css Classes which matches some prefix + */ + export function removeCssClassByPrefix(el: HTMLElement, prefix: string) { + let regx = new RegExp('\\b' + prefix + '\\S*', 'g'); + el.className = el.className.replace(regx, ''); + } + + /** + * Remove All Css Classes which matches some suffix + */ + export function removeCssClassBySuffix(el: HTMLElement, suffix: string) { + let regx = new RegExp('\\S+' + suffix + '\\S*', 'g'); + el.className = el.className.replace(regx, ''); + } +} \ No newline at end of file diff --git a/src/app/layout/services/body-state-classes.service.spec.ts b/src/app/layout/services/body-state-classes.service.spec.ts new file mode 100644 index 0000000..5d8c7bb --- /dev/null +++ b/src/app/layout/services/body-state-classes.service.spec.ts @@ -0,0 +1,63 @@ +import {provideFilters} from '../../../spec/helpers'; +import {BodyStateClassesService} from "./body-state-classes.service"; +import {AuthService} from "./../../login/auth.service"; + +describe("BodyStateClasses Service", () => { + let currentStateName = "main"; + let bodyStateClasseService: BodyStateClassesService; + let $rootScope: ng.IRootScopeService = {}, + $document: ng.IDocumentService = {}, + $state: ng.ui.IStateService = { + current: { + name: currentStateName + } + }, + authService: AuthService = { + isAuthenticated: () => false + }, + bodyEl: { className: string } = { className: "" }, + bodyElJq: any = [bodyEl]; + + let getService = (): BodyStateClassesService => { + return new BodyStateClassesService($rootScope, $document, $state, authService); + }; + + it("should add the class noosfero-user-logged to the body element if the user is authenticated", () => { + authService.isAuthenticated = jasmine.createSpy("isAuthenticated").and.returnValue(true); + $rootScope.$on = jasmine.createSpy("$on"); + + let service = getService(); + + bodyElJq.addClass = jasmine.createSpy("addClass"); + bodyElJq.removeClass = jasmine.createSpy("removeClass"); + service["bodyElement"] = bodyElJq; + + service.start(); + expect(bodyElJq.addClass).toHaveBeenCalledWith(BodyStateClassesService.USER_LOGGED_CLASSNAME); + }); + + it("should add the class noosfero-route-[currentStateName] the body element", () => { + $rootScope.$on = jasmine.createSpy("$on"); + let service = getService(); + + bodyElJq.addClass = jasmine.createSpy("addClass"); + bodyElJq.removeClass = jasmine.createSpy("removeClass"); + service["bodyElement"] = bodyElJq; + + service.start(); + let stateClassName = BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX + currentStateName; + expect(bodyElJq.addClass).toHaveBeenCalledWith(stateClassName); + }); + + it("should capture loginSuccess event and add noosfero-user-logged class to the body element", () => { + pending("Test not yet implemented!"); + }); + + it("should capture logoutSuccess event and remove noosfero-user-logged class to the body element", () => { + pending("Test not yet implemented!"); + }); + + it("should capture $stateChangeSuccess event and switch route class in the body element", () => { + pending("Test not yet implemented!"); + }); +}); \ No newline at end of file diff --git a/src/app/layout/services/body-state-classes.service.ts b/src/app/layout/services/body-state-classes.service.ts new file mode 100644 index 0000000..5b33191 --- /dev/null +++ b/src/app/layout/services/body-state-classes.service.ts @@ -0,0 +1,93 @@ +import {Directive, Inject, Injectable} from "ng-forward"; +import {AUTH_EVENTS} from "./../../login/auth-events"; +import {AuthService} from "./../../login/auth.service"; +import {HtmlUtils} from "../html-utils"; + +/** + * This is a service which adds classes to the body element + * indicating some app states information as + * eg: + * User Logged: + * - noosfero-user-logged + * Route States: + * - noosfero-route-main + * - noosfero-route-main.profile.info + */ +@Injectable() +@Inject("$rootScope", "$document", "$state", AuthService) +export class BodyStateClassesService { + + public static get USER_LOGGED_CLASSNAME(): string { return "noosfero-user-logged"; } + public static get ROUTE_STATE_CLASSNAME_PREFIX(): string { return "noosfero-route-"; } + + private bodyElement: ng.IAugmentedJQuery = null; + + constructor( + private $rootScope: ng.IRootScopeService, + private $document: ng.IDocumentService, + private $state: ng.ui.IStateService, + private authService: AuthService + ) { + + } + + start() { + this.setupUserLoggedClassToggle(); + this.setupStateClassToggle(); + } + + getStateChangeSuccessHandlerFunction(bodyElement: ng.IAugmentedJQuery): (event: ng.IAngularEvent, toState: ng.ui.IState) => void { + let self = this; + return (event: ng.IAngularEvent, toState: ng.ui.IState) => { + self.switchStateClasses(bodyElement, BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX); + }; + } + + switchStateClasses(bodyElement: ng.IAugmentedJQuery, state: ng.ui.IState) { + HtmlUtils.removeCssClassByPrefix(bodyElement[0], BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX); + bodyElement.addClass(BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX + state.name); + } + + /** + * Setup the initial class name on body element indicating the current route + * and adds event handler to swith this class when the current page/state changes + */ + private setupStateClassToggle() { + let bodyElement = this.getBodyElement(); + bodyElement.addClass(BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX + this.$state.current.name); + this.$rootScope.$on("$stateChangeSuccess", this.getStateChangeSuccessHandlerFunction(bodyElement)); + } + + /** + * Setup the initial state of the user-logged css class + * and adds events handlers to switch this class when the login events happens + */ + private setupUserLoggedClassToggle() { + let bodyElement = this.getBodyElement(); + + // get initial logged information from the AuthService + // add add the css class when the user is authenticated + if (this.authService.isAuthenticated()) { + bodyElement.addClass(BodyStateClassesService.USER_LOGGED_CLASSNAME); + } + + // listen to the AUTH_EVENTS.loginSuccess and AUTH_EVENTS.logoutSuccess + // to switch the css class which indicates user logged in + this.$rootScope.$on(AUTH_EVENTS.loginSuccess, () => { + bodyElement.addClass(BodyStateClassesService.USER_LOGGED_CLASSNAME); + }); + this.$rootScope.$on(AUTH_EVENTS.logoutSuccess, () => { + bodyElement.removeClass(BodyStateClassesService.USER_LOGGED_CLASSNAME); + }); + } + + /** + * Returns the user 'body' html Element + */ + private getBodyElement(): ng.IAugmentedJQuery { + if (this.bodyElement === null) { + this.bodyElement = angular.element(this.$document.find("body")); + } + return this.bodyElement; + } +} \ No newline at end of file diff --git a/src/app/login/auth.service.spec.ts b/src/app/login/auth.service.spec.ts index 5b417ba..0d7a3bc 100644 --- a/src/app/login/auth.service.spec.ts +++ b/src/app/login/auth.service.spec.ts @@ -1,5 +1,3 @@ - - import {AuthService, AUTH_EVENTS} from "./"; describe("Services", () => { diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 837c567..5fda8f6 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -1,4 +1,4 @@ -import {bundle, Component, StateConfig} from "ng-forward"; +import {bundle, Component, StateConfig, Inject} from "ng-forward"; import {ArticleBlogComponent} from "./../article/types/blog/blog.component"; import {ArticleViewComponent} from "./../article/article-default-view.component"; @@ -20,6 +20,7 @@ import {SessionService} from "../login/session.service"; import {NotificationService} from "../shared/services/notification.service"; +import {BodyStateClassesService} from "./../layout/services/body-state-classes.service"; import {Navbar} from "../layout/navbar/navbar"; @@ -41,8 +42,11 @@ import {MainBlockComponent} from "../layout/blocks/main-block/main-block.compone templateUrl: "app/main/main.html", providers: [AuthService, SessionService] }) +@Inject(BodyStateClassesService) export class MainContentComponent { - + constructor(private bodyStateClassesService: BodyStateClassesService) { + bodyStateClassesService.start(); + } } /** @@ -67,7 +71,7 @@ export class MainContentComponent { MainBlockComponent, RecentDocumentsBlockComponent, Navbar, ProfileImageBlockComponent, MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent ], - providers: [AuthService, SessionService, NotificationService] + providers: [AuthService, SessionService, NotificationService, BodyStateClassesService] }) @StateConfig([ { @@ -90,5 +94,4 @@ export class MainContentComponent { } ]) export class MainComponent { - } -- libgit2 0.21.2