diff --git a/ng2-components/ng2-activiti-tasklist/README.md b/ng2-components/ng2-activiti-tasklist/README.md index e3b71364ba..dd176ad64a 100644 --- a/ng2-components/ng2-activiti-tasklist/README.md +++ b/ng2-components/ng2-activiti-tasklist/README.md @@ -425,6 +425,31 @@ The purpose of the component is populate the local variable called `properties` "description": "string" } ``` +## Task People Component + +This component displays involved users to a specified task + +```html + + +``` + +![activiti-people](docs/assets/activiti_people.png) + +### Properties + +| Name | Type | Description | +| --- | --- | --- | +| people | User[] | The array of User object to display | +| taskId | string | The numeric ID of the task | +| readOnly | boolean | The boolean flag | + +#### Events + +No Events ## Build from sources diff --git a/ng2-components/ng2-activiti-tasklist/docs/assets/activiti_people.png b/ng2-components/ng2-activiti-tasklist/docs/assets/activiti_people.png new file mode 100644 index 0000000000..42c7c41728 Binary files /dev/null and b/ng2-components/ng2-activiti-tasklist/docs/assets/activiti_people.png differ diff --git a/ng2-components/ng2-activiti-tasklist/index.ts b/ng2-components/ng2-activiti-tasklist/index.ts index 111c2c9862..6f8d987063 100644 --- a/ng2-components/ng2-activiti-tasklist/index.ts +++ b/ng2-components/ng2-activiti-tasklist/index.ts @@ -36,11 +36,13 @@ import { ActivitiStartTaskButton, ActivitiPeopleSearch, TaskAttachmentListComponent, - ActivitiCreateTaskAttachmentComponent + ActivitiCreateTaskAttachmentComponent, + ActivitiPeopleList } from './src/components/index'; export * from './src/components/index'; export * from './src/services/activiti-tasklist.service'; +export * from './src/services/activiti-people.service'; export * from './src/models/index'; export const ACTIVITI_TASKLIST_DIRECTIVES: any[] = [ @@ -56,7 +58,8 @@ export const ACTIVITI_TASKLIST_DIRECTIVES: any[] = [ ActivitiStartTaskButton, ActivitiPeopleSearch, TaskAttachmentListComponent, - ActivitiCreateTaskAttachmentComponent + ActivitiCreateTaskAttachmentComponent, + ActivitiPeopleList ]; export const ACTIVITI_TASKLIST_PROVIDERS: any[] = [ diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-list.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-list.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-list.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-list.component.html new file mode 100644 index 0000000000..ad652783f0 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-list.component.html @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-list.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-list.component.spec.ts new file mode 100644 index 0000000000..3ec6e1a778 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-list.component.spec.ts @@ -0,0 +1,100 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { Observable } from 'rxjs/Observable'; +import { CoreModule, AlfrescoTranslationService } from 'ng2-alfresco-core'; +import { ActivitiPeopleList } from './activiti-people-list.component'; +import { User, UserEventModel } from '../models/index'; +import { DataTableModule, ObjectDataRow, DataRowEvent, DataRowActionEvent } from 'ng2-alfresco-datatable'; + +declare let jasmine: any; + +const fakeUser: User = new User({ + id: '1', + firstName: 'fake-name', + lastName: 'fake-last', + email: 'fake@mail.com' +}); + +describe('ActivitiPeopleList', () => { + + let activitiPeopleListComponent: ActivitiPeopleList; + let fixture: ComponentFixture; + let element: HTMLElement; + let componentHandler; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CoreModule.forRoot(), + DataTableModule + ], + declarations: [ + ActivitiPeopleList + ] + }).compileComponents().then(() => { + + let translateService = TestBed.get(AlfrescoTranslationService); + spyOn(translateService, 'addTranslationFolder').and.stub(); + spyOn(translateService.translate, 'get').and.callFake((key) => { return Observable.of(key); }); + + fixture = TestBed.createComponent(ActivitiPeopleList); + activitiPeopleListComponent = fixture.componentInstance; + element = fixture.nativeElement; + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + + window['componentHandler'] = componentHandler; + fixture.detectChanges(); + }); + })); + + it('should emit row click event', (done) => { + let row = new ObjectDataRow(fakeUser); + let rowEvent = new DataRowEvent(row, null); + + activitiPeopleListComponent.clickRow.subscribe(selectedUser => { + expect(selectedUser.id).toEqual('1'); + expect(selectedUser.email).toEqual('fake@mail.com'); + expect(activitiPeopleListComponent.user.id).toEqual('1'); + expect(activitiPeopleListComponent.user.email).toEqual('fake@mail.com'); + done(); + }); + + activitiPeopleListComponent.selectUser(rowEvent); + }); + + it('should emit row action event', (done) => { + let row = new ObjectDataRow(fakeUser); + let removeObj = { + name: 'remove', + title: 'Remove' + }; + let rowActionEvent = new DataRowActionEvent(row, removeObj); + + activitiPeopleListComponent.clickAction.subscribe((selectedAction: UserEventModel) => { + expect(selectedAction.type).toEqual('remove'); + expect(selectedAction.value.id).toEqual('1'); + expect(selectedAction.value.email).toEqual('fake@mail.com'); + done(); + }); + + activitiPeopleListComponent.onExecuteRowAction(rowActionEvent); + }); +}); diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-list.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-list.component.ts new file mode 100644 index 0000000000..eeb4645130 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-list.component.ts @@ -0,0 +1,99 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, Input, Output, EventEmitter, ViewChild, ContentChild } from '@angular/core'; +import { User, UserEventModel } from '../models/index'; +import { DataColumnListComponent } from 'ng2-alfresco-core'; +import { DataTableComponent } from 'ng2-alfresco-datatable'; + +declare let componentHandler: any; + +@Component({ + selector: 'activiti-people-list', + templateUrl: './activiti-people-list.component.html', + styleUrls: ['./activiti-people-list.component.css'] +}) + +export class ActivitiPeopleList { + + @ContentChild(DataColumnListComponent) columnList: DataColumnListComponent; + + @ViewChild(DataTableComponent) + peopleDataTable: DataTableComponent; + + @Input() + users: User[]; + + @Input() + actions: boolean = false; + + @Output() + clickRow: EventEmitter = new EventEmitter(); + + @Output() + clickAction: EventEmitter = new EventEmitter(); + + user: User; + + constructor() { + } + + ngAfterContentInit() { + this.peopleDataTable.columnList = this.columnList; + } + + ngAfterViewInit() { + this.setupMaterialComponents(componentHandler); + } + + setupMaterialComponents(handler?: any): boolean { + // workaround for MDL issues with dynamic components + let isUpgraded: boolean = false; + if (handler) { + handler.upgradeAllRegistered(); + isUpgraded = true; + } + return isUpgraded; + } + + selectUser(event: any) { + this.user = event.value.obj; + this.clickRow.emit(this.user); + } + + hasActions(): boolean { + return this.actions; + } + + onShowRowActionsMenu(event: any) { + + let removeAction = { + title: 'Remove', + name: 'remove' + }; + + event.value.actions = [ + removeAction + ]; + } + + onExecuteRowAction(event: any) { + let args = event.value; + let action = args.action; + this.clickAction.emit(new UserEventModel({type: action.name, value: args.row.obj})); + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.css index 2bbb854137..aeed7723a1 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.css +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.css @@ -33,4 +33,60 @@ .mdl-chip-search-people__text{ padding-left: 10px; -} \ No newline at end of file +} + +.search-text-header{ + font-weight: bold; + opacity: 0.54; +} + +.search-list-container{ + max-height: 152px; + width: 100%; + overflow-y: auto; +} + +activiti-people-list >>> alfresco-datatable >>> thead { + display: none; +} + +.search-list-action-container { + border-top: 1px solid #eee; + text-align: right; + padding: 5px 0px; + margin-top: 5px; +} + +.search-list-action-container>button{ + opacity: 0.54; + font-weight: bolder; +} + +.search-list-action-container>button:hover{ + color: rgb(255, 152, 0); +} + +activiti-people-list >>> alfresco-datatable >>> .people-full-name { + font-family: 'Muli'; +} + +activiti-people-list >>> alfresco-datatable >>> .people-pic { + background: #ffc800; + padding: 10px 6px; + border-radius: 100px; + color: #fff; + text-align: center; + font-weight: bolder; + font-size: 16px; + font-family: Muli; + text-transform: uppercase; + min-width: 30px; +} + +activiti-people-list >>> alfresco-datatable >>> td.mdl-data-table__cell--non-numeric.non-selectable.data-cell{ + padding: 4px 12px; +} + +.mdl-textfield { + width: 100%; +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.html index 106ad72f01..c9d407a6a1 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.html +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.html @@ -1,15 +1,33 @@ -
- - -
-
    -
  • - -
  • -
    - {{'PEOPLE.SEARCH.NO_USERS' | translate }} +
    {{ 'TASK_DETAILS.LABELS.ADD_PEOPLE' | translate }}
    +
    +
    + +
    -
+ +
+ + + + +
{{getInitialUserName(entry.row.obj.firstName, entry.row.obj.lastName)}}
+
+
+ + +
{{ getDisplayUser(entry.row.obj.firstName, entry.row.obj.lastName, ' ') }}
+
+
+
+
+
+
+ + +
\ No newline at end of file diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.spec.ts index 3def161a37..04a7676d6e 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.spec.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.spec.ts @@ -19,6 +19,8 @@ import { ComponentFixture, TestBed, async } from '@angular/core/testing'; import { Observable } from 'rxjs/Observable'; import { CoreModule, AlfrescoTranslationService } from 'ng2-alfresco-core'; import { ActivitiPeopleSearch } from './activiti-people-search.component'; +import { ActivitiPeopleList } from './activiti-people-list.component'; +import { DataTableModule } from 'ng2-alfresco-datatable'; import { User } from '../models/user.model'; declare let jasmine: any; @@ -49,10 +51,12 @@ describe('ActivitiPeopleSearch', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ - CoreModule.forRoot() + CoreModule.forRoot(), + DataTableModule ], declarations: [ - ActivitiPeopleSearch + ActivitiPeopleSearch, + ActivitiPeopleList ] }).compileComponents().then(() => { @@ -78,28 +82,25 @@ describe('ActivitiPeopleSearch', () => { expect(element.querySelector('#userSearchText')).not.toBeNull(); }); - it('should show no user found to involve message', () => { + it('should hide people-list container', () => { fixture.detectChanges(); fixture.whenStable() .then(() => { - expect(element.querySelector('#no-user-found')).not.toBeNull(); - expect(element.querySelector('#no-user-found').textContent).toContain('PEOPLE.SEARCH.NO_USERS'); + expect(element.querySelector('#search-people-list')).toBeNull(); }); + }); it('should show user which can be involved ', (done) => { - activitiPeopleSearchComponent.onSearch.subscribe(() => { + activitiPeopleSearchComponent.searchPeople.subscribe(() => { activitiPeopleSearchComponent.results = Observable.of(userArray); activitiPeopleSearchComponent.ngOnInit(); fixture.detectChanges(); fixture.whenStable() .then(() => { - expect(element.querySelector('#user-1')).not.toBeNull(); - expect(element.querySelector('#user-1').textContent) - .toContain('fake-name - fake-last'); - expect(element.querySelector('#user-2')).not.toBeNull(); - expect(element.querySelector('#user-2').textContent) - .toContain('fake-involve-name - fake-involve-last'); + let gatewayElement: any = element.querySelector('#search-people-list tbody'); + expect(gatewayElement).not.toBeNull(); + expect(gatewayElement.children.length).toBe(2); done(); }); }); @@ -110,7 +111,7 @@ describe('ActivitiPeopleSearch', () => { }); it('should send an event when an user is clicked', (done) => { - activitiPeopleSearchComponent.onRowClicked.subscribe((user) => { + activitiPeopleSearchComponent.success.subscribe((user) => { expect(user).toBeDefined(); expect(user.firstName).toBe('fake-name'); done(); @@ -120,8 +121,9 @@ describe('ActivitiPeopleSearch', () => { fixture.detectChanges(); fixture.whenStable() .then(() => { - let userToSelect = element.querySelector('#user-1'); - userToSelect.click(); + activitiPeopleSearchComponent.onRowClick(fakeUser); + let addUserButton = element.querySelector('#add-people'); + addUserButton.click(); }); }); @@ -129,13 +131,16 @@ describe('ActivitiPeopleSearch', () => { activitiPeopleSearchComponent.results = Observable.of(userArray); activitiPeopleSearchComponent.ngOnInit(); fixture.detectChanges(); - let userToSelect = element.querySelector('#user-1'); - userToSelect.click(); + activitiPeopleSearchComponent.onRowClick(fakeUser); + let addUserButton = element.querySelector('#add-people'); + addUserButton.click(); fixture.detectChanges(); fixture.whenStable() .then(() => { - expect(element.querySelector('#user-1')).toBeNull(); + let gatewayElement: any = element.querySelector('#search-people-list tbody'); + expect(gatewayElement).not.toBeNull(); + expect(gatewayElement.children.length).toBe(1); done(); }); }); diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.ts index e5425d136d..ffb6be6235 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.ts @@ -32,21 +32,23 @@ declare var require: any; export class ActivitiPeopleSearch implements OnInit, AfterViewInit { - @Input() - iconImageUrl: string = require('../assets/images/user.jpg'); - @Input() results: Observable; @Output() - onSearch: EventEmitter = new EventEmitter(); + searchPeople: EventEmitter = new EventEmitter(); @Output() - onRowClicked: EventEmitter = new EventEmitter(); + success: EventEmitter = new EventEmitter(); + + @Output() + closeSearch = new EventEmitter(); searchUser: FormControl = new FormControl(); - userList: User[] = []; + users: User[] = []; + + selectedUser: User; constructor(private translateService: AlfrescoTranslationService) { if (translateService) { @@ -58,16 +60,16 @@ export class ActivitiPeopleSearch implements OnInit, AfterViewInit { .debounceTime(200) .subscribe((event: string) => { if (event && event.trim()) { - this.onSearch.emit(event); + this.searchPeople.emit(event); } else { - this.userList = []; + this.users = []; } }); } ngOnInit() { this.results.subscribe((list) => { - this.userList = list; + this.users = list; }); } @@ -85,17 +87,38 @@ export class ActivitiPeopleSearch implements OnInit, AfterViewInit { return isUpgraded; } - onRowClick(userClicked: User) { - this.onRowClicked.emit(userClicked); - this.userList = this.userList.filter((user) => { + onRowClick(user: User) { + this.selectedUser = user; + } + + closeSearchList() { + this.closeSearch.emit(); + } + + addInvolvedUser() { + if (this.selectedUser === undefined) { + return; + } + this.success.emit(this.selectedUser); + this.users = this.users.filter((user) => { this.searchUser.reset(); - return user.id !== userClicked.id; + return user.id !== this.selectedUser.id; }); } - getDisplayUser(user: User): string { - let firstName = user.firstName && user.firstName !== 'null' ? user.firstName : 'N/A'; - let lastName = user.lastName && user.lastName !== 'null' ? user.lastName : 'N/A'; - return firstName + ' - ' + lastName; + getDisplayUser(firstName: string, lastName: string, delimiter: string = '-'): string { + firstName = (firstName !== null ? firstName : ''); + lastName = (lastName !== null ? lastName : ''); + return firstName + delimiter + lastName; + } + + getInitialUserName(firstName: string, lastName: string) { + firstName = (firstName !== null && firstName !== '' ? firstName[0] : ''); + lastName = (lastName !== null && lastName !== '' ? lastName[0] : ''); + return this.getDisplayUser(firstName, lastName, ''); + } + + hasUsers() { + return (this.users && this.users.length > 0); } } diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.css index c618a45766..a1bce0db70 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.css +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.css @@ -1,23 +1,74 @@ -:host { +.assignment-header{ + width: 100%; + border-bottom: 1px solid #eee; + padding: 6px 20px; +} + +.assigment-count{ + float: left; + padding: 10px 0px; + font-weight: bolder; + font-family: Muli; + opacity: 0.54; +} + +.add-people{ + float: right; + padding: 8px; + height: 26px; + opacity: 0.54; + cursor: pointer; +} + +.add-people:hover{ + color: #ff9100; +} + +.assignment-top-container{ + border-top: 2px solid #eee; + margin: 8px; + padding: 0px; +} + +.assignment-container{ + padding: 10px 20px; + border-bottom: 1px solid #eee; width: 100%; } -.activiti-label { +.assignment-list-container { + padding: 0px; +} + +activiti-people-list >>> alfresco-datatable >>> thead { + display: none; +} + +activiti-people-list >>> alfresco-datatable >>> .people-full-name { + font-family: 'Muli'; +} + +activiti-people-list >>> alfresco-datatable >>> .people-email { + font-family: 'Muli'; + opacity: 0.54; +} + +activiti-people-list >>> alfresco-datatable >>> .people-edit-label { + font-family: 'Muli'; +} + +activiti-people-list >>> alfresco-datatable >>> .people-pic { + background: #ffc800; + padding: 12px 10px; + border-radius: 100px; + color: #fff; + text-align: center; font-weight: bolder; + font-size: 18px; + font-family: Muli; + text-transform: uppercase } -.material-icons.people__icon:hover { - color: rgb(255, 152, 0); -} - -.add-people-dialog__content { - padding: 20px 24px 2px; -} - -.mdl-tooltip { - will-change: unset; -} - -.material-icons { - cursor: pointer; +activiti-people-list >>> alfresco-datatable >>> td.mdl-data-table__cell--non-numeric.non-selectable.data-cell{ + padding: 10px; } diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.html index 2d7e3f0a50..409a1aace5 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.html +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.html @@ -1,36 +1,42 @@ -{{ 'TASK_DETAILS.LABELS.PEOPLE' | translate }} -
add
-
- Add a person -
- -
- {{ 'TASK_DETAILS.PEOPLE.NONE' | translate }} -
- - -

Involve User

-
- +
+
+
+ {{ 'TASK_DETAILS.LABELS.PEOPLE' | translate }} {{ ' (' + people.length + ')' }} +
+
+ {{ 'TASK_DETAILS.PEOPLE.NONE' | translate }} +
+
+ person_add +
+
+
+
-
- +
+ + + + +
{{getInitialUserName(entry.row.obj.firstName, entry.row.obj.lastName)}}
+
+
+ + +
{{ getDisplayUser(entry.row.obj.firstName, entry.row.obj.lastName, ' ') }}
+
{{ entry.row.obj.email }}
+
can edit
+
+
+
+
-
+ diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.spec.ts index f1b00410ed..ee251b9b5f 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.spec.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.spec.ts @@ -16,12 +16,14 @@ */ import { ComponentFixture, TestBed, async } from '@angular/core/testing'; -import { Observable } from 'rxjs/Rx'; +import { Observable } from 'rxjs/Observable'; import { CoreModule, AlfrescoTranslationService, LogService } from 'ng2-alfresco-core'; -import { ActivitiPeopleService } from '../services/activiti-people.service'; -import { ActivitiPeople } from './activiti-people.component'; import { ActivitiPeopleSearch } from './activiti-people-search.component'; +import { ActivitiPeopleList } from './activiti-people-list.component'; +import { ActivitiPeople } from './activiti-people.component'; +import { DataTableModule } from 'ng2-alfresco-datatable'; import { User } from '../models/user.model'; +import { ActivitiPeopleService } from '../services/activiti-people.service'; declare let jasmine: any; @@ -32,7 +34,7 @@ const fakeUser: User = new User({ email: 'fake@mail.com' }); -const fakeUserToInvolve: User = new User({ +const fakeSecondUser: User = new User({ id: 'fake-involve-id', firstName: 'fake-involve-name', lastName: 'fake-involve-last', @@ -45,21 +47,25 @@ describe('ActivitiPeople', () => { let fixture: ComponentFixture; let element: HTMLElement; let componentHandler; + let userArray = [fakeUser, fakeSecondUser]; let logService: LogService; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ - CoreModule.forRoot() + CoreModule.forRoot(), + DataTableModule ], declarations: [ - ActivitiPeople, - ActivitiPeopleSearch + ActivitiPeopleSearch, + ActivitiPeopleList, + ActivitiPeople ], providers: [ ActivitiPeopleService ] }).compileComponents().then(() => { + logService = TestBed.get(LogService); let translateService = TestBed.get(AlfrescoTranslationService); @@ -74,16 +80,24 @@ describe('ActivitiPeople', () => { ]); window['componentHandler'] = componentHandler; + + activitiPeopleComponent.people = []; + activitiPeopleComponent.readOnly = true; + fixture.detectChanges(); }); })); - it('should show people component title', () => { - expect(element.querySelector('#people-title')).toBeDefined(); - expect(element.querySelector('#people-title')).not.toBeNull(); - }); + it('should show people component title', async(() => { + activitiPeopleComponent.people = [...userArray]; + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#people-title')).toBeDefined(); + expect(element.querySelector('#people-title')).not.toBeNull(); + }); + })); it('should show no people involved message', () => { - fixture.detectChanges(); fixture.whenStable() .then(() => { expect(element.querySelector('#no-people-label')).not.toBeNull(); @@ -91,51 +105,11 @@ describe('ActivitiPeople', () => { }); }); - describe('when interact with people dialog', () => { - - beforeEach(() => { - activitiPeopleComponent.taskId = 'fake-task-id'; - activitiPeopleComponent.people = []; - fixture.detectChanges(); - }); - - it('should show dialog when clicked on add', () => { - expect(element.querySelector('#addPeople')).not.toBeNull(); - activitiPeopleComponent.showDialog(); - - expect(element.querySelector('#add-people-dialog')).not.toBeNull(); - expect(element.querySelector('#add-people-dialog-title')).not.toBeNull(); - expect(element.querySelector('#add-people-dialog-title').textContent).toContain('Involve User'); - }); - - it('should close dialog when clicked on cancel', () => { - activitiPeopleComponent.showDialog(); - expect(element.querySelector('#addPeople')).not.toBeNull(); - activitiPeopleComponent.closeDialog(); - let dialogWindow = element.querySelector('#add-people-dialog'); - expect(dialogWindow.getAttribute('open')).toBeNull(); - }); - - it('should reset search input when the dialog is closed', () => { - let userInputSearch: HTMLInputElement; - activitiPeopleComponent.showDialog(); - expect(element.querySelector('#addPeople')).not.toBeNull(); - userInputSearch = element.querySelector('#userSearchText'); - userInputSearch.value = 'fake-search-value'; - activitiPeopleComponent.closeDialog(); - activitiPeopleComponent.showDialog(); - userInputSearch = element.querySelector('#userSearchText'); - - expect(userInputSearch).not.toBeNull(); - expect(userInputSearch.value).toBeFalsy(); - }); - }); - describe('when there are involved people', () => { beforeEach(() => { activitiPeopleComponent.taskId = 'fake-task-id'; - activitiPeopleComponent.people.push(fakeUser); + activitiPeopleComponent.people.push(...userArray); fixture.detectChanges(); }); @@ -147,11 +121,14 @@ describe('ActivitiPeople', () => { jasmine.Ajax.uninstall(); }); - it('should show people involved', () => { - expect(element.querySelector('#user-fake-id')).not.toBeNull(); - expect(element.querySelector('#user-fake-id').textContent).toContain('fake-name'); - expect(element.querySelector('#user-fake-id').textContent).toContain('fake-last'); - }); + it('should show people involved', async(() => { + fixture.whenStable() + .then(() => { + let gatewayElement: any = element.querySelector('#assignment-people-list tbody'); + expect(gatewayElement).not.toBeNull(); + expect(gatewayElement.children.length).toBe(2); + }); + })); it('should remove pepole involved', async(() => { activitiPeopleComponent.removeInvolvedUser(fakeUser); @@ -161,21 +138,23 @@ describe('ActivitiPeople', () => { fixture.whenStable() .then(() => { fixture.detectChanges(); - expect(element.querySelector('#user-fake-id')).toBeNull(); + let gatewayElement: any = element.querySelector('#assignment-people-list tbody'); + expect(gatewayElement).not.toBeNull(); + expect(gatewayElement.children.length).toBe(1); }); })); it('should involve pepole', async(() => { - activitiPeopleComponent.involveUser(fakeUserToInvolve); + activitiPeopleComponent.involveUser(fakeUser); jasmine.Ajax.requests.mostRecent().respondWith({ status: 200 }); fixture.whenStable() .then(() => { fixture.detectChanges(); - expect(element.querySelector('#user-fake-involve-id')).not.toBeNull(); - expect(element.querySelector('#user-fake-involve-id').textContent) - .toBe('fake-involve-name fake-involve-last'); + let gatewayElement: any = element.querySelector('#assignment-people-list tbody'); + expect(gatewayElement).not.toBeNull(); + expect(gatewayElement.children.length).toBe(3); }); })); @@ -226,6 +205,8 @@ describe('ActivitiPeople', () => { beforeEach(() => { jasmine.Ajax.install(); + activitiPeopleComponent.people.push(...userArray); + fixture.detectChanges(); }); afterEach(() => { @@ -243,8 +224,6 @@ describe('ActivitiPeople', () => { })); it('should not remove user if remove involved user fail', async(() => { - activitiPeopleComponent.people.push(fakeUser); - fixture.detectChanges(); activitiPeopleComponent.removeInvolvedUser(fakeUser); jasmine.Ajax.requests.mostRecent().respondWith({ status: 403 @@ -252,22 +231,23 @@ describe('ActivitiPeople', () => { fixture.whenStable() .then(() => { fixture.detectChanges(); - expect(element.querySelector('#user-fake-id')).not.toBeNull(); - expect(element.querySelector('#user-fake-id').textContent) - .toBe('fake-name fake-last'); + let gatewayElement: any = element.querySelector('#assignment-people-list tbody'); + expect(gatewayElement).not.toBeNull(); + expect(gatewayElement.children.length).toBe(2); }); })); it('should not involve user if involve user fail', async(() => { - activitiPeopleComponent.involveUser(fakeUserToInvolve); + activitiPeopleComponent.involveUser(fakeUser); jasmine.Ajax.requests.mostRecent().respondWith({ status: 403 }); fixture.whenStable() .then(() => { fixture.detectChanges(); - expect(element.querySelector('#user-fake-id')).toBeNull(); - expect(element.querySelector('#no-people-label').textContent).toContain('TASK_DETAILS.PEOPLE.NONE'); + let gatewayElement: any = element.querySelector('#assignment-people-list tbody'); + expect(gatewayElement).not.toBeNull(); + expect(gatewayElement.children.length).toBe(2); }); })); }); diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.ts index da7a46e4ad..3bc010b3f8 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.ts @@ -15,13 +15,13 @@ * limitations under the License. */ -import { Component, Input, ViewChild } from '@angular/core'; +import { Component, Input, AfterViewInit } from '@angular/core'; import { Observer, Observable } from 'rxjs/Rx'; import { AlfrescoTranslationService, LogService } from 'ng2-alfresco-core'; -import { User } from '../models/user.model'; +import { User, UserEventModel } from '../models/index'; import { ActivitiPeopleService } from '../services/activiti-people.service'; -declare let dialogPolyfill: any; +declare let componentHandler: any; declare var require: any; @Component({ @@ -29,7 +29,7 @@ declare var require: any; templateUrl: './activiti-people.component.html', styleUrls: ['./activiti-people.component.css'] }) -export class ActivitiPeople { +export class ActivitiPeople implements AfterViewInit { @Input() iconImageUrl: string = require('../assets/images/user.jpg'); @@ -43,11 +43,7 @@ export class ActivitiPeople { @Input() readOnly: boolean = false; - @ViewChild('dialog') - dialog: any; - - @ViewChild('activitipeoplesearch') - activitipeoplesearch: any; + showAssignment: boolean = false; private peopleSearchObserver: Observer; peopleSearch$: Observable; @@ -66,21 +62,18 @@ export class ActivitiPeople { this.peopleSearch$ = new Observable(observer => this.peopleSearchObserver = observer).share(); } - public showDialog() { - if (!this.dialog.nativeElement.showModal) { - dialogPolyfill.registerDialog(this.dialog.nativeElement); - } - if (this.dialog) { - this.dialog.nativeElement.showModal(); - } + ngAfterViewInit() { + this.setupMaterialComponents(componentHandler); } - public closeDialog() { - if (this.dialog) { - this.dialog.nativeElement.close(); - this.peopleSearchObserver.next([]); - this.activitipeoplesearch.searchUser.reset(); + setupMaterialComponents(handler?: any): boolean { + // workaround for MDL issues with dynamic components + let isUpgraded: boolean = false; + if (handler) { + handler.upgradeAllRegistered(); + isUpgraded = true; } + return isUpgraded; } searchUser(searchedWord: string) { @@ -91,9 +84,10 @@ export class ActivitiPeople { } involveUser(user: User) { + this.showAssignment = false; this.peopleService.involveUserWithTask(this.taskId, user.id.toString()) .subscribe(() => { - this.people.push(user); + this.people = [...this.people, user]; }, error => this.logService.error('Impossible to involve user with task')); } @@ -106,10 +100,38 @@ export class ActivitiPeople { }, error => this.logService.error('Impossible to remove involved user from task')); } - getDisplayUser(user: User): string { - let firstName = user.firstName && user.firstName !== 'null' ? user.firstName : 'N/A'; - let lastName = user.lastName && user.lastName !== 'null' ? user.lastName : 'N/A'; - return firstName + ' ' + lastName; + getDisplayUser(firstName: string, lastName: string, delimiter: string = '-'): string { + firstName = (firstName !== null ? firstName : ''); + lastName = (lastName !== null ? lastName : ''); + return firstName + delimiter + lastName; + } + + getInitialUserName(firstName: string, lastName: string) { + firstName = (firstName !== null && firstName !== '' ? firstName[0] : ''); + lastName = (lastName !== null && lastName !== '' ? lastName[0] : ''); + return this.getDisplayUser(firstName, lastName, ''); + } + + onAddAssignement() { + this.showAssignment = true; + } + + onClickAction(event: UserEventModel) { + if (event.type === 'remove') { + this.removeInvolvedUser(event.value); + } + } + + hasPeople() { + return this.people && this.people.length > 0; + } + + isEditMode() { + return !this.readOnly; + } + + onCloseSearch() { + this.showAssignment = false; } } diff --git a/ng2-components/ng2-activiti-tasklist/src/components/index.ts b/ng2-components/ng2-activiti-tasklist/src/components/index.ts index fcf5c34c79..4c7a7818b5 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/index.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/index.ts @@ -28,3 +28,4 @@ export * from './activiti-task-details.component'; export * from './activiti-start-task.component'; export * from './activiti-people-search.component'; export * from './adf-create-task-attachment.component'; +export * from './activiti-people-list.component'; diff --git a/ng2-components/ng2-activiti-tasklist/src/i18n/en.json b/ng2-components/ng2-activiti-tasklist/src/i18n/en.json index f356102189..93942ee3d2 100644 --- a/ng2-components/ng2-activiti-tasklist/src/i18n/en.json +++ b/ng2-components/ng2-activiti-tasklist/src/i18n/en.json @@ -12,9 +12,10 @@ "ASSIGNEE": "Assignee", "DUE": "Due", "FORM": "Form", - "PEOPLE": "People", + "PEOPLE": "People this task is shared with", "COMMENTS": "Comments", - "CHECKLIST": "Checklist" + "CHECKLIST": "Checklist", + "ADD_PEOPLE": "Add people & groups" }, "BUTTON": { "COMPLETE": "Complete", @@ -84,6 +85,7 @@ }, "PEOPLE": { "DIALOG_CLOSE": "CLOSE", + "ADD_USER": "ADD", "SEARCH": { "NO_USERS": "No user found to involve" } diff --git a/ng2-components/ng2-activiti-tasklist/src/models/index.ts b/ng2-components/ng2-activiti-tasklist/src/models/index.ts index e3fe92d601..45b713f383 100644 --- a/ng2-components/ng2-activiti-tasklist/src/models/index.ts +++ b/ng2-components/ng2-activiti-tasklist/src/models/index.ts @@ -21,3 +21,4 @@ export * from './icon.model'; export * from './user.model'; export * from './task-details.model'; export * from './task-details.event'; +export * from './user-event.model'; diff --git a/ng2-components/ng2-activiti-tasklist/src/models/user-event.model.ts b/ng2-components/ng2-activiti-tasklist/src/models/user-event.model.ts new file mode 100644 index 0000000000..1837a25af8 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/models/user-event.model.ts @@ -0,0 +1,33 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * + * This object represent the User Event. + * + * + * @returns {UserEventModel} . + */ +export class UserEventModel { + type: string = ''; + value: any = {}; + + constructor(obj?: any) { + this.type = obj && obj.type; + this.value = obj && obj.value || {}; + } +}