mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[AAE-10773] Make Form core process agonostic (#8032)
* move form list in a component * move things in the right place * move last pice in the right place * move things in the right place * move people and group in the right place * move radio and typehead form service start remove responsibilities * remove model service and editor service from formService * move dropdwon in process-service finish remove service from form service * fix some wrong import * move activiti * fix double quote imports * move dynamic table * fix shell * move unit test * [ci:force] fix lint issues * fix build and some unit test * fix process spec type spy problems [ci:foce] * fix * fix broken tests * fix lint issues * fix cloud dropdown test * cleanup process-service-cloud tests * fix people process * improve e2e test Co-authored-by: Kasia Biernat <kasia.biernat@hyland.com>
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
<adf-datatable *ngIf="!isEmpty()"
|
||||
[rows]="forms">
|
||||
<data-columns>
|
||||
<data-column key="name" type="text" title="Name" class="adf-ellipsis-cell" [sortable]="true"></data-column>
|
||||
<data-column key="lastUpdatedByFullName" type="text" title="User" class="adf-ellipsis-cell" [sortable]="true"></data-column>
|
||||
<data-column key="lastUpdated" type="date" format="shortDate" title="Date"></data-column>
|
||||
</data-columns>
|
||||
</adf-datatable>
|
@@ -1,60 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 } from '@angular/core/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { FormService } from '../services/form.service';
|
||||
import { FormListComponent } from './form-list.component';
|
||||
import { setupTestBed } from '../../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
describe('TaskAttachmentList', () => {
|
||||
|
||||
let component: FormListComponent;
|
||||
let fixture: ComponentFixture<FormListComponent>;
|
||||
let service: FormService;
|
||||
let element: HTMLElement;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FormListComponent);
|
||||
component = fixture.componentInstance;
|
||||
element = fixture.debugElement.nativeElement;
|
||||
service = TestBed.inject(FormService);
|
||||
});
|
||||
|
||||
it('should show the forms as a list', async () => {
|
||||
spyOn(service, 'getForms').and.returnValue(of([
|
||||
{ name: 'FakeName-1', lastUpdatedByFullName: 'FakeUser-1', lastUpdated: '2017-01-02' },
|
||||
{ name: 'FakeName-2', lastUpdatedByFullName: 'FakeUser-2', lastUpdated: '2017-01-03' }
|
||||
]));
|
||||
|
||||
component.ngOnChanges();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(element.querySelectorAll('.adf-datatable-body > .adf-datatable-row').length).toBe(2);
|
||||
});
|
||||
});
|
@@ -1,49 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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, OnChanges, ViewEncapsulation } from '@angular/core';
|
||||
import { FormService } from '../services/form.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-form-list',
|
||||
templateUrl: './form-list.component.html',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class FormListComponent implements OnChanges {
|
||||
|
||||
/** The array that contains the information to show inside the list. */
|
||||
@Input()
|
||||
forms: any [] = [];
|
||||
|
||||
constructor(protected formService: FormService) {
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.getForms();
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.forms && this.forms.length === 0;
|
||||
}
|
||||
|
||||
getForms() {
|
||||
this.formService.getForms().subscribe((forms) => {
|
||||
this.forms.push(...forms);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
<mat-card class="adf-content-container" *ngIf="content">
|
||||
<mat-card-content *ngIf="showDocumentContent">
|
||||
<div *ngIf="content.isThumbnailSupported()" >
|
||||
<img id="thumbnailPreview" class="adf-img-upload-widget" [src]="content.thumbnailUrl" alt="{{content.name}}">
|
||||
</div>
|
||||
<div *ngIf="!content.isThumbnailSupported()">
|
||||
<mat-icon>image</mat-icon>
|
||||
<div id="unsupported-thumbnail" class="adf-content-widget-preview-text">{{ 'FORM.PREVIEW.IMAGE_NOT_AVAILABLE' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-card__supporting-text upload-widget__content-text">{{content.name | translate }}</div>
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions>
|
||||
<button mat-icon-button id="view" (click)="openViewer(content)">
|
||||
<mat-icon class="mat-24">zoom_in</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button id="download" (click)="download(content)">
|
||||
<mat-icon class="mat-24">file_download</mat-icon>
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
@@ -1,15 +0,0 @@
|
||||
.adf {
|
||||
&-img-upload-widget {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid rgba(117, 117, 117, 0.57);
|
||||
box-shadow: 1px 1px 2px #ddd;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
&-content-widget-preview-text {
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
@@ -1,283 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { SimpleChange } from '@angular/core';
|
||||
import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { ContentService } from '../../../../services';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { ProcessContentService } from '../../../services/process-content.service';
|
||||
import { ContentLinkModel } from '../index';
|
||||
import { ContentWidgetComponent } from './content.widget';
|
||||
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../../../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('ContentWidgetComponent', () => {
|
||||
|
||||
let component: ContentWidgetComponent;
|
||||
let fixture: ComponentFixture<ContentWidgetComponent>;
|
||||
let element: HTMLElement;
|
||||
|
||||
let processContentService: ProcessContentService;
|
||||
let serviceContent: ContentService;
|
||||
|
||||
const createFakeImageBlob = () => {
|
||||
const data = atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
|
||||
return new Blob([data], { type: 'image/png' });
|
||||
};
|
||||
|
||||
const createFakePdfBlob = (): Blob => {
|
||||
const pdfData = atob(
|
||||
'JVBERi0xLjcKCjEgMCBvYmogICUgZW50cnkgcG9pbnQKPDwKICAvVHlwZSAvQ2F0YWxvZwog' +
|
||||
'IC9QYWdlcyAyIDAgUgo+PgplbmRvYmoKCjIgMCBvYmoKPDwKICAvVHlwZSAvUGFnZXMKICAv' +
|
||||
'TWVkaWFCb3ggWyAwIDAgMjAwIDIwMCBdCiAgL0NvdW50IDEKICAvS2lkcyBbIDMgMCBSIF0K' +
|
||||
'Pj4KZW5kb2JqCgozIDAgb2JqCjw8CiAgL1R5cGUgL1BhZ2UKICAvUGFyZW50IDIgMCBSCiAg' +
|
||||
'L1Jlc291cmNlcyA8PAogICAgL0ZvbnQgPDwKICAgICAgL0YxIDQgMCBSIAogICAgPj4KICA+' +
|
||||
'PgogIC9Db250ZW50cyA1IDAgUgo+PgplbmRvYmoKCjQgMCBvYmoKPDwKICAvVHlwZSAvRm9u' +
|
||||
'dAogIC9TdWJ0eXBlIC9UeXBlMQogIC9CYXNlRm9udCAvVGltZXMtUm9tYW4KPj4KZW5kb2Jq' +
|
||||
'Cgo1IDAgb2JqICAlIHBhZ2UgY29udGVudAo8PAogIC9MZW5ndGggNDQKPj4Kc3RyZWFtCkJU' +
|
||||
'CjcwIDUwIFRECi9GMSAxMiBUZgooSGVsbG8sIHdvcmxkISkgVGoKRVQKZW5kc3RyZWFtCmVu' +
|
||||
'ZG9iagoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDEwIDAwMDAwIG4g' +
|
||||
'CjAwMDAwMDAwNzkgMDAwMDAgbiAKMDAwMDAwMDE3MyAwMDAwMCBuIAowMDAwMDAwMzAxIDAw' +
|
||||
'MDAwIG4gCjAwMDAwMDAzODAgMDAwMDAgbiAKdHJhaWxlcgo8PAogIC9TaXplIDYKICAvUm9v' +
|
||||
'dCAxIDAgUgo+PgpzdGFydHhyZWYKNDkyCiUlRU9G');
|
||||
return new Blob([pdfData], { type: 'application/pdf' });
|
||||
};
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
serviceContent = TestBed.inject(ContentService);
|
||||
processContentService = TestBed.inject(ProcessContentService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ContentWidgetComponent);
|
||||
component = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe('Rendering tests', () => {
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('should display content thumbnail', () => {
|
||||
component.showDocumentContent = true;
|
||||
component.content = new ContentLinkModel();
|
||||
fixture.detectChanges();
|
||||
|
||||
const content = fixture.debugElement.query(By.css('div.upload-widget__content-thumbnail'));
|
||||
expect(content).toBeDefined();
|
||||
});
|
||||
|
||||
it('should load the thumbnail preview of the png image', fakeAsync(() => {
|
||||
const blob = createFakeImageBlob();
|
||||
spyOn(processContentService, 'getFileRawContent').and.returnValue(of(blob));
|
||||
|
||||
component.thumbnailLoaded.subscribe((res) => {
|
||||
fixture.detectChanges();
|
||||
expect(res).toBeDefined();
|
||||
expect(res.changingThisBreaksApplicationSecurity).toBeDefined();
|
||||
expect(res.changingThisBreaksApplicationSecurity).toContain('blob');
|
||||
|
||||
const thumbnailPreview: any = element.querySelector('#thumbnailPreview');
|
||||
expect(thumbnailPreview.src).toContain('blob');
|
||||
});
|
||||
|
||||
const contentId = 1;
|
||||
const change = new SimpleChange(null, contentId, true);
|
||||
component.ngOnChanges({ id: change });
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: {
|
||||
id: 4004,
|
||||
name: 'Useful expressions - Email_English.png',
|
||||
created: 1490354907883,
|
||||
createdBy: {
|
||||
id: 2,
|
||||
firstName: 'admin', lastName: 'admin', email: 'administrator@admin.com'
|
||||
},
|
||||
relatedContent: false,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'image/png',
|
||||
simpleType: 'image',
|
||||
previewStatus: 'unsupported',
|
||||
thumbnailStatus: 'unsupported'
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it('should load the thumbnail preview of a pdf', fakeAsync(() => {
|
||||
const blob = createFakePdfBlob();
|
||||
spyOn(processContentService, 'getContentThumbnail').and.returnValue(of(blob));
|
||||
|
||||
component.thumbnailLoaded.subscribe((res) => {
|
||||
fixture.detectChanges();
|
||||
expect(res).toBeDefined();
|
||||
expect(res.changingThisBreaksApplicationSecurity).toBeDefined();
|
||||
expect(res.changingThisBreaksApplicationSecurity).toContain('blob');
|
||||
|
||||
const thumbnailPreview: any = element.querySelector('#thumbnailPreview');
|
||||
expect(thumbnailPreview.src).toContain('blob');
|
||||
});
|
||||
|
||||
const contentId = 1;
|
||||
const change = new SimpleChange(null, contentId, true);
|
||||
component.ngOnChanges({ id: change });
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: {
|
||||
id: 4004,
|
||||
name: 'FakeBlob.pdf',
|
||||
created: 1490354907883,
|
||||
createdBy: {
|
||||
id: 2,
|
||||
firstName: 'admin', lastName: 'admin', email: 'administrator@admin.com'
|
||||
},
|
||||
relatedContent: false,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'application/pdf',
|
||||
simpleType: 'pdf',
|
||||
previewStatus: 'created',
|
||||
thumbnailStatus: 'created'
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it('should show unsupported preview with unsupported file', fakeAsync(() => {
|
||||
|
||||
const contentId = 1;
|
||||
const change = new SimpleChange(null, contentId, true);
|
||||
component.ngOnChanges({ id: change });
|
||||
|
||||
component.contentLoaded.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
const thumbnailPreview: any = element.querySelector('#unsupported-thumbnail');
|
||||
expect(thumbnailPreview).toBeDefined();
|
||||
expect(element.querySelector('div.upload-widget__content-text').innerHTML).toEqual('FakeBlob.zip');
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: {
|
||||
id: 4004,
|
||||
name: 'FakeBlob.zip',
|
||||
created: 1490354907883,
|
||||
createdBy: {
|
||||
id: 2,
|
||||
firstName: 'admin', lastName: 'admin', email: 'administrator@admin.com'
|
||||
},
|
||||
relatedContent: false,
|
||||
contentAvailable: false,
|
||||
link: false,
|
||||
mimeType: 'application/zip',
|
||||
simpleType: 'zip',
|
||||
previewStatus: 'unsupported',
|
||||
thumbnailStatus: 'unsupported'
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it('should open the viewer when the view button is clicked', () => {
|
||||
const blob = createFakePdfBlob();
|
||||
spyOn(processContentService, 'getContentPreview').and.returnValue(of(blob));
|
||||
spyOn(processContentService, 'getFileRawContent').and.returnValue(of(blob));
|
||||
|
||||
component.content = new ContentLinkModel({
|
||||
id: 4004,
|
||||
name: 'FakeBlob.pdf',
|
||||
created: 1490354907883,
|
||||
createdBy: {
|
||||
id: 2,
|
||||
firstName: 'admin', lastName: 'admin', email: 'administrator@admin.com'
|
||||
},
|
||||
relatedContent: false,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'application/pdf',
|
||||
simpleType: 'pdf',
|
||||
previewStatus: 'created',
|
||||
thumbnailStatus: 'created'
|
||||
});
|
||||
|
||||
component.content.thumbnailUrl = '/alfresco-logo.svg';
|
||||
|
||||
component.contentClick.subscribe((content) => {
|
||||
expect(content.contentBlob).toBe(blob);
|
||||
expect(content.mimeType).toBe('application/pdf');
|
||||
expect(content.name).toBe('FakeBlob.pdf');
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
const viewButton: any = element.querySelector('#view');
|
||||
viewButton.click();
|
||||
});
|
||||
|
||||
it('should download the pdf when the download button is clicked', () => {
|
||||
const blob = createFakePdfBlob();
|
||||
spyOn(processContentService, 'getFileRawContent').and.returnValue(of(blob));
|
||||
spyOn(serviceContent, 'downloadBlob').and.callThrough();
|
||||
|
||||
component.content = new ContentLinkModel({
|
||||
id: 4004,
|
||||
name: 'FakeBlob.pdf',
|
||||
created: 1490354907883,
|
||||
createdBy: {
|
||||
id: 2,
|
||||
firstName: 'admin', lastName: 'admin', email: 'administrator@admin.com'
|
||||
},
|
||||
relatedContent: false,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'application/pdf',
|
||||
simpleType: 'pdf',
|
||||
previewStatus: 'created',
|
||||
thumbnailStatus: 'created'
|
||||
});
|
||||
|
||||
component.content.thumbnailUrl = '/alfresco-logo.svg';
|
||||
|
||||
fixture.detectChanges();
|
||||
const downloadButton: any = element.querySelector('#download');
|
||||
downloadButton.click();
|
||||
|
||||
expect(serviceContent.downloadBlob).toHaveBeenCalledWith(blob, 'FakeBlob.pdf');
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,142 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { ContentService } from '../../../../services/content.service';
|
||||
import { LogService } from '../../../../services/log.service';
|
||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ProcessContentService } from '../../../services/process-content.service';
|
||||
import { ContentLinkModel } from '../core/content-link.model';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-content',
|
||||
templateUrl: './content.widget.html',
|
||||
styleUrls: ['./content.widget.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class ContentWidgetComponent implements OnChanges {
|
||||
|
||||
/** The content id to show. */
|
||||
@Input()
|
||||
id: string;
|
||||
|
||||
/** Toggles showing document content. */
|
||||
@Input()
|
||||
showDocumentContent: boolean = true;
|
||||
|
||||
/** Emitted when the content is clicked. */
|
||||
@Output()
|
||||
contentClick = new EventEmitter();
|
||||
|
||||
/** Emitted when the thumbnail has loaded. */
|
||||
@Output()
|
||||
thumbnailLoaded: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
/** Emitted when the content has loaded. */
|
||||
@Output()
|
||||
contentLoaded: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
/** Emitted when an error occurs. */
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
content: ContentLinkModel;
|
||||
|
||||
constructor(protected formService: FormService,
|
||||
private logService: LogService,
|
||||
private contentService: ContentService,
|
||||
private processContentService: ProcessContentService) {
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const contentId = changes['id'];
|
||||
if (contentId && contentId.currentValue) {
|
||||
this.loadContent(contentId.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
loadContent(id: number) {
|
||||
this.processContentService
|
||||
.getFileContent(id)
|
||||
.subscribe(
|
||||
(response: ContentLinkModel) => {
|
||||
this.content = new ContentLinkModel(response);
|
||||
this.contentLoaded.emit(this.content);
|
||||
this.loadThumbnailUrl(this.content);
|
||||
},
|
||||
(error) => {
|
||||
this.error.emit(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadThumbnailUrl(content: ContentLinkModel) {
|
||||
if (this.content.isThumbnailSupported()) {
|
||||
let observable: Observable<any>;
|
||||
|
||||
if (this.content.isTypeImage()) {
|
||||
observable = this.processContentService.getFileRawContent(content.id);
|
||||
} else {
|
||||
observable = this.processContentService.getContentThumbnail(content.id);
|
||||
}
|
||||
|
||||
if (observable) {
|
||||
observable.subscribe(
|
||||
(response: Blob) => {
|
||||
this.content.thumbnailUrl = this.contentService.createTrustedUrl(response);
|
||||
this.thumbnailLoaded.emit(this.content.thumbnailUrl);
|
||||
},
|
||||
(error) => {
|
||||
this.error.emit(error);
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
openViewer(content: ContentLinkModel): void {
|
||||
let fetch = this.processContentService.getContentPreview(content.id);
|
||||
if (content.isTypeImage() || content.isTypePdf()) {
|
||||
fetch = this.processContentService.getFileRawContent(content.id);
|
||||
}
|
||||
fetch.subscribe(
|
||||
(blob: Blob) => {
|
||||
content.contentBlob = blob;
|
||||
this.contentClick.emit(content);
|
||||
this.logService.info('Content clicked' + content.id);
|
||||
this.formService.formContentClicked.next(content);
|
||||
},
|
||||
(error) => {
|
||||
this.error.emit(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke content download.
|
||||
*/
|
||||
download(content: ContentLinkModel): void {
|
||||
this.processContentService.getFileRawContent(content.id).subscribe(
|
||||
(blob: Blob) => this.contentService.downloadBlob(blob, content.name),
|
||||
(error) => {
|
||||
this.error.emit(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@@ -27,13 +27,10 @@ import { TabModel } from './tab.model';
|
||||
import { fakeMetadataForm, fakeViewerForm } from '../../mock/form.mock';
|
||||
import { Node } from '@alfresco/js-api';
|
||||
import { UploadWidgetContentLinkModel } from './upload-widget-content-link.model';
|
||||
import { AlfrescoApiService } from '../../../../services';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { CoreTestingModule, setupTestBed } from '../../../../testing';
|
||||
|
||||
describe('FormModel', () => {
|
||||
let formService: FormService;
|
||||
let alfrescoApiService: AlfrescoApiService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
@@ -42,9 +39,7 @@ describe('FormModel', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
||||
|
||||
formService = new FormService(null, alfrescoApiService, null);
|
||||
formService = new FormService();
|
||||
});
|
||||
|
||||
it('should store original json', () => {
|
||||
|
@@ -1,5 +0,0 @@
|
||||
<div class="adf-form-document-widget {{field.className}}">
|
||||
<ng-container *ngIf="hasFile">
|
||||
<adf-content [id]="fileId" [showDocumentContent]="true"></adf-content>
|
||||
</ng-container>
|
||||
</div>
|
@@ -1,60 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { WidgetComponent } from '../widget.component';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-form-document-widget',
|
||||
templateUrl: './document.widget.html',
|
||||
host: {
|
||||
'(click)': 'event($event)',
|
||||
'(blur)': 'event($event)',
|
||||
'(change)': 'event($event)',
|
||||
'(focus)': 'event($event)',
|
||||
'(focusin)': 'event($event)',
|
||||
'(focusout)': 'event($event)',
|
||||
'(input)': 'event($event)',
|
||||
'(invalid)': 'event($event)',
|
||||
'(select)': 'event($event)'
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class DocumentWidgetComponent extends WidgetComponent implements OnInit {
|
||||
|
||||
fileId: string = null;
|
||||
hasFile: boolean = false;
|
||||
|
||||
constructor(public formService: FormService) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field) {
|
||||
const file = this.field.value;
|
||||
|
||||
if (file) {
|
||||
this.fileId = file.id;
|
||||
this.hasFile = true;
|
||||
} else {
|
||||
this.fileId = null;
|
||||
this.hasFile = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
<div class="adf-dropdown-widget {{field.className}}"
|
||||
[class.adf-invalid]="!field.isValid && isTouched()" [class.adf-readonly]="field.readOnly">
|
||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||
<mat-form-field>
|
||||
<mat-select class="adf-select"
|
||||
[id]="field.id"
|
||||
[(ngModel)]="field.value"
|
||||
[disabled]="field.readOnly"
|
||||
(ngModelChange)="onFieldChanged(field)"
|
||||
(blur)="markAsTouched()">
|
||||
<mat-option *ngFor="let opt of field.options"
|
||||
[value]="getOptionValue(opt, field.value)"
|
||||
[id]="opt.id">{{opt.name}}
|
||||
</mat-option>
|
||||
<mat-option id="readonlyOption" *ngIf="isReadOnlyType()" [value]="field.value">{{field.value}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<error-widget [error]="field.validationSummary"></error-widget>
|
||||
<error-widget class="adf-dropdown-required-message" *ngIf="showRequiredMessage()"
|
||||
required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||
</div>
|
@@ -1,18 +0,0 @@
|
||||
.adf {
|
||||
&-dropdown-widget {
|
||||
width: 100%;
|
||||
|
||||
.adf-select {
|
||||
padding-top: 0 !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mat-select-value-text {
|
||||
font-size: var(--theme-body-1-font-size);
|
||||
}
|
||||
|
||||
&-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,329 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
import { FormFieldOption } from '../core/form-field-option';
|
||||
import { FormFieldModel } from '../core/form-field.model';
|
||||
import { FormModel } from '../core/form.model';
|
||||
import { FormFieldTypes } from '../core/form-field-types';
|
||||
import { DropdownWidgetComponent } from './dropdown.widget';
|
||||
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../../../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
describe('DropdownWidgetComponent', () => {
|
||||
|
||||
let formService: FormService;
|
||||
let widget: DropdownWidgetComponent;
|
||||
let visibilityService: WidgetVisibilityService;
|
||||
let fixture: ComponentFixture<DropdownWidgetComponent>;
|
||||
let element: HTMLElement;
|
||||
|
||||
const openSelect = () => {
|
||||
const dropdown = fixture.debugElement.nativeElement.querySelector('.mat-select-trigger');
|
||||
dropdown.click();
|
||||
};
|
||||
|
||||
const fakeOptionList: FormFieldOption[] = [
|
||||
{ id: 'opt_1', name: 'option_1' },
|
||||
{ id: 'opt_2', name: 'option_2' },
|
||||
{ id: 'opt_3', name: 'option_3' }];
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DropdownWidgetComponent);
|
||||
widget = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
formService = TestBed.inject(FormService);
|
||||
visibilityService = TestBed.inject(WidgetVisibilityService);
|
||||
widget.field = new FormFieldModel(new FormModel());
|
||||
});
|
||||
|
||||
it('should require field with restUrl', () => {
|
||||
spyOn(formService, 'getRestFieldValues').and.stub();
|
||||
|
||||
widget.field = null;
|
||||
widget.ngOnInit();
|
||||
expect(formService.getRestFieldValues).not.toHaveBeenCalled();
|
||||
|
||||
widget.field = new FormFieldModel(null, { restUrl: null });
|
||||
widget.ngOnInit();
|
||||
expect(formService.getRestFieldValues).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should request field values from service', () => {
|
||||
const taskId = '<form-id>';
|
||||
const fieldId = '<field-id>';
|
||||
|
||||
const form = new FormModel({
|
||||
taskId
|
||||
});
|
||||
|
||||
widget.field = new FormFieldModel(form, {
|
||||
id: fieldId,
|
||||
restUrl: '<url>'
|
||||
});
|
||||
|
||||
spyOn(formService, 'getRestFieldValues').and.returnValue(
|
||||
new Observable((observer) => {
|
||||
observer.next(null);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
widget.ngOnInit();
|
||||
expect(formService.getRestFieldValues).toHaveBeenCalledWith(taskId, fieldId);
|
||||
});
|
||||
|
||||
it('should preserve empty option when loading fields', () => {
|
||||
const restFieldValue: FormFieldOption = { id: '1', name: 'Option1' } as FormFieldOption;
|
||||
spyOn(formService, 'getRestFieldValues').and.callFake(() => new Observable((observer) => {
|
||||
observer.next([restFieldValue]);
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
const form = new FormModel({ taskId: '<id>' });
|
||||
const emptyOption: FormFieldOption = { id: 'empty', name: 'Empty' } as FormFieldOption;
|
||||
widget.field = new FormFieldModel(form, {
|
||||
id: '<id>',
|
||||
restUrl: '/some/url/address',
|
||||
hasEmptyValue: true,
|
||||
options: [emptyOption]
|
||||
});
|
||||
widget.ngOnInit();
|
||||
|
||||
expect(formService.getRestFieldValues).toHaveBeenCalled();
|
||||
expect(widget.field.options.length).toBe(2);
|
||||
expect(widget.field.options[0]).toBe(emptyOption);
|
||||
expect(widget.field.options[1]).toBe(restFieldValue);
|
||||
});
|
||||
|
||||
describe('when is required', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
|
||||
type: FormFieldTypes.DROPDOWN,
|
||||
required: true
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to display label with asterisk', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||
|
||||
expect(asterisk).toBeTruthy();
|
||||
expect(asterisk.textContent).toEqual('*');
|
||||
});
|
||||
|
||||
it('should be invalid if no default option after interaction', async () => {
|
||||
expect(element.querySelector('.adf-invalid')).toBeFalsy();
|
||||
|
||||
const dropdownSelect = element.querySelector('.adf-select');
|
||||
dropdownSelect.dispatchEvent(new Event('blur'));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(element.querySelector('.adf-invalid')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be valid if default option', async () => {
|
||||
widget.field.options = fakeOptionList;
|
||||
widget.field.value = fakeOptionList[0].id;
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(element.querySelector('.adf-invalid')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when template is ready', () => {
|
||||
|
||||
describe('and dropdown is populated via taskId', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(visibilityService, 'refreshVisibility').and.stub();
|
||||
spyOn(formService, 'getRestFieldValues').and.callFake(() => of(fakeOptionList));
|
||||
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
|
||||
id: 'dropdown-id',
|
||||
name: 'date-name',
|
||||
type: 'dropdown',
|
||||
readOnly: 'false',
|
||||
restUrl: 'fake-rest-url'
|
||||
});
|
||||
widget.field.emptyOption = { id: 'empty', name: 'Choose one...' };
|
||||
widget.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show visible dropdown widget', async () => {
|
||||
expect(element.querySelector('#dropdown-id')).toBeDefined();
|
||||
expect(element.querySelector('#dropdown-id')).not.toBeNull();
|
||||
|
||||
openSelect();
|
||||
|
||||
const optOne = fixture.debugElement.queryAll(By.css('[id="mat-option-1"]'));
|
||||
const optTwo = fixture.debugElement.queryAll(By.css('[id="mat-option-2"]'));
|
||||
const optThree = fixture.debugElement.queryAll(By.css('[id="mat-option-3"]'));
|
||||
|
||||
expect(optOne).not.toBeNull();
|
||||
expect(optTwo).not.toBeNull();
|
||||
expect(optThree).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should select the default value when an option is chosen as default', async () => {
|
||||
widget.field.value = 'option_2';
|
||||
widget.ngOnInit();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const dropDownElement: any = element.querySelector('#dropdown-id');
|
||||
expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('option_2');
|
||||
expect(dropDownElement.attributes['ng-reflect-model'].textContent).toBe('option_2');
|
||||
});
|
||||
|
||||
it('should select the empty value when no default is chosen', async () => {
|
||||
widget.field.value = 'empty';
|
||||
widget.ngOnInit();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
openSelect();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const dropDownElement: any = element.querySelector('#dropdown-id');
|
||||
expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('empty');
|
||||
});
|
||||
});
|
||||
|
||||
describe('and dropdown is populated via processDefinitionId', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(visibilityService, 'refreshVisibility').and.stub();
|
||||
spyOn(formService, 'getRestFieldValuesByProcessId').and.callFake(() => of(fakeOptionList));
|
||||
widget.field = new FormFieldModel(new FormModel({ processDefinitionId: 'fake-process-id' }), {
|
||||
id: 'dropdown-id',
|
||||
name: 'date-name',
|
||||
type: 'dropdown',
|
||||
readOnly: 'false',
|
||||
restUrl: 'fake-rest-url'
|
||||
});
|
||||
widget.field.emptyOption = { id: 'empty', name: 'Choose one...' };
|
||||
widget.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show visible dropdown widget', () => {
|
||||
expect(element.querySelector('#dropdown-id')).toBeDefined();
|
||||
expect(element.querySelector('#dropdown-id')).not.toBeNull();
|
||||
|
||||
openSelect();
|
||||
|
||||
const optOne = fixture.debugElement.queryAll(By.css('[id="mat-option-1"]'));
|
||||
const optTwo = fixture.debugElement.queryAll(By.css('[id="mat-option-2"]'));
|
||||
const optThree = fixture.debugElement.queryAll(By.css('[id="mat-option-3"]'));
|
||||
|
||||
expect(optOne).not.toBeNull();
|
||||
expect(optTwo).not.toBeNull();
|
||||
expect(optThree).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should select the default value when an option is chosen as default', async () => {
|
||||
widget.field.value = 'option_2';
|
||||
widget.ngOnInit();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const dropDownElement: any = element.querySelector('#dropdown-id');
|
||||
expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('option_2');
|
||||
expect(dropDownElement.attributes['ng-reflect-model'].textContent).toBe('option_2');
|
||||
});
|
||||
|
||||
it('should select the empty value when no default is chosen', async () => {
|
||||
widget.field.value = 'empty';
|
||||
widget.ngOnInit();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
openSelect();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const dropDownElement: any = element.querySelector('#dropdown-id');
|
||||
expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('empty');
|
||||
});
|
||||
|
||||
it('should be disabled when the field is readonly', async () => {
|
||||
widget.field = new FormFieldModel(new FormModel({ processDefinitionId: 'fake-process-id' }), {
|
||||
id: 'dropdown-id',
|
||||
name: 'date-name',
|
||||
type: 'dropdown',
|
||||
readOnly: 'true',
|
||||
restUrl: 'fake-rest-url'
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const dropDownElement = element.querySelector<HTMLSelectElement>('#dropdown-id');
|
||||
expect(dropDownElement).not.toBeNull();
|
||||
expect(dropDownElement.getAttribute('aria-disabled')).toBe('true');
|
||||
});
|
||||
|
||||
it('should show the option value when the field is readonly', async () => {
|
||||
widget.field = new FormFieldModel(new FormModel({ processDefinitionId: 'fake-process-id' }), {
|
||||
id: 'dropdown-id',
|
||||
name: 'date-name',
|
||||
type: 'readonly',
|
||||
value: 'FakeValue',
|
||||
readOnly: true,
|
||||
params: { field: { name: 'date-name', type: 'dropdown' } }
|
||||
});
|
||||
|
||||
openSelect();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const options = fixture.debugElement.queryAll(By.css('.mat-option-text'));
|
||||
expect(options.length).toBe(1);
|
||||
|
||||
const option = options[0].nativeElement;
|
||||
expect(option.innerText).toEqual('FakeValue');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,119 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { LogService } from '../../../../services/log.service';
|
||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { FormFieldOption } from '../core/form-field-option';
|
||||
import { WidgetComponent } from '../widget.component';
|
||||
|
||||
@Component({
|
||||
selector: 'dropdown-widget',
|
||||
templateUrl: './dropdown.widget.html',
|
||||
styleUrls: ['./dropdown.widget.scss'],
|
||||
host: {
|
||||
'(click)': 'event($event)',
|
||||
'(blur)': 'event($event)',
|
||||
'(change)': 'event($event)',
|
||||
'(focus)': 'event($event)',
|
||||
'(focusin)': 'event($event)',
|
||||
'(focusout)': 'event($event)',
|
||||
'(input)': 'event($event)',
|
||||
'(invalid)': 'event($event)',
|
||||
'(select)': 'event($event)'
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class DropdownWidgetComponent extends WidgetComponent implements OnInit {
|
||||
|
||||
constructor(public formService: FormService,
|
||||
private logService: LogService) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field && this.field.restUrl) {
|
||||
if (this.field.form.taskId) {
|
||||
this.getValuesByTaskId();
|
||||
} else {
|
||||
this.getValuesByProcessDefinitionId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getValuesByTaskId() {
|
||||
this.formService
|
||||
.getRestFieldValues(
|
||||
this.field.form.taskId,
|
||||
this.field.id
|
||||
)
|
||||
.subscribe(
|
||||
(formFieldOption: FormFieldOption[]) => {
|
||||
const options = [];
|
||||
if (this.field.emptyOption) {
|
||||
options.push(this.field.emptyOption);
|
||||
}
|
||||
this.field.options = options.concat((formFieldOption || []));
|
||||
this.field.updateForm();
|
||||
},
|
||||
(err) => this.handleError(err)
|
||||
);
|
||||
}
|
||||
|
||||
getValuesByProcessDefinitionId() {
|
||||
this.formService
|
||||
.getRestFieldValuesByProcessId(
|
||||
this.field.form.processDefinitionId,
|
||||
this.field.id
|
||||
)
|
||||
.subscribe(
|
||||
(formFieldOption: FormFieldOption[]) => {
|
||||
const options = [];
|
||||
if (this.field.emptyOption) {
|
||||
options.push(this.field.emptyOption);
|
||||
}
|
||||
this.field.options = options.concat((formFieldOption || []));
|
||||
this.field.updateForm();
|
||||
},
|
||||
(err) => this.handleError(err)
|
||||
);
|
||||
}
|
||||
|
||||
getOptionValue(option: FormFieldOption, fieldValue: string): string {
|
||||
let optionValue: string = '';
|
||||
if (option.id === 'empty' || option.name !== fieldValue) {
|
||||
optionValue = option.id;
|
||||
} else {
|
||||
optionValue = option.name;
|
||||
}
|
||||
return optionValue;
|
||||
}
|
||||
|
||||
handleError(error: any) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
|
||||
isReadOnlyType(): boolean {
|
||||
return this.field.type === 'readonly';
|
||||
}
|
||||
|
||||
showRequiredMessage(): boolean {
|
||||
return (this.isInvalidFieldRequired() || this.field.value === 'empty') && this.isTouched();
|
||||
}
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { DynamicTableColumn } from './dynamic-table-column.model';
|
||||
import { DynamicTableRow } from './dynamic-table-row.model';
|
||||
import { DynamicRowValidationSummary } from './dynamic-row-validation-summary.model';
|
||||
|
||||
export interface CellValidator {
|
||||
|
||||
isSupported(column: DynamicTableColumn): boolean;
|
||||
validate(row: DynamicTableRow, column: DynamicTableColumn, summary?: DynamicRowValidationSummary): boolean;
|
||||
|
||||
}
|
@@ -1,57 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import moment from 'moment';
|
||||
import { CellValidator } from './cell-validator.model';
|
||||
import { DynamicRowValidationSummary } from './dynamic-row-validation-summary.model';
|
||||
import { DynamicTableColumn } from './dynamic-table-column.model';
|
||||
import { DynamicTableRow } from './dynamic-table-row.model';
|
||||
|
||||
export class DateCellValidator implements CellValidator {
|
||||
|
||||
private supportedTypes: string[] = [
|
||||
'Date'
|
||||
];
|
||||
|
||||
isSupported(column: DynamicTableColumn): boolean {
|
||||
return column && column.editable && this.supportedTypes.indexOf(column.type) > -1;
|
||||
}
|
||||
|
||||
validate(row: DynamicTableRow, column: DynamicTableColumn, summary?: DynamicRowValidationSummary): boolean {
|
||||
|
||||
if (this.isSupported(column)) {
|
||||
const value = row.value[column.id];
|
||||
|
||||
if (!value && !column.required) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const dateValue = moment(value, 'YYYY-MM-DDTHH:mm:ss.SSSSZ', true);
|
||||
if (!dateValue.isValid()) {
|
||||
if (summary) {
|
||||
summary.isValid = false;
|
||||
summary.message = `Invalid '${column.name}' format.`;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { ErrorMessageModel } from '../core/error-message.model';
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
export class DynamicRowValidationSummary extends ErrorMessageModel {
|
||||
|
||||
isValid: boolean;
|
||||
|
||||
constructor(json?: any) {
|
||||
super(json);
|
||||
this.isValid = json.isValid;
|
||||
}
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
// maps to: com.activiti.model.editor.form.OptionRepresentation
|
||||
export interface DynamicTableColumnOption {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { DynamicTableColumnOption } from './dynamic-table-column-option.model';
|
||||
|
||||
// maps to: com.activiti.model.editor.form.ColumnDefinitionRepresentation
|
||||
export interface DynamicTableColumn {
|
||||
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
value: any;
|
||||
optionType: string;
|
||||
options: DynamicTableColumnOption[];
|
||||
restResponsePath: string;
|
||||
restUrl: string;
|
||||
restIdProperty: string;
|
||||
restLabelProperty: string;
|
||||
amountCurrency: string;
|
||||
amountEnableFractions: boolean;
|
||||
required: boolean;
|
||||
editable: boolean;
|
||||
sortable: boolean;
|
||||
visible: boolean;
|
||||
|
||||
// TODO: com.activiti.domain.idm.EndpointConfiguration.EndpointConfigurationRepresentation
|
||||
endpoint: any;
|
||||
// TODO: com.activiti.model.editor.form.RequestHeaderRepresentation
|
||||
requestHeaders: any;
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
export interface DynamicTableRow {
|
||||
isNew: boolean;
|
||||
selected: boolean;
|
||||
value: any;
|
||||
}
|
@@ -1,71 +0,0 @@
|
||||
<div class="adf-dynamic-table-scrolling {{field.className}}"
|
||||
[class.adf-invalid]="!isValid()">
|
||||
<div class="adf-label">{{content.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></div>
|
||||
|
||||
<div *ngIf="!editMode">
|
||||
<div class="adf-table-container">
|
||||
<table class="adf-full-width adf-dynamic-table" id="dynamic-table-{{content.id}}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th *ngFor="let column of content.visibleColumns">
|
||||
{{column.name}}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let row of content.rows; let idx = index" tabindex="0" id="{{content.id}}-row-{{idx}}"
|
||||
[class.adf-dynamic-table-widget__row-selected]="row.selected" (keyup)="onKeyPressed($event, row)">
|
||||
<td *ngFor="let column of content.visibleColumns"
|
||||
(click)="onRowClicked(row)">
|
||||
<span *ngIf="column.type !== 'Boolean' else checkbox">
|
||||
{{ getCellValue(row, column) }}
|
||||
</span>
|
||||
<ng-template #checkbox>
|
||||
<mat-checkbox disabled [checked]="getCellValue(row, column)">
|
||||
</mat-checkbox>
|
||||
</ng-template>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!readOnly">
|
||||
<button mat-button
|
||||
[disabled]="!hasSelection()"
|
||||
(click)="moveSelectionUp()">
|
||||
<mat-icon>arrow_upward</mat-icon>
|
||||
</button>
|
||||
<button mat-button
|
||||
[disabled]="!hasSelection()"
|
||||
(click)="moveSelectionDown()">
|
||||
<mat-icon>arrow_downward</mat-icon>
|
||||
</button>
|
||||
<button mat-button
|
||||
[disabled]="field.readOnly"
|
||||
id="{{content.id}}-add-row"
|
||||
(click)="addNewRow()">
|
||||
<mat-icon>add_circle_outline</mat-icon>
|
||||
</button>
|
||||
<button mat-button
|
||||
[disabled]="!hasSelection()"
|
||||
(click)="deleteSelection()">
|
||||
<mat-icon>remove_circle_outline</mat-icon>
|
||||
</button>
|
||||
<button mat-button
|
||||
[disabled]="!hasSelection()"
|
||||
(click)="editSelection()">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<row-editor *ngIf="editMode"
|
||||
[table]="content"
|
||||
[row]="editRow"
|
||||
(save)="onSaveChanges()"
|
||||
(cancel)="onCancelChanges()">
|
||||
</row-editor>
|
||||
<error-widget [error]="field.validationSummary" ></error-widget>
|
||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||
</div>
|
@@ -1,203 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import moment from 'moment';
|
||||
import { ValidateDynamicTableRowEvent } from '../../../events/validate-dynamic-table-row.event';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { FormFieldModel } from '../core/form-field.model';
|
||||
import { FormWidgetModel } from '../core/form-widget.model';
|
||||
import { CellValidator } from './cell-validator.model';
|
||||
import { DateCellValidator } from './date-cell-validator-model';
|
||||
import { DynamicRowValidationSummary } from './dynamic-row-validation-summary.model';
|
||||
import { DynamicTableColumn } from './dynamic-table-column.model';
|
||||
import { DynamicTableRow } from './dynamic-table-row.model';
|
||||
import { NumberCellValidator } from './number-cell-validator.model';
|
||||
import { RequiredCellValidator } from './required-cell-validator.model';
|
||||
|
||||
export class DynamicTableModel extends FormWidgetModel {
|
||||
|
||||
field: FormFieldModel;
|
||||
columns: DynamicTableColumn[] = [];
|
||||
visibleColumns: DynamicTableColumn[] = [];
|
||||
rows: DynamicTableRow[] = [];
|
||||
|
||||
private _selectedRow: DynamicTableRow;
|
||||
private readonly _validators: CellValidator[] = [];
|
||||
|
||||
get selectedRow(): DynamicTableRow {
|
||||
return this._selectedRow;
|
||||
}
|
||||
|
||||
set selectedRow(value: DynamicTableRow) {
|
||||
if (this._selectedRow && this._selectedRow === value) {
|
||||
this._selectedRow.selected = false;
|
||||
this._selectedRow = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.rows.forEach((row) => row.selected = false);
|
||||
|
||||
this._selectedRow = value;
|
||||
|
||||
if (value) {
|
||||
this._selectedRow.selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(field: FormFieldModel, private formService: FormService) {
|
||||
super(field.form, field.json);
|
||||
this.field = field;
|
||||
|
||||
if (field.json) {
|
||||
const columns = this.getColumns(field);
|
||||
if (columns) {
|
||||
this.columns = columns;
|
||||
this.visibleColumns = this.columns.filter((col) => col.visible);
|
||||
}
|
||||
|
||||
if (field.json.value) {
|
||||
this.rows = field.json.value.map((obj) => ({ selected: false, value: obj } as DynamicTableRow));
|
||||
}
|
||||
}
|
||||
|
||||
this._validators = [
|
||||
new RequiredCellValidator(),
|
||||
new DateCellValidator(),
|
||||
new NumberCellValidator()
|
||||
];
|
||||
}
|
||||
|
||||
private getColumns(field: FormFieldModel): DynamicTableColumn[] {
|
||||
if (field && field.json) {
|
||||
let definitions = field.json.columnDefinitions;
|
||||
if (!definitions && field.json.params && field.json.params.field) {
|
||||
definitions = field.json.params.field.columnDefinitions;
|
||||
}
|
||||
|
||||
if (definitions) {
|
||||
return definitions.map((obj) => obj as DynamicTableColumn);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
flushValue() {
|
||||
if (this.field) {
|
||||
this.field.value = this.rows.map((r) => r.value);
|
||||
this.field.updateForm();
|
||||
}
|
||||
}
|
||||
|
||||
moveRow(row: DynamicTableRow, offset: number) {
|
||||
const oldIndex = this.rows.indexOf(row);
|
||||
if (oldIndex > -1) {
|
||||
let newIndex = (oldIndex + offset);
|
||||
|
||||
if (newIndex < 0) {
|
||||
newIndex = 0;
|
||||
} else if (newIndex >= this.rows.length) {
|
||||
newIndex = this.rows.length;
|
||||
}
|
||||
|
||||
const arr = this.rows.slice();
|
||||
arr.splice(oldIndex, 1);
|
||||
arr.splice(newIndex, 0, row);
|
||||
this.rows = arr;
|
||||
|
||||
this.flushValue();
|
||||
}
|
||||
}
|
||||
|
||||
deleteRow(row: DynamicTableRow) {
|
||||
if (row) {
|
||||
if (this.selectedRow === row) {
|
||||
this.selectedRow = null;
|
||||
}
|
||||
const idx = this.rows.indexOf(row);
|
||||
if (idx > -1) {
|
||||
this.rows.splice(idx, 1);
|
||||
this.flushValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addRow(row: DynamicTableRow) {
|
||||
if (row) {
|
||||
this.rows.push(row);
|
||||
// this.selectedRow = row;
|
||||
}
|
||||
}
|
||||
|
||||
validateRow(row: DynamicTableRow): DynamicRowValidationSummary {
|
||||
const summary = new DynamicRowValidationSummary( {
|
||||
isValid: true,
|
||||
message: null
|
||||
});
|
||||
|
||||
const event = new ValidateDynamicTableRowEvent(this.form, this.field, row, summary);
|
||||
this.formService.validateDynamicTableRow.next(event);
|
||||
|
||||
if (event.defaultPrevented || !summary.isValid) {
|
||||
return summary;
|
||||
}
|
||||
|
||||
if (row) {
|
||||
for (const col of this.columns) {
|
||||
for (const validator of this._validators) {
|
||||
if (!validator.validate(row, col, summary)) {
|
||||
return summary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
getCellValue(row: DynamicTableRow, column: DynamicTableColumn): any {
|
||||
const rowValue = row.value[column.id];
|
||||
|
||||
if (column.type === 'Dropdown') {
|
||||
if (rowValue) {
|
||||
return rowValue.name;
|
||||
}
|
||||
}
|
||||
|
||||
if (column.type === 'Boolean') {
|
||||
return !!rowValue;
|
||||
}
|
||||
|
||||
if (column.type === 'Date') {
|
||||
if (rowValue) {
|
||||
return moment(rowValue.split('T')[0], 'YYYY-MM-DD').format('DD-MM-YYYY');
|
||||
}
|
||||
}
|
||||
|
||||
return rowValue || '';
|
||||
}
|
||||
|
||||
getDisplayText(column: DynamicTableColumn): string {
|
||||
let columnName = column.name;
|
||||
if (column.type === 'Amount') {
|
||||
const currency = column.amountCurrency || '$';
|
||||
columnName = `${column.name} (${currency})`;
|
||||
}
|
||||
return columnName;
|
||||
}
|
||||
}
|
@@ -1,172 +0,0 @@
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
@import '../../../../styles/mixins';
|
||||
|
||||
$dynamic-table-font-size: var(--theme-body-1-font-size) !default;
|
||||
$dynamic-table-header-font-size: var(--theme-caption-font-size) !default;
|
||||
$dynamic-table-header-sort-icon-size: 16px !default;
|
||||
$dynamic-table-hover-color: #eee !default;
|
||||
$dynamic-table-selection-color: #e0f7fa !default;
|
||||
$dynamic-table-row-height: 56px !default;
|
||||
$dynamic-table-column-spacing: 36px !default;
|
||||
$dynamic-table-column-padding: 18px !default;
|
||||
$dynamic-table-card-padding: 24px !default;
|
||||
$dynamic-table-cell-top: 12px !default;
|
||||
$dynamic-table-drag-border: 1px dashed rgb(68, 138, 255);
|
||||
|
||||
dynamic-table-widget .adf-label {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.adf {
|
||||
&-dynamic-table-scrolling {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&-dynamic-table {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
border: 1px solid var(--theme-border-color);
|
||||
white-space: nowrap;
|
||||
font-size: $dynamic-table-font-size;
|
||||
|
||||
/* Firefox fixes */
|
||||
border-collapse: unset;
|
||||
border-spacing: 0;
|
||||
|
||||
thead {
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
position: relative;
|
||||
height: $dynamic-table-row-height;
|
||||
|
||||
@include material-animation-default(0.28s);
|
||||
|
||||
transition-property: background-color;
|
||||
|
||||
&:hover {
|
||||
background-color: $dynamic-table-hover-color;
|
||||
}
|
||||
|
||||
&.adf-is-selected,
|
||||
&.adf-is-selected:hover {
|
||||
background-color: $dynamic-table-selection-color;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline-offset: -1px;
|
||||
outline: rgb(68, 138, 255) solid 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 0 $dynamic-table-column-padding 12px $dynamic-table-column-padding;
|
||||
text-align: center;
|
||||
|
||||
&:first-of-type {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
padding-right: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
color: var(--theme-text-fg-color);
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
height: $dynamic-table-row-height;
|
||||
border-top: 1px solid var(--theme-border-color);
|
||||
border-bottom: 1px solid var(--theme-border-color);
|
||||
padding-top: $dynamic-table-cell-top;
|
||||
box-sizing: border-box;
|
||||
|
||||
@include adf-no-select;
|
||||
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
th {
|
||||
@include adf-no-select;
|
||||
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
vertical-align: bottom;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
height: $dynamic-table-row-height;
|
||||
font-size: $dynamic-table-header-font-size;
|
||||
color: var(--theme-text-fg-color);
|
||||
padding-bottom: 8px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.adf-sortable {
|
||||
@include adf-no-select;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&.adf-dynamic-table__header--sorted-asc,
|
||||
&.adf-dynamic-table__header--sorted-desc {
|
||||
color: var(--theme-text-fg-color);
|
||||
|
||||
&::before {
|
||||
@include typo-icon;
|
||||
|
||||
font-size: $dynamic-table-header-sort-icon-size;
|
||||
content: '\e5d8';
|
||||
margin-right: 5px;
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
|
||||
&::before {
|
||||
color: var(--theme-disabled-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.adf-dynamic-table__header--sorted-desc::before {
|
||||
content: '\e5db';
|
||||
}
|
||||
}
|
||||
|
||||
.adf-dynamic-table-cell {
|
||||
text-align: left;
|
||||
cursor: default;
|
||||
|
||||
&--text {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&--number {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&--image {
|
||||
text-align: left;
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.adf-full-width {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,374 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 } from '@angular/core/testing';
|
||||
import { LogService } from '../../../../services';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { FormFieldModel, FormFieldTypes, FormModel } from '../core';
|
||||
import { DynamicTableColumn } from './dynamic-table-column.model';
|
||||
import { DynamicTableRow } from './dynamic-table-row.model';
|
||||
import { DynamicTableWidgetComponent } from './dynamic-table.widget';
|
||||
import { DynamicTableModel } from './dynamic-table.widget.model';
|
||||
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../../../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
const fakeFormField = {
|
||||
id: 'fake-dynamic-table',
|
||||
name: 'fake-label',
|
||||
value: [{1: 1, 2: 2, 3: 4}],
|
||||
required: false,
|
||||
readOnly: false,
|
||||
overrideId: false,
|
||||
colspan: 1,
|
||||
placeholder: null,
|
||||
minLength: 0,
|
||||
maxLength: 0,
|
||||
params: {
|
||||
existingColspan: 1,
|
||||
maxColspan: 1
|
||||
},
|
||||
sizeX: 2,
|
||||
sizeY: 2,
|
||||
row: -1,
|
||||
col: -1,
|
||||
columnDefinitions: [
|
||||
{
|
||||
id: 1,
|
||||
name: 1,
|
||||
type: 'String',
|
||||
visible: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 2,
|
||||
type: 'String',
|
||||
visible: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 3,
|
||||
type: 'String',
|
||||
visible: true
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
describe('DynamicTableWidgetComponent', () => {
|
||||
|
||||
let widget: DynamicTableWidgetComponent;
|
||||
let fixture: ComponentFixture<DynamicTableWidgetComponent>;
|
||||
let element: HTMLElement;
|
||||
let table: DynamicTableModel;
|
||||
let logService: LogService;
|
||||
let formService: FormService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const field = new FormFieldModel(new FormModel());
|
||||
logService = TestBed.inject(LogService);
|
||||
formService = TestBed.inject(FormService);
|
||||
table = new DynamicTableModel(field, formService);
|
||||
const changeDetectorSpy = jasmine.createSpyObj('cd', ['detectChanges']);
|
||||
const nativeElementSpy = jasmine.createSpyObj('nativeElement', ['querySelector']);
|
||||
changeDetectorSpy.nativeElement = nativeElementSpy;
|
||||
const elementRefSpy = jasmine.createSpyObj('elementRef', ['']);
|
||||
elementRefSpy.nativeElement = nativeElementSpy;
|
||||
|
||||
fixture = TestBed.createComponent(DynamicTableWidgetComponent);
|
||||
element = fixture.nativeElement;
|
||||
widget = fixture.componentInstance;
|
||||
widget.content = table;
|
||||
widget.field = field;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should select row on click', () => {
|
||||
const row = {selected: false} as DynamicTableRow;
|
||||
widget.onRowClicked(row);
|
||||
|
||||
expect(row.selected).toBeTruthy();
|
||||
expect(widget.content.selectedRow).toBe(row);
|
||||
});
|
||||
|
||||
it('should require table to select clicked row', () => {
|
||||
const row = {selected: false} as DynamicTableRow;
|
||||
widget.content = null;
|
||||
widget.onRowClicked(row);
|
||||
|
||||
expect(row.selected).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should reset selected row', () => {
|
||||
const row = {selected: false} as DynamicTableRow;
|
||||
widget.content.rows.push(row);
|
||||
widget.content.selectedRow = row;
|
||||
expect(widget.content.selectedRow).toBe(row);
|
||||
expect(row.selected).toBeTruthy();
|
||||
|
||||
widget.onRowClicked(null);
|
||||
expect(widget.content.selectedRow).toBeNull();
|
||||
expect(row.selected).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should check selection', () => {
|
||||
const row = {selected: false} as DynamicTableRow;
|
||||
widget.content.rows.push(row);
|
||||
widget.content.selectedRow = row;
|
||||
expect(widget.hasSelection()).toBeTruthy();
|
||||
|
||||
widget.content.selectedRow = null;
|
||||
expect(widget.hasSelection()).toBeFalsy();
|
||||
|
||||
widget.content = null;
|
||||
expect(widget.hasSelection()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should require table to move selection up', () => {
|
||||
widget.content = null;
|
||||
expect(widget.moveSelectionUp()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should move selection up', () => {
|
||||
const row1 = {} as DynamicTableRow;
|
||||
const row2 = {} as DynamicTableRow;
|
||||
widget.content.rows.push(...[row1, row2]);
|
||||
widget.content.selectedRow = row2;
|
||||
|
||||
expect(widget.moveSelectionUp()).toBeTruthy();
|
||||
expect(widget.content.rows.indexOf(row2)).toBe(0);
|
||||
});
|
||||
|
||||
it('should require table to move selection down', () => {
|
||||
widget.content = null;
|
||||
expect(widget.moveSelectionDown()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should move selection down', () => {
|
||||
const row1 = {} as DynamicTableRow;
|
||||
const row2 = {} as DynamicTableRow;
|
||||
widget.content.rows.push(...[row1, row2]);
|
||||
widget.content.selectedRow = row1;
|
||||
|
||||
expect(widget.moveSelectionDown()).toBeTruthy();
|
||||
expect(widget.content.rows.indexOf(row1)).toBe(1);
|
||||
});
|
||||
|
||||
it('should require table to delete selection', () => {
|
||||
widget.content = null;
|
||||
expect(widget.deleteSelection()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should delete selected row', () => {
|
||||
const row = {} as DynamicTableRow;
|
||||
widget.content.rows.push(row);
|
||||
widget.content.selectedRow = row;
|
||||
widget.deleteSelection();
|
||||
expect(widget.content.rows.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should require table to add new row', () => {
|
||||
widget.content = null;
|
||||
expect(widget.addNewRow()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should start editing new row', () => {
|
||||
expect(widget.editMode).toBeFalsy();
|
||||
expect(widget.editRow).toBeNull();
|
||||
|
||||
expect(widget.addNewRow()).toBeTruthy();
|
||||
expect(widget.editRow).not.toBeNull();
|
||||
expect(widget.editMode).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should require table to edit selected row', () => {
|
||||
widget.content = null;
|
||||
expect(widget.editSelection()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should start editing selected row', () => {
|
||||
expect(widget.editMode).toBeFalsy();
|
||||
expect(widget.editRow).toBeFalsy();
|
||||
|
||||
const row = {value: true} as DynamicTableRow;
|
||||
widget.content.selectedRow = row;
|
||||
|
||||
expect(widget.editSelection()).toBeTruthy();
|
||||
expect(widget.editMode).toBeTruthy();
|
||||
expect(widget.editRow).not.toBeNull();
|
||||
expect(widget.editRow.value).toEqual(row.value);
|
||||
});
|
||||
|
||||
it('should copy row', () => {
|
||||
const row = {value: {opt: {key: '1', value: 1}}} as DynamicTableRow;
|
||||
const copy = widget.copyRow(row);
|
||||
expect(copy.value).toEqual(row.value);
|
||||
});
|
||||
|
||||
it('should require table to retrieve cell value', () => {
|
||||
widget.content = null;
|
||||
expect(widget.getCellValue(null, null)).toBeNull();
|
||||
});
|
||||
|
||||
it('should retrieve cell value', () => {
|
||||
const value = '<value>';
|
||||
const row = {value: {key: value}} as DynamicTableRow;
|
||||
const column = {id: 'key'} as DynamicTableColumn;
|
||||
|
||||
expect(widget.getCellValue(row, column)).toBe(value);
|
||||
});
|
||||
|
||||
it('should save changes and add new row', () => {
|
||||
const row = {isNew: true, value: {key: 'value'}} as DynamicTableRow;
|
||||
widget.editMode = true;
|
||||
widget.editRow = row;
|
||||
|
||||
widget.onSaveChanges();
|
||||
|
||||
expect(row.isNew).toBeFalsy();
|
||||
expect(widget.content.selectedRow).toBeNull();
|
||||
expect(widget.content.rows.length).toBe(1);
|
||||
expect(widget.content.rows[0].value).toEqual(row.value);
|
||||
});
|
||||
|
||||
it('should save changes and update row', () => {
|
||||
const row = {isNew: false, value: {key: 'value'}} as DynamicTableRow;
|
||||
widget.editMode = true;
|
||||
widget.editRow = row;
|
||||
widget.content.selectedRow = row;
|
||||
|
||||
widget.onSaveChanges();
|
||||
expect(widget.content.selectedRow.value).toEqual(row.value);
|
||||
});
|
||||
|
||||
it('should require table to save changes', () => {
|
||||
spyOn(logService, 'error').and.stub();
|
||||
widget.editMode = true;
|
||||
widget.content = null;
|
||||
widget.onSaveChanges();
|
||||
|
||||
expect(widget.editMode).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should cancel changes', () => {
|
||||
widget.editMode = true;
|
||||
widget.editRow = {} as DynamicTableRow;
|
||||
widget.onCancelChanges();
|
||||
|
||||
expect(widget.editMode).toBeFalsy();
|
||||
expect(widget.editRow).toBeNull();
|
||||
});
|
||||
|
||||
it('should be valid by default', () => {
|
||||
widget.content.field = null;
|
||||
expect(widget.isValid()).toBeTruthy();
|
||||
|
||||
widget.content = null;
|
||||
expect(widget.isValid()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should take validation state from underlying field', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
type: FormFieldTypes.DYNAMIC_TABLE,
|
||||
required: true,
|
||||
value: null
|
||||
});
|
||||
widget.content = new DynamicTableModel(field, formService);
|
||||
|
||||
expect(widget.content.field.validate()).toBeFalsy();
|
||||
expect(widget.isValid()).toBe(widget.content.field.isValid);
|
||||
expect(widget.content.field.isValid).toBeFalsy();
|
||||
|
||||
widget.content.field.value = [{}];
|
||||
|
||||
expect(widget.content.field.validate()).toBeTruthy();
|
||||
expect(widget.isValid()).toBe(widget.content.field.isValid);
|
||||
expect(widget.content.field.isValid).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should prepend default currency for amount columns', () => {
|
||||
const row = {value: {key: '100'}} as DynamicTableRow;
|
||||
const column = {id: 'key', type: 'Amount'} as DynamicTableColumn;
|
||||
const actual = widget.getCellValue(row, column);
|
||||
expect(actual).toBe('$ 100');
|
||||
});
|
||||
|
||||
it('should prepend custom currency for amount columns', () => {
|
||||
const row = {value: {key: '100'}} as DynamicTableRow;
|
||||
const column = {id: 'key', type: 'Amount', amountCurrency: 'GBP'} as DynamicTableColumn;
|
||||
const actual = widget.getCellValue(row, column);
|
||||
expect(actual).toBe('GBP 100');
|
||||
});
|
||||
|
||||
describe('when template is ready', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
widget.field = new FormFieldModel(new FormModel({taskId: 'fake-task-id'}), fakeFormField);
|
||||
widget.field.type = FormFieldTypes.DYNAMIC_TABLE;
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should select a row when press space bar', async () => {
|
||||
const rowElement = element.querySelector('#fake-dynamic-table-row-0');
|
||||
|
||||
expect(element.querySelector('#dynamic-table-fake-dynamic-table')).not.toBeNull();
|
||||
expect(rowElement).not.toBeNull();
|
||||
expect(rowElement.className).not.toContain('adf-dynamic-table-widget__row-selected');
|
||||
|
||||
const event: any = new Event('keyup');
|
||||
event.keyCode = 32;
|
||||
rowElement.dispatchEvent(event);
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const selectedRow = element.querySelector('#fake-dynamic-table-row-0');
|
||||
expect(selectedRow.className).toContain('adf-dynamic-table-widget__row-selected');
|
||||
});
|
||||
|
||||
it('should focus on add button when a new row is saved', async () => {
|
||||
const addNewRowButton = element.querySelector<HTMLButtonElement>('#fake-dynamic-table-add-row');
|
||||
|
||||
expect(element.querySelector('#dynamic-table-fake-dynamic-table')).not.toBeNull();
|
||||
expect(addNewRowButton).not.toBeNull();
|
||||
|
||||
widget.addNewRow();
|
||||
widget.onSaveChanges();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(document.activeElement.id).toBe('fake-dynamic-table-add-row');
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,215 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { LogService } from '../../../../services/log.service';
|
||||
import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { WidgetComponent } from '../widget.component';
|
||||
import { DynamicTableColumn } from './dynamic-table-column.model';
|
||||
import { DynamicTableRow } from './dynamic-table-row.model';
|
||||
import { DynamicTableModel } from './dynamic-table.widget.model';
|
||||
|
||||
@Component({
|
||||
selector: 'dynamic-table-widget',
|
||||
templateUrl: './dynamic-table.widget.html',
|
||||
styleUrls: ['./dynamic-table.widget.scss'],
|
||||
host: {
|
||||
'(click)': 'event($event)',
|
||||
'(blur)': 'event($event)',
|
||||
'(change)': 'event($event)',
|
||||
'(focus)': 'event($event)',
|
||||
'(focusin)': 'event($event)',
|
||||
'(focusout)': 'event($event)',
|
||||
'(input)': 'event($event)',
|
||||
'(invalid)': 'event($event)',
|
||||
'(select)': 'event($event)'
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class DynamicTableWidgetComponent extends WidgetComponent implements OnInit {
|
||||
|
||||
ERROR_MODEL_NOT_FOUND = 'Table model not found';
|
||||
|
||||
content: DynamicTableModel;
|
||||
|
||||
editMode: boolean = false;
|
||||
editRow: DynamicTableRow = null;
|
||||
|
||||
private selectArrayCode = [32, 0, 13];
|
||||
|
||||
constructor(public formService: FormService,
|
||||
public elementRef: ElementRef,
|
||||
private visibilityService: WidgetVisibilityService,
|
||||
private logService: LogService,
|
||||
private cd: ChangeDetectorRef) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field) {
|
||||
this.content = new DynamicTableModel(this.field, this.formService);
|
||||
this.visibilityService.refreshVisibility(this.field.form);
|
||||
}
|
||||
}
|
||||
|
||||
forceFocusOnAddButton() {
|
||||
if (this.content) {
|
||||
this.cd.detectChanges();
|
||||
const buttonAddRow = this.elementRef.nativeElement.querySelector('#' + this.content.id + '-add-row');
|
||||
if (this.isDynamicTableReady(buttonAddRow)) {
|
||||
buttonAddRow.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isDynamicTableReady(buttonAddRow) {
|
||||
return this.field && !this.editMode && buttonAddRow;
|
||||
}
|
||||
|
||||
isValid() {
|
||||
let valid = true;
|
||||
|
||||
if (this.content && this.content.field) {
|
||||
valid = this.content.field.isValid;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
onRowClicked(row: DynamicTableRow) {
|
||||
if (this.content) {
|
||||
this.content.selectedRow = row;
|
||||
}
|
||||
}
|
||||
|
||||
onKeyPressed($event: KeyboardEvent, row: DynamicTableRow) {
|
||||
if (this.content && this.isEnterOrSpacePressed($event.keyCode)) {
|
||||
this.content.selectedRow = row;
|
||||
}
|
||||
}
|
||||
|
||||
private isEnterOrSpacePressed(keyCode) {
|
||||
return this.selectArrayCode.indexOf(keyCode) !== -1;
|
||||
}
|
||||
|
||||
hasSelection(): boolean {
|
||||
return !!(this.content && this.content.selectedRow);
|
||||
}
|
||||
|
||||
moveSelectionUp(): boolean {
|
||||
if (this.content && !this.readOnly) {
|
||||
this.content.moveRow(this.content.selectedRow, -1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
moveSelectionDown(): boolean {
|
||||
if (this.content && !this.readOnly) {
|
||||
this.content.moveRow(this.content.selectedRow, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
deleteSelection(): boolean {
|
||||
if (this.content && !this.readOnly) {
|
||||
this.content.deleteRow(this.content.selectedRow);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
addNewRow(): boolean {
|
||||
if (this.content && !this.readOnly) {
|
||||
this.editRow = {
|
||||
isNew: true,
|
||||
selected: false,
|
||||
value: {}
|
||||
};
|
||||
this.editMode = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
editSelection(): boolean {
|
||||
if (this.content && !this.readOnly) {
|
||||
this.editRow = this.copyRow(this.content.selectedRow);
|
||||
this.editMode = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getCellValue(row: DynamicTableRow, column: DynamicTableColumn): any {
|
||||
if (this.content) {
|
||||
const cellValue = this.content.getCellValue(row, column);
|
||||
if (column.type === 'Amount') {
|
||||
return (column.amountCurrency || '$') + ' ' + (cellValue || 0);
|
||||
}
|
||||
return cellValue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
onSaveChanges() {
|
||||
if (this.content) {
|
||||
if (this.editRow.isNew) {
|
||||
const row = this.copyRow(this.editRow);
|
||||
this.content.selectedRow = null;
|
||||
this.content.addRow(row);
|
||||
this.editRow.isNew = false;
|
||||
} else {
|
||||
this.content.selectedRow.value = this.copyObject(this.editRow.value);
|
||||
}
|
||||
this.content.flushValue();
|
||||
} else {
|
||||
this.logService.error(this.ERROR_MODEL_NOT_FOUND);
|
||||
}
|
||||
this.editMode = false;
|
||||
this.forceFocusOnAddButton();
|
||||
}
|
||||
|
||||
onCancelChanges() {
|
||||
this.editMode = false;
|
||||
this.editRow = null;
|
||||
this.forceFocusOnAddButton();
|
||||
}
|
||||
|
||||
copyRow(row: DynamicTableRow): DynamicTableRow {
|
||||
return { value: this.copyObject(row.value) } as DynamicTableRow;
|
||||
}
|
||||
|
||||
private copyObject(obj: any): any {
|
||||
let result = obj;
|
||||
|
||||
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||
result = Object.assign({}, obj);
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (typeof obj[key] === 'object') {
|
||||
result[key] = this.copyObject(obj[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
<div class="adf-amount-editor">
|
||||
<mat-form-field>
|
||||
<label [attr.for]="column.id">{{displayName}}</label>
|
||||
<input matInput
|
||||
type="number"
|
||||
[value]="table.getCellValue(row, column)"
|
||||
(keyup)="onValueChanged(row, column, $event)"
|
||||
[required]="column.required"
|
||||
[disabled]="!column.editable"
|
||||
[id]="column.id">
|
||||
</mat-form-field>
|
||||
</div>
|
@@ -1,5 +0,0 @@
|
||||
.adf {
|
||||
&-text-editor {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { DynamicTableColumn } from '../../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from '../../dynamic-table-row.model';
|
||||
import { AmountEditorComponent } from './amount.editor';
|
||||
|
||||
describe('AmountEditorComponent', () => {
|
||||
|
||||
let editor: AmountEditorComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
editor = new AmountEditorComponent();
|
||||
});
|
||||
|
||||
it('should update row value on change', () => {
|
||||
const row = { value: {} } as DynamicTableRow;
|
||||
const column = { id: 'key' } as DynamicTableColumn;
|
||||
|
||||
const value = 100;
|
||||
const event = { target: { value } };
|
||||
|
||||
editor.onValueChanged(row, column, event);
|
||||
expect(row.value[column.id]).toBe(value);
|
||||
});
|
||||
});
|
@@ -1,52 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { DynamicTableColumn } from '../../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from '../../dynamic-table-row.model';
|
||||
import { DynamicTableModel } from '../../dynamic-table.widget.model';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-amount-editor',
|
||||
templateUrl: './amount.editor.html',
|
||||
styleUrls: ['./amount.editor.scss']
|
||||
})
|
||||
export class AmountEditorComponent implements OnInit {
|
||||
|
||||
@Input()
|
||||
table: DynamicTableModel;
|
||||
|
||||
@Input()
|
||||
row: DynamicTableRow;
|
||||
|
||||
@Input()
|
||||
column: DynamicTableColumn;
|
||||
|
||||
displayName: string;
|
||||
|
||||
ngOnInit() {
|
||||
this.displayName = this.table.getDisplayText(this.column);
|
||||
}
|
||||
|
||||
onValueChanged(row: DynamicTableRow, column: DynamicTableColumn, event: any) {
|
||||
const value: number = Number(event.target.value);
|
||||
row.value[column.id] = value;
|
||||
}
|
||||
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
<label [attr.for]="column.id">
|
||||
<mat-checkbox
|
||||
color="primary"
|
||||
[id]="column.id"
|
||||
[checked]="table.getCellValue(row, column)"
|
||||
[required]="column.required"
|
||||
[disabled]="!column.editable"
|
||||
(change)="onValueChanged(row, column, $event)">
|
||||
<span class="adf-checkbox-label">{{column.name}}</span>
|
||||
</mat-checkbox>
|
||||
</label>
|
@@ -1,9 +0,0 @@
|
||||
.adf {
|
||||
&-checkbox-label {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
font-size: var(--theme-subheading-2-font-size);
|
||||
line-height: 24px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { DynamicTableColumn } from '../../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from '../../dynamic-table-row.model';
|
||||
|
||||
import { BooleanEditorComponent } from './boolean.editor';
|
||||
|
||||
describe('BooleanEditorComponent', () => {
|
||||
|
||||
let component: BooleanEditorComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
component = new BooleanEditorComponent();
|
||||
});
|
||||
|
||||
it('should update row value on change', () => {
|
||||
const row = { value: {} } as DynamicTableRow;
|
||||
const column = { id: 'key' } as DynamicTableColumn;
|
||||
const event = { checked: true } ;
|
||||
|
||||
component.onValueChanged(row, column, event);
|
||||
expect(row.value[column.id]).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -1,46 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { DynamicTableColumn } from '../../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from '../../dynamic-table-row.model';
|
||||
import { DynamicTableModel } from '../../dynamic-table.widget.model';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-boolean-editor',
|
||||
templateUrl: './boolean.editor.html',
|
||||
styleUrls: ['./boolean.editor.scss']
|
||||
})
|
||||
export class BooleanEditorComponent {
|
||||
|
||||
@Input()
|
||||
table: DynamicTableModel;
|
||||
|
||||
@Input()
|
||||
row: DynamicTableRow;
|
||||
|
||||
@Input()
|
||||
column: DynamicTableColumn;
|
||||
|
||||
onValueChanged(row: DynamicTableRow, column: DynamicTableColumn, event: any) {
|
||||
const value: boolean = event.checked;
|
||||
row.value[column.id] = value;
|
||||
}
|
||||
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
<div>
|
||||
<mat-form-field class="adf-date-editor">
|
||||
<label [attr.for]="column.id">{{column.name}} ({{DATE_FORMAT}})</label>
|
||||
<input matInput
|
||||
id="dateInput"
|
||||
type="text"
|
||||
[matDatepicker]="datePicker"
|
||||
[value]="value"
|
||||
[id]="column.id"
|
||||
[required]="column.required"
|
||||
[disabled]="!column.editable"
|
||||
(focusout)="onDateChanged($any($event).srcElement)"
|
||||
(dateChange)="onDateChanged($event)">
|
||||
<mat-datepicker-toggle *ngIf="column.editable" matSuffix [for]="datePicker" class="adf-date-editor-button" ></mat-datepicker-toggle>
|
||||
</mat-form-field>
|
||||
<mat-datepicker #datePicker [touchUi]="true"></mat-datepicker>
|
||||
</div>
|
@@ -1,10 +0,0 @@
|
||||
.adf {
|
||||
&-date-editor {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-date-editor-button {
|
||||
position: relative;
|
||||
top: 25px;
|
||||
}
|
||||
}
|
@@ -1,143 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 } from '@angular/core/testing';
|
||||
import { FormFieldModel, FormModel } from '../../../index';
|
||||
import { DynamicTableColumn } from '../../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from '../../dynamic-table-row.model';
|
||||
import { DynamicTableModel } from '../../dynamic-table.widget.model';
|
||||
import { DateEditorComponent } from './date.editor';
|
||||
import { setupTestBed } from '../../../../../../testing/setup-test-bed';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { MatDatepickerInputEvent } from '@angular/material/datepicker';
|
||||
import { CoreTestingModule } from '../../../../../../testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
describe('DateEditorComponent', () => {
|
||||
let component: DateEditorComponent;
|
||||
let fixture: ComponentFixture<DateEditorComponent>;
|
||||
let row: DynamicTableRow;
|
||||
let column: DynamicTableColumn;
|
||||
let table: DynamicTableModel;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DateEditorComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
row = { value: { date: '1879-03-14T00:00:00.000Z' } } as DynamicTableRow;
|
||||
column = { id: 'date', type: 'Date' } as DynamicTableColumn;
|
||||
const field = new FormFieldModel(new FormModel());
|
||||
table = new DynamicTableModel(field, null);
|
||||
table.rows.push(row);
|
||||
table.columns.push(column);
|
||||
component.table = table;
|
||||
component.row = row;
|
||||
component.column = column;
|
||||
});
|
||||
|
||||
describe('using Date Piker', () => {
|
||||
it('should update row value on change', () => {
|
||||
const input = {value: '14-03-2016' } as MatDatepickerInputEvent<any>;
|
||||
|
||||
component.ngOnInit();
|
||||
component.onDateChanged(input);
|
||||
|
||||
const actual = row.value[column.id];
|
||||
expect(actual).toBe('2016-03-14T00:00:00.000Z');
|
||||
});
|
||||
|
||||
it('should flush value on user input', () => {
|
||||
spyOn(table, 'flushValue').and.callThrough();
|
||||
const input = {value: '14-03-2016' } as MatDatepickerInputEvent<any>;
|
||||
|
||||
component.ngOnInit();
|
||||
component.onDateChanged(input);
|
||||
|
||||
expect(table.flushValue).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('user manual input', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(component, 'onDateChanged').and.callThrough();
|
||||
spyOn(table, 'flushValue').and.callThrough();
|
||||
});
|
||||
|
||||
it('should update row value upon user input', () => {
|
||||
const inputElement = fixture.debugElement.query(By.css('input'));
|
||||
inputElement.nativeElement.value = '14-03-1879';
|
||||
inputElement.nativeElement.dispatchEvent(new Event('focusout'));
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.onDateChanged).toHaveBeenCalled();
|
||||
const actual = row.value[column.id];
|
||||
expect(actual).toBe('1879-03-14T00:00:00.000Z');
|
||||
});
|
||||
|
||||
it('should flush value on user input', () => {
|
||||
const inputElement = fixture.debugElement.query(By.css('input'));
|
||||
inputElement.nativeElement.value = '14-03-1879';
|
||||
inputElement.nativeElement.dispatchEvent(new Event('focusout'));
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(table.flushValue).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not flush value when user input is wrong', () => {
|
||||
const inputElement = fixture.debugElement.query(By.css('input'));
|
||||
inputElement.nativeElement.value = 'ab-bc-de';
|
||||
inputElement.nativeElement.dispatchEvent(new Event('focusout'));
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(table.flushValue).not.toHaveBeenCalled();
|
||||
|
||||
inputElement.nativeElement.value = '12';
|
||||
inputElement.nativeElement.dispatchEvent(new Event('focusout'));
|
||||
fixture.detectChanges();
|
||||
expect(table.flushValue).not.toHaveBeenCalled();
|
||||
|
||||
inputElement.nativeElement.value = '12-11';
|
||||
inputElement.nativeElement.dispatchEvent(new Event('focusout'));
|
||||
fixture.detectChanges();
|
||||
expect(table.flushValue).not.toHaveBeenCalled();
|
||||
|
||||
inputElement.nativeElement.value = '12-13-12';
|
||||
inputElement.nativeElement.dispatchEvent(new Event('focusout'));
|
||||
fixture.detectChanges();
|
||||
expect(table.flushValue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should remove the date when user removes manually', () => {
|
||||
const inputElement = fixture.debugElement.query(By.css('input'));
|
||||
inputElement.nativeElement.value = '';
|
||||
inputElement.nativeElement.dispatchEvent(new Event('focusout'));
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.onDateChanged).toHaveBeenCalled();
|
||||
const actual = row.value[column.id];
|
||||
expect(actual).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,100 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { UserPreferencesService, UserPreferenceValues } from '../../../../../../services/user-preferences.service';
|
||||
|
||||
import { MomentDateAdapter } from '../../../../../../utils/moment-date-adapter';
|
||||
import { MOMENT_DATE_FORMATS } from '../../../../../../utils/moment-date-formats.model';
|
||||
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
||||
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
|
||||
import { MatDatepickerInputEvent } from '@angular/material/datepicker';
|
||||
import moment, { Moment } from 'moment';
|
||||
import { DynamicTableColumn } from '../../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from '../../dynamic-table-row.model';
|
||||
import { DynamicTableModel } from '../../dynamic-table.widget.model';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-date-editor',
|
||||
templateUrl: './date.editor.html',
|
||||
providers: [
|
||||
{provide: DateAdapter, useClass: MomentDateAdapter},
|
||||
{provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS}],
|
||||
styleUrls: ['./date.editor.scss']
|
||||
})
|
||||
export class DateEditorComponent implements OnInit, OnDestroy {
|
||||
|
||||
DATE_FORMAT: string = 'DD-MM-YYYY';
|
||||
|
||||
value: any;
|
||||
|
||||
@Input()
|
||||
table: DynamicTableModel;
|
||||
|
||||
@Input()
|
||||
row: DynamicTableRow;
|
||||
|
||||
@Input()
|
||||
column: DynamicTableColumn;
|
||||
|
||||
minDate: Moment;
|
||||
maxDate: Moment;
|
||||
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
|
||||
constructor(private dateAdapter: DateAdapter<Moment>,
|
||||
private userPreferencesService: UserPreferencesService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.userPreferencesService
|
||||
.select(UserPreferenceValues.Locale)
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe(locale => this.dateAdapter.setLocale(locale));
|
||||
|
||||
const momentDateAdapter = this.dateAdapter as MomentDateAdapter;
|
||||
momentDateAdapter.overrideDisplayFormat = this.DATE_FORMAT;
|
||||
|
||||
this.value = moment(this.table.getCellValue(this.row, this.column), this.DATE_FORMAT);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
onDateChanged(newDateValue: MatDatepickerInputEvent<any> | HTMLInputElement) {
|
||||
if (newDateValue && newDateValue.value) {
|
||||
/* validates the user inputs */
|
||||
const momentDate = moment(newDateValue.value, this.DATE_FORMAT, true);
|
||||
|
||||
if (!momentDate.isValid()) {
|
||||
this.row.value[this.column.id] = newDateValue.value;
|
||||
} else {
|
||||
this.row.value[this.column.id] = `${momentDate.format('YYYY-MM-DD')}T00:00:00.000Z`;
|
||||
this.table.flushValue();
|
||||
}
|
||||
} else {
|
||||
/* removes the date */
|
||||
this.row.value[this.column.id] = '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
<div>
|
||||
<mat-form-field class="adf-date-editor">
|
||||
<label [attr.for]="column.id">{{column.name}} {{DATE_TIME_FORMAT}}</label>
|
||||
<input matInput
|
||||
[matDatetimepicker]="datetimePicker"
|
||||
[(ngModel)]="value"
|
||||
[id]="column.id"
|
||||
[required]="column.required"
|
||||
[disabled]="!column.editable"
|
||||
(focusout)="onDateChanged($any($event).srcElement.value)"
|
||||
(dateChange)="onDateChanged($event)">
|
||||
<mat-datetimepicker-toggle
|
||||
matSuffix
|
||||
[for]="datetimePicker"
|
||||
class="adf-date-editor-button">
|
||||
</mat-datetimepicker-toggle>
|
||||
</mat-form-field>
|
||||
<mat-datetimepicker
|
||||
#datetimePicker
|
||||
type="datetime"
|
||||
[openOnFocus]="true"
|
||||
[timeInterval]="5">
|
||||
</mat-datetimepicker>
|
||||
</div>
|
@@ -1,10 +0,0 @@
|
||||
.adf {
|
||||
&-date-editor {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-date-editor-button {
|
||||
position: relative;
|
||||
top: 25px;
|
||||
}
|
||||
}
|
@@ -1,84 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 } from '@angular/core/testing';
|
||||
import moment from 'moment';
|
||||
import { FormFieldModel, FormModel } from '../../../index';
|
||||
import { DynamicTableColumn } from '../../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from '../../dynamic-table-row.model';
|
||||
import { DynamicTableModel } from '../../dynamic-table.widget.model';
|
||||
import { DateTimeEditorComponent } from './datetime.editor';
|
||||
import { setupTestBed } from '../../../../../../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../../../../../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
describe('DateTimeEditorComponent', () => {
|
||||
let component: DateTimeEditorComponent;
|
||||
let fixture: ComponentFixture<DateTimeEditorComponent>;
|
||||
let row: DynamicTableRow;
|
||||
let column: DynamicTableColumn;
|
||||
let table: DynamicTableModel;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DateTimeEditorComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
row = { value: { date: '1879-03-14T00:00:00.000Z' } } as DynamicTableRow;
|
||||
column = { id: 'datetime', type: 'Datetime' } as DynamicTableColumn;
|
||||
const field = new FormFieldModel(new FormModel());
|
||||
table = new DynamicTableModel(field, null);
|
||||
table.rows.push(row);
|
||||
table.columns.push(column);
|
||||
component.table = table;
|
||||
component.row = row;
|
||||
component.column = column;
|
||||
});
|
||||
|
||||
it('should update fow value on change', () => {
|
||||
component.ngOnInit();
|
||||
const newDate = moment('22-6-2018 04:20 AM', 'D-M-YYYY hh:mm A');
|
||||
component.onDateChanged(newDate);
|
||||
expect(moment(row.value[column.id]).isSame(newDate)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should update row value upon user input', () => {
|
||||
const input = '22-6-2018 04:20 AM';
|
||||
|
||||
component.ngOnInit();
|
||||
component.onDateChanged(input);
|
||||
|
||||
const actual = row.value[column.id];
|
||||
expect(actual).toBe('22-6-2018 04:20 AM');
|
||||
});
|
||||
|
||||
it('should flush value on user input', () => {
|
||||
spyOn(table, 'flushValue').and.callThrough();
|
||||
const input = '22-6-2018 04:20 AM';
|
||||
|
||||
component.ngOnInit();
|
||||
component.onDateChanged(input);
|
||||
|
||||
expect(table.flushValue).toHaveBeenCalled();
|
||||
});
|
||||
});
|
@@ -1,102 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { UserPreferencesService, UserPreferenceValues } from '../../../../../../services/user-preferences.service';
|
||||
import { MomentDateAdapter } from '../../../../../../utils/moment-date-adapter';
|
||||
import { MOMENT_DATE_FORMATS } from '../../../../../../utils/moment-date-formats.model';
|
||||
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
||||
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
|
||||
import moment, { Moment } from 'moment';
|
||||
import { DynamicTableColumn } from '../../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from '../../dynamic-table-row.model';
|
||||
import { DynamicTableModel } from '../../dynamic-table.widget.model';
|
||||
import { DatetimeAdapter, MAT_DATETIME_FORMATS } from '@mat-datetimepicker/core';
|
||||
import { MomentDatetimeAdapter, MAT_MOMENT_DATETIME_FORMATS } from '@mat-datetimepicker/moment';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-datetime-editor',
|
||||
templateUrl: './datetime.editor.html',
|
||||
providers: [
|
||||
{ provide: DateAdapter, useClass: MomentDateAdapter },
|
||||
{ provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS },
|
||||
{ provide: DatetimeAdapter, useClass: MomentDatetimeAdapter },
|
||||
{ provide: MAT_DATETIME_FORMATS, useValue: MAT_MOMENT_DATETIME_FORMATS }
|
||||
],
|
||||
styleUrls: ['./datetime.editor.scss']
|
||||
})
|
||||
export class DateTimeEditorComponent implements OnInit, OnDestroy {
|
||||
|
||||
DATE_TIME_FORMAT: string = 'DD/MM/YYYY HH:mm';
|
||||
|
||||
value: any;
|
||||
|
||||
@Input()
|
||||
table: DynamicTableModel;
|
||||
|
||||
@Input()
|
||||
row: DynamicTableRow;
|
||||
|
||||
@Input()
|
||||
column: DynamicTableColumn;
|
||||
|
||||
minDate: Moment;
|
||||
maxDate: Moment;
|
||||
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
|
||||
constructor(private dateAdapter: DateAdapter<Moment>,
|
||||
private userPreferencesService: UserPreferencesService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.userPreferencesService
|
||||
.select(UserPreferenceValues.Locale)
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe(locale => this.dateAdapter.setLocale(locale));
|
||||
|
||||
const momentDateAdapter = this.dateAdapter as MomentDateAdapter;
|
||||
momentDateAdapter.overrideDisplayFormat = this.DATE_TIME_FORMAT;
|
||||
|
||||
this.value = moment(this.table.getCellValue(this.row, this.column), this.DATE_TIME_FORMAT);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
onDateChanged(newDateValue) {
|
||||
if (newDateValue && newDateValue.value) {
|
||||
const newValue = moment(newDateValue.value, this.DATE_TIME_FORMAT);
|
||||
this.row.value[this.column.id] = newDateValue.value.format(this.DATE_TIME_FORMAT);
|
||||
this.value = newValue;
|
||||
this.table.flushValue();
|
||||
} else if (newDateValue) {
|
||||
const newValue = moment(newDateValue, this.DATE_TIME_FORMAT);
|
||||
this.value = newValue;
|
||||
this.row.value[this.column.id] = newDateValue;
|
||||
this.table.flushValue();
|
||||
} else {
|
||||
this.row.value[this.column.id] = '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<div class="dropdown-editor">
|
||||
<label [attr.for]="column.id">{{column.name}}</label>
|
||||
<mat-form-field>
|
||||
<mat-select
|
||||
floatPlaceholder="never"
|
||||
class="adf-dropdown-editor-select"
|
||||
[id]="column.id"
|
||||
[(ngModel)]="value"
|
||||
[required]="column.required"
|
||||
[disabled]="!column.editable"
|
||||
(selectionChange)="onValueChanged(row, column, $event)">
|
||||
<mat-option></mat-option>
|
||||
<mat-option *ngFor="let opt of options" [value]="opt.name" [id]="opt.id">{{opt.name}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
@@ -1,5 +0,0 @@
|
||||
.adf {
|
||||
&-dropdown-editor-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@@ -1,307 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Observable, of, throwError } from 'rxjs';
|
||||
import { FormService } from '../../../../../services/form.service';
|
||||
import { FormFieldModel, FormModel } from '../../../core';
|
||||
import { DynamicTableColumnOption } from '../../dynamic-table-column-option.model';
|
||||
import { DynamicTableColumn } from '../../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from '../../dynamic-table-row.model';
|
||||
import { DynamicTableModel } from '../../dynamic-table.widget.model';
|
||||
import { DropdownEditorComponent } from './dropdown.editor';
|
||||
import { setupTestBed } from '../../../../../../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../../../../../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AlfrescoApiService } from '../../../../../../services';
|
||||
|
||||
describe('DropdownEditorComponent', () => {
|
||||
|
||||
let component: DropdownEditorComponent;
|
||||
let formService: FormService;
|
||||
let form: FormModel;
|
||||
let table: DynamicTableModel;
|
||||
let column: DynamicTableColumn;
|
||||
let row: DynamicTableRow;
|
||||
let alfrescoApiService: AlfrescoApiService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
||||
formService = new FormService(null, alfrescoApiService, null);
|
||||
|
||||
row = {value: {dropdown: 'one'}} as DynamicTableRow;
|
||||
column = {
|
||||
id: 'dropdown',
|
||||
options: [
|
||||
{id: '1', name: 'one'},
|
||||
{id: '2', name: 'two'}
|
||||
]
|
||||
} as DynamicTableColumn;
|
||||
|
||||
form = new FormModel({taskId: '<task-id>'});
|
||||
table = new DynamicTableModel(new FormFieldModel(form, {id: '<field-id>'}), formService);
|
||||
table.rows.push(row);
|
||||
table.columns.push(column);
|
||||
|
||||
component = new DropdownEditorComponent(formService, null);
|
||||
component.table = table;
|
||||
component.row = row;
|
||||
component.column = column;
|
||||
});
|
||||
|
||||
it('should require table field to setup', () => {
|
||||
table.field = null;
|
||||
component.ngOnInit();
|
||||
expect(component.value).toBeNull();
|
||||
expect(component.options).toEqual([]);
|
||||
});
|
||||
|
||||
it('should setup with manual mode', () => {
|
||||
row.value[column.id] = 'two';
|
||||
component.ngOnInit();
|
||||
expect(component.options).toEqual(column.options);
|
||||
expect(component.value).toBe(row.value[column.id]);
|
||||
});
|
||||
|
||||
it('should setup empty columns for manual mode', () => {
|
||||
column.options = null;
|
||||
component.ngOnInit();
|
||||
expect(component.options).toEqual([]);
|
||||
});
|
||||
|
||||
it('should setup with REST mode', () => {
|
||||
column.optionType = 'rest';
|
||||
row.value[column.id] = 'twelve';
|
||||
|
||||
const restResults: DynamicTableColumnOption[] = [
|
||||
{id: '11', name: 'eleven'},
|
||||
{id: '12', name: 'twelve'}
|
||||
];
|
||||
|
||||
spyOn(formService, 'getRestFieldValuesColumn').and.returnValue(
|
||||
new Observable((observer) => {
|
||||
observer.next(restResults);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(formService.getRestFieldValuesColumn).toHaveBeenCalledWith(
|
||||
form.taskId,
|
||||
table.field.id,
|
||||
column.id
|
||||
);
|
||||
|
||||
expect(column.options).toEqual(restResults);
|
||||
expect(component.options).toEqual(restResults);
|
||||
expect(component.value).toBe(row.value[column.id]);
|
||||
});
|
||||
|
||||
it('should create empty options array on REST response', () => {
|
||||
column.optionType = 'rest';
|
||||
|
||||
spyOn(formService, 'getRestFieldValuesColumn').and.returnValue(
|
||||
new Observable((observer) => {
|
||||
observer.next(null);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(formService.getRestFieldValuesColumn).toHaveBeenCalledWith(
|
||||
form.taskId,
|
||||
table.field.id,
|
||||
column.id
|
||||
);
|
||||
|
||||
expect(column.options).toEqual([]);
|
||||
expect(component.options).toEqual([]);
|
||||
expect(component.value).toBe(row.value[column.id]);
|
||||
});
|
||||
|
||||
it('should handle REST error getting options with task id', () => {
|
||||
column.optionType = 'rest';
|
||||
const error = 'error';
|
||||
|
||||
spyOn(formService, 'getRestFieldValuesColumn').and.returnValue(
|
||||
throwError(error)
|
||||
);
|
||||
spyOn(component, 'handleError').and.stub();
|
||||
|
||||
component.ngOnInit();
|
||||
expect(component.handleError).toHaveBeenCalledWith(error);
|
||||
});
|
||||
|
||||
it('should handle REST error getting option with processDefinitionId', () => {
|
||||
column.optionType = 'rest';
|
||||
const procForm = new FormModel({processDefinitionId: '<process-definition-id>'});
|
||||
const procTable = new DynamicTableModel(new FormFieldModel(procForm, {id: '<field-id>'}), formService);
|
||||
component.table = procTable;
|
||||
const error = 'error';
|
||||
|
||||
spyOn(formService, 'getRestFieldValuesColumnByProcessId').and.returnValue(
|
||||
throwError(error)
|
||||
);
|
||||
spyOn(component, 'handleError').and.stub();
|
||||
|
||||
component.ngOnInit();
|
||||
expect(component.handleError).toHaveBeenCalledWith(error);
|
||||
});
|
||||
|
||||
it('should update row on value change', () => {
|
||||
const event = {value: 'two'};
|
||||
component.onValueChanged(row, column, event);
|
||||
expect(row.value[column.id]).toBe(column.options[1]);
|
||||
});
|
||||
|
||||
describe('when template is ready', () => {
|
||||
let dropDownEditorComponent: DropdownEditorComponent;
|
||||
let fixture: ComponentFixture<DropdownEditorComponent>;
|
||||
let element: HTMLElement;
|
||||
let stubFormService;
|
||||
const fakeOptionList: DynamicTableColumnOption[] = [{
|
||||
id: 'opt_1',
|
||||
name: 'option_1'
|
||||
}, {
|
||||
id: 'opt_2',
|
||||
name: 'option_2'
|
||||
}, {id: 'opt_3', name: 'option_3'}];
|
||||
let dynamicTable: DynamicTableModel;
|
||||
|
||||
const openSelect = () => {
|
||||
const dropdown = fixture.debugElement.query(By.css('.mat-select-trigger'));
|
||||
dropdown.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DropdownEditorComponent);
|
||||
dropDownEditorComponent = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
describe('and dropdown is populated via taskId', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
stubFormService = fixture.debugElement.injector.get(FormService);
|
||||
spyOn(stubFormService, 'getRestFieldValuesColumn').and.returnValue(of(fakeOptionList));
|
||||
row = {value: {dropdown: 'one'}} as DynamicTableRow;
|
||||
column = {
|
||||
id: 'column-id',
|
||||
optionType: 'rest',
|
||||
options: [
|
||||
{id: '1', name: 'one'},
|
||||
{id: '2', name: 'two'}
|
||||
]
|
||||
} as DynamicTableColumn;
|
||||
form = new FormModel({taskId: '<task-id>'});
|
||||
dynamicTable = new DynamicTableModel(new FormFieldModel(form, {id: '<field-id>'}), formService);
|
||||
dynamicTable.rows.push(row);
|
||||
dynamicTable.columns.push(column);
|
||||
dropDownEditorComponent.table = dynamicTable;
|
||||
dropDownEditorComponent.column = column;
|
||||
dropDownEditorComponent.row = row;
|
||||
dropDownEditorComponent.table.field = new FormFieldModel(form, {
|
||||
id: 'dropdown-id',
|
||||
name: 'date-name',
|
||||
type: 'dropdown',
|
||||
readOnly: 'false',
|
||||
restUrl: 'fake-rest-url'
|
||||
});
|
||||
dropDownEditorComponent.table.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show visible dropdown widget', () => {
|
||||
expect(element.querySelector('#column-id')).toBeDefined();
|
||||
expect(element.querySelector('#column-id')).not.toBeNull();
|
||||
|
||||
openSelect();
|
||||
|
||||
const optOne = fixture.debugElement.queryAll(By.css('[id="mat-option-1"]'));
|
||||
const optTwo = fixture.debugElement.queryAll(By.css('[id="mat-option-2"]'));
|
||||
const optThree = fixture.debugElement.queryAll(By.css('[id="mat-option-3"]'));
|
||||
|
||||
expect(optOne).not.toBeNull();
|
||||
expect(optTwo).not.toBeNull();
|
||||
expect(optThree).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('and dropdown is populated via processDefinitionId', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
stubFormService = fixture.debugElement.injector.get(FormService);
|
||||
spyOn(stubFormService, 'getRestFieldValuesColumnByProcessId').and.returnValue(of(fakeOptionList));
|
||||
row = {value: {dropdown: 'one'}} as DynamicTableRow;
|
||||
column = {
|
||||
id: 'column-id',
|
||||
optionType: 'rest',
|
||||
options: [
|
||||
{id: '1', name: 'one'},
|
||||
{id: '2', name: 'two'}
|
||||
]
|
||||
} as DynamicTableColumn;
|
||||
form = new FormModel({processDefinitionId: '<proc-id>'});
|
||||
dynamicTable = new DynamicTableModel(new FormFieldModel(form, {id: '<field-id>'}), formService);
|
||||
dynamicTable.rows.push(row);
|
||||
dynamicTable.columns.push(column);
|
||||
dropDownEditorComponent.table = dynamicTable;
|
||||
dropDownEditorComponent.column = column;
|
||||
dropDownEditorComponent.row = row;
|
||||
dropDownEditorComponent.table.field = new FormFieldModel(form, {
|
||||
id: 'dropdown-id',
|
||||
name: 'date-name',
|
||||
type: 'dropdown',
|
||||
readOnly: 'false',
|
||||
restUrl: 'fake-rest-url'
|
||||
});
|
||||
dropDownEditorComponent.table.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show visible dropdown widget', () => {
|
||||
expect(element.querySelector('#column-id')).toBeDefined();
|
||||
expect(element.querySelector('#column-id')).not.toBeNull();
|
||||
|
||||
openSelect();
|
||||
|
||||
const optOne = fixture.debugElement.queryAll(By.css('[id="mat-option-1"]'));
|
||||
const optTwo = fixture.debugElement.queryAll(By.css('[id="mat-option-2"]'));
|
||||
const optThree = fixture.debugElement.queryAll(By.css('[id="mat-option-3"]'));
|
||||
|
||||
expect(optOne).not.toBeNull();
|
||||
expect(optTwo).not.toBeNull();
|
||||
expect(optThree).not.toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,110 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { LogService } from '../../../../../../services/log.service';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { FormService } from '../../../../../services/form.service';
|
||||
import { DynamicTableColumnOption } from '../../dynamic-table-column-option.model';
|
||||
import { DynamicTableColumn } from '../../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from '../../dynamic-table-row.model';
|
||||
import { DynamicTableModel } from '../../dynamic-table.widget.model';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-dropdown-editor',
|
||||
templateUrl: './dropdown.editor.html',
|
||||
styleUrls: ['./dropdown.editor.scss']
|
||||
})
|
||||
export class DropdownEditorComponent implements OnInit {
|
||||
|
||||
value: any = null;
|
||||
options: DynamicTableColumnOption[] = [];
|
||||
|
||||
@Input()
|
||||
table: DynamicTableModel;
|
||||
|
||||
@Input()
|
||||
row: DynamicTableRow;
|
||||
|
||||
@Input()
|
||||
column: DynamicTableColumn;
|
||||
|
||||
constructor(public formService: FormService,
|
||||
private logService: LogService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const field = this.table.field;
|
||||
if (field) {
|
||||
if (this.column.optionType === 'rest') {
|
||||
if (this.table.form && this.table.form.taskId) {
|
||||
this.getValuesByTaskId(field);
|
||||
} else {
|
||||
this.getValuesByProcessDefinitionId(field);
|
||||
}
|
||||
} else {
|
||||
this.options = this.column.options || [];
|
||||
this.value = this.table.getCellValue(this.row, this.column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getValuesByTaskId(field) {
|
||||
this.formService
|
||||
.getRestFieldValuesColumn(
|
||||
field.form.taskId,
|
||||
field.id,
|
||||
this.column.id
|
||||
)
|
||||
.subscribe(
|
||||
(dynamicTableColumnOption: DynamicTableColumnOption[]) => {
|
||||
this.column.options = dynamicTableColumnOption || [];
|
||||
this.options = this.column.options;
|
||||
this.value = this.table.getCellValue(this.row, this.column);
|
||||
},
|
||||
(err) => this.handleError(err)
|
||||
);
|
||||
}
|
||||
|
||||
getValuesByProcessDefinitionId(field) {
|
||||
this.formService
|
||||
.getRestFieldValuesColumnByProcessId(
|
||||
field.form.processDefinitionId,
|
||||
field.id,
|
||||
this.column.id
|
||||
)
|
||||
.subscribe(
|
||||
(dynamicTableColumnOption: DynamicTableColumnOption[]) => {
|
||||
this.column.options = dynamicTableColumnOption || [];
|
||||
this.options = this.column.options;
|
||||
this.value = this.table.getCellValue(this.row, this.column);
|
||||
},
|
||||
(err) => this.handleError(err)
|
||||
);
|
||||
}
|
||||
|
||||
onValueChanged(row: DynamicTableRow, column: DynamicTableColumn, event: any) {
|
||||
let value: any = event.value;
|
||||
value = column.options.find((opt) => opt.name === value);
|
||||
row.value[column.id] = value;
|
||||
}
|
||||
|
||||
handleError(error: any) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
.row-editor {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.row-editor__validation-summary {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.row-editor__invalid .row-editor__validation-summary {
|
||||
padding: 8px 16px;
|
||||
color: #d50000;
|
||||
visibility: visible;
|
||||
}
|
@@ -1,54 +0,0 @@
|
||||
<div class="row-editor mdl-shadow--2dp"
|
||||
[class.row-editor__invalid]="!validationSummary.isValid">
|
||||
<div class="mdl-grid" *ngFor="let column of table.columns">
|
||||
<div class="mdl-cell mdl-cell--6-col" [ngSwitch]="column.type">
|
||||
<div *ngSwitchCase="'Dropdown'">
|
||||
<adf-dropdown-editor
|
||||
[table]="table"
|
||||
[row]="row"
|
||||
[column]="column">
|
||||
</adf-dropdown-editor>
|
||||
</div>
|
||||
<div *ngSwitchCase="'Date'">
|
||||
<adf-date-editor
|
||||
[table]="table"
|
||||
[row]="row"
|
||||
[column]="column">
|
||||
</adf-date-editor>
|
||||
</div>
|
||||
<div *ngSwitchCase="'Datetime'">
|
||||
<adf-datetime-editor
|
||||
[table]="table"
|
||||
[row]="row"
|
||||
[column]="column">
|
||||
</adf-datetime-editor>
|
||||
</div>
|
||||
<div *ngSwitchCase="'Boolean'">
|
||||
<adf-boolean-editor
|
||||
[table]="table"
|
||||
[row]="row"
|
||||
[column]="column">
|
||||
</adf-boolean-editor>
|
||||
</div>
|
||||
<div *ngSwitchCase="'Amount'">
|
||||
<adf-amount-editor
|
||||
[table]="table"
|
||||
[row]="row"
|
||||
[column]="column">
|
||||
</adf-amount-editor>
|
||||
</div>
|
||||
<div *ngSwitchDefault>
|
||||
<adf-text-editor
|
||||
[table]="table"
|
||||
[row]="row"
|
||||
[column]="column">
|
||||
</adf-text-editor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<error-widget [error]="validationSummary"></error-widget>
|
||||
<div>
|
||||
<button mat-button (click)="onCancelChanges()">Cancel</button>
|
||||
<button mat-button (click)="onSaveChanges()">Save</button>
|
||||
</div>
|
||||
</div>
|
@@ -1,95 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { FormFieldModel, FormModel } from '../../core';
|
||||
import { FormService } from '../../../../services/form.service';
|
||||
import { DynamicTableColumn } from '../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from '../dynamic-table-row.model';
|
||||
import { DynamicTableModel } from '../dynamic-table.widget.model';
|
||||
import { RowEditorComponent } from './row.editor';
|
||||
import { AlfrescoApiService } from '../../../../../services';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { CoreTestingModule, setupTestBed } from '../../../../../testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DynamicRowValidationSummary } from '../dynamic-row-validation-summary.model';
|
||||
|
||||
describe('RowEditorComponent', () => {
|
||||
|
||||
let component: RowEditorComponent;
|
||||
let alfrescoApiService: AlfrescoApiService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
||||
|
||||
component = new RowEditorComponent();
|
||||
const field = new FormFieldModel(new FormModel());
|
||||
component.table = new DynamicTableModel(field, new FormService(null, alfrescoApiService, null));
|
||||
component.row = {} as DynamicTableRow;
|
||||
component.column = {} as DynamicTableColumn;
|
||||
});
|
||||
|
||||
it('should be valid upon init', () => {
|
||||
expect(component.validationSummary.isValid).toBeTruthy();
|
||||
expect(component.validationSummary.message).toBe('');
|
||||
});
|
||||
|
||||
it('should emit [cancel] event', (done) => {
|
||||
component.cancel.subscribe((e) => {
|
||||
expect(e.table).toBe(component.table);
|
||||
expect(e.row).toBe(component.row);
|
||||
expect(e.column).toBe(component.column);
|
||||
done();
|
||||
});
|
||||
component.onCancelChanges();
|
||||
});
|
||||
|
||||
it('should validate row on save', () => {
|
||||
spyOn(component.table, 'validateRow').and.callThrough();
|
||||
component.onSaveChanges();
|
||||
expect(component.table.validateRow).toHaveBeenCalledWith(component.row);
|
||||
});
|
||||
|
||||
it('should emit [save] event', (done) => {
|
||||
spyOn(component.table, 'validateRow').and.returnValue(
|
||||
new DynamicRowValidationSummary({ isValid: true, message: null })
|
||||
);
|
||||
component.save.subscribe((event) => {
|
||||
expect(event.table).toBe(component.table);
|
||||
expect(event.row).toBe(component.row);
|
||||
expect(event.column).toBe(component.column);
|
||||
done();
|
||||
});
|
||||
component.onSaveChanges();
|
||||
});
|
||||
|
||||
it('should not emit [save] event for invalid row', () => {
|
||||
spyOn(component.table, 'validateRow').and.returnValue(
|
||||
new DynamicRowValidationSummary({ isValid: false, message: 'error' })
|
||||
);
|
||||
let raised = false;
|
||||
component.save.subscribe(() => raised = true);
|
||||
component.onSaveChanges();
|
||||
expect(raised).toBeFalsy();
|
||||
});
|
||||
});
|
@@ -1,81 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { DynamicRowValidationSummary } from '../dynamic-row-validation-summary.model';
|
||||
import { DynamicTableColumn } from '../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from '../dynamic-table-row.model';
|
||||
import { DynamicTableModel } from '../dynamic-table.widget.model';
|
||||
|
||||
@Component({
|
||||
selector: 'row-editor',
|
||||
templateUrl: './row.editor.html',
|
||||
styleUrls: ['./row.editor.css']
|
||||
})
|
||||
export class RowEditorComponent {
|
||||
|
||||
@Input()
|
||||
table: DynamicTableModel;
|
||||
|
||||
@Input()
|
||||
row: DynamicTableRow;
|
||||
|
||||
@Input()
|
||||
column: DynamicTableColumn;
|
||||
|
||||
@Output()
|
||||
save: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@Output()
|
||||
cancel: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
validationSummary: DynamicRowValidationSummary;
|
||||
|
||||
constructor() {
|
||||
this.validationSummary = new DynamicRowValidationSummary({ isValid: true, message: '' });
|
||||
}
|
||||
|
||||
onCancelChanges() {
|
||||
this.cancel.emit({
|
||||
table: this.table,
|
||||
row: this.row,
|
||||
column: this.column
|
||||
});
|
||||
}
|
||||
|
||||
onSaveChanges() {
|
||||
this.validate();
|
||||
if (this.isValid()) {
|
||||
this.save.emit({
|
||||
table: this.table,
|
||||
row: this.row,
|
||||
column: this.column
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private isValid(): boolean {
|
||||
return this.validationSummary && this.validationSummary.isValid;
|
||||
}
|
||||
|
||||
private validate() {
|
||||
this.validationSummary = this.table.validateRow(this.row);
|
||||
}
|
||||
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
<div class="adf-text-editor">
|
||||
<mat-form-field>
|
||||
<label [attr.for]="column.id">{{displayName}}</label>
|
||||
<input matInput
|
||||
type="text"
|
||||
[value]="table.getCellValue(row, column)"
|
||||
(keyup)="onValueChanged(row, column, $event)"
|
||||
[required]="column.required"
|
||||
[disabled]="!column.editable"
|
||||
[id]="column.id">
|
||||
</mat-form-field>
|
||||
</div>
|
@@ -1,5 +0,0 @@
|
||||
.adf {
|
||||
&-text-editor {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { DynamicTableColumn } from '../../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from '../../dynamic-table-row.model';
|
||||
import { TextEditorComponent } from './text.editor';
|
||||
|
||||
describe('TextEditorComponent', () => {
|
||||
|
||||
let editor: TextEditorComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
editor = new TextEditorComponent();
|
||||
});
|
||||
|
||||
it('should update row value on change', () => {
|
||||
const row = { value: {} } as DynamicTableRow;
|
||||
const column = { id: 'key' } as DynamicTableColumn;
|
||||
|
||||
const value = '<value>';
|
||||
const event = { target: { value } };
|
||||
|
||||
editor.onValueChanged(row, column, event);
|
||||
expect(row.value[column.id]).toBe(value);
|
||||
});
|
||||
});
|
@@ -1,52 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { DynamicTableColumn } from '../../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from '../../dynamic-table-row.model';
|
||||
import { DynamicTableModel } from '../../dynamic-table.widget.model';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-text-editor',
|
||||
templateUrl: './text.editor.html',
|
||||
styleUrls: ['./text.editor.scss']
|
||||
})
|
||||
export class TextEditorComponent implements OnInit {
|
||||
|
||||
@Input()
|
||||
table: DynamicTableModel;
|
||||
|
||||
@Input()
|
||||
row: DynamicTableRow;
|
||||
|
||||
@Input()
|
||||
column: DynamicTableColumn;
|
||||
|
||||
displayName: string;
|
||||
|
||||
ngOnInit() {
|
||||
this.displayName = this.table.getDisplayText(this.column);
|
||||
}
|
||||
|
||||
onValueChanged(row: DynamicTableRow, column: DynamicTableColumn, event: any) {
|
||||
const value: any = event.target.value;
|
||||
row.value[column.id] = value;
|
||||
}
|
||||
|
||||
}
|
@@ -1,63 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { CellValidator } from './cell-validator.model';
|
||||
import { DynamicRowValidationSummary } from './dynamic-row-validation-summary.model';
|
||||
import { DynamicTableColumn } from './dynamic-table-column.model';
|
||||
import { DynamicTableRow } from './dynamic-table-row.model';
|
||||
|
||||
export class NumberCellValidator implements CellValidator {
|
||||
|
||||
private supportedTypes: string[] = [
|
||||
'Number',
|
||||
'Amount'
|
||||
];
|
||||
|
||||
isSupported(column: DynamicTableColumn): boolean {
|
||||
return column && column.required && this.supportedTypes.indexOf(column.type) > -1;
|
||||
}
|
||||
|
||||
isNumber(value: any): boolean {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !isNaN(+value);
|
||||
}
|
||||
|
||||
validate(row: DynamicTableRow, column: DynamicTableColumn, summary?: DynamicRowValidationSummary): boolean {
|
||||
|
||||
if (this.isSupported(column)) {
|
||||
const value = row.value[column.id];
|
||||
if (value === null ||
|
||||
value === undefined ||
|
||||
value === '' ||
|
||||
this.isNumber(value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (summary) {
|
||||
summary.isValid = false;
|
||||
summary.message = `Field '${column.name}' must be a number.`;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,55 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { CellValidator } from './cell-validator.model';
|
||||
import { DynamicRowValidationSummary } from './dynamic-row-validation-summary.model';
|
||||
import { DynamicTableColumn } from './dynamic-table-column.model';
|
||||
import { DynamicTableRow } from './dynamic-table-row.model';
|
||||
|
||||
export class RequiredCellValidator implements CellValidator {
|
||||
|
||||
private supportedTypes: string[] = [
|
||||
'String',
|
||||
'Number',
|
||||
'Amount',
|
||||
'Date',
|
||||
'Dropdown'
|
||||
];
|
||||
|
||||
isSupported(column: DynamicTableColumn): boolean {
|
||||
return column && column.required && this.supportedTypes.indexOf(column.type) > -1;
|
||||
}
|
||||
|
||||
validate(row: DynamicTableRow, column: DynamicTableColumn, summary?: DynamicRowValidationSummary): boolean {
|
||||
if (this.isSupported(column)) {
|
||||
const value = row.value[column.id];
|
||||
if (column.required) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
if (summary) {
|
||||
summary.isValid = false;
|
||||
summary.message = `Field '${column.name}' is required.`;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
<div class="adf-group-widget {{field.className}}"
|
||||
[class.is-dirty]="!!field.value"
|
||||
[class.adf-invalid]="!field.isValid && isTouched()"
|
||||
[class.adf-readonly]="field.readOnly"
|
||||
id="functional-group-div">
|
||||
|
||||
<mat-form-field>
|
||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||
<input matInput
|
||||
class="adf-input"
|
||||
type="text"
|
||||
data-automation-id="adf-group-search-input"
|
||||
[id]="field.id"
|
||||
[formControl]="searchTerm"
|
||||
[placeholder]="field.placeholder"
|
||||
(blur)="markAsTouched()"
|
||||
[matAutocomplete]="auto">
|
||||
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="updateOption($event.option.value)" [displayWith]="getDisplayName">
|
||||
<mat-option *ngFor="let item of groups$ | async; let i = index"
|
||||
id="adf-group-widget-user-{{i}}"
|
||||
[id]="field.id +'-'+item.id"
|
||||
[value]="item">
|
||||
<span id="adf-group-label-name">{{item.name}}</span>
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
|
||||
</mat-form-field>
|
||||
<error-widget [error]="field.validationSummary"></error-widget>
|
||||
<error-widget *ngIf="isInvalidFieldRequired() && isTouched()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||
</div>
|
@@ -1,5 +0,0 @@
|
||||
.adf {
|
||||
&-group-widget {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@@ -1,186 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { of, timer } from 'rxjs';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { FormFieldModel } from '../core/form-field.model';
|
||||
import { FormModel } from '../core/form.model';
|
||||
import { GroupModel } from '../core/group.model';
|
||||
import { FunctionalGroupWidgetComponent } from './functional-group.widget';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { CoreTestingModule, setupTestBed } from '../../../../testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { FormFieldTypes } from '../core/form-field-types';
|
||||
|
||||
describe('FunctionalGroupWidgetComponent', () => {
|
||||
let fixture: ComponentFixture<FunctionalGroupWidgetComponent>;
|
||||
let component: FunctionalGroupWidgetComponent;
|
||||
let formService: FormService;
|
||||
let getWorkflowGroupsSpy: jasmine.Spy;
|
||||
let element: HTMLElement;
|
||||
const groups: GroupModel[] = [
|
||||
{ id: '1', name: 'group 1' },
|
||||
{ id: '2', name: 'group 2' }
|
||||
];
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
formService = TestBed.inject(FormService);
|
||||
getWorkflowGroupsSpy = spyOn(formService, 'getWorkflowGroups').and.returnValue(of([]));
|
||||
|
||||
fixture = TestBed.createComponent(FunctionalGroupWidgetComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.field = new FormFieldModel(new FormModel());
|
||||
element = fixture.nativeElement;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
getWorkflowGroupsSpy.calls.reset();
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
const typeIntoInput = async (text: string) => {
|
||||
component.searchTerm.setValue(text);
|
||||
fixture.detectChanges();
|
||||
|
||||
await timer(300).toPromise();
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
const input = fixture.nativeElement.querySelector('input');
|
||||
input.focus();
|
||||
input.dispatchEvent(new Event('focusin'));
|
||||
input.dispatchEvent(new Event('input'));
|
||||
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
it('should setup text from underlying field on init', async () => {
|
||||
const group: GroupModel = { name: 'group-1'};
|
||||
component.field.value = group;
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.searchTerm.value).toEqual(group.name);
|
||||
});
|
||||
|
||||
it('should not setup text on init', () => {
|
||||
component.field.value = null;
|
||||
component.ngOnInit();
|
||||
expect(component.searchTerm.value).toBeNull();
|
||||
});
|
||||
|
||||
it('should setup group restriction', () => {
|
||||
component.ngOnInit();
|
||||
expect(component.groupId).toBeUndefined();
|
||||
|
||||
component.field.params = { restrictWithGroup: { id: '<id>' } };
|
||||
component.ngOnInit();
|
||||
expect(component.groupId).toBe('<id>');
|
||||
});
|
||||
|
||||
it('should update form on value flush', () => {
|
||||
spyOn(component.field, 'updateForm').and.callThrough();
|
||||
component.updateOption();
|
||||
expect(component.field.updateForm).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should flush selected value', () => {
|
||||
getWorkflowGroupsSpy.and.returnValue(of(groups));
|
||||
|
||||
component.updateOption(groups[1]);
|
||||
|
||||
expect(component.field.value).toBe(groups[1]);
|
||||
});
|
||||
|
||||
it('should fetch groups and show popup on key up', async () => {
|
||||
component.groupId = 'parentGroup';
|
||||
getWorkflowGroupsSpy.and.returnValue(of(groups));
|
||||
|
||||
await typeIntoInput('group');
|
||||
|
||||
const options: HTMLElement[] = Array.from(document.querySelectorAll('[id="adf-group-label-name"]'));
|
||||
expect(options.map(option => option.innerText)).toEqual(['group 1', 'group 2']);
|
||||
expect(getWorkflowGroupsSpy).toHaveBeenCalledWith('group', 'parentGroup');
|
||||
});
|
||||
|
||||
it('should hide popup when fetching empty group list', async () => {
|
||||
component.groupId = 'parentGroup';
|
||||
getWorkflowGroupsSpy.and.returnValues(of(groups), of([]));
|
||||
|
||||
await typeIntoInput('group');
|
||||
|
||||
let options: HTMLElement[] = Array.from(document.querySelectorAll('[id="adf-group-label-name"]'));
|
||||
expect(options.map(option => option.innerText)).toEqual(['group 1', 'group 2']);
|
||||
|
||||
await typeIntoInput('unknown-group');
|
||||
|
||||
options = Array.from(document.querySelectorAll('[id="adf-group-label-name"]'));
|
||||
expect(options).toEqual([]);
|
||||
expect(getWorkflowGroupsSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should not fetch groups when value is missing', async () => {
|
||||
await typeIntoInput('');
|
||||
expect(getWorkflowGroupsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not fetch groups when value violates constraints', async () => {
|
||||
component.minTermLength = 4;
|
||||
await typeIntoInput('123');
|
||||
expect(getWorkflowGroupsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('when is required', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
component.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
|
||||
type: FormFieldTypes.FUNCTIONAL_GROUP,
|
||||
required: true
|
||||
});
|
||||
});
|
||||
|
||||
it('should be marked as invalid after interaction', async () => {
|
||||
const functionalGroupInput = fixture.nativeElement.querySelector('input');
|
||||
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeFalsy();
|
||||
|
||||
functionalGroupInput.dispatchEvent(new Event('blur'));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be able to display label with asterisk', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||
|
||||
expect(asterisk).toBeTruthy();
|
||||
expect(asterisk.textContent).toEqual('*');
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,118 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { Component, ElementRef, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { GroupModel } from '../core/group.model';
|
||||
import { WidgetComponent } from '../widget.component';
|
||||
import { catchError, debounceTime, filter, switchMap, tap } from 'rxjs/operators';
|
||||
import { merge, of } from 'rxjs';
|
||||
import { UntypedFormControl } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'functional-group-widget',
|
||||
templateUrl: './functional-group.widget.html',
|
||||
styleUrls: ['./functional-group.widget.scss'],
|
||||
host: {
|
||||
'(click)': 'event($event)',
|
||||
'(blur)': 'event($event)',
|
||||
'(change)': 'event($event)',
|
||||
'(focus)': 'event($event)',
|
||||
'(focusin)': 'event($event)',
|
||||
'(focusout)': 'event($event)',
|
||||
'(input)': 'event($event)',
|
||||
'(invalid)': 'event($event)',
|
||||
'(select)': 'event($event)'
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class FunctionalGroupWidgetComponent extends WidgetComponent implements OnInit {
|
||||
|
||||
minTermLength: number = 1;
|
||||
groupId: string;
|
||||
searchTerm = new UntypedFormControl();
|
||||
groups$ = merge(this.searchTerm.valueChanges).pipe(
|
||||
tap((search: GroupModel | string) => {
|
||||
const isValid = typeof search !== 'string';
|
||||
const empty = search === '';
|
||||
this.updateOption( isValid ? search as GroupModel : null );
|
||||
this.validateGroup(isValid, empty);
|
||||
}),
|
||||
filter((group: string | GroupModel) => typeof group === 'string' && group.length >= this.minTermLength),
|
||||
debounceTime(300),
|
||||
switchMap((searchTerm: string) => this.formService.getWorkflowGroups(searchTerm, this.groupId)
|
||||
.pipe(catchError(() => of([]))))
|
||||
);
|
||||
|
||||
constructor(public formService: FormService,
|
||||
public elementRef: ElementRef) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field) {
|
||||
|
||||
if (this.field.readOnly) {
|
||||
this.searchTerm.disable();
|
||||
}
|
||||
|
||||
const params = this.field.params;
|
||||
if (params && params.restrictWithGroup) {
|
||||
const restrictWithGroup = params.restrictWithGroup;
|
||||
this.groupId = restrictWithGroup.id;
|
||||
}
|
||||
|
||||
if (this.field.value?.name) {
|
||||
this.searchTerm.setValue(this.field.value.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateOption(option?: GroupModel) {
|
||||
if (option) {
|
||||
this.field.value = option;
|
||||
} else {
|
||||
this.field.value = null;
|
||||
}
|
||||
|
||||
this.field.updateForm();
|
||||
}
|
||||
|
||||
validateGroup(valid: boolean, empty: boolean) {
|
||||
const isEmpty = !this.field.required && (empty || valid);
|
||||
const hasValue = this.field.required && valid;
|
||||
|
||||
if (hasValue || isEmpty) {
|
||||
this.field.validationSummary.message = '';
|
||||
this.field.validate();
|
||||
this.field.form.validateForm();
|
||||
} else {
|
||||
this.field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_VALUE';
|
||||
this.field.markAsInvalid();
|
||||
this.field.form.markAsInvalid();
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(model: GroupModel | string) {
|
||||
if (model) {
|
||||
return typeof model === 'string' ? model : model.name;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
@@ -21,30 +21,14 @@ import { AmountWidgetComponent } from './amount/amount.widget';
|
||||
import { CheckboxWidgetComponent } from './checkbox/checkbox.widget';
|
||||
import { DateWidgetComponent } from './date/date.widget';
|
||||
import { DisplayTextWidgetComponent } from './display-text/display-text.widget';
|
||||
import { DocumentWidgetComponent } from './document/document.widget';
|
||||
import { DropdownWidgetComponent } from './dropdown/dropdown.widget';
|
||||
import { DynamicTableWidgetComponent } from './dynamic-table/dynamic-table.widget';
|
||||
import { BooleanEditorComponent } from './dynamic-table/editors/boolean/boolean.editor';
|
||||
import { DateEditorComponent } from './dynamic-table/editors/date/date.editor';
|
||||
import { DateTimeEditorComponent } from './dynamic-table/editors/datetime/datetime.editor';
|
||||
import { DropdownEditorComponent } from './dynamic-table/editors/dropdown/dropdown.editor';
|
||||
import { RowEditorComponent } from './dynamic-table/editors/row.editor';
|
||||
import { TextEditorComponent } from './dynamic-table/editors/text/text.editor';
|
||||
import { AmountEditorComponent } from './dynamic-table/editors/amount/amount.editor';
|
||||
import { ErrorWidgetComponent } from './error/error.component';
|
||||
import { FunctionalGroupWidgetComponent } from './functional-group/functional-group.widget';
|
||||
import { HyperlinkWidgetComponent } from './hyperlink/hyperlink.widget';
|
||||
import { MultilineTextWidgetComponentComponent } from './multiline-text/multiline-text.widget';
|
||||
import { NumberWidgetComponent } from './number/number.widget';
|
||||
import { PeopleWidgetComponent } from './people/people.widget';
|
||||
import { RadioButtonsWidgetComponent } from './radio-buttons/radio-buttons.widget';
|
||||
import { InputMaskDirective } from './text/text-mask.component';
|
||||
import { TextWidgetComponent } from './text/text.widget';
|
||||
import { TypeaheadWidgetComponent } from './typeahead/typeahead.widget';
|
||||
import { UploadWidgetComponent } from './upload/upload.widget';
|
||||
import { DateTimeWidgetComponent } from './date-time/date-time.widget';
|
||||
import { JsonWidgetComponent } from './json/json.widget';
|
||||
import { UploadFolderWidgetComponent } from './upload-folder/upload-folder.widget';
|
||||
import { FileViewerWidgetComponent } from './file-viewer/file-viewer.widget';
|
||||
import { DisplayRichTextWidgetComponent } from './display-rich-text/display-rich-text.widget';
|
||||
|
||||
@@ -52,41 +36,21 @@ import { DisplayRichTextWidgetComponent } from './display-rich-text/display-rich
|
||||
export * from './widget.component';
|
||||
export * from './core';
|
||||
|
||||
|
||||
// primitives
|
||||
export * from './unknown/unknown.widget';
|
||||
export * from './text/text.widget';
|
||||
export * from './number/number.widget';
|
||||
export * from './checkbox/checkbox.widget';
|
||||
export * from './multiline-text/multiline-text.widget';
|
||||
export * from './dropdown/dropdown.widget';
|
||||
export * from './hyperlink/hyperlink.widget';
|
||||
export * from './radio-buttons/radio-buttons.widget';
|
||||
export * from './display-text/display-text.widget';
|
||||
export * from './upload/upload.widget';
|
||||
export * from './typeahead/typeahead.widget';
|
||||
export * from './functional-group/functional-group.widget';
|
||||
export * from './people/people.widget';
|
||||
export * from './date/date.widget';
|
||||
export * from './amount/amount.widget';
|
||||
export * from './dynamic-table/dynamic-table.widget';
|
||||
export * from './error/error.component';
|
||||
export * from './document/document.widget';
|
||||
export * from './date-time/date-time.widget';
|
||||
export * from './json/json.widget';
|
||||
export * from './upload-folder/upload-folder.widget';
|
||||
export * from './file-viewer/file-viewer.widget';
|
||||
export * from './display-rich-text/display-rich-text.widget';
|
||||
|
||||
// editors (dynamic table)
|
||||
export * from './dynamic-table/dynamic-table.widget.model';
|
||||
export * from './dynamic-table/editors/row.editor';
|
||||
export * from './dynamic-table/editors/date/date.editor';
|
||||
export * from './dynamic-table/editors/dropdown/dropdown.editor';
|
||||
export * from './dynamic-table/editors/boolean/boolean.editor';
|
||||
export * from './dynamic-table/editors/text/text.editor';
|
||||
export * from './dynamic-table/editors/datetime/datetime.editor';
|
||||
export * from './dynamic-table/editors/amount/amount.editor';
|
||||
export * from './text/text-mask.component';
|
||||
|
||||
export const WIDGET_DIRECTIVES: any[] = [
|
||||
@@ -95,29 +59,13 @@ export const WIDGET_DIRECTIVES: any[] = [
|
||||
NumberWidgetComponent,
|
||||
CheckboxWidgetComponent,
|
||||
MultilineTextWidgetComponentComponent,
|
||||
DropdownWidgetComponent,
|
||||
HyperlinkWidgetComponent,
|
||||
RadioButtonsWidgetComponent,
|
||||
DisplayTextWidgetComponent,
|
||||
UploadWidgetComponent,
|
||||
TypeaheadWidgetComponent,
|
||||
FunctionalGroupWidgetComponent,
|
||||
PeopleWidgetComponent,
|
||||
DateWidgetComponent,
|
||||
AmountWidgetComponent,
|
||||
DynamicTableWidgetComponent,
|
||||
DateEditorComponent,
|
||||
DropdownEditorComponent,
|
||||
BooleanEditorComponent,
|
||||
TextEditorComponent,
|
||||
RowEditorComponent,
|
||||
ErrorWidgetComponent,
|
||||
DocumentWidgetComponent,
|
||||
DateTimeWidgetComponent,
|
||||
DateTimeEditorComponent,
|
||||
JsonWidgetComponent,
|
||||
AmountEditorComponent,
|
||||
UploadFolderWidgetComponent,
|
||||
FileViewerWidgetComponent,
|
||||
DisplayRichTextWidgetComponent
|
||||
];
|
||||
|
@@ -1,38 +0,0 @@
|
||||
<div class="adf-people-widget {{field.className}}"
|
||||
[class.adf-invalid]="!field.isValid && isTouched()"
|
||||
[class.adf-readonly]="field.readOnly"
|
||||
id="people-widget-content">
|
||||
<mat-form-field>
|
||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||
<input #inputValue
|
||||
matInput
|
||||
class="adf-input"
|
||||
data-automation-id="adf-people-search-input"
|
||||
type="text"
|
||||
[id]="field.id"
|
||||
[formControl]="searchTerm"
|
||||
[placeholder]="field.placeholder"
|
||||
[matAutocomplete]="auto"
|
||||
(blur)="markAsTouched()"
|
||||
[matTooltip]="field.tooltip"
|
||||
matTooltipPosition="above"
|
||||
matTooltipShowDelay="1000">
|
||||
<mat-autocomplete class="adf-people-widget-list"
|
||||
#auto="matAutocomplete"
|
||||
(optionSelected)="onItemSelect($event.option.value)"
|
||||
[displayWith]="getDisplayName">
|
||||
<mat-option *ngFor="let user of users$ | async; let i = index" [value]="user">
|
||||
<div class="adf-people-widget-row" id="adf-people-widget-user-{{i}}">
|
||||
<div [outerHTML]="user | usernameInitials:'adf-people-widget-pic'"></div>
|
||||
<div *ngIf="user.pictureId" class="adf-people-widget-image-row">
|
||||
<img id="adf-people-widget-pic-{{i}}" class="adf-people-widget-image"
|
||||
[alt]="getDisplayName(user)" [src]="peopleProcessService.getUserImage(user)"/>
|
||||
</div>
|
||||
<span class="adf-people-label-name">{{getDisplayName(user)}}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
<error-widget [error]="field.validationSummary"></error-widget>
|
||||
<error-widget *ngIf="isInvalidFieldRequired() && isTouched()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||
</div>
|
@@ -1,53 +0,0 @@
|
||||
.adf {
|
||||
&-people-widget {
|
||||
width: 100%;
|
||||
|
||||
.mat-form-field-label-wrapper {
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&-people-widget-list {
|
||||
margin: 5px 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
&-people-widget-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-people-widget-pic {
|
||||
background: var(--theme-primary-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 100px;
|
||||
color: var(--theme-text-fg-color);
|
||||
font-weight: bolder;
|
||||
font-size: var(--theme-adf-picture-1-font-size);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&-people-widget-image {
|
||||
margin-left: -44px;
|
||||
left: 21px;
|
||||
background: var(--theme-dialog-bg-color);
|
||||
border-radius: 100px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&-people-widget-image-row {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&-people-label-name {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
@@ -1,309 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { UserProcessModel } from '../../../../models';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { FormFieldTypes } from '../core/form-field-types';
|
||||
import { FormFieldModel } from '../core/form-field.model';
|
||||
import { FormModel } from '../core/form.model';
|
||||
import { PeopleWidgetComponent } from './people.widget';
|
||||
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
||||
import { TranslateService, TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreTestingModule } from '../../../../testing/core.testing.module';
|
||||
|
||||
describe('PeopleWidgetComponent', () => {
|
||||
|
||||
let widget: PeopleWidgetComponent;
|
||||
let fixture: ComponentFixture<PeopleWidgetComponent>;
|
||||
let element: HTMLElement;
|
||||
let formService: FormService;
|
||||
let translationService: TranslateService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PeopleWidgetComponent);
|
||||
formService = TestBed.inject(FormService);
|
||||
|
||||
translationService = TestBed.inject(TranslateService);
|
||||
spyOn(translationService, 'instant').and.callFake((key) => key);
|
||||
spyOn(translationService, 'get').and.callFake((key) => of(key));
|
||||
|
||||
element = fixture.nativeElement;
|
||||
widget = fixture.componentInstance;
|
||||
widget.field = new FormFieldModel(new FormModel());
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should return empty display name for missing model', () => {
|
||||
expect(widget.getDisplayName(null)).toBe('');
|
||||
});
|
||||
|
||||
it('should return full name for a given model', () => {
|
||||
const model = new UserProcessModel({
|
||||
firstName: 'John',
|
||||
lastName: 'Doe'
|
||||
});
|
||||
expect(widget.getDisplayName(model)).toBe('John Doe');
|
||||
});
|
||||
|
||||
it('should skip first name for display name', () => {
|
||||
const model = new UserProcessModel({ firstName: null, lastName: 'Doe' });
|
||||
expect(widget.getDisplayName(model)).toBe('Doe');
|
||||
});
|
||||
|
||||
it('should skip last name for display name', () => {
|
||||
const model = new UserProcessModel({ firstName: 'John', lastName: null });
|
||||
expect(widget.getDisplayName(model)).toBe('John');
|
||||
});
|
||||
|
||||
it('should init value from the field', async () => {
|
||||
widget.field.value = new UserProcessModel({
|
||||
id: 'people-id',
|
||||
firstName: 'John',
|
||||
lastName: 'Doe'
|
||||
});
|
||||
|
||||
spyOn(formService, 'getWorkflowUsers').and.returnValue(of(null));
|
||||
|
||||
widget.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect((element.querySelector('input') as HTMLInputElement).value).toBe('John Doe');
|
||||
});
|
||||
|
||||
it('should show the readonly value when the form is readonly', async () => {
|
||||
widget.field.value = new UserProcessModel({
|
||||
id: 'people-id',
|
||||
firstName: 'John',
|
||||
lastName: 'Doe'
|
||||
});
|
||||
widget.field.readOnly = true;
|
||||
widget.field.form.readOnly = true;
|
||||
|
||||
spyOn(formService, 'getWorkflowUsers').and.returnValue(of(null));
|
||||
|
||||
widget.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect((element.querySelector('input') as HTMLInputElement).value).toBe('John Doe');
|
||||
expect((element.querySelector('input') as HTMLInputElement).disabled).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should require form field to setup values on init', () => {
|
||||
widget.field.value = null;
|
||||
widget.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
const input = widget.input;
|
||||
expect(input.nativeElement.value).toBe('');
|
||||
expect(widget.groupId).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should setup group restriction', () => {
|
||||
widget.ngOnInit();
|
||||
expect(widget.groupId).toBeUndefined();
|
||||
|
||||
widget.field.params = { restrictWithGroup: { id: '<id>' } };
|
||||
widget.ngOnInit();
|
||||
expect(widget.groupId).toBe('<id>');
|
||||
});
|
||||
|
||||
it('should display involved user in task form', async () => {
|
||||
spyOn(formService, 'getWorkflowUsers').and.returnValue(
|
||||
new Observable((observer) => {
|
||||
observer.next(null);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
|
||||
widget.field.value = new UserProcessModel({
|
||||
id: 'people-id',
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'john@test.com'
|
||||
});
|
||||
widget.ngOnInit();
|
||||
|
||||
const involvedUser = fixture.debugElement.nativeElement.querySelector('input[data-automation-id="adf-people-search-input"]');
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(involvedUser.value).toBe('John Doe');
|
||||
});
|
||||
|
||||
describe('when is required', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
|
||||
type: FormFieldTypes.PEOPLE,
|
||||
required: true
|
||||
});
|
||||
});
|
||||
|
||||
it('should be marked as invalid after interaction', async () => {
|
||||
const peopleInput = fixture.nativeElement.querySelector('input');
|
||||
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeFalsy();
|
||||
|
||||
peopleInput.dispatchEvent(new Event('blur'));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be able to display label with asterisk', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||
|
||||
expect(asterisk).toBeTruthy();
|
||||
expect(asterisk.textContent).toEqual('*');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when template is ready', () => {
|
||||
|
||||
const fakeUserResult = [
|
||||
{ id: 1001, firstName: 'Test01', lastName: 'Test01', email: 'test' },
|
||||
{ id: 1002, firstName: 'Test02', lastName: 'Test02', email: 'test2' }];
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(formService, 'getWorkflowUsers').and.returnValue(new Observable((observer) => {
|
||||
observer.next(fakeUserResult);
|
||||
observer.complete();
|
||||
}));
|
||||
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
|
||||
id: 'people-id',
|
||||
name: 'people-name',
|
||||
type: FormFieldTypes.PEOPLE,
|
||||
readOnly: false
|
||||
});
|
||||
fixture.detectChanges();
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should render the people component', () => {
|
||||
expect(element.querySelector('#people-widget-content')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should show an error message if the user is invalid', async () => {
|
||||
const peopleHTMLElement = element.querySelector<HTMLInputElement>('input');
|
||||
peopleHTMLElement.focus();
|
||||
peopleHTMLElement.value = 'K';
|
||||
peopleHTMLElement.dispatchEvent(new Event('keyup'));
|
||||
peopleHTMLElement.dispatchEvent(new Event('input'));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(element.querySelector('.adf-error-text')).not.toBeNull();
|
||||
expect(element.querySelector('.adf-error-text').textContent).toContain('FORM.FIELD.VALIDATOR.INVALID_VALUE');
|
||||
});
|
||||
|
||||
it('should show the people if the typed result match', async () => {
|
||||
const peopleHTMLElement = element.querySelector<HTMLInputElement>('input');
|
||||
peopleHTMLElement.focus();
|
||||
peopleHTMLElement.value = 'T';
|
||||
peopleHTMLElement.dispatchEvent(new Event('keyup'));
|
||||
peopleHTMLElement.dispatchEvent(new Event('input'));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(fixture.debugElement.query(By.css('#adf-people-widget-user-0'))).not.toBeNull();
|
||||
expect(fixture.debugElement.query(By.css('#adf-people-widget-user-1'))).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should hide result list if input is empty', async () => {
|
||||
const peopleHTMLElement = element.querySelector<HTMLInputElement>('input');
|
||||
peopleHTMLElement.focus();
|
||||
peopleHTMLElement.value = '';
|
||||
peopleHTMLElement.dispatchEvent(new Event('keyup'));
|
||||
peopleHTMLElement.dispatchEvent(new Event('focusin'));
|
||||
peopleHTMLElement.dispatchEvent(new Event('input'));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(fixture.debugElement.query(By.css('#adf-people-widget-user-0'))).toBeNull();
|
||||
});
|
||||
|
||||
it('should display two options if we tap one letter', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const peopleHTMLElement = element.querySelector<HTMLInputElement>('input');
|
||||
peopleHTMLElement.focus();
|
||||
peopleHTMLElement.value = 'T';
|
||||
peopleHTMLElement.dispatchEvent(new Event('keyup'));
|
||||
peopleHTMLElement.dispatchEvent(new Event('input'));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(fixture.debugElement.query(By.css('#adf-people-widget-user-0'))).not.toBeNull();
|
||||
expect(fixture.debugElement.query(By.css('#adf-people-widget-user-1'))).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should emit peopleSelected if option is valid', async () => {
|
||||
const selectEmitSpy = spyOn(widget.peopleSelected, 'emit');
|
||||
const peopleHTMLElement = element.querySelector<HTMLInputElement>('input');
|
||||
peopleHTMLElement.focus();
|
||||
peopleHTMLElement.value = 'Test01 Test01';
|
||||
peopleHTMLElement.dispatchEvent(new Event('keyup'));
|
||||
peopleHTMLElement.dispatchEvent(new Event('input'));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(selectEmitSpy).toHaveBeenCalledWith(1001);
|
||||
});
|
||||
|
||||
it('should display tooltip when tooltip is set', async () => {
|
||||
widget.field.tooltip = 'people widget';
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const radioButtonsElement: any = element.querySelector('#people-id');
|
||||
const tooltip = radioButtonsElement.getAttribute('ng-reflect-message');
|
||||
|
||||
expect(tooltip).toEqual(widget.field.tooltip);
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,146 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { PeopleProcessService } from '../../../../services/people-process.service';
|
||||
import { UserProcessModel } from '../../../../models';
|
||||
import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { WidgetComponent } from '../widget.component';
|
||||
import { UntypedFormControl } from '@angular/forms';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import {
|
||||
catchError,
|
||||
distinctUntilChanged,
|
||||
map,
|
||||
switchMap,
|
||||
tap
|
||||
} from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'people-widget',
|
||||
templateUrl: './people.widget.html',
|
||||
styleUrls: ['./people.widget.scss'],
|
||||
host: {
|
||||
'(click)': 'event($event)',
|
||||
'(blur)': 'event($event)',
|
||||
'(change)': 'event($event)',
|
||||
'(focus)': 'event($event)',
|
||||
'(focusin)': 'event($event)',
|
||||
'(focusout)': 'event($event)',
|
||||
'(input)': 'event($event)',
|
||||
'(invalid)': 'event($event)',
|
||||
'(select)': 'event($event)'
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class PeopleWidgetComponent extends WidgetComponent implements OnInit {
|
||||
|
||||
@ViewChild('inputValue', { static: true })
|
||||
input: ElementRef;
|
||||
|
||||
@Output()
|
||||
peopleSelected: EventEmitter<number> = new EventEmitter();
|
||||
|
||||
groupId: string;
|
||||
value: any;
|
||||
|
||||
searchTerm = new UntypedFormControl();
|
||||
searchTerms$: Observable<any> = this.searchTerm.valueChanges;
|
||||
|
||||
users$ = this.searchTerms$.pipe(
|
||||
tap((searchInput) => {
|
||||
if (typeof searchInput === 'string') {
|
||||
this.onItemSelect();
|
||||
}
|
||||
}),
|
||||
distinctUntilChanged(),
|
||||
switchMap((searchTerm) => {
|
||||
const value = searchTerm.email ? this.getDisplayName(searchTerm) : searchTerm;
|
||||
return this.formService.getWorkflowUsers(value, this.groupId)
|
||||
.pipe(catchError(() => of([])));
|
||||
}),
|
||||
map((list: UserProcessModel[]) => {
|
||||
const value = this.searchTerm.value.email ? this.getDisplayName(this.searchTerm.value) : this.searchTerm.value;
|
||||
this.checkUserAndValidateForm(list, value);
|
||||
return list;
|
||||
})
|
||||
);
|
||||
|
||||
constructor(public formService: FormService, public peopleProcessService: PeopleProcessService) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field) {
|
||||
if (this.field.value) {
|
||||
this.searchTerm.setValue(this.field.value);
|
||||
}
|
||||
if (this.field.readOnly) {
|
||||
this.searchTerm.disable();
|
||||
}
|
||||
const params = this.field.params;
|
||||
if (params && params.restrictWithGroup) {
|
||||
const restrictWithGroup = params.restrictWithGroup;
|
||||
this.groupId = restrictWithGroup.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkUserAndValidateForm(list: UserProcessModel[], value: string): void {
|
||||
const isValidUser = this.isValidUser(list, value);
|
||||
if (isValidUser || value === '') {
|
||||
this.field.validationSummary.message = '';
|
||||
this.field.validate();
|
||||
this.field.form.validateForm();
|
||||
} else {
|
||||
this.field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_VALUE';
|
||||
this.field.markAsInvalid();
|
||||
this.field.form.markAsInvalid();
|
||||
}
|
||||
}
|
||||
|
||||
isValidUser(users: UserProcessModel[], name: string): boolean {
|
||||
if (users) {
|
||||
return !!users.find((user) => {
|
||||
const selectedUser = this.getDisplayName(user).toLocaleLowerCase() === name.toLocaleLowerCase();
|
||||
if (selectedUser) {
|
||||
this.peopleSelected.emit(user && user.id || undefined);
|
||||
}
|
||||
return selectedUser;
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getDisplayName(model: UserProcessModel) {
|
||||
if (model) {
|
||||
const displayName = `${model.firstName || ''} ${model.lastName || ''}`;
|
||||
return displayName.trim();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
onItemSelect(item?: UserProcessModel) {
|
||||
if (item) {
|
||||
this.field.value = item;
|
||||
} else {
|
||||
this.field.value = null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
<div class="adf-radio-buttons-widget {{field.className}}"
|
||||
[class.adf-readonly]="field.readOnly" [id]="field.id">
|
||||
<div class="adf-radio-button-container">
|
||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||
<mat-radio-group class="adf-radio-group" [(ngModel)]="field.value" [disabled]="field.readOnly">
|
||||
<mat-radio-button
|
||||
[matTooltip]="field.tooltip"
|
||||
matTooltipPosition="above"
|
||||
matTooltipShowDelay="1000"
|
||||
[id]="field.id + '-' + opt.id"
|
||||
[name]="field.id"
|
||||
[value]="opt.id"
|
||||
[checked]="field.value === opt.id"
|
||||
(change)="onOptionClick(opt.id)"
|
||||
color="primary"
|
||||
class="adf-radio-button" *ngFor="let opt of field.options" >
|
||||
{{opt.name}}
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
<error-widget [error]="field.validationSummary" ></error-widget>
|
||||
</div>
|
@@ -1,18 +0,0 @@
|
||||
.adf {
|
||||
&-radio-button-container {
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&-radio-group {
|
||||
margin-top: 15px;
|
||||
margin-left: 5px;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&-radio-button {
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
@@ -1,345 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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, fakeAsync, TestBed } from '@angular/core/testing';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { ContainerModel } from '../core/container.model';
|
||||
import { FormFieldTypes } from '../core/form-field-types';
|
||||
import { FormFieldOption } from '../core/form-field-option';
|
||||
import { FormFieldModel } from '../core/form-field.model';
|
||||
import { FormModel } from '../core/form.model';
|
||||
import { RadioButtonsWidgetComponent } from './radio-buttons.widget';
|
||||
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatRadioModule } from '@angular/material/radio';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { CoreTestingModule } from '../../../../testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AlfrescoApiService } from '../../../../services';
|
||||
|
||||
describe('RadioButtonsWidgetComponent', () => {
|
||||
|
||||
let formService: FormService;
|
||||
let widget: RadioButtonsWidgetComponent;
|
||||
let alfrescoApiService: AlfrescoApiService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule,
|
||||
MatRadioModule,
|
||||
FormsModule,
|
||||
MatIconModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
||||
|
||||
formService = new FormService(null, alfrescoApiService, null);
|
||||
widget = new RadioButtonsWidgetComponent(formService, null);
|
||||
widget.field = new FormFieldModel(new FormModel(), { restUrl: '<url>' });
|
||||
});
|
||||
|
||||
it('should request field values from service', () => {
|
||||
const taskId = '<form-id>';
|
||||
const fieldId = '<field-id>';
|
||||
|
||||
const form = new FormModel({
|
||||
taskId
|
||||
});
|
||||
|
||||
widget.field = new FormFieldModel(form, {
|
||||
id: fieldId,
|
||||
restUrl: '<url>'
|
||||
});
|
||||
|
||||
spyOn(formService, 'getRestFieldValues').and.returnValue(new Observable((observer) => {
|
||||
observer.next(null);
|
||||
observer.complete();
|
||||
}));
|
||||
widget.ngOnInit();
|
||||
expect(formService.getRestFieldValues).toHaveBeenCalledWith(taskId, fieldId);
|
||||
});
|
||||
|
||||
it('should update form on values fetched', () => {
|
||||
const taskId = '<form-id>';
|
||||
const fieldId = '<field-id>';
|
||||
|
||||
const form = new FormModel({
|
||||
taskId
|
||||
});
|
||||
|
||||
widget.field = new FormFieldModel(form, {
|
||||
id: fieldId,
|
||||
restUrl: '<url>'
|
||||
});
|
||||
const field = widget.field;
|
||||
spyOn(field, 'updateForm').and.stub();
|
||||
|
||||
spyOn(formService, 'getRestFieldValues').and.returnValue(new Observable((observer) => {
|
||||
observer.next(null);
|
||||
observer.complete();
|
||||
}));
|
||||
widget.ngOnInit();
|
||||
expect(field.updateForm).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should require field with rest URL to fetch data', () => {
|
||||
const taskId = '<form-id>';
|
||||
const fieldId = '<field-id>';
|
||||
|
||||
const form = new FormModel({
|
||||
taskId
|
||||
});
|
||||
|
||||
widget.field = new FormFieldModel(form, {
|
||||
id: fieldId,
|
||||
restUrl: '<url>'
|
||||
});
|
||||
spyOn(formService, 'getRestFieldValues').and.returnValue(new Observable((observer) => {
|
||||
observer.next(null);
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
const field = widget.field;
|
||||
widget.field = null;
|
||||
widget.ngOnInit();
|
||||
expect(formService.getRestFieldValues).not.toHaveBeenCalled();
|
||||
widget.field = field;
|
||||
|
||||
widget.field.restUrl = null;
|
||||
widget.ngOnInit();
|
||||
expect(formService.getRestFieldValues).not.toHaveBeenCalled();
|
||||
|
||||
widget.field.restUrl = '<url>';
|
||||
widget.ngOnInit();
|
||||
expect(formService.getRestFieldValues).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should update the field value when an option is selected', () => {
|
||||
spyOn(widget, 'onFieldChanged').and.stub();
|
||||
widget.onOptionClick('fake-opt');
|
||||
|
||||
expect(widget.field.value).toEqual('fake-opt');
|
||||
});
|
||||
|
||||
describe('when template is ready', () => {
|
||||
let radioButtonWidget: RadioButtonsWidgetComponent;
|
||||
let fixture: ComponentFixture<RadioButtonsWidgetComponent>;
|
||||
let element: HTMLElement;
|
||||
let stubFormService: FormService;
|
||||
const restOption: FormFieldOption[] = [
|
||||
{
|
||||
id: 'opt-1',
|
||||
name: 'opt-name-1'
|
||||
},
|
||||
{
|
||||
id: 'opt-2',
|
||||
name: 'opt-name-2'
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RadioButtonsWidgetComponent);
|
||||
radioButtonWidget = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
stubFormService = fixture.debugElement.injector.get(FormService);
|
||||
});
|
||||
|
||||
it('should show radio buttons as text when is readonly', async () => {
|
||||
radioButtonWidget.field = new FormFieldModel(new FormModel({}), {
|
||||
id: 'radio-id',
|
||||
name: 'radio-name',
|
||||
type: FormFieldTypes.RADIO_BUTTONS,
|
||||
readOnly: true
|
||||
});
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('display-text-widget')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should be able to set label property for Radio Button widget', () => {
|
||||
radioButtonWidget.field = new FormFieldModel(new FormModel({}), {
|
||||
id: 'radio-id',
|
||||
name: 'radio-name-label',
|
||||
type: FormFieldTypes.RADIO_BUTTONS,
|
||||
readOnly: true
|
||||
});
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('label').innerText).toBe('radio-name-label');
|
||||
});
|
||||
|
||||
it('should be able to set a Radio Button widget as required', async () => {
|
||||
radioButtonWidget.field = new FormFieldModel(new FormModel({}), {
|
||||
id: 'radio-id',
|
||||
name: 'radio-name-label',
|
||||
type: FormFieldTypes.RADIO_BUTTONS,
|
||||
readOnly: false,
|
||||
required: true,
|
||||
optionType: 'manual',
|
||||
options: restOption,
|
||||
restUrl: null
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
const widgetLabel = element.querySelector('label');
|
||||
expect(widgetLabel.innerText).toBe('radio-name-label*');
|
||||
expect(radioButtonWidget.field.isValid).toBe(false);
|
||||
|
||||
const option = element.querySelector<HTMLElement>('#radio-id-opt-1 label');
|
||||
option.click();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
const selectedOption = element.querySelector<HTMLElement>('[class*="mat-radio-checked"]');
|
||||
expect(selectedOption.innerText).toBe('opt-name-1');
|
||||
expect(radioButtonWidget.field.isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('should be able to set a Radio Button widget as required', () => {
|
||||
radioButtonWidget.field = new FormFieldModel(new FormModel({}), {
|
||||
id: 'radio-id',
|
||||
name: 'radio-name-label',
|
||||
type: FormFieldTypes.RADIO_BUTTONS,
|
||||
readOnly: false,
|
||||
required: true,
|
||||
optionType: 'manual',
|
||||
options: restOption,
|
||||
restUrl: null,
|
||||
value: 'opt-name-2'
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
const selectedOption = element.querySelector<HTMLElement>('[class*="mat-radio-checked"]');
|
||||
expect(selectedOption.innerText).toBe('opt-name-2');
|
||||
expect(radioButtonWidget.field.isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('should display tooltip when tooltip is set', async () => {
|
||||
radioButtonWidget.field = new FormFieldModel(new FormModel(), {
|
||||
id: 'radio-id',
|
||||
name: 'radio-name-label',
|
||||
type: FormFieldTypes.RADIO_BUTTONS,
|
||||
readOnly: false,
|
||||
required: true,
|
||||
optionType: 'manual',
|
||||
options: restOption,
|
||||
value: 'opt-name-2',
|
||||
tooltip: 'radio widget'
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const radioButtonsElement: any = element.querySelector('#radio-id-opt-1');
|
||||
const tooltip = radioButtonsElement.getAttribute('ng-reflect-message');
|
||||
|
||||
expect(tooltip).toEqual(radioButtonWidget.field.tooltip);
|
||||
});
|
||||
|
||||
describe('and radioButton is populated via taskId', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(stubFormService, 'getRestFieldValues').and.returnValue(of(restOption));
|
||||
radioButtonWidget.field = new FormFieldModel(new FormModel({ taskId: 'task-id' }), {
|
||||
id: 'radio-id',
|
||||
name: 'radio-name',
|
||||
type: FormFieldTypes.RADIO_BUTTONS,
|
||||
restUrl: 'rest-url'
|
||||
});
|
||||
radioButtonWidget.field.isVisible = true;
|
||||
const fakeContainer = new ContainerModel(radioButtonWidget.field);
|
||||
radioButtonWidget.field.form.fields.push(fakeContainer);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show radio buttons', () => {
|
||||
expect(element.querySelector('#radio-id')).toBeDefined();
|
||||
expect(element.querySelector('#radio-id-opt-1-input')).not.toBeNull();
|
||||
expect(element.querySelector('#radio-id-opt-1')).not.toBeNull();
|
||||
expect(element.querySelector('#radio-id-opt-2-input')).not.toBeNull();
|
||||
expect(element.querySelector('#radio-id-opt-2')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should trigger field changed event on click', fakeAsync(() => {
|
||||
const option = element.querySelector<HTMLElement>('#radio-id-opt-1-input');
|
||||
expect(element.querySelector('#radio-id')).not.toBeNull();
|
||||
expect(option).not.toBeNull();
|
||||
option.click();
|
||||
widget.fieldChanged.subscribe(() => {
|
||||
expect(element.querySelector('#radio-id')).toBeNull();
|
||||
expect(element.querySelector('#radio-id-opt-1-input')).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
describe('and radioButton is readonly', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
radioButtonWidget.field.readOnly = true;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show radio buttons disabled', () => {
|
||||
expect(element.querySelector('.mat-radio-disabled[ng-reflect-id="radio-id-opt-1"]')).toBeDefined();
|
||||
expect(element.querySelector('.mat-radio-disabled[ng-reflect-id="radio-id-opt-1"]')).not.toBeNull();
|
||||
expect(element.querySelector('.mat-radio-disabled[ng-reflect-id="radio-id-opt-2"]')).toBeDefined();
|
||||
expect(element.querySelector('.mat-radio-disabled[ng-reflect-id="radio-id-opt-2"]')).not.toBeNull();
|
||||
});
|
||||
|
||||
describe('and a value is selected', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
radioButtonWidget.field.value = restOption[0].id;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should check the selected value', () => {
|
||||
expect(element.querySelector('.mat-radio-checked')).toBe(element.querySelector('mat-radio-button[ng-reflect-id="radio-id-opt-1"]'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('and radioButton is populated via processDefinitionId', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
radioButtonWidget.field = new FormFieldModel(new FormModel({ processDefinitionId: 'proc-id' }), {
|
||||
id: 'radio-id',
|
||||
name: 'radio-name',
|
||||
type: FormFieldTypes.RADIO_BUTTONS,
|
||||
restUrl: 'rest-url'
|
||||
});
|
||||
spyOn(stubFormService, 'getRestFieldValuesByProcessId').and.returnValue(of(restOption));
|
||||
radioButtonWidget.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show visible radio buttons', () => {
|
||||
expect(element.querySelector('#radio-id')).toBeDefined();
|
||||
expect(element.querySelector('#radio-id-opt-1-input')).not.toBeNull();
|
||||
expect(element.querySelector('#radio-id-opt-1')).not.toBeNull();
|
||||
expect(element.querySelector('#radio-id-opt-2-input')).not.toBeNull();
|
||||
expect(element.querySelector('#radio-id-opt-2')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,99 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { LogService } from '../../../../services/log.service';
|
||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { FormFieldOption } from '../core/form-field-option';
|
||||
import { WidgetComponent } from '../widget.component';
|
||||
|
||||
@Component({
|
||||
selector: 'radio-buttons-widget',
|
||||
templateUrl: './radio-buttons.widget.html',
|
||||
styleUrls: ['./radio-buttons.widget.scss'],
|
||||
host: {
|
||||
'(click)': 'event($event)',
|
||||
'(blur)': 'event($event)',
|
||||
'(change)': 'event($event)',
|
||||
'(focus)': 'event($event)',
|
||||
'(focusin)': 'event($event)',
|
||||
'(focusout)': 'event($event)',
|
||||
'(input)': 'event($event)',
|
||||
'(invalid)': 'event($event)',
|
||||
'(select)': 'event($event)'
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class RadioButtonsWidgetComponent extends WidgetComponent implements OnInit {
|
||||
|
||||
constructor(public formService: FormService,
|
||||
private logService: LogService) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field && this.field.restUrl) {
|
||||
if (this.field.form.taskId) {
|
||||
this.getOptionsByTaskId();
|
||||
} else {
|
||||
this.getOptionsByProcessDefinitionId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getOptionsByTaskId() {
|
||||
this.formService
|
||||
.getRestFieldValues(
|
||||
this.field.form.taskId,
|
||||
this.field.id
|
||||
)
|
||||
.subscribe(
|
||||
(formFieldOption: FormFieldOption[]) => {
|
||||
this.field.options = formFieldOption || [];
|
||||
this.field.updateForm();
|
||||
},
|
||||
(err) => this.handleError(err)
|
||||
);
|
||||
}
|
||||
|
||||
getOptionsByProcessDefinitionId() {
|
||||
this.formService
|
||||
.getRestFieldValuesByProcessId(
|
||||
this.field.form.processDefinitionId,
|
||||
this.field.id
|
||||
)
|
||||
.subscribe(
|
||||
(formFieldOption: FormFieldOption[]) => {
|
||||
this.field.options = formFieldOption || [];
|
||||
this.field.updateForm();
|
||||
},
|
||||
(err) => this.handleError(err)
|
||||
);
|
||||
}
|
||||
|
||||
onOptionClick(optionSelected: any) {
|
||||
this.field.value = optionSelected;
|
||||
this.fieldChanged.emit(this.field);
|
||||
}
|
||||
|
||||
handleError(error: any) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
<div class="adf-typeahead-widget-container">
|
||||
<div class="adf-typeahead-widget {{field.className}}"
|
||||
[class.is-dirty]="value"
|
||||
[class.adf-invalid]="!field.isValid"
|
||||
[class.adf-readonly]="field.readOnly"
|
||||
id="typehead-div">
|
||||
<mat-form-field>
|
||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}</label>
|
||||
<input matInput class="adf-input"
|
||||
type="text"
|
||||
[id]="field.id"
|
||||
[(ngModel)]="value"
|
||||
(ngModelChange)="validate()"
|
||||
(keyup)="onKeyUp($event)"
|
||||
[disabled]="field.readOnly"
|
||||
data-automation-id="adf-typeahed-search-input"
|
||||
placeholder="{{field.placeholder}}"
|
||||
[matAutocomplete]="auto">
|
||||
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="onItemSelect($event.option.value)">
|
||||
<mat-option *ngFor="let item of options; let i = index" id="adf-typeahed-widget-user-{{i}}" [value]="item">
|
||||
<span id="adf-typeahed-label-name">{{item.name}}</span>
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
|
||||
<error-widget [error]="field.validationSummary"></error-widget>
|
||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||
</div>
|
||||
</div>
|
@@ -1,10 +0,0 @@
|
||||
.adf {
|
||||
&-typeahead-widget-container {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&-typeahead-widget {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@@ -1,390 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 } from '@angular/core/testing';
|
||||
import { Observable, of, throwError } from 'rxjs';
|
||||
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { FormFieldOption } from '../core/form-field-option';
|
||||
import { FormFieldTypes } from '../core/form-field-types';
|
||||
import { FormFieldModel } from '../core/form-field.model';
|
||||
import { FormModel } from '../core/form.model';
|
||||
import { TypeaheadWidgetComponent } from './typeahead.widget';
|
||||
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
||||
import { TranslateService, TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreTestingModule } from '../../../../testing/core.testing.module';
|
||||
import { AlfrescoApiService } from '../../../../services';
|
||||
|
||||
describe('TypeaheadWidgetComponent', () => {
|
||||
|
||||
let formService: FormService;
|
||||
let widget: TypeaheadWidgetComponent;
|
||||
let translationService: TranslateService;
|
||||
let alfrescoApiService: AlfrescoApiService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
||||
translationService = TestBed.inject(TranslateService);
|
||||
spyOn(translationService, 'instant').and.callFake((key) => key);
|
||||
spyOn(translationService, 'get').and.callFake((key) => of(key));
|
||||
|
||||
formService = new FormService(null, alfrescoApiService, null);
|
||||
widget = new TypeaheadWidgetComponent(formService, null);
|
||||
widget.field = new FormFieldModel(new FormModel({ taskId: 'task-id' }));
|
||||
widget.field.restUrl = 'whateverURL';
|
||||
});
|
||||
|
||||
it('should request field values from service', () => {
|
||||
const taskId = '<form-id>';
|
||||
const fieldId = '<field-id>';
|
||||
|
||||
const form = new FormModel({
|
||||
taskId
|
||||
});
|
||||
|
||||
widget.field = new FormFieldModel(form, {
|
||||
id: fieldId,
|
||||
restUrl: 'whateverURL'
|
||||
});
|
||||
|
||||
spyOn(formService, 'getRestFieldValues').and.returnValue(new Observable((observer) => {
|
||||
observer.next(null);
|
||||
observer.complete();
|
||||
}));
|
||||
widget.ngOnInit();
|
||||
expect(formService.getRestFieldValues).toHaveBeenCalledWith(taskId, fieldId);
|
||||
});
|
||||
|
||||
it('should not perform any request if restUrl is not present', () => {
|
||||
const taskId = '<form-id>';
|
||||
const fieldId = '<field-id>';
|
||||
|
||||
const form = new FormModel({
|
||||
taskId
|
||||
});
|
||||
|
||||
widget.field = new FormFieldModel(form, {
|
||||
id: fieldId
|
||||
});
|
||||
|
||||
spyOn(formService, 'getRestFieldValues');
|
||||
widget.ngOnInit();
|
||||
expect(formService.getRestFieldValues).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle error when requesting fields with task id', () => {
|
||||
const taskId = '<form-id>';
|
||||
const fieldId = '<field-id>';
|
||||
|
||||
const form = new FormModel({
|
||||
taskId
|
||||
});
|
||||
|
||||
widget.field = new FormFieldModel(form, {
|
||||
id: fieldId,
|
||||
restUrl: 'whateverURL'
|
||||
});
|
||||
const err = 'Error';
|
||||
spyOn(formService, 'getRestFieldValues').and.returnValue(throwError(err));
|
||||
spyOn(widget, 'handleError').and.stub();
|
||||
|
||||
widget.ngOnInit();
|
||||
|
||||
expect(formService.getRestFieldValues).toHaveBeenCalled();
|
||||
expect(widget.handleError).toHaveBeenCalledWith(err);
|
||||
});
|
||||
|
||||
it('should handle error when requesting fields with process id', () => {
|
||||
const processDefinitionId = '<process-id>';
|
||||
const fieldId = '<field-id>';
|
||||
|
||||
const form = new FormModel({
|
||||
processDefinitionId
|
||||
});
|
||||
|
||||
widget.field = new FormFieldModel(form, {
|
||||
id: fieldId,
|
||||
restUrl: 'whateverURL'
|
||||
});
|
||||
const err = 'Error';
|
||||
spyOn(formService, 'getRestFieldValuesByProcessId').and.returnValue(throwError(err));
|
||||
spyOn(widget, 'handleError').and.stub();
|
||||
|
||||
widget.ngOnInit();
|
||||
|
||||
expect(formService.getRestFieldValuesByProcessId).toHaveBeenCalled();
|
||||
expect(widget.handleError).toHaveBeenCalledWith(err);
|
||||
});
|
||||
|
||||
it('should setup initial value', () => {
|
||||
spyOn(formService, 'getRestFieldValues').and.returnValue(new Observable((observer) => {
|
||||
observer.next([
|
||||
{ id: '1', name: 'One' },
|
||||
{ id: '2', name: 'Two' }
|
||||
]);
|
||||
observer.complete();
|
||||
}));
|
||||
widget.field.value = '2';
|
||||
widget.field.restUrl = 'whateverURL';
|
||||
widget.ngOnInit();
|
||||
|
||||
expect(formService.getRestFieldValues).toHaveBeenCalled();
|
||||
expect(widget.value).toBe('Two');
|
||||
});
|
||||
|
||||
it('should not setup initial value due to missing option', () => {
|
||||
spyOn(formService, 'getRestFieldValues').and.returnValue(new Observable((observer) => {
|
||||
observer.next([
|
||||
{ id: '1', name: 'One' },
|
||||
{ id: '2', name: 'Two' }
|
||||
]);
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
widget.field.value = '3';
|
||||
widget.field.restUrl = 'whateverURL';
|
||||
widget.ngOnInit();
|
||||
|
||||
expect(formService.getRestFieldValues).toHaveBeenCalled();
|
||||
expect(widget.value).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should setup field options on load', () => {
|
||||
const options: FormFieldOption[] = [
|
||||
{ id: '1', name: 'One' },
|
||||
{ id: '2', name: 'Two' }
|
||||
];
|
||||
|
||||
spyOn(formService, 'getRestFieldValues').and.returnValue(new Observable((observer) => {
|
||||
observer.next(options);
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
widget.ngOnInit();
|
||||
expect(widget.field.options).toEqual(options);
|
||||
});
|
||||
|
||||
it('should update form upon options setup', () => {
|
||||
spyOn(formService, 'getRestFieldValues').and.returnValue(new Observable((observer) => {
|
||||
observer.next([]);
|
||||
observer.complete();
|
||||
}));
|
||||
widget.field.restUrl = 'whateverURL';
|
||||
|
||||
spyOn(widget.field, 'updateForm').and.callThrough();
|
||||
widget.ngOnInit();
|
||||
expect(widget.field.updateForm).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should get filtered options', () => {
|
||||
const options: FormFieldOption[] = [
|
||||
{ id: '1', name: 'Item one' },
|
||||
{ id: '2', name: 'Item two' }
|
||||
];
|
||||
widget.field.options = options;
|
||||
widget.value = 'tw';
|
||||
|
||||
const filtered = widget.getOptions();
|
||||
expect(filtered.length).toBe(1);
|
||||
expect(filtered[0]).toEqual(options[1]);
|
||||
});
|
||||
|
||||
it('should be case insensitive when filtering options', () => {
|
||||
const options: FormFieldOption[] = [
|
||||
{ id: '1', name: 'Item one' },
|
||||
{ id: '2', name: 'iTEM TWo' }
|
||||
];
|
||||
widget.field.options = options;
|
||||
widget.value = 'tW';
|
||||
|
||||
const filtered = widget.getOptions();
|
||||
expect(filtered.length).toBe(1);
|
||||
expect(filtered[0]).toEqual(options[1]);
|
||||
});
|
||||
|
||||
describe('when template is ready', () => {
|
||||
let typeaheadWidgetComponent: TypeaheadWidgetComponent;
|
||||
let fixture: ComponentFixture<TypeaheadWidgetComponent>;
|
||||
let element: HTMLElement;
|
||||
let stubFormService;
|
||||
const fakeOptionList: FormFieldOption[] = [{
|
||||
id: '1',
|
||||
name: 'Fake Name 1 '
|
||||
}, {
|
||||
id: '2',
|
||||
name: 'Fake Name 2'
|
||||
}, { id: '3', name: 'Fake Name 3' }];
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TypeaheadWidgetComponent);
|
||||
typeaheadWidgetComponent = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
describe ('and typeahead is in readonly mode', () => {
|
||||
|
||||
it('should show typeahead value with input disabled', async () => {
|
||||
typeaheadWidgetComponent.field = new FormFieldModel(
|
||||
new FormModel({ processVariables: [{ name: 'typeahead-id_LABEL', value: 'FakeProcessValue' }] }), {
|
||||
id: 'typeahead-id',
|
||||
name: 'typeahead-name',
|
||||
type: 'readonly',
|
||||
params: { field: { id: 'typeahead-id', name: 'typeahead-name', type: 'typeahead' } }
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const readonlyInput = element.querySelector<HTMLInputElement>('#typeahead-id');
|
||||
expect(readonlyInput.disabled).toBeTruthy();
|
||||
expect(readonlyInput).not.toBeNull();
|
||||
expect(readonlyInput.value).toBe('FakeProcessValue');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('and typeahead is populated via taskId', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
stubFormService = fixture.debugElement.injector.get(FormService);
|
||||
spyOn(stubFormService, 'getRestFieldValues').and.returnValue(of(fakeOptionList));
|
||||
typeaheadWidgetComponent.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
|
||||
id: 'typeahead-id',
|
||||
name: 'typeahead-name',
|
||||
type: FormFieldTypes.TYPEAHEAD,
|
||||
readOnly: false,
|
||||
restUrl: 'whateverURL'
|
||||
});
|
||||
typeaheadWidgetComponent.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show visible typeahead widget', () => {
|
||||
expect(element.querySelector('#typeahead-id')).toBeDefined();
|
||||
expect(element.querySelector('#typeahead-id')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should show typeahead options', async () => {
|
||||
const typeaheadElement = fixture.debugElement.query(By.css('#typeahead-id'));
|
||||
const typeaheadHTMLElement = typeaheadElement.nativeElement as HTMLInputElement;
|
||||
typeaheadHTMLElement.focus();
|
||||
typeaheadWidgetComponent.value = 'F';
|
||||
typeaheadHTMLElement.value = 'F';
|
||||
typeaheadHTMLElement.dispatchEvent(new Event('keyup'));
|
||||
typeaheadHTMLElement.dispatchEvent(new Event('input'));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(fixture.debugElement.query(By.css('[id="adf-typeahed-widget-user-0"] span'))).not.toBeNull();
|
||||
expect(fixture.debugElement.query(By.css('[id="adf-typeahed-widget-user-1"] span'))).not.toBeNull();
|
||||
expect(fixture.debugElement.query(By.css('[id="adf-typeahed-widget-user-2"] span'))).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should hide the option when the value is empty', async () => {
|
||||
const typeaheadElement = fixture.debugElement.query(By.css('#typeahead-id'));
|
||||
const typeaheadHTMLElement = typeaheadElement.nativeElement as HTMLInputElement;
|
||||
typeaheadHTMLElement.focus();
|
||||
typeaheadWidgetComponent.value = 'F';
|
||||
typeaheadHTMLElement.value = 'F';
|
||||
typeaheadHTMLElement.dispatchEvent(new Event('keyup'));
|
||||
typeaheadHTMLElement.dispatchEvent(new Event('input'));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(fixture.debugElement.query(By.css('[id="adf-typeahed-widget-user-0"] span'))).not.toBeNull();
|
||||
typeaheadHTMLElement.focus();
|
||||
typeaheadWidgetComponent.value = '';
|
||||
typeaheadHTMLElement.dispatchEvent(new Event('keyup'));
|
||||
typeaheadHTMLElement.dispatchEvent(new Event('input'));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(fixture.debugElement.query(By.css('[id="adf-typeahed-widget-user-0"] span'))).toBeNull();
|
||||
});
|
||||
|
||||
it('should show error message when the value is not valid', async () => {
|
||||
typeaheadWidgetComponent.value = 'Fake Name';
|
||||
typeaheadWidgetComponent.field.value = 'Fake Name';
|
||||
typeaheadWidgetComponent.field.options = fakeOptionList;
|
||||
expect(element.querySelector('.adf-error-text')).toBeNull();
|
||||
const keyboardEvent = new KeyboardEvent('keypress');
|
||||
typeaheadWidgetComponent.onKeyUp(keyboardEvent);
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(element.querySelector('.adf-error-text')).not.toBeNull();
|
||||
expect(element.querySelector('.adf-error-text').textContent).toContain('FORM.FIELD.VALIDATOR.INVALID_VALUE');
|
||||
});
|
||||
});
|
||||
|
||||
describe('and typeahead is populated via processDefinitionId', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
stubFormService = fixture.debugElement.injector.get(FormService);
|
||||
spyOn(stubFormService, 'getRestFieldValuesByProcessId').and.returnValue(of(fakeOptionList));
|
||||
typeaheadWidgetComponent.field = new FormFieldModel(new FormModel({ processDefinitionId: 'fake-process-id' }), {
|
||||
id: 'typeahead-id',
|
||||
name: 'typeahead-name',
|
||||
type: FormFieldTypes.TYPEAHEAD,
|
||||
readOnly: 'false'
|
||||
});
|
||||
typeaheadWidgetComponent.field.emptyOption = { id: 'empty', name: 'Choose one...' };
|
||||
typeaheadWidgetComponent.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show visible typeahead widget', () => {
|
||||
expect(element.querySelector('#typeahead-id')).toBeDefined();
|
||||
expect(element.querySelector('#typeahead-id')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should show typeahead options', async () => {
|
||||
const keyboardEvent = new KeyboardEvent('keypress');
|
||||
typeaheadWidgetComponent.value = 'F';
|
||||
typeaheadWidgetComponent.onKeyUp(keyboardEvent);
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(fixture.debugElement.queryAll(By.css('[id="adf-typeahed-widget-user-0"] span'))).toBeDefined();
|
||||
expect(fixture.debugElement.queryAll(By.css('[id="adf-typeahed-widget-user-1"] span'))).toBeDefined();
|
||||
expect(fixture.debugElement.queryAll(By.css('[id="adf-typeahed-widget-user-2"] span'))).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,172 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { LogService } from '../../../../services/log.service';
|
||||
import { ENTER, ESCAPE } from '@angular/cdk/keycodes';
|
||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { FormFieldOption } from '../core/form-field-option';
|
||||
import { WidgetComponent } from '../widget.component';
|
||||
|
||||
@Component({
|
||||
selector: 'typeahead-widget',
|
||||
templateUrl: './typeahead.widget.html',
|
||||
styleUrls: ['./typeahead.widget.scss'],
|
||||
host: {
|
||||
'(click)': 'event($event)',
|
||||
'(blur)': 'event($event)',
|
||||
'(change)': 'event($event)',
|
||||
'(focus)': 'event($event)',
|
||||
'(focusin)': 'event($event)',
|
||||
'(focusout)': 'event($event)',
|
||||
'(input)': 'event($event)',
|
||||
'(invalid)': 'event($event)',
|
||||
'(select)': 'event($event)'
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class TypeaheadWidgetComponent extends WidgetComponent implements OnInit {
|
||||
|
||||
minTermLength: number = 1;
|
||||
value: string;
|
||||
oldValue: string;
|
||||
options: FormFieldOption[] = [];
|
||||
|
||||
constructor(public formService: FormService,
|
||||
private logService: LogService) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field.form.taskId && this.field.restUrl) {
|
||||
this.getValuesByTaskId();
|
||||
} else if (this.field.form.processDefinitionId && this.field.restUrl) {
|
||||
this.getValuesByProcessDefinitionId();
|
||||
}
|
||||
if (this.isReadOnlyType()) {
|
||||
this.value = this.field.value;
|
||||
}
|
||||
}
|
||||
|
||||
getValuesByTaskId() {
|
||||
this.formService
|
||||
.getRestFieldValues(
|
||||
this.field.form.taskId,
|
||||
this.field.id
|
||||
)
|
||||
.subscribe(
|
||||
(formFieldOption: FormFieldOption[]) => {
|
||||
const options = formFieldOption || [];
|
||||
this.field.options = options;
|
||||
|
||||
const fieldValue = this.field.value;
|
||||
if (fieldValue) {
|
||||
const toSelect = options.find((item) => item.id === fieldValue || item.name.toLocaleLowerCase() === fieldValue.toLocaleLowerCase());
|
||||
if (toSelect) {
|
||||
this.value = toSelect.name;
|
||||
}
|
||||
}
|
||||
this.onFieldChanged(this.field);
|
||||
this.field.updateForm();
|
||||
},
|
||||
(err) => this.handleError(err)
|
||||
);
|
||||
}
|
||||
|
||||
getValuesByProcessDefinitionId() {
|
||||
this.formService
|
||||
.getRestFieldValuesByProcessId(
|
||||
this.field.form.processDefinitionId,
|
||||
this.field.id
|
||||
)
|
||||
.subscribe(
|
||||
(formFieldOption: FormFieldOption[]) => {
|
||||
const options = formFieldOption || [];
|
||||
this.field.options = options;
|
||||
|
||||
const fieldValue = this.field.value;
|
||||
if (fieldValue) {
|
||||
const toSelect = options.find((item) => item.id === fieldValue);
|
||||
if (toSelect) {
|
||||
this.value = toSelect.name;
|
||||
}
|
||||
}
|
||||
this.onFieldChanged(this.field);
|
||||
this.field.updateForm();
|
||||
},
|
||||
(err) => this.handleError(err)
|
||||
);
|
||||
}
|
||||
|
||||
getOptions(): FormFieldOption[] {
|
||||
const val = this.value.trim().toLocaleLowerCase();
|
||||
return this.field.options.filter((item) => {
|
||||
const name = item.name.toLocaleLowerCase();
|
||||
return name.indexOf(val) > -1;
|
||||
});
|
||||
}
|
||||
|
||||
isValidOptionName(optionName: string): boolean {
|
||||
const option = this.field.options.find((item) => item.name && item.name.toLocaleLowerCase() === optionName.toLocaleLowerCase());
|
||||
return option ? true : false;
|
||||
}
|
||||
|
||||
onKeyUp(event: KeyboardEvent) {
|
||||
if (this.value && this.value.trim().length >= this.minTermLength && this.oldValue !== this.value) {
|
||||
if (event.keyCode !== ESCAPE && event.keyCode !== ENTER) {
|
||||
if (this.value.length >= this.minTermLength) {
|
||||
this.options = this.getOptions();
|
||||
this.oldValue = this.value;
|
||||
if (this.isValidOptionName(this.value)) {
|
||||
this.field.value = this.options[0].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.isValueDefined() && this.value.trim().length === 0) {
|
||||
this.oldValue = this.value;
|
||||
this.options = [];
|
||||
}
|
||||
}
|
||||
|
||||
onItemSelect(item: FormFieldOption) {
|
||||
if (item) {
|
||||
this.field.value = item.id;
|
||||
this.value = item.name;
|
||||
this.onFieldChanged(this.field);
|
||||
}
|
||||
}
|
||||
|
||||
validate() {
|
||||
this.field.value = this.value;
|
||||
}
|
||||
|
||||
isValueDefined() {
|
||||
return this.value !== null && this.value !== undefined;
|
||||
}
|
||||
|
||||
handleError(error: any) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
|
||||
isReadOnlyType(): boolean {
|
||||
return this.field.type === 'readonly' ? true : false;
|
||||
}
|
||||
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
<div class="adf-upload-folder-widget {{field.className}}"
|
||||
[class.adf-invalid]="!field.isValid"
|
||||
[class.adf-readonly]="field.readOnly">
|
||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||
<div class="adf-upload-widget-container">
|
||||
</div>
|
||||
</div>
|
@@ -1,8 +0,0 @@
|
||||
.adf {
|
||||
&-upload-folder-widget {
|
||||
width: 100%;
|
||||
word-break: break-all;
|
||||
padding: 0.4375em 0;
|
||||
border-top: 0.8438em solid transparent;
|
||||
}
|
||||
}
|
@@ -1,61 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 } from '@angular/core/testing';
|
||||
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../../../../testing';
|
||||
import { UploadFolderWidgetComponent } from './upload-folder.widget';
|
||||
import { FormFieldModel } from '../core/form-field.model';
|
||||
import { FormModel } from '../core/form.model';
|
||||
import { FormFieldTypes } from '../core/form-field-types';
|
||||
|
||||
describe('UploadFolderWidgetComponent', () => {
|
||||
|
||||
let widget: UploadFolderWidgetComponent;
|
||||
let fixture: ComponentFixture<UploadFolderWidgetComponent>;
|
||||
let element: HTMLElement;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(UploadFolderWidgetComponent);
|
||||
widget = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
|
||||
describe('when is required', () => {
|
||||
|
||||
it('should be able to display label with asterisk', async () => {
|
||||
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
required: true
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||
|
||||
expect(asterisk).toBeTruthy();
|
||||
expect(asterisk.textContent).toEqual('*');
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,165 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { LogService } from '../../../../services/log.service';
|
||||
import { ThumbnailService } from '../../../../services/thumbnail.service';
|
||||
import { Component, ElementRef, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { Observable, from } from 'rxjs';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { ProcessContentService } from '../../../services/process-content.service';
|
||||
import { ContentLinkModel } from '../core/content-link.model';
|
||||
import { WidgetComponent } from '../widget.component';
|
||||
import { mergeMap, map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'upload-folder-widget',
|
||||
templateUrl: './upload-folder.widget.html',
|
||||
styleUrls: ['./upload-folder.widget.scss'],
|
||||
host: {
|
||||
'(click)': 'event($event)',
|
||||
'(blur)': 'event($event)',
|
||||
'(change)': 'event($event)',
|
||||
'(focus)': 'event($event)',
|
||||
'(focusin)': 'event($event)',
|
||||
'(focusout)': 'event($event)',
|
||||
'(input)': 'event($event)',
|
||||
'(invalid)': 'event($event)',
|
||||
'(select)': 'event($event)'
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class UploadFolderWidgetComponent extends WidgetComponent implements OnInit {
|
||||
|
||||
hasFile: boolean;
|
||||
displayText: string;
|
||||
multipleOption: string = '';
|
||||
mimeTypeIcon: string;
|
||||
|
||||
@ViewChild('uploadFiles')
|
||||
fileInput: ElementRef;
|
||||
|
||||
constructor(public formService: FormService,
|
||||
private logService: LogService,
|
||||
private thumbnailService: ThumbnailService,
|
||||
public processContentService: ProcessContentService) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field &&
|
||||
this.field.value &&
|
||||
this.field.value.length > 0) {
|
||||
this.hasFile = true;
|
||||
}
|
||||
this.getMultipleFileParam();
|
||||
}
|
||||
|
||||
removeFile(file: any) {
|
||||
if (this.field) {
|
||||
this.removeElementFromList(file);
|
||||
}
|
||||
}
|
||||
|
||||
onFileChanged(event: any) {
|
||||
const files = event.target.files;
|
||||
let filesSaved = [];
|
||||
|
||||
if (this.field.json.value) {
|
||||
filesSaved = [...this.field.json.value];
|
||||
}
|
||||
|
||||
if (files && files.length > 0) {
|
||||
from(files)
|
||||
.pipe(mergeMap((file) => this.uploadRawContent(file)))
|
||||
.subscribe(
|
||||
(res) => {
|
||||
filesSaved.push(res);
|
||||
},
|
||||
() => {
|
||||
this.logService.error('Error uploading file. See console output for more details.');
|
||||
},
|
||||
() => {
|
||||
this.field.value = filesSaved;
|
||||
this.field.json.value = filesSaved;
|
||||
}
|
||||
);
|
||||
|
||||
this.hasFile = true;
|
||||
}
|
||||
}
|
||||
|
||||
private uploadRawContent(file): Observable<any> {
|
||||
return this.processContentService.createTemporaryRawRelatedContent(file).pipe(
|
||||
map((response: any) => {
|
||||
this.logService.info(response);
|
||||
return response;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private getMultipleFileParam() {
|
||||
if (this.field &&
|
||||
this.field.params &&
|
||||
this.field.params.multiple) {
|
||||
this.multipleOption = this.field.params.multiple ? 'multiple' : '';
|
||||
}
|
||||
}
|
||||
|
||||
private removeElementFromList(file) {
|
||||
const index = this.field.value.indexOf(file);
|
||||
|
||||
if (index !== -1) {
|
||||
this.field.value.splice(index, 1);
|
||||
this.field.json.value = this.field.value;
|
||||
this.field.updateForm();
|
||||
}
|
||||
|
||||
this.hasFile = this.field.value.length > 0;
|
||||
|
||||
this.resetFormValueWithNoFiles();
|
||||
}
|
||||
|
||||
private resetFormValueWithNoFiles() {
|
||||
if (this.field.value.length === 0) {
|
||||
this.field.value = [];
|
||||
this.field.json.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
getIcon(mimeType) {
|
||||
return this.thumbnailService.getMimeTypeIcon(mimeType);
|
||||
}
|
||||
|
||||
fileClicked(contentLinkModel: any): void {
|
||||
const file = new ContentLinkModel(contentLinkModel);
|
||||
let fetch = this.processContentService.getContentPreview(file.id);
|
||||
if (file.isTypeImage() || file.isTypePdf()) {
|
||||
fetch = this.processContentService.getFileRawContent(file.id);
|
||||
}
|
||||
fetch.subscribe(
|
||||
(blob: Blob) => {
|
||||
file.contentBlob = blob;
|
||||
this.formService.formContentClicked.next(file);
|
||||
},
|
||||
() => {
|
||||
this.logService.error('Unable to send event for file ' + file.name);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
<div class="adf-upload-widget {{field.className}}"
|
||||
[class.adf-invalid]="!field.isValid"
|
||||
[class.adf-readonly]="field.readOnly">
|
||||
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
|
||||
<div class="adf-upload-widget-container">
|
||||
<div>
|
||||
<mat-list *ngIf="hasFile">
|
||||
<mat-list-item class="adf-upload-files-row" *ngFor="let file of field.value">
|
||||
<img mat-list-icon class="adf-upload-widget__icon"
|
||||
[id]="'file-'+file.id+'-icon'"
|
||||
[src]="getIcon(file.mimeType)"
|
||||
[alt]="mimeTypeIcon"
|
||||
(click)="fileClicked(file)"
|
||||
(keyup.enter)="fileClicked(file)"
|
||||
role="button"
|
||||
tabindex="0"/>
|
||||
<span matLine id="{{'file-'+file.id}}" (click)="fileClicked(file)" (keyup.enter)="fileClicked(file)"
|
||||
role="button" tabindex="0" class="adf-file">{{file.name}}</span>
|
||||
<button *ngIf="!field.readOnly" mat-icon-button [id]="'file-'+file.id+'-remove'"
|
||||
(click)="removeFile(file);" (keyup.enter)="removeFile(file);">
|
||||
<mat-icon class="mat-24">highlight_off</mat-icon>
|
||||
</button>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</div>
|
||||
|
||||
<div *ngIf="(!hasFile || multipleOption) && !field.readOnly">
|
||||
<button mat-raised-button color="primary" (click)="uploadFiles.click()">
|
||||
{{ 'FORM.FIELD.UPLOAD' | translate }}<mat-icon>file_upload</mat-icon>
|
||||
<input #uploadFiles
|
||||
[multiple]="multipleOption"
|
||||
type="file"
|
||||
[id]="field.id"
|
||||
(change)="onFileChanged($event)"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<error-widget [error]="field.validationSummary"></error-widget>
|
||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||
</div>
|
@@ -1,32 +0,0 @@
|
||||
.adf {
|
||||
&-upload-widget-container {
|
||||
margin-bottom: 15px;
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-upload-widget {
|
||||
width: 100%;
|
||||
word-break: break-all;
|
||||
padding: 0.4375em 0;
|
||||
border-top: 0.8438em solid transparent;
|
||||
}
|
||||
|
||||
&-upload-widget__icon {
|
||||
padding: 6px;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-upload-widget__reset {
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
&-upload-files-row {
|
||||
.mat-line {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,411 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { DebugElement } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { of } from 'rxjs';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { ProcessContentService } from '../../../services/process-content.service';
|
||||
import { FormFieldTypes } from '../core/form-field-types';
|
||||
import { FormModel } from '../core/form.model';
|
||||
import { FormFieldModel } from '../core/form-field.model';
|
||||
import { UploadWidgetComponent } from './upload.widget';
|
||||
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../../../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RelatedContentRepresentation } from '@alfresco/js-api';
|
||||
|
||||
const fakePngAnswer = new RelatedContentRepresentation({
|
||||
id: 1155,
|
||||
name: 'a_png_file.png',
|
||||
created: '2017-07-25T17:17:37.099Z',
|
||||
createdBy: { id: 1001, firstName: 'Admin', lastName: 'admin', email: 'admin' },
|
||||
relatedContent: false,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'image/png',
|
||||
simpleType: 'image',
|
||||
previewStatus: 'queued',
|
||||
thumbnailStatus: 'queued'
|
||||
});
|
||||
|
||||
const fakeJpgAnswer = {
|
||||
id: 1156,
|
||||
name: 'a_jpg_file.jpg',
|
||||
created: '2017-07-25T17:17:37.118Z',
|
||||
createdBy: { id: 1001, firstName: 'Admin', lastName: 'admin', email: 'admin' },
|
||||
relatedContent: false,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'image/jpeg',
|
||||
simpleType: 'image',
|
||||
previewStatus: 'queued',
|
||||
thumbnailStatus: 'queued'
|
||||
};
|
||||
|
||||
describe('UploadWidgetComponent', () => {
|
||||
|
||||
const fakeCreationFile = (name: string, id: string | number) => ({
|
||||
id,
|
||||
name,
|
||||
created: '2017-07-25T17:17:37.118Z',
|
||||
createdBy: { id: 1001, firstName: 'Admin', lastName: 'admin', email: 'admin' },
|
||||
relatedContent: false,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'image/jpeg',
|
||||
simpleType: 'image',
|
||||
previewStatus: 'queued',
|
||||
thumbnailStatus: 'queued'
|
||||
});
|
||||
|
||||
let contentService: ProcessContentService;
|
||||
|
||||
const filePngFake = new File(['fakePng'], 'file-fake.png', { type: 'image/png' });
|
||||
const filJpgFake = new File(['fakeJpg'], 'file-fake.jpg', { type: 'image/jpg' });
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
describe('when template is ready', () => {
|
||||
let uploadWidgetComponent: UploadWidgetComponent;
|
||||
let fixture: ComponentFixture<UploadWidgetComponent>;
|
||||
let element: HTMLInputElement;
|
||||
let debugElement: DebugElement;
|
||||
let inputElement: HTMLInputElement;
|
||||
let formServiceInstance: FormService;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(UploadWidgetComponent);
|
||||
uploadWidgetComponent = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
debugElement = fixture.debugElement;
|
||||
contentService = TestBed.inject(ProcessContentService);
|
||||
});
|
||||
|
||||
it('should setup with field data', () => {
|
||||
const fileName = 'hello world';
|
||||
const encodedFileName = encodeURI(fileName);
|
||||
|
||||
uploadWidgetComponent.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
value: [
|
||||
{ name: encodedFileName }
|
||||
]
|
||||
});
|
||||
|
||||
uploadWidgetComponent.ngOnInit();
|
||||
expect(uploadWidgetComponent.hasFile).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should require form field to setup', () => {
|
||||
uploadWidgetComponent.field = null;
|
||||
uploadWidgetComponent.ngOnInit();
|
||||
|
||||
expect(uploadWidgetComponent.hasFile).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should reset field value', () => {
|
||||
uploadWidgetComponent.field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
value: [
|
||||
{ name: 'filename' }
|
||||
]
|
||||
});
|
||||
|
||||
uploadWidgetComponent.removeFile(uploadWidgetComponent.field.value[0]);
|
||||
expect(uploadWidgetComponent.field.value).toBeNull();
|
||||
expect(uploadWidgetComponent.field.json.value).toBeNull();
|
||||
expect(uploadWidgetComponent.hasFile).toBeFalsy();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
uploadWidgetComponent.field = new FormFieldModel(new FormModel({ taskId: 'fake-upload-id' }), {
|
||||
id: 'upload-id',
|
||||
name: 'upload-name',
|
||||
value: '',
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
readOnly: false
|
||||
});
|
||||
formServiceInstance = TestBed.inject(FormService);
|
||||
uploadWidgetComponent.field.value = [];
|
||||
});
|
||||
|
||||
it('should be not present in readonly forms', async () => {
|
||||
uploadWidgetComponent.field.form.readOnly = true;
|
||||
fixture.detectChanges();
|
||||
inputElement = element.querySelector<HTMLInputElement>('#upload-id');
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(inputElement).toBeNull();
|
||||
});
|
||||
|
||||
it('should have the multiple attribute when is selected in parameters', async () => {
|
||||
uploadWidgetComponent.field.params.multiple = true;
|
||||
fixture.detectChanges();
|
||||
inputElement = element.querySelector<HTMLInputElement>('#upload-id');
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(inputElement).toBeDefined();
|
||||
expect(inputElement).not.toBeNull();
|
||||
expect(inputElement.getAttributeNode('multiple')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not have the multiple attribute if multiple is false', async () => {
|
||||
uploadWidgetComponent.field.params.multiple = false;
|
||||
fixture.detectChanges();
|
||||
inputElement = element.querySelector<HTMLInputElement>('#upload-id');
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(inputElement).toBeDefined();
|
||||
expect(inputElement).not.toBeNull();
|
||||
expect(inputElement.getAttributeNode('multiple')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should show the list file after upload a new content', async () => {
|
||||
spyOn(contentService, 'createTemporaryRawRelatedContent').and.returnValue(of(fakePngAnswer));
|
||||
|
||||
uploadWidgetComponent.field.params.multiple = false;
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const inputDebugElement = fixture.debugElement.query(By.css('#upload-id'));
|
||||
inputDebugElement.triggerEventHandler('change', { target: { files: [filJpgFake] } });
|
||||
|
||||
const filesList = fixture.debugElement.query(By.css('#file-1156'));
|
||||
expect(filesList).toBeDefined();
|
||||
|
||||
});
|
||||
|
||||
it('should update the form after deleted a file', async () => {
|
||||
spyOn(contentService, 'createTemporaryRawRelatedContent').and.callFake((file: any) => {
|
||||
if (file.name === 'file-fake.png') {
|
||||
return of(fakePngAnswer);
|
||||
}
|
||||
|
||||
if (file.name === 'file-fake.jpg') {
|
||||
return of(fakeJpgAnswer);
|
||||
}
|
||||
|
||||
return of(null);
|
||||
});
|
||||
|
||||
uploadWidgetComponent.field.params.multiple = true;
|
||||
|
||||
spyOn(uploadWidgetComponent.field, 'updateForm');
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const inputDebugElement = fixture.debugElement.query(By.css('#upload-id'));
|
||||
inputDebugElement.triggerEventHandler('change', { target: { files: [filePngFake, filJpgFake] } });
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const deleteButton = element.querySelector<HTMLInputElement>('#file-1155-remove');
|
||||
deleteButton.click();
|
||||
|
||||
expect(uploadWidgetComponent.field.updateForm).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set has field value all the files uploaded', async () => {
|
||||
spyOn(contentService, 'createTemporaryRawRelatedContent').and.callFake((file: any) => {
|
||||
if (file.name === 'file-fake.png') {
|
||||
return of(fakePngAnswer);
|
||||
}
|
||||
|
||||
if (file.name === 'file-fake.jpg') {
|
||||
return of(fakeJpgAnswer);
|
||||
}
|
||||
|
||||
return of(null);
|
||||
});
|
||||
|
||||
uploadWidgetComponent.field.params.multiple = true;
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const inputDebugElement = fixture.debugElement.query(By.css('#upload-id'));
|
||||
inputDebugElement.triggerEventHandler('change', { target: { files: [filePngFake, filJpgFake] } });
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
inputElement = element.querySelector<HTMLInputElement>('#upload-id');
|
||||
expect(inputElement).toBeDefined();
|
||||
expect(inputElement).not.toBeNull();
|
||||
expect(uploadWidgetComponent.field.value).not.toBeNull();
|
||||
expect(uploadWidgetComponent.field.value.length).toBe(2);
|
||||
expect(uploadWidgetComponent.field.value[0].id).toBe(1155);
|
||||
expect(uploadWidgetComponent.field.value[1].id).toBe(1156);
|
||||
expect(uploadWidgetComponent.field.json.value.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should show all the file uploaded on multiple field', async () => {
|
||||
uploadWidgetComponent.field.params.multiple = true;
|
||||
uploadWidgetComponent.field.value.push(fakeJpgAnswer);
|
||||
uploadWidgetComponent.field.value.push(fakePngAnswer);
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const jpegElement = element.querySelector('#file-1156');
|
||||
const pngElement = element.querySelector('#file-1155');
|
||||
expect(jpegElement).not.toBeNull();
|
||||
expect(pngElement).not.toBeNull();
|
||||
expect(jpegElement.textContent).toBe('a_jpg_file.jpg');
|
||||
expect(pngElement.textContent).toBe('a_png_file.png');
|
||||
});
|
||||
|
||||
it('should show correctly the file name when is formed with special characters', async () => {
|
||||
uploadWidgetComponent.field.value.push(fakeCreationFile('±!@#$%^&*()_+{}:”|<>?§™£-=[];’\\,./.jpg', 10));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const jpegElement = element.querySelector('#file-10');
|
||||
expect(jpegElement).not.toBeNull();
|
||||
expect(jpegElement.textContent).toBe(`±!@#$%^&*()_+{}:”|<>?§™£-=[];’\\,./.jpg`);
|
||||
});
|
||||
|
||||
it('should show correctly the file name when is formed with Arabic characters', async () => {
|
||||
const name = 'غ ظ ض ذ خ ث ت ش ر ق ص ف ع س ن م ل ك ي ط ح ز و ه د ج ب ا.jpg';
|
||||
uploadWidgetComponent.field.value.push(fakeCreationFile(name, 11));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const jpegElement = element.querySelector('#file-11');
|
||||
expect(jpegElement).not.toBeNull();
|
||||
expect(jpegElement.textContent).toBe('غ ظ ض ذ خ ث ت ش ر ق ص ف ع س ن م ل ك ي ط ح ز و ه د ج ب ا.jpg');
|
||||
});
|
||||
|
||||
it('should show correctly the file name when is formed with French characters', async () => {
|
||||
// cspell: disable-next
|
||||
uploadWidgetComponent.field.value.push(fakeCreationFile('Àâæçéèêëïîôœùûüÿ.jpg', 12));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const jpegElement = element.querySelector('#file-12');
|
||||
expect(jpegElement).not.toBeNull();
|
||||
// cspell: disable-next
|
||||
expect(jpegElement.textContent).toBe('Àâæçéèêëïîôœùûüÿ.jpg');
|
||||
});
|
||||
|
||||
it('should show correctly the file name when is formed with Greek characters', async () => {
|
||||
// cspell: disable-next
|
||||
uploadWidgetComponent.field.value.push(fakeCreationFile('άέήίϊϊΐόύϋΰώθωερτψυιοπασδφγηςκλζχξωβνμ.jpg', 13));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const jpegElement = element.querySelector('#file-13');
|
||||
expect(jpegElement).not.toBeNull();
|
||||
// cspell: disable-next
|
||||
expect(jpegElement.textContent).toBe('άέήίϊϊΐόύϋΰώθωερτψυιοπασδφγηςκλζχξωβνμ.jpg');
|
||||
});
|
||||
|
||||
it('should show correctly the file name when is formed with Polish accented characters', async () => {
|
||||
uploadWidgetComponent.field.value.push(fakeCreationFile('Ą Ć Ę Ł Ń Ó Ś Ź Żą ć ę ł ń ó ś ź ż.jpg', 14));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const jpegElement = element.querySelector('#file-14');
|
||||
expect(jpegElement).not.toBeNull();
|
||||
expect(jpegElement.textContent).toBe('Ą Ć Ę Ł Ń Ó Ś Ź Żą ć ę ł ń ó ś ź ż.jpg');
|
||||
});
|
||||
|
||||
it('should show correctly the file name when is formed with Spanish accented characters', async () => {
|
||||
uploadWidgetComponent.field.value.push(fakeCreationFile('á, é, í, ó, ú, ñ, Ñ, ü, Ü, ¿, ¡. Á, É, Í, Ó, Ú.jpg', 15));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const jpegElement = element.querySelector('#file-15');
|
||||
expect(jpegElement).not.toBeNull();
|
||||
expect(jpegElement.textContent).toBe('á, é, í, ó, ú, ñ, Ñ, ü, Ü, ¿, ¡. Á, É, Í, Ó, Ú.jpg');
|
||||
});
|
||||
|
||||
it('should show correctly the file name when is formed with Swedish characters', async () => {
|
||||
// cspell: disable-next
|
||||
uploadWidgetComponent.field.value.push(fakeCreationFile('Äåéö.jpg', 16));
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const jpegElement = element.querySelector('#file-16');
|
||||
expect(jpegElement).not.toBeNull();
|
||||
// cspell: disable-next
|
||||
expect(jpegElement.textContent).toBe('Äåéö.jpg');
|
||||
});
|
||||
|
||||
it('should remove file from field value', async () => {
|
||||
uploadWidgetComponent.field.params.multiple = true;
|
||||
uploadWidgetComponent.field.value.push(fakeJpgAnswer);
|
||||
uploadWidgetComponent.field.value.push(fakePngAnswer);
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const buttonElement = element.querySelector<HTMLButtonElement>('#file-1156-remove');
|
||||
buttonElement.click();
|
||||
fixture.detectChanges();
|
||||
const jpegElement = element.querySelector('#file-1156');
|
||||
expect(jpegElement).toBeNull();
|
||||
expect(uploadWidgetComponent.field.value.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should emit form content clicked event on icon click', (done) => {
|
||||
spyOn(contentService, 'getContentPreview').and.returnValue(of(new Blob()));
|
||||
spyOn(contentService, 'getFileRawContent').and.returnValue(of(new Blob()));
|
||||
|
||||
formServiceInstance.formContentClicked.subscribe((content: any) => {
|
||||
expect(content.name).toBe(fakeJpgAnswer.name);
|
||||
expect(content.id).toBe(fakeJpgAnswer.id);
|
||||
expect(content.contentBlob).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
|
||||
uploadWidgetComponent.field.params.multiple = true;
|
||||
uploadWidgetComponent.field.value.push(fakeJpgAnswer);
|
||||
uploadWidgetComponent.field.value.push(fakePngAnswer);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
const fileJpegIcon = debugElement.query(By.css('#file-1156-icon'));
|
||||
fileJpegIcon.nativeElement.dispatchEvent(new MouseEvent('click'));
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,158 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { LogService } from '../../../../services/log.service';
|
||||
import { ThumbnailService } from '../../../../services/thumbnail.service';
|
||||
import { Component, ElementRef, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { Observable, from } from 'rxjs';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { ProcessContentService } from '../../../services/process-content.service';
|
||||
import { ContentLinkModel } from '../core/content-link.model';
|
||||
import { WidgetComponent } from '../widget.component';
|
||||
import { mergeMap, map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'upload-widget',
|
||||
templateUrl: './upload.widget.html',
|
||||
styleUrls: ['./upload.widget.scss'],
|
||||
host: {
|
||||
'(click)': 'event($event)',
|
||||
'(blur)': 'event($event)',
|
||||
'(change)': 'event($event)',
|
||||
'(focus)': 'event($event)',
|
||||
'(focusin)': 'event($event)',
|
||||
'(focusout)': 'event($event)',
|
||||
'(input)': 'event($event)',
|
||||
'(invalid)': 'event($event)',
|
||||
'(select)': 'event($event)'
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class UploadWidgetComponent extends WidgetComponent implements OnInit {
|
||||
|
||||
hasFile: boolean;
|
||||
displayText: string;
|
||||
multipleOption: string = '';
|
||||
mimeTypeIcon: string;
|
||||
|
||||
@ViewChild('uploadFiles')
|
||||
fileInput: ElementRef;
|
||||
|
||||
constructor(public formService: FormService,
|
||||
private logService: LogService,
|
||||
private thumbnailService: ThumbnailService,
|
||||
public processContentService: ProcessContentService) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field &&
|
||||
this.field.value &&
|
||||
this.field.value.length > 0) {
|
||||
this.hasFile = true;
|
||||
}
|
||||
this.getMultipleFileParam();
|
||||
}
|
||||
|
||||
removeFile(file: any) {
|
||||
if (this.field) {
|
||||
this.removeElementFromList(file);
|
||||
}
|
||||
}
|
||||
|
||||
onFileChanged(event: any) {
|
||||
const files = event.target.files;
|
||||
let filesSaved = [];
|
||||
|
||||
if (this.field.json.value) {
|
||||
filesSaved = [...this.field.json.value];
|
||||
}
|
||||
|
||||
if (files && files.length > 0) {
|
||||
from(files)
|
||||
.pipe(mergeMap((file) => this.uploadRawContent(file)))
|
||||
.subscribe(
|
||||
(res) => filesSaved.push(res),
|
||||
() => this.logService.error('Error uploading file. See console output for more details.'),
|
||||
() => {
|
||||
this.field.value = filesSaved;
|
||||
this.field.json.value = filesSaved;
|
||||
this.hasFile = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private uploadRawContent(file): Observable<any> {
|
||||
return this.processContentService.createTemporaryRawRelatedContent(file)
|
||||
.pipe(
|
||||
map((response: any) => {
|
||||
this.logService.info(response);
|
||||
response.contentBlob = file;
|
||||
return response;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getMultipleFileParam() {
|
||||
if (this.field &&
|
||||
this.field.params &&
|
||||
this.field.params.multiple) {
|
||||
this.multipleOption = this.field.params.multiple ? 'multiple' : '';
|
||||
}
|
||||
}
|
||||
|
||||
private removeElementFromList(file: any) {
|
||||
const index = this.field.value.indexOf(file);
|
||||
|
||||
if (index !== -1) {
|
||||
this.field.value.splice(index, 1);
|
||||
this.field.json.value = this.field.value;
|
||||
this.field.updateForm();
|
||||
}
|
||||
|
||||
this.hasFile = this.field.value.length > 0;
|
||||
|
||||
if (!this.hasFile) {
|
||||
this.field.value = null;
|
||||
this.field.json.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
getIcon(mimeType: string): string {
|
||||
return this.thumbnailService.getMimeTypeIcon(mimeType);
|
||||
}
|
||||
|
||||
fileClicked(contentLinkModel: any): void {
|
||||
const file = new ContentLinkModel(contentLinkModel);
|
||||
let fetch = this.processContentService.getContentPreview(file.id);
|
||||
if (file.isTypeImage() || file.isTypePdf()) {
|
||||
fetch = this.processContentService.getFileRawContent(file.id);
|
||||
}
|
||||
fetch.subscribe(
|
||||
(blob: Blob) => {
|
||||
file.contentBlob = blob;
|
||||
this.formService.formContentClicked.next(file);
|
||||
},
|
||||
() => {
|
||||
this.logService.error('Unable to send event for file ' + file.name);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@@ -20,5 +20,4 @@ export * from './form-error.event';
|
||||
export * from './form-field.event';
|
||||
export * from './validate-form-field.event';
|
||||
export * from './validate-form.event';
|
||||
export * from './validate-dynamic-table-row.event';
|
||||
export * from './form-rules.event';
|
||||
|
@@ -1,35 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { FormFieldModel } from '../components/widgets/core/form-field.model';
|
||||
import { FormModel } from '../components/widgets/core/form.model';
|
||||
import { DynamicRowValidationSummary } from '../components/widgets/dynamic-table/dynamic-row-validation-summary.model';
|
||||
import { DynamicTableRow } from '../components/widgets/dynamic-table/dynamic-table-row.model';
|
||||
import { FormFieldEvent } from './form-field.event';
|
||||
|
||||
export class ValidateDynamicTableRowEvent extends FormFieldEvent {
|
||||
|
||||
isValid = true;
|
||||
|
||||
constructor(form: FormModel,
|
||||
field: FormFieldModel,
|
||||
public row: DynamicTableRow,
|
||||
public summary: DynamicRowValidationSummary) {
|
||||
super(form, field);
|
||||
}
|
||||
|
||||
}
|
@@ -31,8 +31,6 @@ import { MASK_DIRECTIVE, WIDGET_DIRECTIVES } from './components/widgets';
|
||||
import { StartFormCustomButtonDirective } from './components/form-custom-button.directive';
|
||||
|
||||
import { FormFieldComponent } from './components/form-field/form-field.component';
|
||||
import { FormListComponent } from './components/form-list.component';
|
||||
import { ContentWidgetComponent } from './components/widgets/content/content.widget';
|
||||
import { WidgetComponent } from './components/widgets/widget.component';
|
||||
import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core';
|
||||
import { FormRendererComponent } from './components/form-renderer.component';
|
||||
@@ -61,9 +59,7 @@ import { InplaceFormInputComponent } from './components/inplace-form-input/inpla
|
||||
ViewerModule
|
||||
],
|
||||
declarations: [
|
||||
ContentWidgetComponent,
|
||||
FormFieldComponent,
|
||||
FormListComponent,
|
||||
FormRendererComponent,
|
||||
StartFormCustomButtonDirective,
|
||||
...WIDGET_DIRECTIVES,
|
||||
@@ -72,9 +68,7 @@ import { InplaceFormInputComponent } from './components/inplace-form-input/inpla
|
||||
InplaceFormInputComponent
|
||||
],
|
||||
exports: [
|
||||
ContentWidgetComponent,
|
||||
FormFieldComponent,
|
||||
FormListComponent,
|
||||
FormRendererComponent,
|
||||
StartFormCustomButtonDirective,
|
||||
...WIDGET_DIRECTIVES,
|
||||
|
@@ -17,21 +17,14 @@
|
||||
|
||||
export * from './components/form-field/form-field.component';
|
||||
export * from './components/form-base.component';
|
||||
export * from './components/form-list.component';
|
||||
export * from './components/inplace-form-input/inplace-form-input.component';
|
||||
export * from './components/widgets/content/content.widget';
|
||||
export * from './components/form-custom-button.directive';
|
||||
export * from './components/form-renderer.component';
|
||||
export * from './components/widgets';
|
||||
export * from './components/widgets/dynamic-table/dynamic-table-row.model';
|
||||
|
||||
export * from './services/activiti-alfresco.service';
|
||||
export * from './services/ecm-model.service';
|
||||
export * from './services/form-rendering.service';
|
||||
export * from './services/form.service';
|
||||
export * from './services/form-validation-service.interface';
|
||||
export * from './services/node.service';
|
||||
export * from './services/process-content.service';
|
||||
export * from './services/widget-visibility.service';
|
||||
|
||||
export * from './events';
|
||||
@@ -39,3 +32,5 @@ export * from './events';
|
||||
export * from './form-base.module';
|
||||
|
||||
export * from './models/form-rules.model';
|
||||
export * from './models/form-definition.model';
|
||||
export * from './models/task-process-variable.model';
|
||||
|
@@ -1,151 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||
import { LogService } from '../../services/log.service';
|
||||
import { SitesService } from '../../services/sites.service';
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
IntegrationAlfrescoOnPremiseApi,
|
||||
MinimalNode,
|
||||
RelatedContentRepresentation,
|
||||
ActivitiContentApi
|
||||
} from '@alfresco/js-api';
|
||||
import { Observable, from, throwError } from 'rxjs';
|
||||
import { ExternalContent } from '../components/widgets/core/external-content';
|
||||
import { ExternalContentLink } from '../components/widgets/core/external-content-link';
|
||||
import { map, catchError } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ActivitiContentService {
|
||||
|
||||
static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error';
|
||||
static GENERIC_ERROR_MESSAGE: string = 'Server error';
|
||||
|
||||
_integrationAlfrescoOnPremiseApi: IntegrationAlfrescoOnPremiseApi;
|
||||
get integrationAlfrescoOnPremiseApi(): IntegrationAlfrescoOnPremiseApi {
|
||||
this._integrationAlfrescoOnPremiseApi = this._integrationAlfrescoOnPremiseApi ?? new IntegrationAlfrescoOnPremiseApi(this.apiService.getInstance());
|
||||
return this._integrationAlfrescoOnPremiseApi;
|
||||
}
|
||||
|
||||
_contentApi: ActivitiContentApi;
|
||||
get contentApi(): ActivitiContentApi {
|
||||
this._contentApi = this._contentApi ?? new ActivitiContentApi(this.apiService.getInstance());
|
||||
return this._contentApi;
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private logService: LogService,
|
||||
private sitesService: SitesService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of child nodes below the specified folder
|
||||
*
|
||||
* @param accountId
|
||||
* @param folderId
|
||||
*/
|
||||
getAlfrescoNodes(accountId: string, folderId: string): Observable<[ExternalContent]> {
|
||||
const accountShortId = accountId.replace('alfresco-', '');
|
||||
return from(this.integrationAlfrescoOnPremiseApi.getContentInFolder(accountShortId, folderId))
|
||||
.pipe(
|
||||
map(this.toJsonArray),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all the repositories configured
|
||||
*
|
||||
* @param tenantId
|
||||
* @param includeAccount
|
||||
*/
|
||||
getAlfrescoRepositories(tenantId?: number, includeAccount?: boolean): Observable<any> {
|
||||
const opts = {
|
||||
tenantId,
|
||||
includeAccounts: includeAccount ? includeAccount : true
|
||||
};
|
||||
return from(this.integrationAlfrescoOnPremiseApi.getRepositories(opts))
|
||||
.pipe(
|
||||
map(this.toJsonArray),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of child nodes below the specified folder
|
||||
*
|
||||
* @param accountId
|
||||
* @param node
|
||||
* @param siteId
|
||||
*/
|
||||
linkAlfrescoNode(accountId: string, node: ExternalContent, siteId: string): Observable<ExternalContentLink> {
|
||||
return from(this.contentApi.createTemporaryRelatedContent({
|
||||
link: true,
|
||||
name: node.title,
|
||||
simpleType: node.simpleType,
|
||||
source: accountId,
|
||||
sourceId: node.id + '@' + siteId
|
||||
}))
|
||||
.pipe(
|
||||
map(this.toJson),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
applyAlfrescoNode(node: MinimalNode, siteId: string, accountId: string) {
|
||||
const currentSideId = siteId ? siteId : this.sitesService.getSiteNameFromNodePath(node);
|
||||
const params: RelatedContentRepresentation = {
|
||||
source: accountId,
|
||||
mimeType: node?.content?.mimeType,
|
||||
sourceId: node.id + ';' + node.properties['cm:versionLabel'] + '@' + currentSideId,
|
||||
name: node.name,
|
||||
link: node.isLink
|
||||
};
|
||||
return from(this.contentApi.createTemporaryRelatedContent(params))
|
||||
.pipe(
|
||||
map(this.toJson),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
toJson(res: any) {
|
||||
if (res) {
|
||||
return res || {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
toJsonArray(res: any) {
|
||||
if (res) {
|
||||
return res.data || [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
handleError(error: any): Observable<any> {
|
||||
let errMsg = ActivitiContentService.UNKNOWN_ERROR_MESSAGE;
|
||||
if (error) {
|
||||
errMsg = (error.message) ? error.message :
|
||||
error.status ? `${error.status} - ${error.statusText}` : ActivitiContentService.GENERIC_ERROR_MESSAGE;
|
||||
}
|
||||
this.logService.error(errMsg);
|
||||
return throwError(errMsg);
|
||||
}
|
||||
}
|
@@ -1,314 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { Observable } from 'rxjs';
|
||||
import { FormModel } from '../components/widgets/core/form.model';
|
||||
import { EcmModelService } from './ecm-model.service';
|
||||
import { setupTestBed } from '../../testing/setup-test-bed';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('EcmModelService', () => {
|
||||
|
||||
let service: EcmModelService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(EcmModelService);
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('Should fetch ECM models', (done) => {
|
||||
service.getEcmModels().subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('alfresco/versions/1/cmm')).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
|
||||
it('Should fetch ECM types', (done) => {
|
||||
|
||||
const modelName = 'modelTest';
|
||||
|
||||
service.getEcmType(modelName).subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('versions/1/cmm/' + modelName + '/types')).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
|
||||
it('Should create ECM types', (done) => {
|
||||
|
||||
const typeName = 'typeTest';
|
||||
|
||||
service.createEcmType(typeName, EcmModelService.MODEL_NAME, EcmModelService.TYPE_MODEL).subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('versions/1/cmm/' + EcmModelService.MODEL_NAME + '/types')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).name).toEqual(typeName);
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).title).toEqual(typeName);
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).parentName).toEqual(EcmModelService.TYPE_MODEL);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
|
||||
it('Should create ECM types with a clean and preserve real name in the title', (done) => {
|
||||
|
||||
const typeName = 'typeTest:testName@#$*!';
|
||||
const cleanName = 'testName';
|
||||
|
||||
service.createEcmType(typeName, EcmModelService.MODEL_NAME, EcmModelService.TYPE_MODEL).subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('versions/1/cmm/' + EcmModelService.MODEL_NAME + '/types')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).name).toEqual(cleanName);
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).title).toEqual(typeName);
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).parentName).toEqual(EcmModelService.TYPE_MODEL);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
|
||||
it('Should add property to a type', (done) => {
|
||||
|
||||
const typeName = 'typeTest';
|
||||
const formFields = {
|
||||
values: {
|
||||
test: 'test',
|
||||
test2: 'test2'
|
||||
}
|
||||
};
|
||||
|
||||
service.addPropertyToAType(EcmModelService.MODEL_NAME, typeName, formFields).subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('1/cmm/' + EcmModelService.MODEL_NAME + '/types/' + typeName + '?select=props')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).properties).toEqual([{
|
||||
name: 'test',
|
||||
title: 'test',
|
||||
description: 'test',
|
||||
dataType: 'd:text',
|
||||
multiValued: false,
|
||||
mandatory: false,
|
||||
mandatoryEnforced: false
|
||||
}, {
|
||||
name: 'test2',
|
||||
title: 'test2',
|
||||
description: 'test2',
|
||||
dataType: 'd:text',
|
||||
multiValued: false,
|
||||
mandatory: false,
|
||||
mandatoryEnforced: false
|
||||
}]);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
|
||||
it('Should add property to a type and clean name type', (done) => {
|
||||
|
||||
const typeName = 'typeTest:testName@#$*!';
|
||||
const cleanName = 'testName';
|
||||
const formFields = {
|
||||
values: {
|
||||
test: 'test',
|
||||
test2: 'test2'
|
||||
}
|
||||
};
|
||||
|
||||
service.addPropertyToAType(EcmModelService.MODEL_NAME, typeName, formFields).subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('1/cmm/' + EcmModelService.MODEL_NAME + '/types/' + cleanName + '?select=props')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).properties).toEqual([{
|
||||
name: 'test',
|
||||
title: 'test',
|
||||
description: 'test',
|
||||
dataType: 'd:text',
|
||||
multiValued: false,
|
||||
mandatory: false,
|
||||
mandatoryEnforced: false
|
||||
}, {
|
||||
name: 'test2',
|
||||
title: 'test2',
|
||||
description: 'test2',
|
||||
dataType: 'd:text',
|
||||
multiValued: false,
|
||||
mandatory: false,
|
||||
mandatoryEnforced: false
|
||||
}]);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
|
||||
it('Should create ECM model', (done) => {
|
||||
|
||||
service.createEcmModel(EcmModelService.MODEL_NAME, EcmModelService.MODEL_NAMESPACE).subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('alfresco/versions/1/cmm')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).status).toEqual('DRAFT');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
|
||||
it('Should activate ECM model', (done) => {
|
||||
|
||||
service.activeEcmModel(EcmModelService.MODEL_NAME).subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('alfresco/versions/1/cmm/' + EcmModelService.MODEL_NAME + '?select=status')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).status).toEqual('ACTIVE');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
|
||||
it('Should create an ECM type with properties', (done) => {
|
||||
spyOn(service, 'createEcmType').and.callFake(() => new Observable((observer) => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
spyOn(service, 'addPropertyToAType').and.callFake(() => new Observable((observer) => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
service.createEcmTypeWithProperties('nameType', new FormModel()).subscribe(() => {
|
||||
expect(service.createEcmType).toHaveBeenCalled();
|
||||
expect(service.addPropertyToAType).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Should return the already existing type', (done) => {
|
||||
spyOn(service, 'searchEcmType').and.callFake(() => new Observable((observer) => {
|
||||
observer.next({test: 'I-EXIST'});
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
spyOn(service, 'createEcmTypeWithProperties').and.callFake(() => new Observable((observer) => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
service.saveFomType('nameType', new FormModel()).subscribe(() => {
|
||||
expect(service.searchEcmType).toHaveBeenCalled();
|
||||
expect(service.createEcmTypeWithProperties).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Should create an ECM type with properties if the ecm Type is not defined already', (done) => {
|
||||
spyOn(service, 'searchEcmType').and.callFake(() => new Observable((observer) => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
spyOn(service, 'createEcmTypeWithProperties').and.callFake(() => new Observable((observer) => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
service.saveFomType('nameType', new FormModel()).subscribe(() => {
|
||||
expect(service.searchEcmType).toHaveBeenCalled();
|
||||
expect(service.createEcmTypeWithProperties).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Should create an ECM model for the activiti if not defined already', (done) => {
|
||||
spyOn(service, 'searchActivitiEcmModel').and.callFake(() => new Observable((observer) => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
spyOn(service, 'createActivitiEcmModel').and.callFake(() => new Observable((observer) => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
service.createEcmTypeForActivitiForm('nameType', new FormModel()).subscribe(() => {
|
||||
expect(service.searchActivitiEcmModel).toHaveBeenCalled();
|
||||
expect(service.createActivitiEcmModel).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('If a model for the activiti is already define has to save the new type', (done) => {
|
||||
spyOn(service, 'searchActivitiEcmModel').and.callFake(() => new Observable((observer) => {
|
||||
observer.next({test: 'I-EXIST'});
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
spyOn(service, 'saveFomType').and.callFake(() => new Observable((observer) => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
service.createEcmTypeForActivitiForm('nameType', new FormModel()).subscribe(() => {
|
||||
expect(service.searchActivitiEcmModel).toHaveBeenCalled();
|
||||
expect(service.saveFomType).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,222 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { LogService } from '../../services/log.service';
|
||||
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, from } from 'rxjs';
|
||||
import { FormModel } from '../components/widgets/core/form.model';
|
||||
import { map, catchError } from 'rxjs/operators';
|
||||
import { CustomModelApi } from '@alfresco/js-api';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EcmModelService {
|
||||
|
||||
public static MODEL_NAMESPACE: string = 'activitiForms';
|
||||
public static MODEL_NAME: string = 'activitiFormsModel';
|
||||
public static TYPE_MODEL: string = 'cm:folder';
|
||||
|
||||
_customModelApi: CustomModelApi;
|
||||
get customModelApi(): CustomModelApi {
|
||||
this._customModelApi = this._customModelApi ?? new CustomModelApi(this.apiService.getInstance());
|
||||
return this._customModelApi;
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private logService: LogService) {
|
||||
}
|
||||
|
||||
public createEcmTypeForActivitiForm(formName: string, form: FormModel): Observable<any> {
|
||||
return new Observable((observer) => {
|
||||
this.searchActivitiEcmModel().subscribe(
|
||||
(model) => {
|
||||
if (!model) {
|
||||
this.createActivitiEcmModel(formName, form).subscribe((typeForm) => {
|
||||
observer.next(typeForm);
|
||||
observer.complete();
|
||||
});
|
||||
} else {
|
||||
this.saveFomType(formName, form).subscribe((typeForm) => {
|
||||
observer.next(typeForm);
|
||||
observer.complete();
|
||||
});
|
||||
}
|
||||
},
|
||||
(err) => this.handleError(err)
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
searchActivitiEcmModel() {
|
||||
return this.getEcmModels().pipe(map((ecmModels: any) => ecmModels.list.entries.find((model) => model.entry.name === EcmModelService.MODEL_NAME)));
|
||||
}
|
||||
|
||||
createActivitiEcmModel(formName: string, form: FormModel): Observable<any> {
|
||||
return new Observable((observer) => {
|
||||
this.createEcmModel(EcmModelService.MODEL_NAME, EcmModelService.MODEL_NAMESPACE).subscribe(
|
||||
(model) => {
|
||||
this.logService.info('model created', model);
|
||||
this.activeEcmModel(EcmModelService.MODEL_NAME).subscribe(
|
||||
(modelActive) => {
|
||||
this.logService.info('model active', modelActive);
|
||||
this.createEcmTypeWithProperties(formName, form).subscribe((typeCreated) => {
|
||||
observer.next(typeCreated);
|
||||
observer.complete();
|
||||
});
|
||||
},
|
||||
(err) => this.handleError(err)
|
||||
);
|
||||
},
|
||||
(err) => this.handleError(err)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
saveFomType(formName: string, form: FormModel): Observable<any> {
|
||||
return new Observable((observer) => {
|
||||
this.searchEcmType(formName, EcmModelService.MODEL_NAME).subscribe(
|
||||
(ecmType) => {
|
||||
this.logService.info('custom types', ecmType);
|
||||
if (!ecmType) {
|
||||
this.createEcmTypeWithProperties(formName, form).subscribe((typeCreated) => {
|
||||
observer.next(typeCreated);
|
||||
observer.complete();
|
||||
});
|
||||
} else {
|
||||
observer.next(ecmType);
|
||||
observer.complete();
|
||||
}
|
||||
},
|
||||
(err) => this.handleError(err)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public createEcmTypeWithProperties(formName: string, form: FormModel): Observable<any> {
|
||||
return new Observable((observer) => {
|
||||
this.createEcmType(formName, EcmModelService.MODEL_NAME, EcmModelService.TYPE_MODEL).subscribe(
|
||||
(typeCreated) => {
|
||||
this.logService.info('type Created', typeCreated);
|
||||
this.addPropertyToAType(EcmModelService.MODEL_NAME, formName, form).subscribe(
|
||||
(propertyAdded) => {
|
||||
this.logService.info('property Added', propertyAdded);
|
||||
observer.next(typeCreated);
|
||||
observer.complete();
|
||||
},
|
||||
(err) => this.handleError(err));
|
||||
},
|
||||
(err) => this.handleError(err));
|
||||
});
|
||||
}
|
||||
|
||||
public searchEcmType(typeName: string, modelName: string): Observable<any> {
|
||||
return this.getEcmType(modelName).pipe(map((customTypes: any) =>
|
||||
customTypes.list.entries.find((type) => type.entry.prefixedName === typeName || type.entry.title === typeName)));
|
||||
}
|
||||
|
||||
public activeEcmModel(modelName: string): Observable<any> {
|
||||
return from(this.customModelApi.activateCustomModel(modelName))
|
||||
.pipe(
|
||||
map(this.toJson),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
public createEcmModel(modelName: string, nameSpace: string): Observable<any> {
|
||||
return from(this.customModelApi.createCustomModel('DRAFT', '', modelName, modelName, nameSpace))
|
||||
.pipe(
|
||||
map(this.toJson),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
public getEcmModels(): Observable<any> {
|
||||
return from(this.customModelApi.getAllCustomModel())
|
||||
.pipe(
|
||||
map(this.toJson),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
public getEcmType(modelName: string): Observable<any> {
|
||||
return from(this.customModelApi.getAllCustomType(modelName))
|
||||
.pipe(
|
||||
map(this.toJson),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
public createEcmType(typeName: string, modelName: string, parentType: string): Observable<any> {
|
||||
const name = this.cleanNameType(typeName);
|
||||
|
||||
return from(this.customModelApi.createCustomType(modelName, name, parentType, typeName, ''))
|
||||
.pipe(
|
||||
map(this.toJson),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
public addPropertyToAType(modelName: string, typeName: string, formFields: any) {
|
||||
const name = this.cleanNameType(typeName);
|
||||
|
||||
const properties = [];
|
||||
if (formFields && formFields.values) {
|
||||
for (const key in formFields.values) {
|
||||
if (key) {
|
||||
properties.push({
|
||||
name: key,
|
||||
title: key,
|
||||
description: key,
|
||||
dataType: 'd:text',
|
||||
multiValued: false,
|
||||
mandatory: false,
|
||||
mandatoryEnforced: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return from(this.customModelApi.addPropertyToType(modelName, name, properties))
|
||||
.pipe(
|
||||
map(this.toJson),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
cleanNameType(name: string): string {
|
||||
let cleanName = name;
|
||||
if (name.indexOf(':') !== -1) {
|
||||
cleanName = name.split(':')[1];
|
||||
}
|
||||
return cleanName.replace(/[^a-zA-Z ]/g, '');
|
||||
}
|
||||
|
||||
toJson(res: any) {
|
||||
if (res) {
|
||||
return res || {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
handleError(err: any): any {
|
||||
this.logService.error(err);
|
||||
}
|
||||
}
|
@@ -17,10 +17,8 @@
|
||||
|
||||
import { DynamicComponentResolver } from '../../../../index';
|
||||
import {
|
||||
FormFieldModel,
|
||||
FormFieldTypes,
|
||||
UnknownWidgetComponent,
|
||||
UploadWidgetComponent,
|
||||
TextWidgetComponent,
|
||||
JsonWidgetComponent,
|
||||
DisplayRichTextWidgetComponent
|
||||
@@ -35,23 +33,6 @@ describe('FormRenderingService', () => {
|
||||
service = new FormRenderingService();
|
||||
});
|
||||
|
||||
it('should resolve Upload field as Upload widget', () => {
|
||||
const field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
params: {
|
||||
link: null
|
||||
}
|
||||
});
|
||||
const type = service.resolveComponentType(field);
|
||||
expect(type).toBe(UploadWidgetComponent);
|
||||
});
|
||||
|
||||
it('should resolve Upload widget for Upload field', () => {
|
||||
const resolver = service.getComponentTypeResolver(FormFieldTypes.UPLOAD);
|
||||
const type = resolver(null);
|
||||
expect(type).toBe(UploadWidgetComponent);
|
||||
});
|
||||
|
||||
it('should resolve Unknown widget for unknown field type', () => {
|
||||
const resolver = service.getComponentTypeResolver('missing-type');
|
||||
const type = resolver(null);
|
||||
@@ -70,12 +51,6 @@ describe('FormRenderingService', () => {
|
||||
expect(type).toBe(UnknownWidgetComponent);
|
||||
});
|
||||
|
||||
it('should fallback to custom resolver when field type missing', () => {
|
||||
const resolver = service.getComponentTypeResolver(null, UploadWidgetComponent);
|
||||
const type = resolver(null);
|
||||
expect(type).toBe(UploadWidgetComponent);
|
||||
});
|
||||
|
||||
it('should require field type to set resolver for type', () => {
|
||||
expect(
|
||||
() => service.setComponentTypeResolver(
|
||||
@@ -120,10 +95,6 @@ describe('FormRenderingService', () => {
|
||||
expect(service.resolveComponentType(null)).toBe(UnknownWidgetComponent);
|
||||
});
|
||||
|
||||
it('should return custom value when resolving with no field', () => {
|
||||
expect(service.resolveComponentType(null, UploadWidgetComponent)).toBe(UploadWidgetComponent);
|
||||
});
|
||||
|
||||
it('should resolve Display Text Widget for JSON field type', () => {
|
||||
const resolver = service.getComponentTypeResolver('json');
|
||||
const type = resolver(null);
|
||||
|
@@ -32,20 +32,12 @@ export class FormRenderingService extends DynamicComponentMapper {
|
||||
integer: DynamicComponentResolver.fromType(widgets.NumberWidgetComponent),
|
||||
'multi-line-text': DynamicComponentResolver.fromType(widgets.MultilineTextWidgetComponentComponent),
|
||||
boolean: DynamicComponentResolver.fromType(widgets.CheckboxWidgetComponent),
|
||||
dropdown: DynamicComponentResolver.fromType(widgets.DropdownWidgetComponent),
|
||||
date: DynamicComponentResolver.fromType(widgets.DateWidgetComponent),
|
||||
amount: DynamicComponentResolver.fromType(widgets.AmountWidgetComponent),
|
||||
'radio-buttons': DynamicComponentResolver.fromType(widgets.RadioButtonsWidgetComponent),
|
||||
hyperlink: DynamicComponentResolver.fromType(widgets.HyperlinkWidgetComponent),
|
||||
'readonly-text': DynamicComponentResolver.fromType(widgets.DisplayTextWidgetComponent),
|
||||
json: DynamicComponentResolver.fromType(widgets.JsonWidgetComponent),
|
||||
readonly: DynamicComponentResolver.fromType(widgets.TextWidgetComponent),
|
||||
typeahead: DynamicComponentResolver.fromType(widgets.TypeaheadWidgetComponent),
|
||||
people: DynamicComponentResolver.fromType(widgets.PeopleWidgetComponent),
|
||||
'functional-group': DynamicComponentResolver.fromType(widgets.FunctionalGroupWidgetComponent),
|
||||
'dynamic-table': DynamicComponentResolver.fromType(widgets.DynamicTableWidgetComponent),
|
||||
document: DynamicComponentResolver.fromType(widgets.DocumentWidgetComponent),
|
||||
upload: DynamicComponentResolver.fromType(widgets.UploadWidgetComponent),
|
||||
datetime: DynamicComponentResolver.fromType(widgets.DateTimeWidgetComponent),
|
||||
'file-viewer': DynamicComponentResolver.fromType(widgets.FileViewerWidgetComponent),
|
||||
'display-rich-text': DynamicComponentResolver.fromType(widgets.DisplayRichTextWidgetComponent)
|
||||
|
@@ -15,40 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { fakeAsync, TestBed } from '@angular/core/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { formModelTabs } from '../../mock';
|
||||
import { FormService } from './form.service';
|
||||
import { setupTestBed } from '../../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
const fakeGroupResponse = {
|
||||
size: 2,
|
||||
total: 2,
|
||||
start: 0,
|
||||
data: [{
|
||||
id: '2004',
|
||||
name: 'PEOPLE_GROUP',
|
||||
externalId: null,
|
||||
status: 'active',
|
||||
groups: null
|
||||
}, { id: 2005, name: 'PEOPLE_GROUP_2', externalId: null, status: 'active', groups: null }]
|
||||
};
|
||||
|
||||
const fakePeopleResponse = {
|
||||
size: 3,
|
||||
total: 3,
|
||||
start: 0,
|
||||
data: [{ id: 2002, firstName: 'Peo', lastName: 'Ple', email: 'people' }, {
|
||||
id: 2003,
|
||||
firstName: 'Peo02',
|
||||
lastName: 'Ple02',
|
||||
email: 'people02'
|
||||
}, { id: 2004, firstName: 'Peo03', lastName: 'Ple03', email: 'people03' }]
|
||||
};
|
||||
|
||||
describe('Form service', () => {
|
||||
|
||||
let service: FormService;
|
||||
@@ -62,289 +35,12 @@ describe('Form service', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(FormService);
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
describe('Content tests', () => {
|
||||
|
||||
const responseBody = {
|
||||
data: [
|
||||
{ id: '1' },
|
||||
{ id: '2' }
|
||||
]
|
||||
};
|
||||
|
||||
const values = {
|
||||
field1: 'one',
|
||||
field2: 'two'
|
||||
};
|
||||
|
||||
const simpleResponseBody = { id: 1, modelType: 'test' };
|
||||
|
||||
it('should fetch and parse process definitions', (done) => {
|
||||
service.getProcessDefinitions().subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/process-definitions')).toBeTruthy();
|
||||
expect([{ id: '1' }, { id: '2' }]).toEqual(JSON.parse(jasmine.Ajax.requests.mostRecent().response).data);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch and parse tasks', (done) => {
|
||||
service.getTasks().subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/tasks/query')).toBeTruthy();
|
||||
expect([{ id: '1' }, { id: '2' }]).toEqual(JSON.parse(jasmine.Ajax.requests.mostRecent().response).data);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch and parse the task by id', (done) => {
|
||||
service.getTask('1').subscribe((result) => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/tasks/1')).toBeTruthy();
|
||||
expect(result.id).toEqual('1');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({ id: '1' })
|
||||
});
|
||||
});
|
||||
|
||||
it('should save task form', (done) => {
|
||||
service.saveTaskForm('1', values).subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1/save-form')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field1).toEqual(values.field1);
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field2).toEqual(values.field2);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
});
|
||||
});
|
||||
|
||||
it('should complete task form', (done) => {
|
||||
service.completeTaskForm('1', values).subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field1).toEqual(values.field1);
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field2).toEqual(values.field2);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
});
|
||||
});
|
||||
|
||||
it('should complete task form with a specific outcome', (done) => {
|
||||
service.completeTaskForm('1', values, 'custom').subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field2).toEqual(values.field2);
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).outcome).toEqual('custom');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
});
|
||||
});
|
||||
|
||||
it('should get task form by id', (done) => {
|
||||
service.getTaskForm('1').subscribe((result) => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1')).toBeTruthy();
|
||||
expect(result.id).toEqual(1);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({ id: 1 })
|
||||
});
|
||||
});
|
||||
|
||||
it('should get form definition by id', (done) => {
|
||||
service.getFormDefinitionById(1).subscribe((result) => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/form-models/1')).toBeTruthy();
|
||||
expect(result.id).toEqual(1);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({ id: 1 })
|
||||
});
|
||||
});
|
||||
|
||||
it('should get form definition id by name', (done) => {
|
||||
const formName = 'form1';
|
||||
const formId = 1;
|
||||
const response = {
|
||||
data: [
|
||||
{ id: formId }
|
||||
]
|
||||
};
|
||||
|
||||
service.getFormDefinitionByName(formName).subscribe((result) => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith(`models?filter=myReusableForms&filterText=${formName}&modelType=2`)).toBeTruthy();
|
||||
expect(result).toEqual(formId);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(response)
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle error with generic message', () => {
|
||||
service.handleError(null).subscribe(() => {
|
||||
}, (error) => {
|
||||
expect(error).toBe(FormService.UNKNOWN_ERROR_MESSAGE);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle error with error message', () => {
|
||||
const message = '<error>';
|
||||
|
||||
service.handleError({ message }).subscribe(() => {
|
||||
}, (error) => {
|
||||
expect(error).toBe(message);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle error with detailed message', () => {
|
||||
service.handleError({
|
||||
status: '400',
|
||||
statusText: 'Bad request'
|
||||
}).subscribe(
|
||||
() => {
|
||||
},
|
||||
(error) => {
|
||||
expect(error).toBe('400 - Bad request');
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle error with generic message', () => {
|
||||
service.handleError({}).subscribe(() => {
|
||||
}, (error) => {
|
||||
expect(error).toBe(FormService.GENERIC_ERROR_MESSAGE);
|
||||
});
|
||||
});
|
||||
|
||||
it('should get all the forms with modelType=2', (done) => {
|
||||
service.getForms().subscribe((result) => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('models?modelType=2')).toBeTruthy();
|
||||
expect(result.length).toEqual(2);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({
|
||||
data: [
|
||||
{ name: 'FakeName-1', lastUpdatedByFullName: 'FakeUser-1', lastUpdated: '2017-01-02' },
|
||||
{ name: 'FakeName-2', lastUpdatedByFullName: 'FakeUser-2', lastUpdated: '2017-01-03' }
|
||||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('should search for Form with modelType=2', (done) => {
|
||||
const response = { data: [{ id: 1, name: 'findMe' }, { id: 2, name: 'testForm' }] };
|
||||
|
||||
service.searchFrom('findMe').subscribe((result) => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('models?modelType=2')).toBeTruthy();
|
||||
expect(result.name).toEqual('findMe');
|
||||
expect(result.id).toEqual(1);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(response)
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a Form with modelType=2', (done) => {
|
||||
service.createForm('testName').subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/models')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).modelType).toEqual(2);
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).name).toEqual('testName');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(simpleResponseBody)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return list of people', (done) => {
|
||||
spyOn(service, 'getUserProfileImageApi').and.returnValue('/app/rest/users/2002/picture');
|
||||
const fakeFilter: string = 'whatever';
|
||||
|
||||
service.getWorkflowUsers(fakeFilter).subscribe((result) => {
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBe(3);
|
||||
expect(result[0].id).toBe(2002);
|
||||
expect(result[0].firstName).toBe('Peo');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakePeopleResponse)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return list of groups', (done) => {
|
||||
const fakeFilter: string = 'whatever';
|
||||
|
||||
service.getWorkflowGroups(fakeFilter).subscribe((result) => {
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBe(2);
|
||||
expect(result[0].id).toBe('2004');
|
||||
expect(result[0].name).toBe('PEOPLE_GROUP');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeGroupResponse)
|
||||
});
|
||||
});
|
||||
describe('parseForm', () => {
|
||||
|
||||
it('should parse a Form Definition with tabs', () => {
|
||||
expect(formModelTabs.formRepresentation.formDefinition).toBeDefined();
|
||||
@@ -352,60 +48,5 @@ describe('Form service', () => {
|
||||
expect(formParsed).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create a Form form a Node', fakeAsync(() => {
|
||||
const nameForm = 'testNode';
|
||||
const formId = 100;
|
||||
|
||||
const stubCreateForm = () => {
|
||||
jasmine.Ajax.stubRequest(
|
||||
'http://localhost:9876/bpm/activiti-app/api/enterprise/models'
|
||||
).andReturn({
|
||||
status: 200,
|
||||
statusText: 'HTTP/1.1 200 OK',
|
||||
contentType: 'text/xml;charset=UTF-8',
|
||||
responseText: { id: formId, name: 'test', lastUpdatedByFullName: 'uset', lastUpdated: '12-12-2016' }
|
||||
});
|
||||
};
|
||||
|
||||
const stubGetEcmModel = () => {
|
||||
jasmine.Ajax.stubRequest(
|
||||
'http://localhost:9876/ecm/alfresco/api/-default-/private/alfresco/versions/1/cmm/activitiFormsModel/types'
|
||||
).andReturn({
|
||||
status: 200,
|
||||
statusText: 'HTTP/1.1 200 OK',
|
||||
contentType: 'text/xml;charset=UTF-8',
|
||||
responseText: {
|
||||
list: {
|
||||
entries: [{
|
||||
entry: {
|
||||
prefixedName: nameForm,
|
||||
title: nameForm,
|
||||
properties: [{ name: 'name' }, { name: 'email' }]
|
||||
}
|
||||
}, { entry: { prefixedName: 'notme', title: 'notme' } }]
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const stubAddFieldsToAForm = () => {
|
||||
jasmine.Ajax.stubRequest(
|
||||
'http://localhost:9876/bpm/activiti-app/api/enterprise/editor/form-models/' + formId
|
||||
).andReturn({
|
||||
status: 200,
|
||||
statusText: 'HTTP/1.1 200 OK',
|
||||
contentType: 'text/xml;charset=UTF-8',
|
||||
responseText: { id: formId, name: 'test', lastUpdatedByFullName: 'user', lastUpdated: '12-12-2016' }
|
||||
});
|
||||
};
|
||||
|
||||
stubCreateForm();
|
||||
stubGetEcmModel();
|
||||
stubAddFieldsToAForm();
|
||||
|
||||
service.createFormFromANode(nameForm).subscribe((result) => {
|
||||
expect(result.id).toEqual(formId);
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@@ -15,29 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||
import { LogService } from '../../services/log.service';
|
||||
import { UserProcessModel } from '../../models';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, Subject, from, of, throwError } from 'rxjs';
|
||||
import { FormDefinitionModel } from '../models/form-definition.model';
|
||||
import { Subject } from 'rxjs';
|
||||
import { ContentLinkModel } from '../components/widgets/core/content-link.model';
|
||||
import { GroupModel } from '../components/widgets/core/group.model';
|
||||
import { EcmModelService } from './ecm-model.service';
|
||||
import { map, catchError, switchMap, combineAll, defaultIfEmpty } from 'rxjs/operators';
|
||||
import {
|
||||
CompleteFormRepresentation,
|
||||
ModelsApi,
|
||||
ProcessInstanceVariablesApi,
|
||||
SaveFormRepresentation,
|
||||
TasksApi,
|
||||
TaskFormsApi,
|
||||
ProcessInstancesApi,
|
||||
FormModelsApi,
|
||||
ProcessDefinitionsApi,
|
||||
UsersApi,
|
||||
ActivitiGroupsApi
|
||||
} from '@alfresco/js-api';
|
||||
import { FormOutcomeEvent } from '../components/widgets/core/form-outcome-event.model';
|
||||
import { FormValues } from '../components/widgets/core/form-values';
|
||||
import { FormModel } from '../components/widgets/core/form.model';
|
||||
@@ -47,7 +27,6 @@ import { FormFieldEvent } from '../events/form-field.event';
|
||||
import { FormErrorEvent } from '../events/form-error.event';
|
||||
import { ValidateFormEvent } from '../events/validate-form.event';
|
||||
import { ValidateFormFieldEvent } from '../events/validate-form-field.event';
|
||||
import { ValidateDynamicTableRowEvent } from '../events/validate-dynamic-table-row.event';
|
||||
import { FormValidationService } from './form-validation-service.interface';
|
||||
import { FormRulesEvent } from '../events/form-rules.event';
|
||||
|
||||
@@ -56,63 +35,6 @@ import { FormRulesEvent } from '../events/form-rules.event';
|
||||
})
|
||||
export class FormService implements FormValidationService {
|
||||
|
||||
static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error';
|
||||
static GENERIC_ERROR_MESSAGE: string = 'Server error';
|
||||
|
||||
_taskFormsApi: TaskFormsApi;
|
||||
get taskFormsApi(): TaskFormsApi {
|
||||
this._taskFormsApi = this._taskFormsApi ?? new TaskFormsApi(this.apiService.getInstance());
|
||||
return this._taskFormsApi;
|
||||
}
|
||||
|
||||
_taskApi: TasksApi;
|
||||
get taskApi(): TasksApi {
|
||||
this._taskApi = this._taskApi ?? new TasksApi(this.apiService.getInstance());
|
||||
return this._taskApi;
|
||||
}
|
||||
|
||||
_modelsApi: ModelsApi;
|
||||
get modelsApi(): ModelsApi {
|
||||
this._modelsApi = this._modelsApi ?? new ModelsApi(this.apiService.getInstance());
|
||||
return this._modelsApi;
|
||||
}
|
||||
|
||||
_editorApi: FormModelsApi;
|
||||
get editorApi(): FormModelsApi {
|
||||
this._editorApi = this._editorApi ?? new FormModelsApi(this.apiService.getInstance());
|
||||
return this._editorApi;
|
||||
}
|
||||
|
||||
_processDefinitionsApi: ProcessDefinitionsApi;
|
||||
get processDefinitionsApi(): ProcessDefinitionsApi {
|
||||
this._processDefinitionsApi = this._processDefinitionsApi ?? new ProcessDefinitionsApi(this.apiService.getInstance());
|
||||
return this._processDefinitionsApi;
|
||||
}
|
||||
|
||||
_processInstanceVariablesApi: ProcessInstanceVariablesApi;
|
||||
get processInstanceVariablesApi(): ProcessInstanceVariablesApi {
|
||||
this._processInstanceVariablesApi = this._processInstanceVariablesApi ?? new ProcessInstanceVariablesApi(this.apiService.getInstance());
|
||||
return this._processInstanceVariablesApi;
|
||||
}
|
||||
|
||||
_processInstancesApi: ProcessInstancesApi;
|
||||
get processInstancesApi(): ProcessInstancesApi {
|
||||
this._processInstancesApi = this._processInstancesApi ?? new ProcessInstancesApi(this.apiService.getInstance());
|
||||
return this._processInstancesApi;
|
||||
}
|
||||
|
||||
_groupsApi: ActivitiGroupsApi;
|
||||
get groupsApi(): ActivitiGroupsApi {
|
||||
this._groupsApi = this._groupsApi ?? new ActivitiGroupsApi(this.apiService.getInstance());
|
||||
return this._groupsApi;
|
||||
}
|
||||
|
||||
_usersApi: UsersApi;
|
||||
get usersApi(): UsersApi {
|
||||
this._usersApi = this._usersApi ?? new UsersApi(this.apiService.getInstance());
|
||||
return this._usersApi;
|
||||
}
|
||||
|
||||
formLoaded = new Subject<FormEvent>();
|
||||
formDataRefreshed = new Subject<FormEvent>();
|
||||
formFieldValueChanged = new Subject<FormFieldEvent>();
|
||||
@@ -125,7 +47,7 @@ export class FormService implements FormValidationService {
|
||||
|
||||
validateForm = new Subject<ValidateFormEvent>();
|
||||
validateFormField = new Subject<ValidateFormFieldEvent>();
|
||||
validateDynamicTableRow = new Subject<ValidateDynamicTableRowEvent>();
|
||||
validateDynamicTableRow = new Subject<FormFieldEvent>();
|
||||
|
||||
executeOutcome = new Subject<FormOutcomeEvent>();
|
||||
|
||||
@@ -133,9 +55,7 @@ export class FormService implements FormValidationService {
|
||||
|
||||
formRulesEvent = new Subject<FormRulesEvent>();
|
||||
|
||||
constructor(private ecmModelService: EcmModelService,
|
||||
private apiService: AlfrescoApiService,
|
||||
protected logService: LogService) {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,449 +83,4 @@ export class FormService implements FormValidationService {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Form with a field for each metadata property.
|
||||
*
|
||||
* @param formName Name of the new form
|
||||
* @returns The new form
|
||||
*/
|
||||
createFormFromANode(formName: string): Observable<any> {
|
||||
return new Observable((observer) => {
|
||||
this.createForm(formName).subscribe(
|
||||
(form) => {
|
||||
this.ecmModelService.searchEcmType(formName, EcmModelService.MODEL_NAME).subscribe(
|
||||
(customType) => {
|
||||
const formDefinitionModel = new FormDefinitionModel(form.id, form.name, form.lastUpdatedByFullName, form.lastUpdated, customType.entry.properties);
|
||||
from(
|
||||
this.editorApi.saveForm(form.id, formDefinitionModel)
|
||||
).subscribe((formData) => {
|
||||
observer.next(formData);
|
||||
observer.complete();
|
||||
}, (err) => this.handleError(err));
|
||||
},
|
||||
(err) => this.handleError(err));
|
||||
},
|
||||
(err) => this.handleError(err));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Form.
|
||||
*
|
||||
* @param formName Name of the new form
|
||||
* @returns The new form
|
||||
*/
|
||||
createForm(formName: string): Observable<any> {
|
||||
const dataModel = {
|
||||
name: formName,
|
||||
description: '',
|
||||
modelType: 2,
|
||||
stencilSet: 0
|
||||
};
|
||||
|
||||
return from(
|
||||
this.modelsApi.createModel(dataModel)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a form.
|
||||
*
|
||||
* @param formId ID of the form to save
|
||||
* @param formModel Model data for the form
|
||||
* @returns Data for the saved form
|
||||
*/
|
||||
saveForm(formId: number, formModel: FormDefinitionModel): Observable<any> {
|
||||
return from(
|
||||
this.editorApi.saveForm(formId, formModel)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a form by name.
|
||||
*
|
||||
* @param name The form name to search for
|
||||
* @returns Form model(s) matching the search name
|
||||
*/
|
||||
searchFrom(name: string): Observable<any> {
|
||||
const opts = {
|
||||
modelType: 2
|
||||
};
|
||||
|
||||
return from(
|
||||
this.modelsApi.getModels(opts)
|
||||
)
|
||||
.pipe(
|
||||
map((forms: any) => forms.data.find((formData) => formData.name === name)),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the forms.
|
||||
*
|
||||
* @returns List of form models
|
||||
*/
|
||||
getForms(): Observable<any> {
|
||||
const opts = {
|
||||
modelType: 2
|
||||
};
|
||||
|
||||
return from(this.modelsApi.getModels(opts))
|
||||
.pipe(
|
||||
map(this.toJsonArray),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets process definitions.
|
||||
*
|
||||
* @returns List of process definitions
|
||||
*/
|
||||
getProcessDefinitions(): Observable<any> {
|
||||
return from(this.processDefinitionsApi.getProcessDefinitions({}))
|
||||
.pipe(
|
||||
map(this.toJsonArray),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets instance variables for a process.
|
||||
*
|
||||
* @param processInstanceId ID of the target process
|
||||
* @returns List of instance variable information
|
||||
*/
|
||||
getProcessVariablesById(processInstanceId: string): Observable<any[]> {
|
||||
return from(this.processInstanceVariablesApi.getProcessInstanceVariables(processInstanceId))
|
||||
.pipe(
|
||||
map(this.toJson),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the tasks.
|
||||
*
|
||||
* @returns List of tasks
|
||||
*/
|
||||
getTasks(): Observable<any> {
|
||||
return from(this.taskApi.listTasks({}))
|
||||
.pipe(
|
||||
map(this.toJsonArray),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a task.
|
||||
*
|
||||
* @param taskId Task Id
|
||||
* @returns Task info
|
||||
*/
|
||||
getTask(taskId: string): Observable<any> {
|
||||
return from(this.taskApi.getTask(taskId))
|
||||
.pipe(
|
||||
map(this.toJson),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a task form.
|
||||
*
|
||||
* @param taskId Task Id
|
||||
* @param formValues Form Values
|
||||
* @returns Null response when the operation is complete
|
||||
*/
|
||||
saveTaskForm(taskId: string, formValues: FormValues): Observable<any> {
|
||||
const saveFormRepresentation = { values: formValues } as SaveFormRepresentation;
|
||||
|
||||
return from(this.taskFormsApi.saveTaskForm(taskId, saveFormRepresentation))
|
||||
.pipe(
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes a Task Form.
|
||||
*
|
||||
* @param taskId Task Id
|
||||
* @param formValues Form Values
|
||||
* @param outcome Form Outcome
|
||||
* @returns Null response when the operation is complete
|
||||
*/
|
||||
completeTaskForm(taskId: string, formValues: FormValues, outcome?: string): Observable<any> {
|
||||
const completeFormRepresentation = { values: formValues } as CompleteFormRepresentation;
|
||||
if (outcome) {
|
||||
completeFormRepresentation.outcome = outcome;
|
||||
}
|
||||
|
||||
return from(this.taskFormsApi.completeTaskForm(taskId, completeFormRepresentation))
|
||||
.pipe(
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a form related to a task.
|
||||
*
|
||||
* @param taskId ID of the target task
|
||||
* @returns Form definition
|
||||
*/
|
||||
getTaskForm(taskId: string): Observable<any> {
|
||||
return from(this.taskFormsApi.getTaskForm(taskId))
|
||||
.pipe(
|
||||
map(this.toJson),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a form definition.
|
||||
*
|
||||
* @param formId ID of the target form
|
||||
* @returns Form definition
|
||||
*/
|
||||
getFormDefinitionById(formId: number): Observable<any> {
|
||||
return from(this.editorApi.getForm(formId))
|
||||
.pipe(
|
||||
map(this.toJson),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the form definition with a given name.
|
||||
*
|
||||
* @param name The form name
|
||||
* @returns Form definition
|
||||
*/
|
||||
getFormDefinitionByName(name: string): Observable<any> {
|
||||
const opts = {
|
||||
filter: 'myReusableForms',
|
||||
filterText: name,
|
||||
modelType: 2
|
||||
};
|
||||
|
||||
return from(this.modelsApi.getModels(opts))
|
||||
.pipe(
|
||||
map(this.getFormId),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the start form instance for a given process.
|
||||
*
|
||||
* @param processId Process definition ID
|
||||
* @returns Form definition
|
||||
*/
|
||||
getStartFormInstance(processId: string): Observable<any> {
|
||||
return from(this.processInstancesApi.getProcessInstanceStartForm(processId))
|
||||
.pipe(
|
||||
map(this.toJson),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a process instance.
|
||||
*
|
||||
* @param processId ID of the process to get
|
||||
* @returns Process instance
|
||||
*/
|
||||
getProcessInstance(processId: string): Observable<any> {
|
||||
return from(this.processInstancesApi.getProcessInstance(processId))
|
||||
.pipe(
|
||||
map(this.toJson),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the start form definition for a given process.
|
||||
*
|
||||
* @param processId Process definition ID
|
||||
* @returns Form definition
|
||||
*/
|
||||
getStartFormDefinition(processId: string): Observable<any> {
|
||||
return from(this.processDefinitionsApi.getProcessDefinitionStartForm(processId))
|
||||
.pipe(
|
||||
map(this.toJson),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets values of fields populated by a REST backend.
|
||||
*
|
||||
* @param taskId Task identifier
|
||||
* @param field Field identifier
|
||||
* @returns Field values
|
||||
*/
|
||||
getRestFieldValues(taskId: string, field: string): Observable<any> {
|
||||
return from(this.taskFormsApi.getRestFieldValues(taskId, field))
|
||||
.pipe(
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets values of fields populated by a REST backend using a process ID.
|
||||
*
|
||||
* @param processDefinitionId Process identifier
|
||||
* @param field Field identifier
|
||||
* @returns Field values
|
||||
*/
|
||||
getRestFieldValuesByProcessId(processDefinitionId: string, field: string): Observable<any> {
|
||||
return from(this.processDefinitionsApi.getRestFieldValues(processDefinitionId, field))
|
||||
.pipe(
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets column values of fields populated by a REST backend using a process ID.
|
||||
*
|
||||
* @param processDefinitionId Process identifier
|
||||
* @param field Field identifier
|
||||
* @param column Column identifier
|
||||
* @returns Field values
|
||||
*/
|
||||
getRestFieldValuesColumnByProcessId(processDefinitionId: string, field: string, column?: string): Observable<any> {
|
||||
return from(this.processDefinitionsApi.getRestTableFieldValues(processDefinitionId, field, column))
|
||||
.pipe(
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets column values of fields populated by a REST backend.
|
||||
*
|
||||
* @param taskId Task identifier
|
||||
* @param field Field identifier
|
||||
* @param column Column identifier
|
||||
* @returns Field values
|
||||
*/
|
||||
getRestFieldValuesColumn(taskId: string, field: string, column?: string): Observable<any> {
|
||||
return from(this.taskFormsApi.getRestFieldColumnValues(taskId, field, column))
|
||||
.pipe(
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URL for the profile picture of a user.
|
||||
*
|
||||
* @param userId ID of the target user
|
||||
* @returns URL string
|
||||
*/
|
||||
getUserProfileImageApi(userId: string): string {
|
||||
return this.usersApi.getUserProfilePictureUrl(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of workflow users.
|
||||
*
|
||||
* @param filter Filter to select specific users
|
||||
* @param groupId Group ID for the search
|
||||
* @returns Array of users
|
||||
*/
|
||||
getWorkflowUsers(filter: string, groupId?: string): Observable<UserProcessModel[]> {
|
||||
const option: any = { filter };
|
||||
if (groupId) {
|
||||
option.groupId = groupId;
|
||||
}
|
||||
return from(this.usersApi.getUsers(option))
|
||||
.pipe(
|
||||
switchMap(response => response.data as UserProcessModel[] || []),
|
||||
map((user) => {
|
||||
user.userImage = this.getUserProfileImageApi(user.id.toString());
|
||||
return of(user);
|
||||
}),
|
||||
combineAll(),
|
||||
defaultIfEmpty([]),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of groups in a workflow.
|
||||
*
|
||||
* @param filter Filter to select specific groups
|
||||
* @param groupId Group ID for the search
|
||||
* @returns Array of groups
|
||||
*/
|
||||
getWorkflowGroups(filter: string, groupId?: string): Observable<GroupModel[]> {
|
||||
const option: any = { filter };
|
||||
if (groupId) {
|
||||
option.groupId = groupId;
|
||||
}
|
||||
return from(this.groupsApi.getGroups(option))
|
||||
.pipe(
|
||||
map((response: any) => response.data || []),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of a form.
|
||||
*
|
||||
* @param form Object representing a form
|
||||
* @returns ID string
|
||||
*/
|
||||
getFormId(form: any): string {
|
||||
let result = null;
|
||||
|
||||
if (form && form.data && form.data.length > 0) {
|
||||
result = form.data[0].id;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON representation of form data.
|
||||
*
|
||||
* @param res Object representing form data
|
||||
* @returns JSON data
|
||||
*/
|
||||
toJson(res: any) {
|
||||
if (res) {
|
||||
return res || {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON array representation of form data.
|
||||
*
|
||||
* @param res Object representing form data
|
||||
* @returns JSON data
|
||||
*/
|
||||
toJsonArray(res: any) {
|
||||
if (res) {
|
||||
return res.data || [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports an error message.
|
||||
*
|
||||
* @param error Data object with optional `message` and `status` fields for the error
|
||||
* @returns Error message
|
||||
*/
|
||||
handleError(error: any): Observable<any> {
|
||||
let errMsg = FormService.UNKNOWN_ERROR_MESSAGE;
|
||||
if (error) {
|
||||
errMsg = (error.message) ? error.message :
|
||||
error.status ? `${error.status} - ${error.statusText}` : FormService.GENERIC_ERROR_MESSAGE;
|
||||
}
|
||||
this.logService.error(errMsg);
|
||||
return throwError(errMsg);
|
||||
}
|
||||
}
|
||||
|
@@ -1,171 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { TestBed } from '@angular/core/testing';
|
||||
import { NodeMetadata } from '../../models/node-metadata.model';
|
||||
import { EcmModelService } from './ecm-model.service';
|
||||
import { NodeService } from './node.service';
|
||||
import { setupTestBed } from '../../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('NodeService', () => {
|
||||
|
||||
let service: NodeService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(NodeService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('Should fetch and node metadata', (done) => {
|
||||
const responseBody = {
|
||||
entry: {
|
||||
id: '111-222-33-44-1123',
|
||||
nodeType: 'typeTest',
|
||||
properties: {
|
||||
test: 'test',
|
||||
testdata: 'testdata'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
service.getNodeMetadata('-nodeid-').subscribe((result) => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('nodes/-nodeid-')).toBeTruthy();
|
||||
const node = new NodeMetadata({
|
||||
test: 'test',
|
||||
testdata: 'testdata'
|
||||
}, 'typeTest');
|
||||
expect(result).toEqual(node);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
});
|
||||
});
|
||||
|
||||
it('Should clean the metadata from :', (done) => {
|
||||
const responseBody = {
|
||||
entry: {
|
||||
id: '111-222-33-44-1123',
|
||||
nodeType: 'typeTest',
|
||||
properties: {
|
||||
'metadata:test': 'test',
|
||||
'metadata:testdata': 'testdata'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
service.getNodeMetadata('-nodeid-').subscribe((result) => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('nodes/-nodeid-')).toBeTruthy();
|
||||
const node = new NodeMetadata({
|
||||
test: 'test',
|
||||
testdata: 'testdata'
|
||||
}, 'typeTest');
|
||||
expect(result).toEqual(node);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
});
|
||||
});
|
||||
|
||||
it('Should create a node with metadata', (done) => {
|
||||
const data = {
|
||||
test: 'test',
|
||||
testdata: 'testdata'
|
||||
};
|
||||
|
||||
const responseBody = {
|
||||
id: 'a74d91fb-ea8a-4812-ad98-ad878366b5be',
|
||||
isFile: false,
|
||||
isFolder: true
|
||||
};
|
||||
|
||||
service.createNodeMetadata('typeTest', EcmModelService.MODEL_NAMESPACE, data, '/Sites/swsdp/documentLibrary', 'testNode').subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('-root-/children')).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
});
|
||||
});
|
||||
|
||||
it('Should add activitiForms suffix to the metadata properties', (done) => {
|
||||
const data = {
|
||||
test: 'test',
|
||||
testdata: 'testdata'
|
||||
};
|
||||
|
||||
service.createNodeMetadata('typeTest', EcmModelService.MODEL_NAMESPACE, data, '/Sites/swsdp/documentLibrary').subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('-root-/children')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).properties[EcmModelService.MODEL_NAMESPACE + ':test']).toBeDefined();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).properties[EcmModelService.MODEL_NAMESPACE + ':testdata']).toBeDefined();
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
|
||||
it('Should assign an UUID to the name when name not passed', (done) => {
|
||||
const data = {
|
||||
test: 'test',
|
||||
testdata: 'testdata'
|
||||
};
|
||||
|
||||
service.createNodeMetadata('typeTest', EcmModelService.MODEL_NAMESPACE, data, '/Sites/swsdp/documentLibrary').subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('-root-/children')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).name).toBeDefined();
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,70 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { NodeEntry } from '@alfresco/js-api';
|
||||
import { NodeMetadata } from '../../models/node-metadata.model';
|
||||
import { NodesApiService } from '../../services/nodes-api.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
/**
|
||||
* @deprecated in 3.8.0, use NodesApiService instead.
|
||||
*/
|
||||
export class NodeService {
|
||||
|
||||
constructor(private nodesApiService: NodesApiService) {}
|
||||
|
||||
/**
|
||||
* @deprecated in 3.8.0, use NodesApiService instead.
|
||||
* Get the metadata and the nodeType for a nodeId cleaned by the prefix.
|
||||
* @param nodeId ID of the target node
|
||||
* @returns Node metadata
|
||||
*/
|
||||
public getNodeMetadata(nodeId: string): Observable<NodeMetadata> {
|
||||
return this.nodesApiService.getNodeMetadata(nodeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated in 3.8.0, use NodesApiService instead.
|
||||
* Create a new Node from form metadata.
|
||||
* @param path Path to the node
|
||||
* @param nodeType Node type
|
||||
* @param name Node name
|
||||
* @param nameSpace Namespace for properties
|
||||
* @param data Property data to store in the node under namespace
|
||||
* @returns The created node
|
||||
*/
|
||||
public createNodeMetadata(nodeType: string, nameSpace: any, data: any, path: string, name?: string): Observable<NodeEntry> {
|
||||
return this.nodesApiService.createNodeMetadata(nodeType, nameSpace, data, path, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated in 3.8.0, use `createNodeInsideRoot` method from NodesApiService instead.
|
||||
* Create a new Node from form metadata
|
||||
* @param name Node name
|
||||
* @param nodeType Node type
|
||||
* @param properties Node body properties
|
||||
* @param path Path to the node
|
||||
* @returns The created node
|
||||
*/
|
||||
public createNode(name: string, nodeType: string, properties: any, path: string): Observable<NodeEntry> {
|
||||
return this.nodesApiService.createNodeInsideRoot(name, nodeType, properties, path);
|
||||
}
|
||||
}
|
@@ -1,194 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { TestBed } from '@angular/core/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { ProcessContentService } from './process-content.service';
|
||||
import { setupTestBed } from '../../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
const fileContentPdfResponseBody = {
|
||||
id: 999,
|
||||
name: 'fake-name.pdf',
|
||||
created: '2017-01-23T12:12:53.219+0000',
|
||||
createdBy: { id: 2, firstName: 'fake-admin', lastName: 'fake-last', email: 'fake-admin' },
|
||||
relatedContent: false,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'application/pdf',
|
||||
simpleType: 'pdf',
|
||||
previewStatus: 'created',
|
||||
thumbnailStatus: 'created'
|
||||
};
|
||||
|
||||
const fileContentJpgResponseBody = {
|
||||
id: 888,
|
||||
name: 'fake-name.jpg',
|
||||
created: '2017-01-23T12:12:53.219+0000',
|
||||
createdBy: { id: 2, firstName: 'fake-admin', lastName: 'fake-last', email: 'fake-admin' },
|
||||
relatedContent: false,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'image/jpeg',
|
||||
simpleType: 'image',
|
||||
previewStatus: 'unsupported',
|
||||
thumbnailStatus: 'unsupported'
|
||||
};
|
||||
|
||||
const createFakeBlob = () => {
|
||||
const data = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
||||
|
||||
const bytes = new Uint8Array(data.length / 2);
|
||||
|
||||
for (let i = 0; i < data.length; i += 2) {
|
||||
bytes[i / 2] = parseInt(data.substring(i, i + 2), /* base = */ 16);
|
||||
}
|
||||
return new Blob([bytes], { type: 'image/png' });
|
||||
};
|
||||
|
||||
describe('ProcessContentService', () => {
|
||||
|
||||
let service: ProcessContentService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(ProcessContentService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('Should fetch the attachments', (done) => {
|
||||
service.getTaskRelatedContent('1234').subscribe((res) => {
|
||||
expect(res.data).toBeDefined();
|
||||
expect(res.data.length).toBe(2);
|
||||
expect(res.data[0].name).toBe('fake.zip');
|
||||
expect(res.data[0].mimeType).toBe('application/zip');
|
||||
expect(res.data[0].relatedContent).toBeTruthy();
|
||||
expect(res.data[1].name).toBe('fake.jpg');
|
||||
expect(res.data[1].mimeType).toBe('image/jpeg');
|
||||
expect(res.data[1].relatedContent).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({
|
||||
size: 2,
|
||||
total: 2,
|
||||
start: 0,
|
||||
data: [
|
||||
{
|
||||
id: 8,
|
||||
name: 'fake.zip',
|
||||
created: 1494595697381,
|
||||
createdBy: {id: 2, firstName: 'user', lastName: 'user', email: 'user@user.com'},
|
||||
relatedContent: true,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'application/zip',
|
||||
simpleType: 'content',
|
||||
previewStatus: 'unsupported',
|
||||
thumbnailStatus: 'unsupported'
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: 'fake.jpg',
|
||||
created: 1494595655381,
|
||||
createdBy: {id: 2, firstName: 'user', lastName: 'user', email: 'user@user.com'},
|
||||
relatedContent: true,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'image/jpeg',
|
||||
simpleType: 'image',
|
||||
previewStatus: 'unsupported',
|
||||
thumbnailStatus: 'unsupported'
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the unsupported content when the file is an image', (done) => {
|
||||
const contentId: number = 888;
|
||||
|
||||
service.getFileContent(contentId).subscribe((result) => {
|
||||
expect(result.id).toEqual(contentId);
|
||||
expect(result.name).toEqual('fake-name.jpg');
|
||||
expect(result.simpleType).toEqual('image');
|
||||
expect(result.thumbnailStatus).toEqual('unsupported');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fileContentJpgResponseBody)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the supported content when the file is a pdf', (done) => {
|
||||
const contentId: number = 999;
|
||||
|
||||
service.getFileContent(contentId).subscribe((result) => {
|
||||
expect(result.id).toEqual(contentId);
|
||||
expect(result.name).toEqual('fake-name.pdf');
|
||||
expect(result.simpleType).toEqual('pdf');
|
||||
expect(result.thumbnailStatus).toEqual('created');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fileContentPdfResponseBody)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the raw content URL', () => {
|
||||
const contentId: number = 999;
|
||||
const contentUrl = service.getFileRawContentUrl(contentId);
|
||||
expect(contentUrl).toContain(`/api/enterprise/content/${contentId}/raw`);
|
||||
});
|
||||
|
||||
it('should return a Blob as thumbnail', (done) => {
|
||||
const contentId: number = 999;
|
||||
const blob = createFakeBlob();
|
||||
spyOn(service, 'getContentThumbnail').and.returnValue(of(blob));
|
||||
service.getContentThumbnail(contentId).subscribe((result) => {
|
||||
expect(result).toEqual(jasmine.any(Blob));
|
||||
expect(result.size).toEqual(48);
|
||||
expect(result.type).toEqual('image/png');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,229 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||
import { LogService } from '../../services/log.service';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivitiContentApi, RelatedContentRepresentation } from '@alfresco/js-api';
|
||||
import { Observable, from, throwError } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ProcessContentService {
|
||||
|
||||
static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error';
|
||||
static GENERIC_ERROR_MESSAGE: string = 'Server error';
|
||||
|
||||
_contentApi: ActivitiContentApi;
|
||||
get contentApi(): ActivitiContentApi {
|
||||
this._contentApi = this._contentApi ?? new ActivitiContentApi(this.apiService.getInstance());
|
||||
return this._contentApi;
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private logService: LogService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create temporary related content from an uploaded file.
|
||||
*
|
||||
* @param file File to use for content
|
||||
* @returns The created content data
|
||||
*/
|
||||
createTemporaryRawRelatedContent(file: any): Observable<RelatedContentRepresentation> {
|
||||
return from(this.contentApi.createTemporaryRawRelatedContent(file))
|
||||
.pipe(catchError((err) => this.handleError(err)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the metadata for a related content item.
|
||||
*
|
||||
* @param contentId ID of the content item
|
||||
* @returns Metadata for the content
|
||||
*/
|
||||
getFileContent(contentId: number): Observable<RelatedContentRepresentation> {
|
||||
return from(this.contentApi.getContent(contentId))
|
||||
.pipe(catchError((err) => this.handleError(err)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets raw binary content data for a related content file.
|
||||
*
|
||||
* @param contentId ID of the related content
|
||||
* @returns Binary data of the related content
|
||||
*/
|
||||
getFileRawContent(contentId: number): Observable<Blob> {
|
||||
return from(this.contentApi.getRawContent(contentId))
|
||||
.pipe(catchError((err) => this.handleError(err)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the preview for a related content file.
|
||||
*
|
||||
* @param contentId ID of the related content
|
||||
* @returns Binary data of the content preview
|
||||
*/
|
||||
getContentPreview(contentId: number): Observable<Blob> {
|
||||
return new Observable((observer) => {
|
||||
this.contentApi.getRawContent(contentId).then(
|
||||
(result) => {
|
||||
observer.next(result);
|
||||
observer.complete();
|
||||
},
|
||||
() => {
|
||||
this.contentApi.getRawContent(contentId).then(
|
||||
(data) => {
|
||||
observer.next(data);
|
||||
observer.complete();
|
||||
},
|
||||
(err) => {
|
||||
observer.error(err);
|
||||
observer.complete();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a URL for direct access to a related content file.
|
||||
*
|
||||
* @param contentId ID of the related content
|
||||
* @returns URL to access the content
|
||||
*/
|
||||
getFileRawContentUrl(contentId: number): string {
|
||||
return this.contentApi.getRawContentUrl(contentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the thumbnail for a related content file.
|
||||
*
|
||||
* @param contentId ID of the related content
|
||||
* @returns Binary data of the thumbnail image
|
||||
*/
|
||||
getContentThumbnail(contentId: number): Observable<Blob> {
|
||||
return from(this.contentApi.getRawContent(contentId, 'thumbnail'))
|
||||
.pipe(catchError((err) => this.handleError(err)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets related content items for a task instance.
|
||||
*
|
||||
* @param taskId ID of the target task
|
||||
* @param opts Options supported by JS-API
|
||||
* @returns Metadata for the content
|
||||
*/
|
||||
getTaskRelatedContent(taskId: string, opts?: any): Observable<any> {
|
||||
return from(this.contentApi.getRelatedContentForTask(taskId, opts))
|
||||
.pipe(catchError((err) => this.handleError(err)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets related content items for a process instance.
|
||||
*
|
||||
* @param processId ID of the target process
|
||||
* @param opts Options supported by JS-API
|
||||
* @returns Metadata for the content
|
||||
*/
|
||||
getProcessRelatedContent(processId: string, opts?: any): Observable<any> {
|
||||
return from(this.contentApi.getRelatedContentForProcessInstance(processId, opts))
|
||||
.pipe(catchError((err) => this.handleError(err)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes related content.
|
||||
*
|
||||
* @param contentId Identifier of the content to delete
|
||||
* @returns Null response that notifies when the deletion is complete
|
||||
*/
|
||||
deleteRelatedContent(contentId: number): Observable<any> {
|
||||
return from(this.contentApi.deleteContent(contentId))
|
||||
.pipe(catchError((err) => this.handleError(err)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates an uploaded file with a process instance.
|
||||
*
|
||||
* @param processInstanceId ID of the target process instance
|
||||
* @param content File to associate
|
||||
* @param opts Options supported by JS-API
|
||||
* @returns Details of created content
|
||||
*/
|
||||
createProcessRelatedContent(processInstanceId: string, content: any, opts?: any): Observable<any> {
|
||||
return from(this.contentApi.createRelatedContentOnProcessInstance(processInstanceId, content, opts))
|
||||
.pipe(catchError((err) => this.handleError(err)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates an uploaded file with a task instance.
|
||||
*
|
||||
* @param taskId ID of the target task
|
||||
* @param file File to associate
|
||||
* @param opts Options supported by JS-API
|
||||
* @returns Details of created content
|
||||
*/
|
||||
createTaskRelatedContent(taskId: string, file: any, opts?: any) {
|
||||
return from(this.contentApi.createRelatedContentOnTask(taskId, file, opts))
|
||||
.pipe(catchError((err) => this.handleError(err)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON representation of data.
|
||||
*
|
||||
* @param res Object representing data
|
||||
* @returns JSON object
|
||||
*/
|
||||
toJson(res: any) {
|
||||
if (res) {
|
||||
return res || {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON array representation of data.
|
||||
*
|
||||
* @param res Object representing data
|
||||
* @returns JSON array object
|
||||
*/
|
||||
toJsonArray(res: any) {
|
||||
if (res) {
|
||||
return res.data || [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports an error message.
|
||||
*
|
||||
* @param error Data object with optional `message` and `status` fields for the error
|
||||
* @returns Callback when an error occurs
|
||||
*/
|
||||
handleError(error: any): Observable<any> {
|
||||
let errMsg = ProcessContentService.UNKNOWN_ERROR_MESSAGE;
|
||||
if (error) {
|
||||
errMsg = (error.message) ? error.message :
|
||||
error.status ? `${error.status} - ${error.statusText}` : ProcessContentService.GENERIC_ERROR_MESSAGE;
|
||||
}
|
||||
this.logService.error(errMsg);
|
||||
return throwError(errMsg);
|
||||
}
|
||||
|
||||
}
|
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { fakeAsync, TestBed } from '@angular/core/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
ContainerModel,
|
||||
FormFieldModel,
|
||||
@@ -24,12 +24,11 @@ import {
|
||||
TabModel,
|
||||
FormOutcomeModel
|
||||
} from '../components/widgets/core';
|
||||
import { TaskProcessVariableModel } from '../models/task-process-variable.model';
|
||||
import { WidgetVisibilityModel, WidgetTypeEnum } from '../models/widget-visibility.model';
|
||||
import { WidgetVisibilityService } from './widget-visibility.service';
|
||||
import { setupTestBed } from '../../testing/setup-test-bed';
|
||||
import {
|
||||
fakeFormJson, fakeTaskProcessVariableModels,
|
||||
fakeFormJson,
|
||||
formTest, formValues, complexVisibilityJsonVisible,
|
||||
nextConditionForm, complexVisibilityJsonNotVisible,
|
||||
headerVisibilityCond
|
||||
@@ -156,160 +155,6 @@ describe('WidgetVisibilityCloudService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('should retrieve the process variables', () => {
|
||||
const fakeFormWithField = new FormModel(fakeFormJson);
|
||||
let visibilityObjTest: WidgetVisibilityModel;
|
||||
const chainedVisibilityObj = new WidgetVisibilityModel({});
|
||||
|
||||
beforeEach(() => {
|
||||
visibilityObjTest = new WidgetVisibilityModel({});
|
||||
});
|
||||
|
||||
it('should return the process variables for task', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
(res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.length).toEqual(3);
|
||||
expect(res[0].id).toEqual('TEST_VAR_1');
|
||||
expect(res[0].type).toEqual('string');
|
||||
expect(res[0].value).toEqual('test_value_1');
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeTaskProcessVariableModels
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to retrieve the value of a process variable', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
(res: TaskProcessVariableModel[]) => {
|
||||
expect(res).toBeDefined();
|
||||
const varValue = service.getVariableValue(formTest, 'TEST_VAR_1', res);
|
||||
expect(varValue).not.toBeUndefined();
|
||||
expect(varValue).toBe('test_value_1');
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeTaskProcessVariableModels
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined if the variable does not exist', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
(res: TaskProcessVariableModel[]) => {
|
||||
const varValue = service.getVariableValue(formTest, 'TEST_MYSTERY_VAR', res);
|
||||
expect(varValue).toBeUndefined();
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeTaskProcessVariableModels
|
||||
});
|
||||
});
|
||||
|
||||
it('should retrieve the value for the right field when it is a process variable', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
() => {
|
||||
visibilityObjTest.rightValue = 'test_value_2';
|
||||
spyOn(service, 'isFormFieldValid').and.returnValue(true);
|
||||
const rightValue = service.getRightValue(formTest, visibilityObjTest);
|
||||
|
||||
expect(rightValue).not.toBeNull();
|
||||
expect(rightValue).toBe('test_value_2');
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeTaskProcessVariableModels
|
||||
});
|
||||
});
|
||||
|
||||
it('should retrieve the value for the left field when it is a process variable', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
() => {
|
||||
visibilityObjTest.leftValue = 'TEST_VAR_2';
|
||||
visibilityObjTest.leftType = WidgetTypeEnum.field;
|
||||
const leftValue = service.getLeftValue(formTest, visibilityObjTest);
|
||||
|
||||
expect(leftValue).not.toBeNull();
|
||||
expect(leftValue).toBe('test_value_2');
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeTaskProcessVariableModels
|
||||
});
|
||||
});
|
||||
|
||||
it('should evaluate the visibility for the field between form value and process var', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
() => {
|
||||
visibilityObjTest.leftType = 'LEFT_FORM_FIELD_ID';
|
||||
visibilityObjTest.operator = '!=';
|
||||
visibilityObjTest.rightValue = 'TEST_VAR_2';
|
||||
const isVisible = service.isFieldVisible(fakeFormWithField, visibilityObjTest);
|
||||
|
||||
expect(isVisible).toBeTruthy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeTaskProcessVariableModels
|
||||
});
|
||||
});
|
||||
|
||||
it('should evaluate visibility with multiple conditions', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
() => {
|
||||
visibilityObjTest.leftType = 'field';
|
||||
visibilityObjTest.leftValue = 'TEST_VAR_2';
|
||||
visibilityObjTest.operator = '!=';
|
||||
visibilityObjTest.rightValue = 'TEST_VAR_2';
|
||||
visibilityObjTest.nextConditionOperator = 'and';
|
||||
chainedVisibilityObj.leftType = 'field';
|
||||
chainedVisibilityObj.leftValue = 'TEST_VAR_2';
|
||||
chainedVisibilityObj.operator = '!empty';
|
||||
visibilityObjTest.nextCondition = chainedVisibilityObj;
|
||||
|
||||
const isVisible = service.isFieldVisible(fakeFormWithField, visibilityObjTest);
|
||||
|
||||
expect(isVisible).toBeTruthy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeTaskProcessVariableModels
|
||||
});
|
||||
});
|
||||
|
||||
it('should catch error on 403 response', fakeAsync(() => {
|
||||
service.getTaskProcessVariable('9999').subscribe(() => {
|
||||
}, (errorMessage) => {
|
||||
expect(errorMessage).toEqual('Error while performing a call - Server error');
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 403
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('should return the value of the field', () => {
|
||||
let visibilityObjTest: WidgetVisibilityModel;
|
||||
let fakeFormWithField = new FormModel(fakeFormJson);
|
||||
@@ -644,143 +489,6 @@ describe('WidgetVisibilityCloudService', () => {
|
||||
expect(fakeFormWithField.outcomes[outcomeIndex].isVisible).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should use the form value to evaluate the visibility condition if the form value is defined', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
(res: TaskProcessVariableModel[]) => {
|
||||
expect(res).toBeDefined();
|
||||
const varValue = service.getVariableValue(formTest, 'FIELD_FORM_EMPTY', res);
|
||||
expect(varValue).not.toBeUndefined();
|
||||
expect(varValue).toBe('PROCESS_RIGHT_FORM_FIELD_VALUE');
|
||||
|
||||
visibilityObjTest.leftType = WidgetTypeEnum.field;
|
||||
visibilityObjTest.leftValue = 'FIELD_FORM_EMPTY';
|
||||
visibilityObjTest.operator = '==';
|
||||
visibilityObjTest.rightValue = 'RIGHT_FORM_FIELD_VALUE';
|
||||
|
||||
const myForm = new FormModel({
|
||||
id: '9999',
|
||||
name: 'FORM_PROCESS_VARIABLE_VISIBILITY',
|
||||
processDefinitionId: 'PROCESS_TEST:9:9999',
|
||||
processDefinitionName: 'PROCESS_TEST',
|
||||
processDefinitionKey: 'PROCESS_TEST',
|
||||
taskId: '999',
|
||||
taskName: 'TEST',
|
||||
fields: [
|
||||
{
|
||||
fieldType: 'ContainerRepresentation',
|
||||
id: '000000000000000000',
|
||||
name: 'Label',
|
||||
type: 'container',
|
||||
value: null,
|
||||
numberOfColumns: 2,
|
||||
fields: {
|
||||
1: [
|
||||
{
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'FIELD_FORM_EMPTY',
|
||||
name: 'FIELD_FORM_EMPTY',
|
||||
type: 'text',
|
||||
value: 'RIGHT_FORM_FIELD_VALUE',
|
||||
visibilityCondition: null,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'FIELD_FORM_WITH_CONDITION',
|
||||
name: 'FIELD_FORM_WITH_CONDITION',
|
||||
type: 'text',
|
||||
value: 'field_form_with_condition_value',
|
||||
visibilityCondition: visibilityObjTest,
|
||||
isVisible: false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
service.refreshVisibility(myForm);
|
||||
|
||||
const fieldWithVisibilityAttached = myForm.getFieldById('FIELD_FORM_WITH_CONDITION');
|
||||
expect(fieldWithVisibilityAttached.isVisible).toBeTruthy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: [{ id: 'FIELD_FORM_EMPTY', type: 'string', value: 'PROCESS_RIGHT_FORM_FIELD_VALUE' }]
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the process value to evaluate the True visibility condition if the form value is empty', (done) => {
|
||||
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
(res: TaskProcessVariableModel[]) => {
|
||||
expect(res).toBeDefined();
|
||||
|
||||
visibilityObjTest.leftType = WidgetTypeEnum.field;
|
||||
visibilityObjTest.leftValue = 'FIELD_FORM_EMPTY';
|
||||
visibilityObjTest.operator = '==';
|
||||
visibilityObjTest.rightType = WidgetTypeEnum.value;
|
||||
visibilityObjTest.rightValue = 'PROCESS_RIGHT_FORM_FIELD_VALUE';
|
||||
|
||||
const myForm = new FormModel({
|
||||
id: '9999',
|
||||
name: 'FORM_PROCESS_VARIABLE_VISIBILITY',
|
||||
processDefinitionId: 'PROCESS_TEST:9:9999',
|
||||
processDefinitionName: 'PROCESS_TEST',
|
||||
processDefinitionKey: 'PROCESS_TEST',
|
||||
taskId: '999',
|
||||
taskName: 'TEST',
|
||||
fields: [
|
||||
{
|
||||
fieldType: 'ContainerRepresentation',
|
||||
id: '000000000000000000',
|
||||
name: 'Label',
|
||||
type: 'container',
|
||||
value: null,
|
||||
numberOfColumns: 2,
|
||||
fields: {
|
||||
1: [
|
||||
{
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'FIELD_FORM_EMPTY',
|
||||
name: 'FIELD_FORM_EMPTY',
|
||||
type: 'text',
|
||||
value: '',
|
||||
visibilityCondition: null,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'FIELD_FORM_WITH_CONDITION',
|
||||
name: 'FIELD_FORM_WITH_CONDITION',
|
||||
type: 'text',
|
||||
value: 'field_form_with_condition_value',
|
||||
visibilityCondition: visibilityObjTest,
|
||||
isVisible: false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
service.refreshVisibility(myForm);
|
||||
|
||||
const fieldWithVisibilityAttached = myForm.getFieldById('FIELD_FORM_WITH_CONDITION');
|
||||
expect(fieldWithVisibilityAttached.isVisible).toBeTruthy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: [{ id: 'FIELD_FORM_EMPTY', type: 'string', value: 'PROCESS_RIGHT_FORM_FIELD_VALUE' }]
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the process variables when they are passed to check the visibility', () => {
|
||||
|
||||
visibilityObjTest.leftType = WidgetTypeEnum.field;
|
||||
@@ -837,72 +545,6 @@ describe('WidgetVisibilityCloudService', () => {
|
||||
expect(fieldWithVisibilityAttached.isVisible).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should use the process value to evaluate the False visibility condition if the form value is empty', (done) => {
|
||||
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
(res: TaskProcessVariableModel[]) => {
|
||||
expect(res).toBeDefined();
|
||||
|
||||
visibilityObjTest.leftType = 'FIELD_FORM_EMPTY';
|
||||
visibilityObjTest.operator = '==';
|
||||
visibilityObjTest.rightValue = 'RIGHT_FORM_FIELD_VALUE';
|
||||
|
||||
const myForm = new FormModel({
|
||||
id: '9999',
|
||||
name: 'FORM_PROCESS_VARIABLE_VISIBILITY',
|
||||
processDefinitionId: 'PROCESS_TEST:9:9999',
|
||||
processDefinitionName: 'PROCESS_TEST',
|
||||
processDefinitionKey: 'PROCESS_TEST',
|
||||
taskId: '999',
|
||||
taskName: 'TEST',
|
||||
fields: [
|
||||
{
|
||||
fieldType: 'ContainerRepresentation',
|
||||
id: '000000000000000000',
|
||||
name: 'Label',
|
||||
type: 'container',
|
||||
value: null,
|
||||
numberOfColumns: 2,
|
||||
fields: {
|
||||
1: [
|
||||
{
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'FIELD_FORM_EMPTY',
|
||||
name: 'FIELD_FORM_EMPTY',
|
||||
type: 'text',
|
||||
value: '',
|
||||
visibilityCondition: null,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'FIELD_FORM_WITH_CONDITION',
|
||||
name: 'FIELD_FORM_WITH_CONDITION',
|
||||
type: 'text',
|
||||
value: 'field_form_with_condition_value',
|
||||
visibilityCondition: visibilityObjTest,
|
||||
isVisible: true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
service.refreshVisibility(myForm);
|
||||
|
||||
const fieldWithVisibilityAttached = myForm.getFieldById('FIELD_FORM_WITH_CONDITION');
|
||||
expect(fieldWithVisibilityAttached.isVisible).toBeFalsy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: [{ id: 'FIELD_FORM_EMPTY', type: 'string', value: 'PROCESS_RIGHT_FORM_FIELD_VALUE' }]
|
||||
});
|
||||
});
|
||||
|
||||
it('should refresh the visibility for single tab', () => {
|
||||
visibilityObjTest.leftType = WidgetTypeEnum.field;
|
||||
visibilityObjTest.leftValue = 'FIELD_TEST';
|
||||
|
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { fakeAsync, TestBed } from '@angular/core/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
ContainerModel,
|
||||
FormFieldModel,
|
||||
@@ -23,12 +23,10 @@ import {
|
||||
FormModel,
|
||||
TabModel
|
||||
} from '../components/widgets/core';
|
||||
import { TaskProcessVariableModel } from '../models/task-process-variable.model';
|
||||
import { WidgetVisibilityModel } from '../models/widget-visibility.model';
|
||||
import { WidgetVisibilityService } from './widget-visibility.service';
|
||||
import { setupTestBed } from '../../testing/setup-test-bed';
|
||||
import {
|
||||
fakeTaskProcessVariableModels,
|
||||
fakeFormJson, formTest,
|
||||
formValues, complexVisibilityJsonVisible,
|
||||
complexVisibilityJsonNotVisible, tabVisibilityJsonMock,
|
||||
@@ -39,8 +37,6 @@ import {
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('WidgetVisibilityService', () => {
|
||||
|
||||
let service: WidgetVisibilityService;
|
||||
@@ -56,11 +52,6 @@ describe('WidgetVisibilityService', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(WidgetVisibilityService);
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
describe('should be able to evaluate next condition operations', () => {
|
||||
@@ -161,158 +152,6 @@ describe('WidgetVisibilityService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('should retrieve the process variables', () => {
|
||||
let fakeFormWithField: FormModel;
|
||||
let visibilityObjTest: WidgetVisibilityModel;
|
||||
const chainedVisibilityObj = new WidgetVisibilityModel({});
|
||||
|
||||
beforeEach(() => {
|
||||
fakeFormWithField = new FormModel(fakeFormJson);
|
||||
visibilityObjTest = new WidgetVisibilityModel({});
|
||||
fakeFormWithField = new FormModel(fakeFormJson);
|
||||
});
|
||||
|
||||
it('should return the process variables for task', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
(res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.length).toEqual(3);
|
||||
expect(res[0].id).toEqual('TEST_VAR_1');
|
||||
expect(res[0].type).toEqual('string');
|
||||
expect(res[0].value).toEqual('test_value_1');
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeTaskProcessVariableModels
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to retrieve the value of a process variable', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
(res: TaskProcessVariableModel[]) => {
|
||||
expect(res).toBeDefined();
|
||||
const varValue = service.getVariableValue(formTest, 'TEST_VAR_1', res);
|
||||
expect(varValue).not.toBeUndefined();
|
||||
expect(varValue).toBe('test_value_1');
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeTaskProcessVariableModels
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined if the variable does not exist', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
(res: TaskProcessVariableModel[]) => {
|
||||
const varValue = service.getVariableValue(formTest, 'TEST_MYSTERY_VAR', res);
|
||||
expect(varValue).toBeUndefined();
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeTaskProcessVariableModels
|
||||
});
|
||||
});
|
||||
|
||||
it('should retrieve the value for the right field when it is a process variable', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
() => {
|
||||
visibilityObjTest.rightRestResponseId = 'TEST_VAR_2';
|
||||
const rightValue = service.getRightValue(formTest, visibilityObjTest);
|
||||
|
||||
expect(rightValue).not.toBeNull();
|
||||
expect(rightValue).toBe('test_value_2');
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeTaskProcessVariableModels
|
||||
});
|
||||
});
|
||||
|
||||
it('should retrieve the value for the left field when it is a process variable', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
() => {
|
||||
visibilityObjTest.leftRestResponseId = 'TEST_VAR_2';
|
||||
const leftValue = service.getLeftValue(formTest, visibilityObjTest);
|
||||
|
||||
expect(leftValue).not.toBeNull();
|
||||
expect(leftValue).toBe('test_value_2');
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeTaskProcessVariableModels
|
||||
});
|
||||
});
|
||||
|
||||
it('should evaluate the visibility for the field between form value and process var', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
() => {
|
||||
visibilityObjTest.leftFormFieldId = 'LEFT_FORM_FIELD_ID';
|
||||
visibilityObjTest.operator = '!=';
|
||||
visibilityObjTest.rightRestResponseId = 'TEST_VAR_2';
|
||||
const isVisible = service.isFieldVisible(fakeFormWithField, new WidgetVisibilityModel(visibilityObjTest));
|
||||
|
||||
expect(isVisible).toBeTruthy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeTaskProcessVariableModels
|
||||
});
|
||||
});
|
||||
|
||||
it('should evaluate visibility with multiple conditions', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
() => {
|
||||
visibilityObjTest.leftFormFieldId = 'LEFT_FORM_FIELD_ID';
|
||||
visibilityObjTest.operator = '!=';
|
||||
visibilityObjTest.rightRestResponseId = 'TEST_VAR_2';
|
||||
visibilityObjTest.nextConditionOperator = 'and';
|
||||
chainedVisibilityObj.leftRestResponseId = 'TEST_VAR_2';
|
||||
chainedVisibilityObj.operator = '!empty';
|
||||
visibilityObjTest.nextCondition = chainedVisibilityObj;
|
||||
|
||||
const isVisible = service.isFieldVisible(fakeFormWithField, visibilityObjTest);
|
||||
|
||||
expect(isVisible).toBeTruthy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeTaskProcessVariableModels
|
||||
});
|
||||
});
|
||||
|
||||
it('should catch error on 403 response', fakeAsync(() => {
|
||||
service.getTaskProcessVariable('9999').subscribe(() => {
|
||||
}, (errorMessage) => {
|
||||
expect(errorMessage).toEqual('Error while performing a call - Server error');
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 403
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('should return the value of the field', () => {
|
||||
let visibilityObjTest: WidgetVisibilityModel;
|
||||
let fakeFormWithField: FormModel;
|
||||
@@ -650,206 +489,6 @@ describe('WidgetVisibilityService', () => {
|
||||
expect(fakeFormWithField.tabs[0].isVisible).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should use the form value to evaluate the visibility condition if the form value is defined', (done) => {
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
(res: TaskProcessVariableModel[]) => {
|
||||
expect(res).toBeDefined();
|
||||
const varValue = service.getVariableValue(formTest, 'FIELD_FORM_EMPTY', res);
|
||||
expect(varValue).not.toBeUndefined();
|
||||
expect(varValue).toBe('PROCESS_RIGHT_FORM_FIELD_VALUE');
|
||||
|
||||
visibilityObjTest.leftFormFieldId = 'FIELD_FORM_EMPTY';
|
||||
visibilityObjTest.operator = '==';
|
||||
visibilityObjTest.rightValue = 'RIGHT_FORM_FIELD_VALUE';
|
||||
|
||||
const myForm = new FormModel({
|
||||
id: '9999',
|
||||
name: 'FORM_PROCESS_VARIABLE_VISIBILITY',
|
||||
processDefinitionId: 'PROCESS_TEST:9:9999',
|
||||
processDefinitionName: 'PROCESS_TEST',
|
||||
processDefinitionKey: 'PROCESS_TEST',
|
||||
taskId: '999',
|
||||
taskName: 'TEST',
|
||||
fields: [
|
||||
{
|
||||
fieldType: 'ContainerRepresentation',
|
||||
id: '000000000000000000',
|
||||
name: 'Label',
|
||||
type: 'container',
|
||||
value: null,
|
||||
numberOfColumns: 2,
|
||||
fields: {
|
||||
1: [
|
||||
{
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'FIELD_FORM_EMPTY',
|
||||
name: 'FIELD_FORM_EMPTY',
|
||||
type: 'text',
|
||||
value: 'RIGHT_FORM_FIELD_VALUE',
|
||||
visibilityCondition: null,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'FIELD_FORM_WITH_CONDITION',
|
||||
name: 'FIELD_FORM_WITH_CONDITION',
|
||||
type: 'text',
|
||||
value: 'field_form_with_condition_value',
|
||||
visibilityCondition: visibilityObjTest,
|
||||
isVisible: false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
service.refreshVisibility(myForm);
|
||||
|
||||
const fieldWithVisibilityAttached = myForm.getFieldById('FIELD_FORM_WITH_CONDITION');
|
||||
expect(fieldWithVisibilityAttached.isVisible).toBeTruthy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: [{ id: 'FIELD_FORM_EMPTY', type: 'string', value: 'PROCESS_RIGHT_FORM_FIELD_VALUE' }]
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the process value to evaluate the True visibility condition if the form value is empty', (done) => {
|
||||
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
(res: TaskProcessVariableModel[]) => {
|
||||
expect(res).toBeDefined();
|
||||
|
||||
visibilityObjTest.leftFormFieldId = 'FIELD_FORM_EMPTY';
|
||||
visibilityObjTest.operator = '==';
|
||||
visibilityObjTest.rightValue = 'PROCESS_RIGHT_FORM_FIELD_VALUE';
|
||||
|
||||
const myForm = new FormModel({
|
||||
id: '9999',
|
||||
name: 'FORM_PROCESS_VARIABLE_VISIBILITY',
|
||||
processDefinitionId: 'PROCESS_TEST:9:9999',
|
||||
processDefinitionName: 'PROCESS_TEST',
|
||||
processDefinitionKey: 'PROCESS_TEST',
|
||||
taskId: '999',
|
||||
taskName: 'TEST',
|
||||
fields: [
|
||||
{
|
||||
fieldType: 'ContainerRepresentation',
|
||||
id: '000000000000000000',
|
||||
name: 'Label',
|
||||
type: 'container',
|
||||
value: null,
|
||||
numberOfColumns: 2,
|
||||
fields: {
|
||||
1: [
|
||||
{
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'FIELD_FORM_EMPTY',
|
||||
name: 'FIELD_FORM_EMPTY',
|
||||
type: 'text',
|
||||
value: '',
|
||||
visibilityCondition: null,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'FIELD_FORM_WITH_CONDITION',
|
||||
name: 'FIELD_FORM_WITH_CONDITION',
|
||||
type: 'text',
|
||||
value: 'field_form_with_condition_value',
|
||||
visibilityCondition: visibilityObjTest,
|
||||
isVisible: false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
service.refreshVisibility(myForm);
|
||||
|
||||
const fieldWithVisibilityAttached = myForm.getFieldById('FIELD_FORM_WITH_CONDITION');
|
||||
expect(fieldWithVisibilityAttached.isVisible).toBeTruthy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: [{ id: 'FIELD_FORM_EMPTY', type: 'string', value: 'PROCESS_RIGHT_FORM_FIELD_VALUE' }]
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the process value to evaluate the False visibility condition if the form value is empty', (done) => {
|
||||
|
||||
service.getTaskProcessVariable('9999').subscribe(
|
||||
(res: TaskProcessVariableModel[]) => {
|
||||
expect(res).toBeDefined();
|
||||
|
||||
visibilityObjTest.leftFormFieldId = 'FIELD_FORM_EMPTY';
|
||||
visibilityObjTest.operator = '==';
|
||||
visibilityObjTest.rightValue = 'RIGHT_FORM_FIELD_VALUE';
|
||||
|
||||
const myForm = new FormModel({
|
||||
id: '9999',
|
||||
name: 'FORM_PROCESS_VARIABLE_VISIBILITY',
|
||||
processDefinitionId: 'PROCESS_TEST:9:9999',
|
||||
processDefinitionName: 'PROCESS_TEST',
|
||||
processDefinitionKey: 'PROCESS_TEST',
|
||||
taskId: '999',
|
||||
taskName: 'TEST',
|
||||
fields: [
|
||||
{
|
||||
fieldType: 'ContainerRepresentation',
|
||||
id: '000000000000000000',
|
||||
name: 'Label',
|
||||
type: 'container',
|
||||
value: null,
|
||||
numberOfColumns: 2,
|
||||
fields: {
|
||||
1: [
|
||||
{
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'FIELD_FORM_EMPTY',
|
||||
name: 'FIELD_FORM_EMPTY',
|
||||
type: 'text',
|
||||
value: '',
|
||||
visibilityCondition: null,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'FIELD_FORM_WITH_CONDITION',
|
||||
name: 'FIELD_FORM_WITH_CONDITION',
|
||||
type: 'text',
|
||||
value: 'field_form_with_condition_value',
|
||||
visibilityCondition: visibilityObjTest,
|
||||
isVisible: true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
service.refreshVisibility(myForm);
|
||||
|
||||
const fieldWithVisibilityAttached = myForm.getFieldById('FIELD_FORM_WITH_CONDITION');
|
||||
expect(fieldWithVisibilityAttached.isVisible).toBeFalsy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: [{ id: 'FIELD_FORM_EMPTY', type: 'string', value: 'PROCESS_RIGHT_FORM_FIELD_VALUE' }]
|
||||
});
|
||||
});
|
||||
|
||||
it('should refresh the visibility for single tab', () => {
|
||||
visibilityObjTest.leftFormFieldId = 'FIELD_TEST';
|
||||
visibilityObjTest.operator = '!=';
|
||||
|
@@ -15,11 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||
import { LogService } from '../../services/log.service';
|
||||
import { Injectable } from '@angular/core';
|
||||
import moment from 'moment';
|
||||
import { Observable, from, throwError } from 'rxjs';
|
||||
import {
|
||||
FormFieldModel,
|
||||
FormModel,
|
||||
@@ -29,25 +27,16 @@ import {
|
||||
} from '../components/widgets/core';
|
||||
import { TaskProcessVariableModel } from '../models/task-process-variable.model';
|
||||
import { WidgetVisibilityModel, WidgetTypeEnum } from '../models/widget-visibility.model';
|
||||
import { map, catchError } from 'rxjs/operators';
|
||||
import { TaskFormsApi } from '@alfresco/js-api';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class WidgetVisibilityService {
|
||||
|
||||
_taskFormsApi: TaskFormsApi;
|
||||
get taskFormsApi(): TaskFormsApi {
|
||||
this._taskFormsApi = this._taskFormsApi ?? new TaskFormsApi(this.apiService.getInstance());
|
||||
return this._taskFormsApi;
|
||||
}
|
||||
|
||||
private processVarList: TaskProcessVariableModel[];
|
||||
private form: FormModel;
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private logService: LogService) {
|
||||
constructor(private logService: LogService) {
|
||||
}
|
||||
|
||||
public refreshVisibility(form: FormModel, processVarList?: TaskProcessVariableModel[]) {
|
||||
@@ -68,15 +57,15 @@ export class WidgetVisibilityService {
|
||||
}
|
||||
}
|
||||
|
||||
refreshEntityVisibility(element: FormFieldModel | TabModel) {
|
||||
public refreshEntityVisibility(element: FormFieldModel | TabModel) {
|
||||
element.isVisible = this.isParentTabVisible(this.form, element) && this.evaluateVisibility(element.form, element.visibilityCondition);
|
||||
}
|
||||
|
||||
refreshOutcomeVisibility(element: FormOutcomeModel) {
|
||||
private refreshOutcomeVisibility(element: FormOutcomeModel) {
|
||||
element.isVisible = this.evaluateVisibility(element.form, element.visibilityCondition);
|
||||
}
|
||||
|
||||
evaluateVisibility(form: FormModel, visibilityObj: WidgetVisibilityModel): boolean {
|
||||
public evaluateVisibility(form: FormModel, visibilityObj: WidgetVisibilityModel): boolean {
|
||||
const isLeftFieldPresent = visibilityObj && (visibilityObj.leftType || visibilityObj.leftValue);
|
||||
if (!isLeftFieldPresent || isLeftFieldPresent === 'null') {
|
||||
return true;
|
||||
@@ -85,12 +74,12 @@ export class WidgetVisibilityService {
|
||||
}
|
||||
}
|
||||
|
||||
isFieldVisible(form: FormModel, visibilityObj: WidgetVisibilityModel, accumulator: any[] = [], result: boolean = false): boolean {
|
||||
public isFieldVisible(form: FormModel, visibilityObj: WidgetVisibilityModel, accumulator: any[] = [], result: boolean = false): boolean {
|
||||
const leftValue = this.getLeftValue(form, visibilityObj);
|
||||
const rightValue = this.getRightValue(form, visibilityObj);
|
||||
const actualResult = this.evaluateCondition(leftValue, rightValue, visibilityObj.operator);
|
||||
|
||||
accumulator.push({ value: actualResult, operator: visibilityObj.nextConditionOperator });
|
||||
accumulator.push({value: actualResult, operator: visibilityObj.nextConditionOperator});
|
||||
|
||||
if (this.isValidCondition(visibilityObj.nextCondition)) {
|
||||
result = this.isFieldVisible(form, visibilityObj.nextCondition, accumulator);
|
||||
@@ -124,7 +113,7 @@ export class WidgetVisibilityService {
|
||||
}
|
||||
}
|
||||
|
||||
getLeftValue(form: FormModel, visibilityObj: WidgetVisibilityModel): string {
|
||||
public getLeftValue(form: FormModel, visibilityObj: WidgetVisibilityModel): string {
|
||||
let leftValue = '';
|
||||
if (visibilityObj.leftType && visibilityObj.leftType === WidgetTypeEnum.variable) {
|
||||
leftValue = this.getVariableValue(form, visibilityObj.leftValue, this.processVarList);
|
||||
@@ -138,7 +127,7 @@ export class WidgetVisibilityService {
|
||||
return leftValue;
|
||||
}
|
||||
|
||||
getRightValue(form: FormModel, visibilityObj: WidgetVisibilityModel): string {
|
||||
public getRightValue(form: FormModel, visibilityObj: WidgetVisibilityModel): string {
|
||||
let valueFound = '';
|
||||
if (visibilityObj.rightType === WidgetTypeEnum.variable) {
|
||||
valueFound = this.getVariableValue(form, visibilityObj.rightValue, this.processVarList);
|
||||
@@ -154,7 +143,7 @@ export class WidgetVisibilityService {
|
||||
return valueFound;
|
||||
}
|
||||
|
||||
getFormValue(form: FormModel, fieldId: string): any {
|
||||
public getFormValue(form: FormModel, fieldId: string): any {
|
||||
const formField = this.getFormFieldById(form, fieldId);
|
||||
let value;
|
||||
|
||||
@@ -168,18 +157,18 @@ export class WidgetVisibilityService {
|
||||
return value;
|
||||
}
|
||||
|
||||
isFormFieldValid(formField: FormFieldModel): boolean {
|
||||
public isFormFieldValid(formField: FormFieldModel): boolean {
|
||||
return formField && formField.isValid;
|
||||
}
|
||||
|
||||
getFieldValue(valueList: any, fieldId: string): any {
|
||||
public getFieldValue(valueList: any, fieldId: string): any {
|
||||
let labelFilterByName;
|
||||
let valueFound;
|
||||
if (fieldId && fieldId.indexOf('_LABEL') > 0) {
|
||||
labelFilterByName = fieldId.substring(0, fieldId.length - 6);
|
||||
if (valueList[labelFilterByName]) {
|
||||
if (Array.isArray(valueList[labelFilterByName])) {
|
||||
valueFound = valueList[labelFilterByName].map(({ name }) => name);
|
||||
valueFound = valueList[labelFilterByName].map(({name}) => name);
|
||||
} else {
|
||||
valueFound = valueList[labelFilterByName].name;
|
||||
}
|
||||
@@ -187,7 +176,7 @@ export class WidgetVisibilityService {
|
||||
} else if (valueList[fieldId] && valueList[fieldId].id) {
|
||||
valueFound = valueList[fieldId].id;
|
||||
} else if (valueList[fieldId] && Array.isArray(valueList[fieldId])) {
|
||||
valueFound = valueList[fieldId].map(({ id }) => id);
|
||||
valueFound = valueList[fieldId].map(({id}) => id);
|
||||
} else {
|
||||
valueFound = valueList[fieldId];
|
||||
}
|
||||
@@ -198,11 +187,11 @@ export class WidgetVisibilityService {
|
||||
return value === undefined || value === null;
|
||||
}
|
||||
|
||||
getFormFieldById(form: FormModel, fieldId: string): FormFieldModel {
|
||||
public getFormFieldById(form: FormModel, fieldId: string): FormFieldModel {
|
||||
return form.getFormFields().find((formField: FormFieldModel) => this.isSearchedField(formField, fieldId));
|
||||
}
|
||||
|
||||
searchValueInForm(formField: FormFieldModel, fieldId: string): string {
|
||||
public searchValueInForm(formField: FormFieldModel, fieldId: string): string {
|
||||
let fieldValue = '';
|
||||
|
||||
if (formField) {
|
||||
@@ -219,7 +208,7 @@ export class WidgetVisibilityService {
|
||||
return fieldValue;
|
||||
}
|
||||
|
||||
isParentTabVisible(form: FormModel, currentFormField: FormFieldModel | TabModel): boolean {
|
||||
private isParentTabVisible(form: FormModel, currentFormField: FormFieldModel | TabModel): boolean {
|
||||
const containers = this.getFormTabContainers(form);
|
||||
let isVisible: boolean = true;
|
||||
containers.map((container: ContainerModel) => {
|
||||
@@ -281,7 +270,7 @@ export class WidgetVisibilityService {
|
||||
return (field.id && fieldToFind) ? field.id.toUpperCase() === fieldToFind.toUpperCase() : false;
|
||||
}
|
||||
|
||||
getVariableValue(form: FormModel, name: string, processVarList: TaskProcessVariableModel[]): string {
|
||||
public getVariableValue(form: FormModel, name: string, processVarList: TaskProcessVariableModel[]): string {
|
||||
const processVariableValue = this.getProcessVariableValue(name, processVarList);
|
||||
const variableDefaultValue = form.getDefaultFormVariableValue(name);
|
||||
|
||||
@@ -303,7 +292,7 @@ export class WidgetVisibilityService {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
evaluateCondition(leftValue: any, rightValue: any, operator: string): boolean | undefined {
|
||||
public evaluateCondition(leftValue: any, rightValue: any, operator: string): boolean | undefined {
|
||||
switch (operator) {
|
||||
case '==':
|
||||
return leftValue + '' === rightValue + '';
|
||||
@@ -339,28 +328,7 @@ export class WidgetVisibilityService {
|
||||
this.processVarList = [];
|
||||
}
|
||||
|
||||
getTaskProcessVariable(taskId: string): Observable<TaskProcessVariableModel[]> {
|
||||
return from(this.taskFormsApi.getTaskFormVariables(taskId))
|
||||
.pipe(
|
||||
map((res) => {
|
||||
const jsonRes = this.toJson(res);
|
||||
this.processVarList = jsonRes;
|
||||
return jsonRes;
|
||||
}),
|
||||
catchError(() => this.handleError())
|
||||
);
|
||||
}
|
||||
|
||||
toJson(res: any): any {
|
||||
return res || {};
|
||||
}
|
||||
|
||||
private isValidCondition(condition: WidgetVisibilityModel): boolean {
|
||||
return !!(condition && condition.operator);
|
||||
}
|
||||
|
||||
private handleError() {
|
||||
this.logService.error('Error while performing a call');
|
||||
return throwError('Error while performing a call - Server error');
|
||||
}
|
||||
}
|
||||
|
@@ -16,16 +16,17 @@
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, from, throwError } from 'rxjs';
|
||||
import { Observable, from, throwError, of } from 'rxjs';
|
||||
import { UserProcessModel } from '../models/user-process.model';
|
||||
import { AlfrescoApiService } from './alfresco-api.service';
|
||||
import { LogService } from './log.service';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { catchError, combineAll, defaultIfEmpty, map, switchMap } from 'rxjs/operators';
|
||||
import {
|
||||
TaskActionsApi,
|
||||
UsersApi,
|
||||
ResultListDataRepresentationLightUserRepresentation
|
||||
ResultListDataRepresentationLightUserRepresentation, ActivitiGroupsApi
|
||||
} from '@alfresco/js-api';
|
||||
import { GroupModel } from '../form';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -44,10 +45,35 @@ export class PeopleProcessService {
|
||||
return this._userApi;
|
||||
}
|
||||
|
||||
_groupsApi: ActivitiGroupsApi;
|
||||
get groupsApi(): ActivitiGroupsApi {
|
||||
this._groupsApi = this._groupsApi ?? new ActivitiGroupsApi(this.apiService.getInstance());
|
||||
return this._groupsApi;
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private logService: LogService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of groups in a workflow.
|
||||
*
|
||||
* @param filter Filter to select specific groups
|
||||
* @param groupId Group ID for the search
|
||||
* @returns Array of groups
|
||||
*/
|
||||
getWorkflowGroups(filter: string, groupId?: string): Observable<GroupModel[]> {
|
||||
const option: any = { filter };
|
||||
if (groupId) {
|
||||
option.groupId = groupId;
|
||||
}
|
||||
return from(this.groupsApi.getGroups(option))
|
||||
.pipe(
|
||||
map((response: any) => response.data || []),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information about users across all tasks.
|
||||
*
|
||||
@@ -55,15 +81,21 @@ export class PeopleProcessService {
|
||||
* @param searchWord Filter text to search for
|
||||
* @returns Array of user information objects
|
||||
*/
|
||||
getWorkflowUsers(taskId?: string, searchWord?: string): Observable<UserProcessModel[]> {
|
||||
const option = { excludeTaskId: taskId, filter: searchWord };
|
||||
getWorkflowUsers(taskId?: string, searchWord?: string, groupId?: string): Observable<UserProcessModel[]> {
|
||||
const option = { excludeTaskId: taskId, filter: searchWord, groupId };
|
||||
|
||||
return from(this.getWorkflowUserApi(option))
|
||||
.pipe(
|
||||
map((response: any) => response.data || []),
|
||||
switchMap(response => response.data as UserProcessModel[] || []),
|
||||
map((user) => {
|
||||
user.userImage = this.getUserProfileImageApi(user.id.toString());
|
||||
return of(user);
|
||||
}),
|
||||
combineAll(),
|
||||
defaultIfEmpty([]),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the profile picture URL for the specified user.
|
||||
*
|
||||
|
Reference in New Issue
Block a user