Commit 64b348aab7aefb9b7a076e2c8f44215e929d70c6

Authored by Michel Felipe
1 parent 77280cd9

Closes #55 - EventEmitter by object structures using Typescript class Mixins

src/app/article/comment/comments.component.spec.ts
... ... @@ -11,8 +11,7 @@ describe("Components", () => {
11 11  
12 12 beforeEach(angular.mock.module("templates"));
13 13  
14   - let commentService = jasmine.createSpyObj("commentService", ["getByArticle"]);
15   - commentService.onSave = helpers.mocks.commentService.onSave;
  14 + let commentService = helpers.mocks.commentService;
16 15  
17 16 let comments = [{ id: 2 }, { id: 3 }];
18 17 commentService.getByArticle = jasmine.createSpy("getByArticle")
... ...
src/app/article/comment/comments.component.ts
... ... @@ -8,7 +8,7 @@ import { CommentComponent } from "./comment.component";
8 8 templateUrl: 'app/article/comment/comments.html',
9 9 directives: [PostCommentComponent, CommentComponent]
10 10 })
11   -@Inject(CommentService, "$element")
  11 +@Inject(CommentService, "$scope")
12 12 export class CommentsComponent {
13 13  
14 14 comments: noosfero.CommentViewModel[] = [];
... ... @@ -30,17 +30,28 @@ export class CommentsComponent {
30 30 this.loadNextPage();
31 31 }
32 32  
33   - this.commentService.onSave.subscribe((comment: noosfero.Comment) => {
34   - this.commentAdded(comment);
  33 + this.commentService.addEvent<noosfero.Comment>({
  34 + event: 'onSave',
  35 + component: 'CommentsComponent',
  36 + callback: (comment: noosfero.Comment) => {
  37 +
  38 + if (this.commentService.called !== <string>comment.id.toString()) {
  39 +
  40 + this.commentAdded(comment);
  41 + this.commentService.called = comment.id.toString();
  42 + }
  43 + }
35 44 });
  45 +
36 46 }
37 47  
38   - commentAdded(comment: noosfero.Comment): void {
  48 + commentAdded(comment: noosfero.CommentViewModel): void {
  49 + comment.__show_reply = false;
39 50 this.comments.push(comment);
40 51 this.resetShowReply();
  52 + this.$scope.$apply();
41 53 }
42 54  
43   -
44 55 loadComments() {
45 56 return this.commentService.getByArticle(this.article, { page: this.page, per_page: this.perPage });
46 57 }
... ...
src/app/article/comment/post-comment/post-comment.component.spec.ts
... ... @@ -11,8 +11,8 @@ describe(&quot;Components&quot;, () =&gt; {
11 11  
12 12 beforeEach(angular.mock.module("templates"));
13 13  
14   - let commentService = jasmine.createSpyObj("commentService", ["createInArticle"]);
15   - commentService.onSave = jasmine.createSpyObj("onSave", ["subscribe", "next"]);
  14 + let commentService = helpers.mocks.commentService;
  15 + commentService.emitEvent = jasmine.createSpy("emitEvent");
16 16 let user = {};
17 17 let providers = [
18 18 new Provider('CommentService', { useValue: commentService }),
... ... @@ -46,7 +46,7 @@ describe(&quot;Components&quot;, () =&gt; {
46 46 let component: PostCommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
47 47 commentService.createInArticle = jasmine.createSpy("createInArticle").and.returnValue(helpers.mocks.promiseResultTemplate({ data: {} }));
48 48 component.save();
49   - expect(component.commentService.onSave.next).toHaveBeenCalled();
  49 + expect(component.commentService.emitEvent).toHaveBeenCalled();
50 50 done();
51 51 });
52 52 });
... ...
src/app/article/comment/post-comment/post-comment.component.ts
  1 +import {ngClass} from 'ng-forward/cjs/testing/test-component-builder';
1 2 import { Inject, Input, Output, EventEmitter, Component } from 'ng-forward';
2 3 import { CommentService } from "../../../../lib/ng-noosfero-api/http/comment.service";
3 4 import { NotificationService } from "../../../shared/services/notification.service";
... ... @@ -18,6 +19,7 @@ export class PostCommentComponent {
18 19 @Input() article: noosfero.Article;
19 20 @Input() parent: noosfero.Comment;
20 21 @Input() comment = <noosfero.Comment>{};
  22 + @Input() identifier: any;
21 23 private currentUser: noosfero.User;
22 24  
23 25 constructor(
... ... @@ -33,7 +35,21 @@ export class PostCommentComponent {
33 35 }
34 36 this.commentService.createInArticle(this.article, this.comment).then((result: noosfero.RestResult<noosfero.Comment>) => {
35 37  
36   - this.commentService.onSave.next(result.data);
  38 + if (this.identifier) {
  39 + this.commentService.emitEvent({
  40 + id: this.identifier,
  41 + event: 'onSave',
  42 + param: result.data
  43 + });
  44 + } else {
  45 + this.commentService.emitEvent({
  46 + id: result.data.id,
  47 + component: 'CommentsComponent',
  48 + event: 'onSave',
  49 + param: result.data
  50 + });
  51 + }
  52 +
37 53 this.comment.body = "";
38 54 this.notificationService.success({ title: "comment.post.success.title", message: "comment.post.success.message" });
39 55 });
... ...
src/lib/ng-noosfero-api/decorators/mixins.ts 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +export function Mixins(...mixins: Function[]) {
  2 + return function(target: Function) {
  3 + mixins.forEach(mixin => {
  4 + Object.getOwnPropertyNames(mixin.prototype).forEach(name => {
  5 + target.prototype[name] = mixin.prototype[name];
  6 + });
  7 + });
  8 + };
  9 +}
... ...
src/lib/ng-noosfero-api/http/comment.service.ts
1 1 import { Injectable, Inject, EventEmitter } from "ng-forward";
2 2 import {RestangularService} from "./restangular_service";
3 3 import {ArticleService} from "./article.service";
  4 +import {Mixins, EventService, IEvent} from '../mixins/event-service.mixin';
  5 +
4 6  
5 7 @Injectable()
  8 +@Mixins(EventService)
6 9 @Inject("Restangular", "$q", "$log", ArticleService)
7   -export class CommentService extends RestangularService<noosfero.Comment> {
  10 +export class CommentService extends RestangularService<noosfero.Comment> implements EventService {
  11 +
  12 + events: IEvent[] = [];
8 13  
9   - public onSave: EventEmitter<noosfero.Comment> = new EventEmitter<noosfero.Comment>();
  14 + called: string;
10 15  
11 16 constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService, protected articleService: ArticleService) {
12 17 super(Restangular, $q, $log);
... ... @@ -33,4 +38,9 @@ export class CommentService extends RestangularService&lt;noosfero.Comment&gt; {
33 38 let articleElement = this.articleService.getElement(<number>article.id);
34 39 return this.create(comment, articleElement, null, { 'Content-Type': 'application/json' }, false);
35 40 }
  41 +
  42 + // Mixin "EventService" declarations
  43 + addEvent: <T>(data: { event: string, callback: Function, id?: any, component?: string, scope?: any }) => void;
  44 + getEvents: (data: { event: string, id?: any, component?: string }) => void;
  45 + emitEvent: <T>(data: { event: string, param: T, id?: any, component?: string }) => void;
36 46 }
... ...
src/lib/ng-noosfero-api/mixins/event-service.mixin.ts 0 → 100644
... ... @@ -0,0 +1,104 @@
  1 +import { EventEmitter } from "ng-forward";
  2 +export {Mixins} from "../decorators/mixins";
  3 +
  4 +export interface IEvent {
  5 + id: any;
  6 + component: string;
  7 + emitters: {
  8 + onSave?: EventEmitter<noosfero.Comment>
  9 + };
  10 +}
  11 +
  12 +/**
  13 + * @ngdoc object
  14 + * @name mixins.EventService
  15 + * @see Typescript official documentation https://www.typescriptlang.org/docs/mixins.html
  16 + * @description
  17 + *
  18 + * A mixin class to be implemented by another class (e.g. Angular Services)
  19 + * to emit's and subscribe events
  20 + *
  21 + * <pre>
  22 + * @Mixins(EventService)
  23 + * export class MyService implements EventService {
  24 + *
  25 + * }
  26 + * </pre>
  27 + */
  28 +export class EventService {
  29 +
  30 + events: IEvent[] = [];
  31 +
  32 + called: string;
  33 +
  34 + addEvent<T>(data: { event: string, callback: Function, id?: any, component?: string, scope?: any }) {
  35 +
  36 + let hasEvent = false;
  37 +
  38 + if (this.events.length > 0) {
  39 +
  40 + this.events.forEach((eventData: IEvent, index: number) => {
  41 + if (data.id === eventData.id) {
  42 +
  43 + if (!(<any>this.events)[index].emitters[data.event]) {
  44 + (<any>this.events)[index].emitters[data.event] = new EventEmitter();
  45 + } else {
  46 + (<EventEmitter<T>>(<any>this.events)[index].emitters[data.event]).subscribe(data.callback.bind(data.scope || this));
  47 + }
  48 +
  49 + return (hasEvent = true);
  50 + }
  51 + });
  52 + }
  53 +
  54 + if (!hasEvent) {
  55 +
  56 + let newEvent: IEvent = {
  57 + id: data.id,
  58 + component: data.component,
  59 + emitters: <any>{}
  60 + };
  61 +
  62 + (<any>newEvent.emitters)[data.event] = <EventEmitter<T>>new EventEmitter();
  63 + (<any>newEvent.emitters)[data.event].subscribe(data.callback);
  64 +
  65 + this.events.push(newEvent);
  66 + }
  67 + }
  68 +
  69 + getEvents(data: { event: string, id?: any, component?: string }): any {
  70 + let result: any = {};
  71 +
  72 + for (let i = 0; i < this.events.length; i++) {
  73 + if (data.id === (<Array<IEvent>>this.events)[i].id && !data.component) {
  74 +
  75 + result = (<any>this.events)[i].emitters[data.event];
  76 + break;
  77 + } else if (data.component === (<any>this.events)[i].component) {
  78 + result[data.component] = (<any>this.events)[i].emitters[data.event];
  79 +
  80 + break;
  81 + } else {
  82 + result[(<any>this.events)[i].component] = (<any>this.events)[i].emitters[data.event];
  83 + }
  84 + }
  85 +
  86 + return result;
  87 + }
  88 +
  89 + emitEvent<T>(data: { event: string, param: T, id?: any, component?: string }) {
  90 +
  91 + let eventOrEmitters = this.getEvents(data);
  92 +
  93 + if (eventOrEmitters instanceof EventEmitter) {
  94 +
  95 + eventOrEmitters.next(data.param);
  96 + } else {
  97 +
  98 + Object.keys(eventOrEmitters).forEach((emitter: string) => {
  99 + (<EventEmitter<T>>eventOrEmitters[emitter]).next(data.param);
  100 + });
  101 +
  102 + }
  103 + }
  104 +}
... ...
src/plugins/comment_paragraph/allow-comment/allow-comment.component.ts
... ... @@ -16,26 +16,37 @@ export class AllowCommentComponent {
16 16 @Input() content: string;
17 17 @Input() paragraphUuid: string;
18 18 @Input() article: noosfero.Article;
19   - commentsCount: number;
  19 + commentsCount: number = 0;
20 20 display = false;
21 21  
22   - constructor(private $scope: ng.IScope,
  22 + constructor(
  23 + private $scope: ng.IScope,
23 24 private commentParagraphEventService: CommentParagraphEventService,
24 25 private commentParagraphService: CommentParagraphService,
25   - private commentService: CommentService) { }
  26 + private commentService: CommentService
  27 + ) { }
26 28  
27 29 ngOnInit() {
28 30 this.commentParagraphEventService.subscribeToggleCommentParagraph((article: noosfero.Article) => {
29 31 this.article = article;
30 32 this.$scope.$apply();
31 33 });
32   -
33 34 this.commentParagraphService.commentParagraphCount(this.article, this.paragraphUuid).then((count: number) => {
34 35 this.commentsCount = count;
35 36 });
36 37  
37   - this.commentService.onSave.subscribe((comment: noosfero.Comment) => {
38   - this.commentsCount++;
  38 + this.commentService.addEvent<noosfero.Comment>({
  39 + id: this.paragraphUuid,
  40 + event: 'onSave',
  41 + component: 'SideCommentsComponent',
  42 + scope: this,
  43 + callback: () => {
  44 + if (!this.commentsCount) {
  45 + this.commentsCount = 0;
  46 + }
  47 + this.commentsCount++;
  48 + this.$scope.$apply();
  49 + }
39 50 });
40 51 }
41 52  
... ...
src/plugins/comment_paragraph/side-comments/side-comments.component.ts
... ... @@ -5,7 +5,7 @@ import {CommentParagraphService} from &quot;../http/comment-paragraph.service&quot;;
5 5  
6 6 @Component({
7 7 selector: "comment-paragraph-side-comments",
8   - templateUrl: 'app/article/comment/comments.html',
  8 + templateUrl: 'plugins/comment_paragraph/side-comments/side-comments.html',
9 9 })
10 10 @Inject(CommentService, "$scope", CommentParagraphService)
11 11 export class SideCommentsComponent extends CommentsComponent {
... ...
src/plugins/comment_paragraph/side-comments/side-comments.html 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +<div class="comments">
  2 + <noosfero-post-comment ng-if="ctrl.showForm" [article]="ctrl.article" [parent]="ctrl.parent" [comment]="ctrl.newComment" [identifier]="ctrl.paragraphUuid"></noosfero-post-comment>
  3 +
  4 + <div class="comments-list">
  5 + <noosfero-comment ng-repeat="comment in ctrl.comments | orderBy: 'created_at':true" [comment]="comment" [article]="ctrl.article"></noosfero-comment>
  6 + </div>
  7 + <button type="button" ng-if="ctrl.displayMore()" class="more-comments btn btn-default btn-block" ng-click="ctrl.loadNextPage()">{{"comment.pagination.more" | translate}}</button>
  8 +</div>
... ...
src/spec/mocks.ts
... ... @@ -122,18 +122,13 @@ export var mocks: any = {
122 122 instant: () => { }
123 123 },
124 124 commentService: {
  125 + events: <any[]>[],
  126 + called: <string>'',
125 127 getByArticle: (article: noosfero.Article) => {
126 128 return Promise.resolve({ data: {} });
127 129 },
128   - onSave: {
129   - event: Function,
130   - subscribe: (fn: Function) => {
131   - mocks.commentService['onSave'].event = fn;
132   - },
133   - next: (param: any) => {
134   - mocks.commentService['onSave'].event(param);
135   - }
136   - }
  130 + addEvent: () => { },
  131 + emitEvent: () => { }
137 132 },
138 133 sessionWithCurrentUser: (user: any) => {
139 134 return {
... ...