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,8 +11,7 @@ describe("Components", () => {
11 11
12 beforeEach(angular.mock.module("templates")); 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 let comments = [{ id: 2 }, { id: 3 }]; 16 let comments = [{ id: 2 }, { id: 3 }];
18 commentService.getByArticle = jasmine.createSpy("getByArticle") 17 commentService.getByArticle = jasmine.createSpy("getByArticle")
src/app/article/comment/comments.component.ts
@@ -8,7 +8,7 @@ import { CommentComponent } from "./comment.component"; @@ -8,7 +8,7 @@ import { CommentComponent } from "./comment.component";
8 templateUrl: 'app/article/comment/comments.html', 8 templateUrl: 'app/article/comment/comments.html',
9 directives: [PostCommentComponent, CommentComponent] 9 directives: [PostCommentComponent, CommentComponent]
10 }) 10 })
11 -@Inject(CommentService, "$element") 11 +@Inject(CommentService, "$scope")
12 export class CommentsComponent { 12 export class CommentsComponent {
13 13
14 comments: noosfero.CommentViewModel[] = []; 14 comments: noosfero.CommentViewModel[] = [];
@@ -30,17 +30,28 @@ export class CommentsComponent { @@ -30,17 +30,28 @@ export class CommentsComponent {
30 this.loadNextPage(); 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 this.comments.push(comment); 50 this.comments.push(comment);
40 this.resetShowReply(); 51 this.resetShowReply();
  52 + this.$scope.$apply();
41 } 53 }
42 54
43 -  
44 loadComments() { 55 loadComments() {
45 return this.commentService.getByArticle(this.article, { page: this.page, per_page: this.perPage }); 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,8 +11,8 @@ describe(&quot;Components&quot;, () =&gt; {
11 11
12 beforeEach(angular.mock.module("templates")); 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 let user = {}; 16 let user = {};
17 let providers = [ 17 let providers = [
18 new Provider('CommentService', { useValue: commentService }), 18 new Provider('CommentService', { useValue: commentService }),
@@ -46,7 +46,7 @@ describe(&quot;Components&quot;, () =&gt; { @@ -46,7 +46,7 @@ describe(&quot;Components&quot;, () =&gt; {
46 let component: PostCommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance; 46 let component: PostCommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
47 commentService.createInArticle = jasmine.createSpy("createInArticle").and.returnValue(helpers.mocks.promiseResultTemplate({ data: {} })); 47 commentService.createInArticle = jasmine.createSpy("createInArticle").and.returnValue(helpers.mocks.promiseResultTemplate({ data: {} }));
48 component.save(); 48 component.save();
49 - expect(component.commentService.onSave.next).toHaveBeenCalled(); 49 + expect(component.commentService.emitEvent).toHaveBeenCalled();
50 done(); 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 import { Inject, Input, Output, EventEmitter, Component } from 'ng-forward'; 2 import { Inject, Input, Output, EventEmitter, Component } from 'ng-forward';
2 import { CommentService } from "../../../../lib/ng-noosfero-api/http/comment.service"; 3 import { CommentService } from "../../../../lib/ng-noosfero-api/http/comment.service";
3 import { NotificationService } from "../../../shared/services/notification.service"; 4 import { NotificationService } from "../../../shared/services/notification.service";
@@ -18,6 +19,7 @@ export class PostCommentComponent { @@ -18,6 +19,7 @@ export class PostCommentComponent {
18 @Input() article: noosfero.Article; 19 @Input() article: noosfero.Article;
19 @Input() parent: noosfero.Comment; 20 @Input() parent: noosfero.Comment;
20 @Input() comment = <noosfero.Comment>{}; 21 @Input() comment = <noosfero.Comment>{};
  22 + @Input() identifier: any;
21 private currentUser: noosfero.User; 23 private currentUser: noosfero.User;
22 24
23 constructor( 25 constructor(
@@ -33,7 +35,21 @@ export class PostCommentComponent { @@ -33,7 +35,21 @@ export class PostCommentComponent {
33 } 35 }
34 this.commentService.createInArticle(this.article, this.comment).then((result: noosfero.RestResult<noosfero.Comment>) => { 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 this.comment.body = ""; 53 this.comment.body = "";
38 this.notificationService.success({ title: "comment.post.success.title", message: "comment.post.success.message" }); 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 @@ @@ -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 import { Injectable, Inject, EventEmitter } from "ng-forward"; 1 import { Injectable, Inject, EventEmitter } from "ng-forward";
2 import {RestangularService} from "./restangular_service"; 2 import {RestangularService} from "./restangular_service";
3 import {ArticleService} from "./article.service"; 3 import {ArticleService} from "./article.service";
  4 +import {Mixins, EventService, IEvent} from '../mixins/event-service.mixin';
  5 +
4 6
5 @Injectable() 7 @Injectable()
  8 +@Mixins(EventService)
6 @Inject("Restangular", "$q", "$log", ArticleService) 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 constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService, protected articleService: ArticleService) { 16 constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService, protected articleService: ArticleService) {
12 super(Restangular, $q, $log); 17 super(Restangular, $q, $log);
@@ -33,4 +38,9 @@ export class CommentService extends RestangularService&lt;noosfero.Comment&gt; { @@ -33,4 +38,9 @@ export class CommentService extends RestangularService&lt;noosfero.Comment&gt; {
33 let articleElement = this.articleService.getElement(<number>article.id); 38 let articleElement = this.articleService.getElement(<number>article.id);
34 return this.create(comment, articleElement, null, { 'Content-Type': 'application/json' }, false); 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 @@ @@ -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,26 +16,37 @@ export class AllowCommentComponent {
16 @Input() content: string; 16 @Input() content: string;
17 @Input() paragraphUuid: string; 17 @Input() paragraphUuid: string;
18 @Input() article: noosfero.Article; 18 @Input() article: noosfero.Article;
19 - commentsCount: number; 19 + commentsCount: number = 0;
20 display = false; 20 display = false;
21 21
22 - constructor(private $scope: ng.IScope, 22 + constructor(
  23 + private $scope: ng.IScope,
23 private commentParagraphEventService: CommentParagraphEventService, 24 private commentParagraphEventService: CommentParagraphEventService,
24 private commentParagraphService: CommentParagraphService, 25 private commentParagraphService: CommentParagraphService,
25 - private commentService: CommentService) { } 26 + private commentService: CommentService
  27 + ) { }
26 28
27 ngOnInit() { 29 ngOnInit() {
28 this.commentParagraphEventService.subscribeToggleCommentParagraph((article: noosfero.Article) => { 30 this.commentParagraphEventService.subscribeToggleCommentParagraph((article: noosfero.Article) => {
29 this.article = article; 31 this.article = article;
30 this.$scope.$apply(); 32 this.$scope.$apply();
31 }); 33 });
32 -  
33 this.commentParagraphService.commentParagraphCount(this.article, this.paragraphUuid).then((count: number) => { 34 this.commentParagraphService.commentParagraphCount(this.article, this.paragraphUuid).then((count: number) => {
34 this.commentsCount = count; 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,7 +5,7 @@ import {CommentParagraphService} from &quot;../http/comment-paragraph.service&quot;;
5 5
6 @Component({ 6 @Component({
7 selector: "comment-paragraph-side-comments", 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 @Inject(CommentService, "$scope", CommentParagraphService) 10 @Inject(CommentService, "$scope", CommentParagraphService)
11 export class SideCommentsComponent extends CommentsComponent { 11 export class SideCommentsComponent extends CommentsComponent {
src/plugins/comment_paragraph/side-comments/side-comments.html 0 → 100644
@@ -0,0 +1,8 @@ @@ -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,18 +122,13 @@ export var mocks: any = {
122 instant: () => { } 122 instant: () => { }
123 }, 123 },
124 commentService: { 124 commentService: {
  125 + events: <any[]>[],
  126 + called: <string>'',
125 getByArticle: (article: noosfero.Article) => { 127 getByArticle: (article: noosfero.Article) => {
126 return Promise.resolve({ data: {} }); 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 sessionWithCurrentUser: (user: any) => { 133 sessionWithCurrentUser: (user: any) => {
139 return { 134 return {