Commit e664cc527c33e23e82d8d0680978692a7b078b2c
1 parent
8bd5efa2
Exists in
master
and in
1 other branch
added body-state-classes service which adds css classes to the body with some app state informations
Showing
7 changed files
with
222 additions
and
6 deletions
Show diff stats
@@ -0,0 +1,41 @@ | @@ -0,0 +1,41 @@ | ||
1 | +import { | ||
2 | +HtmlUtils | ||
3 | +} from "./html-utils"; | ||
4 | + | ||
5 | +describe("HtmlUtils", () => { | ||
6 | + let element: ng.IAugmentedJQuery; | ||
7 | + | ||
8 | + beforeEach(inject(($compile: ng.ICompileService, $rootScope: ng.IRootScopeService) => { | ||
9 | + element = $compile("<a></a>")($rootScope); | ||
10 | + })); | ||
11 | + | ||
12 | + it("removes a css class matching some prefix from an HTML Element", () => { | ||
13 | + element.addClass("noosfero-class1"); | ||
14 | + element.addClass("noosfero-class2"); | ||
15 | + element.addClass("another-class"); | ||
16 | + | ||
17 | + HtmlUtils.removeCssClassByPrefix(element[0], "noosfero-"); | ||
18 | + | ||
19 | + // we expect classes prefixed with 'noosfero-' to be removed | ||
20 | + expect(element[0].classList).not.toContain("noosfero-class1"); | ||
21 | + expect(element[0].classList).not.toContain("noosfero-class2"); | ||
22 | + | ||
23 | + // class not prefixed with 'noosfero-' should be there | ||
24 | + expect(element[0].classList).toContain("another-class"); | ||
25 | + }); | ||
26 | + | ||
27 | + it("removes a css class matching some suffix from an HTML Element", () => { | ||
28 | + element.addClass("class-1-noosfero"); | ||
29 | + element.addClass("class-2-noosfero"); | ||
30 | + element.addClass("another-class"); | ||
31 | + | ||
32 | + HtmlUtils.removeCssClassBySuffix(element[0], "-noosfero"); | ||
33 | + | ||
34 | + // we expect classes prefixed with 'noosfero-' to be removed | ||
35 | + expect(element[0].classList).not.toContain("class-1-noosfero"); | ||
36 | + expect(element[0].classList).not.toContain("class-2-noosfero"); | ||
37 | + | ||
38 | + // class not suffixed with '-noosfero' should be there | ||
39 | + expect(element[0].classList).toContain("another-class"); | ||
40 | + }); | ||
41 | +}); | ||
0 | \ No newline at end of file | 42 | \ No newline at end of file |
@@ -0,0 +1,18 @@ | @@ -0,0 +1,18 @@ | ||
1 | +export namespace HtmlUtils { | ||
2 | + | ||
3 | + /** | ||
4 | + * Remove All Css Classes which matches some prefix | ||
5 | + */ | ||
6 | + export function removeCssClassByPrefix(el: HTMLElement, prefix: string) { | ||
7 | + let regx = new RegExp('\\b' + prefix + '\\S*', 'g'); | ||
8 | + el.className = el.className.replace(regx, ''); | ||
9 | + } | ||
10 | + | ||
11 | + /** | ||
12 | + * Remove All Css Classes which matches some suffix | ||
13 | + */ | ||
14 | + export function removeCssClassBySuffix(el: HTMLElement, suffix: string) { | ||
15 | + let regx = new RegExp('\\S+' + suffix + '\\S*', 'g'); | ||
16 | + el.className = el.className.replace(regx, ''); | ||
17 | + } | ||
18 | +} | ||
0 | \ No newline at end of file | 19 | \ No newline at end of file |
src/app/layout/services/body-state-classes.service.spec.ts
0 → 100644
@@ -0,0 +1,63 @@ | @@ -0,0 +1,63 @@ | ||
1 | +import {provideFilters} from '../../../spec/helpers'; | ||
2 | +import {BodyStateClassesService} from "./body-state-classes.service"; | ||
3 | +import {AuthService} from "./../../login/auth.service"; | ||
4 | + | ||
5 | +describe("BodyStateClasses Service", () => { | ||
6 | + let currentStateName = "main"; | ||
7 | + let bodyStateClasseService: BodyStateClassesService; | ||
8 | + let $rootScope: ng.IRootScopeService = <any>{}, | ||
9 | + $document: ng.IDocumentService = <any>{}, | ||
10 | + $state: ng.ui.IStateService = <any>{ | ||
11 | + current: { | ||
12 | + name: currentStateName | ||
13 | + } | ||
14 | + }, | ||
15 | + authService: AuthService = <any>{ | ||
16 | + isAuthenticated: () => false | ||
17 | + }, | ||
18 | + bodyEl: { className: string } = { className: "" }, | ||
19 | + bodyElJq: any = [bodyEl]; | ||
20 | + | ||
21 | + let getService = (): BodyStateClassesService => { | ||
22 | + return new BodyStateClassesService($rootScope, $document, $state, authService); | ||
23 | + }; | ||
24 | + | ||
25 | + it("should add the class noosfero-user-logged to the body element if the user is authenticated", () => { | ||
26 | + authService.isAuthenticated = jasmine.createSpy("isAuthenticated").and.returnValue(true); | ||
27 | + $rootScope.$on = jasmine.createSpy("$on"); | ||
28 | + | ||
29 | + let service = getService(); | ||
30 | + | ||
31 | + bodyElJq.addClass = jasmine.createSpy("addClass"); | ||
32 | + bodyElJq.removeClass = jasmine.createSpy("removeClass"); | ||
33 | + service["bodyElement"] = bodyElJq; | ||
34 | + | ||
35 | + service.start(); | ||
36 | + expect(bodyElJq.addClass).toHaveBeenCalledWith(BodyStateClassesService.USER_LOGGED_CLASSNAME); | ||
37 | + }); | ||
38 | + | ||
39 | + it("should add the class noosfero-route-[currentStateName] the body element", () => { | ||
40 | + $rootScope.$on = jasmine.createSpy("$on"); | ||
41 | + let service = getService(); | ||
42 | + | ||
43 | + bodyElJq.addClass = jasmine.createSpy("addClass"); | ||
44 | + bodyElJq.removeClass = jasmine.createSpy("removeClass"); | ||
45 | + service["bodyElement"] = bodyElJq; | ||
46 | + | ||
47 | + service.start(); | ||
48 | + let stateClassName = BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX + currentStateName; | ||
49 | + expect(bodyElJq.addClass).toHaveBeenCalledWith(stateClassName); | ||
50 | + }); | ||
51 | + | ||
52 | + it("should capture loginSuccess event and add noosfero-user-logged class to the body element", () => { | ||
53 | + pending("Test not yet implemented!"); | ||
54 | + }); | ||
55 | + | ||
56 | + it("should capture logoutSuccess event and remove noosfero-user-logged class to the body element", () => { | ||
57 | + pending("Test not yet implemented!"); | ||
58 | + }); | ||
59 | + | ||
60 | + it("should capture $stateChangeSuccess event and switch route class in the body element", () => { | ||
61 | + pending("Test not yet implemented!"); | ||
62 | + }); | ||
63 | +}); | ||
0 | \ No newline at end of file | 64 | \ No newline at end of file |
@@ -0,0 +1,93 @@ | @@ -0,0 +1,93 @@ | ||
1 | +import {Directive, Inject, Injectable} from "ng-forward"; | ||
2 | +import {AUTH_EVENTS} from "./../../login/auth-events"; | ||
3 | +import {AuthService} from "./../../login/auth.service"; | ||
4 | +import {HtmlUtils} from "../html-utils"; | ||
5 | + | ||
6 | +/** | ||
7 | + * This is a service which adds classes to the body element | ||
8 | + * indicating some app states information as | ||
9 | + * eg: | ||
10 | + * User Logged: | ||
11 | + * - noosfero-user-logged | ||
12 | + * Route States: | ||
13 | + * - noosfero-route-main | ||
14 | + * - noosfero-route-main.profile.info | ||
15 | + */ | ||
16 | +@Injectable() | ||
17 | +@Inject("$rootScope", "$document", "$state", AuthService) | ||
18 | +export class BodyStateClassesService { | ||
19 | + | ||
20 | + public static get USER_LOGGED_CLASSNAME(): string { return "noosfero-user-logged"; } | ||
21 | + public static get ROUTE_STATE_CLASSNAME_PREFIX(): string { return "noosfero-route-"; } | ||
22 | + | ||
23 | + private bodyElement: ng.IAugmentedJQuery = null; | ||
24 | + | ||
25 | + constructor( | ||
26 | + private $rootScope: ng.IRootScopeService, | ||
27 | + private $document: ng.IDocumentService, | ||
28 | + private $state: ng.ui.IStateService, | ||
29 | + private authService: AuthService | ||
30 | + ) { | ||
31 | + | ||
32 | + } | ||
33 | + | ||
34 | + start() { | ||
35 | + this.setupUserLoggedClassToggle(); | ||
36 | + this.setupStateClassToggle(); | ||
37 | + } | ||
38 | + | ||
39 | + getStateChangeSuccessHandlerFunction(bodyElement: ng.IAugmentedJQuery): (event: ng.IAngularEvent, toState: ng.ui.IState) => void { | ||
40 | + let self = this; | ||
41 | + return (event: ng.IAngularEvent, toState: ng.ui.IState) => { | ||
42 | + self.switchStateClasses(bodyElement, BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX); | ||
43 | + }; | ||
44 | + } | ||
45 | + | ||
46 | + switchStateClasses(bodyElement: ng.IAugmentedJQuery, state: ng.ui.IState) { | ||
47 | + HtmlUtils.removeCssClassByPrefix(bodyElement[0], BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX); | ||
48 | + bodyElement.addClass(BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX + state.name); | ||
49 | + } | ||
50 | + | ||
51 | + /** | ||
52 | + * Setup the initial class name on body element indicating the current route | ||
53 | + * and adds event handler to swith this class when the current page/state changes | ||
54 | + */ | ||
55 | + private setupStateClassToggle() { | ||
56 | + let bodyElement = this.getBodyElement(); | ||
57 | + bodyElement.addClass(BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX + this.$state.current.name); | ||
58 | + this.$rootScope.$on("$stateChangeSuccess", this.getStateChangeSuccessHandlerFunction(bodyElement)); | ||
59 | + } | ||
60 | + | ||
61 | + /** | ||
62 | + * Setup the initial state of the user-logged css class | ||
63 | + * and adds events handlers to switch this class when the login events happens | ||
64 | + */ | ||
65 | + private setupUserLoggedClassToggle() { | ||
66 | + let bodyElement = this.getBodyElement(); | ||
67 | + | ||
68 | + // get initial logged information from the AuthService | ||
69 | + // add add the css class when the user is authenticated | ||
70 | + if (this.authService.isAuthenticated()) { | ||
71 | + bodyElement.addClass(BodyStateClassesService.USER_LOGGED_CLASSNAME); | ||
72 | + } | ||
73 | + | ||
74 | + // listen to the AUTH_EVENTS.loginSuccess and AUTH_EVENTS.logoutSuccess | ||
75 | + // to switch the css class which indicates user logged in | ||
76 | + this.$rootScope.$on(AUTH_EVENTS.loginSuccess, () => { | ||
77 | + bodyElement.addClass(BodyStateClassesService.USER_LOGGED_CLASSNAME); | ||
78 | + }); | ||
79 | + this.$rootScope.$on(AUTH_EVENTS.logoutSuccess, () => { | ||
80 | + bodyElement.removeClass(BodyStateClassesService.USER_LOGGED_CLASSNAME); | ||
81 | + }); | ||
82 | + } | ||
83 | + | ||
84 | + /** | ||
85 | + * Returns the user 'body' html Element | ||
86 | + */ | ||
87 | + private getBodyElement(): ng.IAugmentedJQuery { | ||
88 | + if (this.bodyElement === null) { | ||
89 | + this.bodyElement = angular.element(this.$document.find("body")); | ||
90 | + } | ||
91 | + return this.bodyElement; | ||
92 | + } | ||
93 | +} | ||
0 | \ No newline at end of file | 94 | \ No newline at end of file |
src/app/login/auth.service.spec.ts
src/app/main/main.component.ts
1 | -import {bundle, Component, StateConfig} from "ng-forward"; | 1 | +import {bundle, Component, StateConfig, Inject} from "ng-forward"; |
2 | import {ArticleBlogComponent} from "./../article/types/blog/blog.component"; | 2 | import {ArticleBlogComponent} from "./../article/types/blog/blog.component"; |
3 | 3 | ||
4 | import {ArticleViewComponent} from "./../article/article-default-view.component"; | 4 | import {ArticleViewComponent} from "./../article/article-default-view.component"; |
@@ -20,6 +20,7 @@ import {SessionService} from "../login/session.service"; | @@ -20,6 +20,7 @@ import {SessionService} from "../login/session.service"; | ||
20 | 20 | ||
21 | import {NotificationService} from "../shared/services/notification.service"; | 21 | import {NotificationService} from "../shared/services/notification.service"; |
22 | 22 | ||
23 | +import {BodyStateClassesService} from "./../layout/services/body-state-classes.service"; | ||
23 | 24 | ||
24 | import {Navbar} from "../layout/navbar/navbar"; | 25 | import {Navbar} from "../layout/navbar/navbar"; |
25 | 26 | ||
@@ -41,8 +42,11 @@ import {MainBlockComponent} from "../layout/blocks/main-block/main-block.compone | @@ -41,8 +42,11 @@ import {MainBlockComponent} from "../layout/blocks/main-block/main-block.compone | ||
41 | templateUrl: "app/main/main.html", | 42 | templateUrl: "app/main/main.html", |
42 | providers: [AuthService, SessionService] | 43 | providers: [AuthService, SessionService] |
43 | }) | 44 | }) |
45 | +@Inject(BodyStateClassesService) | ||
44 | export class MainContentComponent { | 46 | export class MainContentComponent { |
45 | - | 47 | + constructor(private bodyStateClassesService: BodyStateClassesService) { |
48 | + bodyStateClassesService.start(); | ||
49 | + } | ||
46 | } | 50 | } |
47 | 51 | ||
48 | /** | 52 | /** |
@@ -67,7 +71,7 @@ export class MainContentComponent { | @@ -67,7 +71,7 @@ export class MainContentComponent { | ||
67 | MainBlockComponent, RecentDocumentsBlockComponent, Navbar, ProfileImageBlockComponent, | 71 | MainBlockComponent, RecentDocumentsBlockComponent, Navbar, ProfileImageBlockComponent, |
68 | MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent | 72 | MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent |
69 | ], | 73 | ], |
70 | - providers: [AuthService, SessionService, NotificationService] | 74 | + providers: [AuthService, SessionService, NotificationService, BodyStateClassesService] |
71 | }) | 75 | }) |
72 | @StateConfig([ | 76 | @StateConfig([ |
73 | { | 77 | { |
@@ -90,5 +94,4 @@ export class MainContentComponent { | @@ -90,5 +94,4 @@ export class MainContentComponent { | ||
90 | } | 94 | } |
91 | ]) | 95 | ]) |
92 | export class MainComponent { | 96 | export class MainComponent { |
93 | - | ||
94 | } | 97 | } |