mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
19
lib/core/form/components/widgets/amount/amount.widget.html
Normal file
19
lib/core/form/components/widgets/amount/amount.widget.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<div class="adf-amount-widget__container adf-amount-widget {{field.className}}" [class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly">
|
||||
<mat-form-field floatPlaceholder="never" class="adf-amount-widget__input">
|
||||
<label class="adf-label" [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label>
|
||||
<span matPrefix class="adf-amount-widget__prefix-spacing"> {{currency }}</span>
|
||||
<input matInput
|
||||
class="adf-amount-widget"
|
||||
type="text"
|
||||
[id]="field.id"
|
||||
[required]="isRequired()"
|
||||
[value]="field.value"
|
||||
[(ngModel)]="field.value"
|
||||
(ngModelChange)="checkVisibility(field)"
|
||||
[disabled]="field.readOnly"
|
||||
placeholder="{{field.placeholder}}">
|
||||
</mat-form-field>
|
||||
<error-widget [error]="field.validationSummary" ></error-widget>
|
||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||
</div>
|
||||
|
45
lib/core/form/components/widgets/amount/amount.widget.scss
Normal file
45
lib/core/form/components/widgets/amount/amount.widget.scss
Normal file
@@ -0,0 +1,45 @@
|
||||
@import '../form';
|
||||
|
||||
.adf {
|
||||
&-amount-widget {
|
||||
width: 100%;
|
||||
vertical-align: baseline !important;
|
||||
|
||||
.mat-input-element {
|
||||
margin-left: 11px;
|
||||
}
|
||||
|
||||
.mat-input-prefix {
|
||||
position: absolute;
|
||||
margin-top: 42px;
|
||||
}
|
||||
|
||||
.mat-input-placeholder {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&-amount-widget__container {
|
||||
max-width: 100%;
|
||||
|
||||
.mat-input-placeholder-wrapper {
|
||||
top: -6px !important;
|
||||
}
|
||||
|
||||
.mat-input-placeholder-wrapper {
|
||||
top: 0 !important;
|
||||
left: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
&-amount-widget__input .mat-focused {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&-amount-widget__prefix-spacing {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,71 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { ActivitiContentService } from '../../../services/activiti-alfresco.service';
|
||||
import { ErrorWidgetComponent } from '../error/error.component';
|
||||
import { EcmModelService } from './../../../services/ecm-model.service';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { AmountWidgetComponent } from './amount.widget';
|
||||
|
||||
describe('AmountWidgetComponent', () => {
|
||||
|
||||
let widget: AmountWidgetComponent;
|
||||
let fixture: ComponentFixture<AmountWidgetComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
AmountWidgetComponent,
|
||||
ErrorWidgetComponent
|
||||
],
|
||||
providers: [
|
||||
FormService,
|
||||
EcmModelService,
|
||||
ActivitiContentService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AmountWidgetComponent);
|
||||
|
||||
widget = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should setup currentcy from field', () => {
|
||||
const currency = 'UAH';
|
||||
widget.field = new FormFieldModel(null, {
|
||||
currency: currency
|
||||
});
|
||||
|
||||
widget.ngOnInit();
|
||||
expect(widget.currency).toBe(currency);
|
||||
});
|
||||
|
||||
it('should setup default currency', () => {
|
||||
widget.field = null;
|
||||
widget.ngOnInit();
|
||||
expect(widget.currency).toBe(AmountWidgetComponent.DEFAULT_CURRENCY);
|
||||
});
|
||||
|
||||
});
|
47
lib/core/form/components/widgets/amount/amount.widget.ts
Normal file
47
lib/core/form/components/widgets/amount/amount.widget.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { baseHost , WidgetComponent } from './../widget.component';
|
||||
|
||||
@Component({
|
||||
selector: 'amount-widget',
|
||||
templateUrl: './amount.widget.html',
|
||||
styleUrls: ['./amount.widget.scss'],
|
||||
host: baseHost,
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AmountWidgetComponent extends WidgetComponent implements OnInit {
|
||||
|
||||
static DEFAULT_CURRENCY: string = '$';
|
||||
|
||||
currency: string = AmountWidgetComponent.DEFAULT_CURRENCY;
|
||||
|
||||
constructor(public formService: FormService) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field && this.field.currency) {
|
||||
this.currency = this.field.currency;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
15
lib/core/form/components/widgets/attach/attach.widget.css
Normal file
15
lib/core/form/components/widgets/attach/attach.widget.css
Normal file
@@ -0,0 +1,15 @@
|
||||
.attach-widget {
|
||||
width:100%
|
||||
}
|
||||
|
||||
.attach-widget__icon {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.attach-widget__file {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.attach-widget__reset {
|
||||
margin-top: 4px;
|
||||
}
|
32
lib/core/form/components/widgets/attach/attach.widget.html
Normal file
32
lib/core/form/components/widgets/attach/attach.widget.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<div class="attach-widget {{field.className}}">
|
||||
<label [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label>
|
||||
<div>
|
||||
<span *ngIf="hasFile()" class="attach-widget__file mdl-chip"><span class="mdl-chip__text">{{getLinkedFileName()}}</span></span>
|
||||
<button #browseFile [disabled]="field.readOnly" (click)="showDialog();" class="mdl-button mdl-jsm-button mdl-js-ripple-effect attach-widget__browser">
|
||||
<mat-icon>image</mat-icon>
|
||||
Browse {{selectedFolderSiteName}}
|
||||
</button>
|
||||
<button *ngIf="hasFile" [disabled]="field.readOnly" (click)="reset(file);" class="mdl-button mdl-js-button mdl-js-ripple-effect attach-widget__reset">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dialog class="mdl-dialog" #dialog>
|
||||
<h4 class="mdl-dialog__title">Select content</h4>
|
||||
<div class="mdl-dialog__content">
|
||||
<ul class='mdl-list'>
|
||||
<li class="mdl-list__item" *ngFor="let node of selectedFolderNodes">
|
||||
<span class="mdl-list__item-primary-content" *ngIf="node.folder">
|
||||
<mat-icon class="mdl-list__item-icon">folder</mat-icon>
|
||||
<a (click)="selectFolder(node, $event)">{{node.title}}</a>
|
||||
</span>
|
||||
<span class="mdl-list__item-primary-content" *ngIf="!node.folder">
|
||||
<mat-icon class="mdl-list__item-icon">description</mat-icon>
|
||||
<a (click)="selectFile(node, $event)">{{node.title}}</a>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mdl-dialog__actions">
|
||||
<button type="button" (click)="cancel()" class="mdl-button close">Cancel</button>
|
||||
</div>
|
||||
</dialog>
|
304
lib/core/form/components/widgets/attach/attach.widget.spec.ts
Normal file
304
lib/core/form/components/widgets/attach/attach.widget.spec.ts
Normal file
@@ -0,0 +1,304 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { ActivitiContentService } from '../../../services/activiti-alfresco.service';
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { ExternalContent } from '../core/external-content';
|
||||
import { ExternalContentLink } from '../core/external-content-link';
|
||||
import { FormFieldTypes } from '../core/form-field-types';
|
||||
import { ErrorWidgetComponent } from '../error/error.component';
|
||||
import { EcmModelService } from './../../../services/ecm-model.service';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { FormModel } from './../core/form.model';
|
||||
import { AttachWidgetComponent } from './attach.widget';
|
||||
|
||||
describe('AttachWidgetComponent', () => {
|
||||
|
||||
let widget: AttachWidgetComponent;
|
||||
let fixture: ComponentFixture<AttachWidgetComponent>;
|
||||
let element: HTMLElement;
|
||||
let contentService: ActivitiContentService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
AttachWidgetComponent,
|
||||
ErrorWidgetComponent
|
||||
],
|
||||
providers: [
|
||||
FormService,
|
||||
EcmModelService,
|
||||
ActivitiContentService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AttachWidgetComponent);
|
||||
contentService = TestBed.get(ActivitiContentService);
|
||||
|
||||
element = fixture.nativeElement;
|
||||
widget = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should require field value to check file', () => {
|
||||
widget.field = null;
|
||||
widget.ngOnInit();
|
||||
expect(widget.hasFile()).toBeFalsy();
|
||||
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
value: null
|
||||
});
|
||||
widget.ngOnInit();
|
||||
expect(widget.hasFile()).toBeFalsy();
|
||||
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
value: [{ name: 'file' }]
|
||||
});
|
||||
widget.ngOnInit();
|
||||
expect(widget.hasFile()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should setup with form field', () => {
|
||||
let nodes: any = [{}];
|
||||
spyOn(contentService, 'getAlfrescoNodes').and.returnValue(
|
||||
Observable.create(observer => {
|
||||
observer.next(nodes);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
|
||||
let config = {
|
||||
siteId: '<id>',
|
||||
site: '<site>',
|
||||
pathId: '<pathId>',
|
||||
accountId: '<accountId>'
|
||||
};
|
||||
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
params: {
|
||||
fileSource: {
|
||||
selectedFolder: config
|
||||
}
|
||||
}
|
||||
});
|
||||
widget.ngOnInit();
|
||||
|
||||
expect(widget.selectedFolderSiteId).toBe(config.siteId);
|
||||
expect(widget.selectedFolderSiteName).toBe(config.site);
|
||||
expect(widget.selectedFolderPathId).toBe(config.pathId);
|
||||
expect(widget.selectedFolderAccountId).toBe(config.accountId);
|
||||
expect(widget.selectedFolderNodes).toEqual(nodes);
|
||||
});
|
||||
|
||||
xit('should link file on select', () => {
|
||||
let link = <ExternalContentLink> {};
|
||||
spyOn(contentService, 'linkAlfrescoNode').and.returnValue(
|
||||
Observable.create(observer => {
|
||||
observer.next(link);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.UPLOAD
|
||||
});
|
||||
widget.ngOnInit();
|
||||
|
||||
let node = <ExternalContent> {};
|
||||
widget.selectFile(node, null);
|
||||
|
||||
expect(contentService.linkAlfrescoNode).toHaveBeenCalled();
|
||||
expect(widget.selectedFile).toBe(node);
|
||||
expect(widget.field.value).toEqual([link]);
|
||||
expect(widget.field.json.value).toEqual([link]);
|
||||
expect(widget.hasFile()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should reset', () => {
|
||||
widget.field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
value: [{ name: 'filename' }]
|
||||
});
|
||||
|
||||
widget.reset();
|
||||
expect(widget.hasFile()).toBeFalsy();
|
||||
expect(widget.field.value).toBeNull();
|
||||
expect(widget.field.json.value).toBeNull();
|
||||
expect(widget.hasFile()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should close dialog on cancel', () => {
|
||||
let closed = false;
|
||||
widget.dialog = {
|
||||
nativeElement: {
|
||||
close: function () {
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
widget.cancel();
|
||||
expect(closed).toBeTruthy();
|
||||
});
|
||||
|
||||
xit('should show modal dialog', () => {
|
||||
spyOn(contentService, 'getAlfrescoNodes').and.returnValue(
|
||||
Observable.create(observer => {
|
||||
observer.next([]);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
params: {
|
||||
fileSource: {
|
||||
selectedFolder: {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let modalShown = false;
|
||||
widget.dialog = {
|
||||
nativeElement: {
|
||||
showModal: function () {
|
||||
modalShown = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
widget.showDialog();
|
||||
expect(modalShown).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should select folder and load nodes', () => {
|
||||
let nodes: any = [{}];
|
||||
spyOn(contentService, 'getAlfrescoNodes').and.returnValue(
|
||||
Observable.create(observer => {
|
||||
observer.next(nodes);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
|
||||
let node = <ExternalContent> { id: '<id>' };
|
||||
widget.selectFolder(node, null);
|
||||
|
||||
expect(widget.selectedFolderPathId).toBe(node.id);
|
||||
expect(widget.selectedFolderNodes).toEqual(nodes);
|
||||
});
|
||||
|
||||
it('should get linked file name via local variable', () => {
|
||||
widget.fileName = '<fileName>';
|
||||
widget.selectedFile = null;
|
||||
widget.field = null;
|
||||
expect(widget.getLinkedFileName()).toBe(widget.fileName);
|
||||
});
|
||||
|
||||
it('should get linked file name via selected file', () => {
|
||||
widget.fileName = null;
|
||||
widget.selectedFile = <ExternalContent> { title: '<title>' };
|
||||
widget.field = null;
|
||||
expect(widget.getLinkedFileName()).toBe(widget.selectedFile.title);
|
||||
});
|
||||
|
||||
it('should get linked file name via form field', () => {
|
||||
widget.fileName = null;
|
||||
widget.selectedFile = null;
|
||||
|
||||
let name = '<file>';
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
value: [{ name: name }]
|
||||
});
|
||||
|
||||
expect(widget.getLinkedFileName()).toBe(name);
|
||||
});
|
||||
|
||||
it('should require form field to setup file browser', () => {
|
||||
widget.field = null;
|
||||
widget.setupFileBrowser();
|
||||
|
||||
expect(widget.selectedFolderPathId).toBeUndefined();
|
||||
expect(widget.selectedFolderAccountId).toBeUndefined();
|
||||
|
||||
const pathId = '<pathId>';
|
||||
const accountId = '<accountId>';
|
||||
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
params: {
|
||||
fileSource: {
|
||||
selectedFolder: {
|
||||
pathId: pathId,
|
||||
accountId: accountId
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
widget.setupFileBrowser();
|
||||
expect(widget.selectedFolderPathId).toBe(pathId);
|
||||
expect(widget.selectedFolderAccountId).toBe(accountId);
|
||||
});
|
||||
|
||||
it('should get external content nodes', () => {
|
||||
let nodes: any = [{}];
|
||||
spyOn(contentService, 'getAlfrescoNodes').and.returnValue(
|
||||
Observable.create(observer => {
|
||||
observer.next(nodes);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
|
||||
const accountId = '<accountId>';
|
||||
const pathId = '<pathId>';
|
||||
widget.selectedFolderAccountId = accountId;
|
||||
widget.selectedFolderPathId = pathId;
|
||||
widget.getExternalContentNodes();
|
||||
|
||||
expect(contentService.getAlfrescoNodes).toHaveBeenCalledWith(accountId, pathId);
|
||||
expect(widget.selectedFolderNodes).toEqual(nodes);
|
||||
});
|
||||
|
||||
it('should handle error', (done) => {
|
||||
let error = 'error';
|
||||
spyOn(contentService, 'getAlfrescoNodes').and.returnValue(
|
||||
Observable.throw(error)
|
||||
);
|
||||
|
||||
widget.error.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
|
||||
widget.getExternalContentNodes();
|
||||
});
|
||||
|
||||
it('should require configured dialog to show modal', () => {
|
||||
widget.dialog = null;
|
||||
spyOn(widget, 'setupFileBrowser').and.stub();
|
||||
spyOn(widget, 'getExternalContentNodes').and.stub();
|
||||
expect(widget.showDialog()).toBeFalsy();
|
||||
});
|
||||
});
|
156
lib/core/form/components/widgets/attach/attach.widget.ts
Normal file
156
lib/core/form/components/widgets/attach/attach.widget.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { Component, EventEmitter, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { ActivitiContentService } from '../../../services/activiti-alfresco.service';
|
||||
import { ExternalContent } from '../core/external-content';
|
||||
import { ExternalContentLink } from '../core/external-content-link';
|
||||
import { FormFieldModel } from '../core/form-field.model';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { baseHost , WidgetComponent } from './../widget.component';
|
||||
|
||||
@Component({
|
||||
selector: 'attach-widget',
|
||||
templateUrl: './attach.widget.html',
|
||||
styleUrls: ['./attach.widget.css'],
|
||||
host: baseHost,
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AttachWidgetComponent extends WidgetComponent implements OnInit {
|
||||
|
||||
selectedFolderPathId: string;
|
||||
selectedFolderSiteId: string;
|
||||
selectedFolderSiteName: string;
|
||||
selectedFolderAccountId: string;
|
||||
fileName: string;
|
||||
selectedFolderNodes: [ExternalContent];
|
||||
selectedFile: ExternalContent;
|
||||
|
||||
@Output()
|
||||
fieldChanged: EventEmitter<FormFieldModel> = new EventEmitter<FormFieldModel>();
|
||||
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@ViewChild('dialog')
|
||||
dialog: any;
|
||||
|
||||
constructor(public formService: FormService,
|
||||
private contentService: ActivitiContentService) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field) {
|
||||
let params = this.field.params;
|
||||
|
||||
if (params &&
|
||||
params.fileSource &&
|
||||
params.fileSource.selectedFolder) {
|
||||
this.selectedFolderSiteId = params.fileSource.selectedFolder.siteId;
|
||||
this.selectedFolderSiteName = params.fileSource.selectedFolder.site;
|
||||
this.setupFileBrowser();
|
||||
this.getExternalContentNodes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupFileBrowser() {
|
||||
if (this.field) {
|
||||
let params = this.field.params;
|
||||
this.selectedFolderPathId = params.fileSource.selectedFolder.pathId;
|
||||
this.selectedFolderAccountId = params.fileSource.selectedFolder.accountId;
|
||||
}
|
||||
}
|
||||
|
||||
getLinkedFileName(): string {
|
||||
let result = this.fileName;
|
||||
|
||||
if (this.selectedFile &&
|
||||
this.selectedFile.title) {
|
||||
result = this.selectedFile.title;
|
||||
}
|
||||
if (this.field &&
|
||||
this.field.value &&
|
||||
this.field.value.length > 0 &&
|
||||
this.field.value[0].name) {
|
||||
result = this.field.value[0].name;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
getExternalContentNodes() {
|
||||
this.contentService.getAlfrescoNodes(this.selectedFolderAccountId, this.selectedFolderPathId)
|
||||
.subscribe(
|
||||
nodes => this.selectedFolderNodes = nodes,
|
||||
(err) => {
|
||||
this.error.emit(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
selectFile(node: ExternalContent, $event: any) {
|
||||
this.contentService.linkAlfrescoNode(this.selectedFolderAccountId, node, this.selectedFolderSiteId).subscribe(
|
||||
(link: ExternalContentLink) => {
|
||||
this.selectedFile = node;
|
||||
this.field.value = [link];
|
||||
this.field.json.value = [link];
|
||||
this.closeDialog();
|
||||
this.fieldChanged.emit(this.field);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
selectFolder(node: ExternalContent, $event: any) {
|
||||
this.selectedFolderPathId = node.id;
|
||||
this.getExternalContentNodes();
|
||||
}
|
||||
|
||||
showDialog(): boolean {
|
||||
this.setupFileBrowser();
|
||||
this.getExternalContentNodes();
|
||||
|
||||
if (this.dialog) {
|
||||
// todo: show dialog
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private closeDialog() {
|
||||
if (this.dialog) {
|
||||
this.dialog.nativeElement.close();
|
||||
}
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.field.value = null;
|
||||
this.field.json.value = null;
|
||||
}
|
||||
|
||||
hasFile(): boolean {
|
||||
return this.field && this.field.value;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
<div [ngClass]="field.className">
|
||||
<mat-checkbox
|
||||
[id]="field.id"
|
||||
color="primary"
|
||||
[required]="field.required"
|
||||
[disabled]="field.readOnly || readOnly"
|
||||
[(ngModel)]="field.value"
|
||||
(change)="onChange()">
|
||||
{{field.name}}
|
||||
<span *ngIf="field.required">*</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
41
lib/core/form/components/widgets/checkbox/checkbox.widget.ts
Normal file
41
lib/core/form/components/widgets/checkbox/checkbox.widget.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { Component, ViewEncapsulation } from '@angular/core';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { baseHost , WidgetComponent } from './../widget.component';
|
||||
|
||||
@Component({
|
||||
selector: 'checkbox-widget',
|
||||
templateUrl: './checkbox.widget.html',
|
||||
host: baseHost,
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class CheckboxWidgetComponent extends WidgetComponent {
|
||||
|
||||
constructor(private visibilityService: WidgetVisibilityService, public formService: FormService) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.visibilityService.refreshVisibility(this.field.form);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ContainerColumnModel } from './../core/container-column.model';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { FormModel } from './../core/form.model';
|
||||
|
||||
describe('ContainerColumnModel', () => {
|
||||
|
||||
it('should have max size by default', () => {
|
||||
let column = new ContainerColumnModel();
|
||||
expect(column.size).toBe(12);
|
||||
});
|
||||
|
||||
it('should check fields', () => {
|
||||
let column = new ContainerColumnModel();
|
||||
|
||||
column.fields = null;
|
||||
expect(column.hasFields()).toBeFalsy();
|
||||
|
||||
column.fields = [];
|
||||
expect(column.hasFields()).toBeFalsy();
|
||||
|
||||
column.fields = [new FormFieldModel(new FormModel(), null)];
|
||||
expect(column.hasFields()).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,20 @@
|
||||
<div [ngClass]="{'hidden':!(content?.isGroup() && content?.isVisible)}" class="container-widget__header">
|
||||
<h4 class="container-widget__header-text" id="container-header"
|
||||
[class.collapsible]="content?.isCollapsible()">
|
||||
<button *ngIf="content?.isCollapsible()"
|
||||
mat-icon-button
|
||||
class="mdl-button--icon"
|
||||
(click)="onExpanderClicked()">
|
||||
<mat-icon>{{ content?.isExpanded ? 'expand_less' : 'expand_more' }}</mat-icon>
|
||||
</button>
|
||||
<span (click)="onExpanderClicked()" id="container-header-label">{{content.name}}</span>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<section class="grid-list" [ngClass]="{'hidden':!(content?.isVisible && content?.isExpanded)}">
|
||||
<div class="grid-list-item" *ngFor="let field of fields" [style.width]="getColumnWith(field)">
|
||||
<form-field *ngIf="field" [field]="field"></form-field>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
@@ -0,0 +1,77 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FormFieldTypes } from './../core/form-field-types';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { FormModel } from './../core/form.model';
|
||||
import { ContainerWidgetComponentModel } from './container.widget.model';
|
||||
|
||||
describe('ContainerWidgetComponentModel', () => {
|
||||
|
||||
it('should store the form reference', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form);
|
||||
let model = new ContainerWidgetComponentModel(field);
|
||||
expect(model.form).toBe(form);
|
||||
});
|
||||
|
||||
it('should allow collapsing only when of a group type', () => {
|
||||
let container = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.CONTAINER,
|
||||
params: {
|
||||
allowCollapse: true
|
||||
}
|
||||
}));
|
||||
|
||||
expect(container.isCollapsible()).toBeFalsy();
|
||||
container = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP,
|
||||
params: {
|
||||
allowCollapse: true
|
||||
}
|
||||
}));
|
||||
expect(container.isCollapsible()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow collapsing only when explicitly defined in params', () => {
|
||||
let container = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP,
|
||||
params: {}
|
||||
}));
|
||||
expect(container.isCollapsible()).toBeFalsy();
|
||||
|
||||
container = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP,
|
||||
params: {
|
||||
allowCollapse: true
|
||||
}
|
||||
}));
|
||||
expect(container.isCollapsible()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be collapsed by default', () => {
|
||||
let container = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP,
|
||||
params: {
|
||||
allowCollapse: true,
|
||||
collapseByDefault: true
|
||||
}
|
||||
}));
|
||||
expect(container.isCollapsedByDefault()).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,66 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { ContainerColumnModel } from './../core/container-column.model';
|
||||
import { ContainerModel } from './../core/container.model';
|
||||
import { FormFieldTypes } from './../core/form-field-types';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
|
||||
export class ContainerWidgetComponentModel extends ContainerModel {
|
||||
|
||||
columns: ContainerColumnModel[] = [];
|
||||
isExpanded: boolean = true;
|
||||
rowspan: number = 1;
|
||||
colspan: number = 1;
|
||||
|
||||
isGroup(): boolean {
|
||||
return this.type === FormFieldTypes.GROUP;
|
||||
}
|
||||
|
||||
isCollapsible(): boolean {
|
||||
let allowCollapse = false;
|
||||
|
||||
if (this.isGroup() && this.field.params['allowCollapse']) {
|
||||
allowCollapse = <boolean> this.field.params['allowCollapse'];
|
||||
}
|
||||
|
||||
return allowCollapse;
|
||||
}
|
||||
|
||||
isCollapsedByDefault(): boolean {
|
||||
let collapseByDefault = false;
|
||||
|
||||
if (this.isCollapsible() && this.field.params['collapseByDefault']) {
|
||||
collapseByDefault = <boolean> this.field.params['collapseByDefault'];
|
||||
}
|
||||
|
||||
return collapseByDefault;
|
||||
}
|
||||
|
||||
constructor(field: FormFieldModel) {
|
||||
super(field);
|
||||
|
||||
if (this.field) {
|
||||
this.columns = this.field.columns || [];
|
||||
this.isExpanded = !this.isCollapsedByDefault();
|
||||
this.colspan = field.colspan;
|
||||
this.rowspan = field.rowspan;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
|
||||
@mixin adf-form-container-widget-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$warn: map-get($theme, warn);
|
||||
|
||||
.hidden{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.adf {
|
||||
&-field-list {
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.container-widget__header-text {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.87);
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
/* Chrome/Safari/Opera */
|
||||
-moz-user-select: none;
|
||||
/* Firefox */
|
||||
-ms-user-select: none;
|
||||
/* IE/Edge */
|
||||
-webkit-touch-callout: none;
|
||||
/* iOS Safari */
|
||||
&.collapsible {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container-widget {
|
||||
.grid-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-left: -1%;
|
||||
margin-right: -1%;
|
||||
}
|
||||
|
||||
.grid-list-item {
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
padding-left: 1%;
|
||||
padding-right: 1%;
|
||||
}
|
||||
|
||||
.mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mat-input-placeholder-wrapper {
|
||||
top: 5px !important;
|
||||
}
|
||||
|
||||
.mat-input-placeholder {
|
||||
top: 1.8em !important;
|
||||
}
|
||||
|
||||
.mat-focused {
|
||||
|
||||
.mat-input-placeholder-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label {
|
||||
transform: scaleX(1);
|
||||
transition: transform 150ms linear,
|
||||
background-color $swift-ease-in-duration $swift-ease-in-timing-function;
|
||||
color: mat-color($primary);
|
||||
}
|
||||
|
||||
.mat-input-prefix {
|
||||
color: mat-color($primary);
|
||||
}
|
||||
}
|
||||
|
||||
.mat-grid-tile {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,258 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ActivitiContentService } from '../../../services/activiti-alfresco.service';
|
||||
import { fakeFormJson } from '../../../../mock';
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { WIDGET_DIRECTIVES } from '../index';
|
||||
import { MASK_DIRECTIVE } from '../index';
|
||||
import { EcmModelService } from './../../../services/ecm-model.service';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { FormFieldComponent } from './../../form-field/form-field.component';
|
||||
import { ContentWidgetComponent } from './../content/content.widget';
|
||||
import { ContainerColumnModel } from './../core/container-column.model';
|
||||
import { FormFieldTypes } from './../core/form-field-types';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { FormModel } from './../core/form.model';
|
||||
import { ContainerWidgetComponent } from './container.widget';
|
||||
import { ContainerWidgetComponentModel } from './container.widget.model';
|
||||
|
||||
describe('ContainerWidgetComponent', () => {
|
||||
|
||||
let widget: ContainerWidgetComponent;
|
||||
let fixture: ComponentFixture<ContainerWidgetComponent>;
|
||||
let element: HTMLElement;
|
||||
let contentService: ActivitiContentService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [FormFieldComponent, ContentWidgetComponent, WIDGET_DIRECTIVES, MASK_DIRECTIVE],
|
||||
providers: [
|
||||
FormService,
|
||||
EcmModelService,
|
||||
ActivitiContentService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ContainerWidgetComponent);
|
||||
contentService = TestBed.get(ActivitiContentService);
|
||||
|
||||
element = fixture.nativeElement;
|
||||
widget = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should wrap field with model instance', () => {
|
||||
let field = new FormFieldModel(null);
|
||||
widget.field = field;
|
||||
widget.ngOnInit();
|
||||
expect(widget.content).toBeDefined();
|
||||
expect(widget.content.field).toBe(field);
|
||||
});
|
||||
|
||||
it('should toggle underlying group container', () => {
|
||||
let container = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP,
|
||||
params: {
|
||||
allowCollapse: true
|
||||
}
|
||||
}));
|
||||
|
||||
widget.content = container;
|
||||
|
||||
expect(container.isExpanded).toBeTruthy();
|
||||
widget.onExpanderClicked();
|
||||
expect(container.isExpanded).toBeFalsy();
|
||||
widget.onExpanderClicked();
|
||||
expect(container.isExpanded).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should toggle only collapsible container', () => {
|
||||
let container = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP
|
||||
}));
|
||||
|
||||
widget.content = container;
|
||||
|
||||
expect(container.isExpanded).toBeTruthy();
|
||||
widget.onExpanderClicked();
|
||||
expect(container.isExpanded).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should toggle only group container', () => {
|
||||
|
||||
let container = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.CONTAINER,
|
||||
params: {
|
||||
allowCollapse: true
|
||||
}
|
||||
}));
|
||||
|
||||
widget.content = container;
|
||||
|
||||
expect(container.isExpanded).toBeTruthy();
|
||||
widget.onExpanderClicked();
|
||||
expect(container.isExpanded).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should send an event when a value is changed in the form', (done) => {
|
||||
let fakeForm = new FormModel();
|
||||
let fakeField = new FormFieldModel(fakeForm, {id: 'fakeField', value: 'fakeValue'});
|
||||
widget.fieldChanged.subscribe(field => {
|
||||
expect(field).not.toBe(null);
|
||||
expect(field.id).toBe('fakeField');
|
||||
expect(field.value).toBe('fakeValue');
|
||||
done();
|
||||
});
|
||||
|
||||
widget.onFieldChanged(fakeField);
|
||||
});
|
||||
|
||||
describe('when template is ready', () => {
|
||||
let fakeContainerVisible;
|
||||
let fakeContainerInvisible;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeContainerVisible = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel(fakeFormJson), {
|
||||
fieldType: FormFieldTypes.GROUP,
|
||||
id: 'fake-cont-id-1',
|
||||
name: 'fake-cont-1-name',
|
||||
type: FormFieldTypes.GROUP
|
||||
}));
|
||||
fakeContainerInvisible = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel(fakeFormJson), {
|
||||
fieldType: FormFieldTypes.GROUP,
|
||||
id: 'fake-cont-id-2',
|
||||
name: 'fake-cont-2-name',
|
||||
type: FormFieldTypes.GROUP
|
||||
}));
|
||||
fakeContainerVisible.field.isVisible = true;
|
||||
fakeContainerInvisible.field.isVisible = false;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should show the container header when it is visible', () => {
|
||||
widget.content = fakeContainerVisible;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(element.querySelector('.container-widget__header').classList.contains('hidden')).toBe(false);
|
||||
expect(element.querySelector('#container-header-label')).toBeDefined();
|
||||
expect(element.querySelector('#container-header-label').innerHTML).toContain('fake-cont-1-name');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not show the container header when it is not visible', () => {
|
||||
widget.content = fakeContainerInvisible;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(element.querySelector('.container-widget__header').classList.contains('hidden')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide header when it becomes not visible', async(() => {
|
||||
widget.content = fakeContainerVisible;
|
||||
fixture.detectChanges();
|
||||
widget.fieldChanged.subscribe((res) => {
|
||||
widget.content.field.isVisible = false;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(element.querySelector('.container-widget__header').classList.contains('hidden')).toBe(true);
|
||||
});
|
||||
});
|
||||
widget.onFieldChanged(null);
|
||||
}));
|
||||
|
||||
it('should show header when it becomes visible', async(() => {
|
||||
widget.content = fakeContainerInvisible;
|
||||
widget.fieldChanged.subscribe((res) => {
|
||||
widget.content.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(element.querySelector('#container-header')).toBeDefined();
|
||||
expect(element.querySelector('#container-header')).not.toBeNull();
|
||||
expect(element.querySelector('#container-header-label')).toBeDefined();
|
||||
expect(element.querySelector('#container-header-label').innerHTML).toContain('fake-cont-2-name');
|
||||
});
|
||||
});
|
||||
widget.onFieldChanged(null);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('fields', () => {
|
||||
|
||||
it('should serializes the content fields', () => {
|
||||
const field1 = <FormFieldModel> {id: '1'},
|
||||
field2 = <FormFieldModel> {id: '2'},
|
||||
field3 = <FormFieldModel> {id: '3'},
|
||||
field4 = <FormFieldModel> {id: '4'},
|
||||
field5 = <FormFieldModel> {id: '5'},
|
||||
field6 = <FormFieldModel> {id: '6'};
|
||||
|
||||
let container = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel()));
|
||||
container.columns = [
|
||||
<ContainerColumnModel> { fields: [
|
||||
field1,
|
||||
field2,
|
||||
field3
|
||||
] },
|
||||
<ContainerColumnModel> { fields: [
|
||||
field4,
|
||||
field5
|
||||
] },
|
||||
<ContainerColumnModel> { fields: [
|
||||
field6
|
||||
] }
|
||||
];
|
||||
|
||||
widget.content = container;
|
||||
|
||||
expect(widget.fields[0].id).toEqual('1');
|
||||
expect(widget.fields[1].id).toEqual('4');
|
||||
expect(widget.fields[2].id).toEqual('6');
|
||||
expect(widget.fields[3].id).toEqual('2');
|
||||
expect(widget.fields[4].id).toEqual('5');
|
||||
expect(widget.fields[5]).toEqual(undefined);
|
||||
expect(widget.fields[6].id).toEqual('3');
|
||||
expect(widget.fields[7]).toEqual(undefined);
|
||||
expect(widget.fields[8]).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getColumnWith', () => {
|
||||
|
||||
it('should calculate the column width based on the numberOfColumns and current field\'s colspan property', () => {
|
||||
let container = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel(), { numberOfColumns: 4 }));
|
||||
widget.content = container;
|
||||
|
||||
expect(widget.getColumnWith(undefined)).toBe('25%');
|
||||
expect(widget.getColumnWith(<FormFieldModel> { colspan: 1 })).toBe('25%');
|
||||
expect(widget.getColumnWith(<FormFieldModel> { colspan: 3 })).toBe('75%');
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,87 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { AfterViewInit, Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { baseHost , WidgetComponent } from './../widget.component';
|
||||
import { ContainerWidgetComponentModel } from './container.widget.model';
|
||||
|
||||
@Component({
|
||||
selector: 'container-widget',
|
||||
templateUrl: './container.widget.html',
|
||||
styleUrls: ['./container.widget.scss'],
|
||||
host: baseHost,
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class ContainerWidgetComponent extends WidgetComponent implements OnInit, AfterViewInit {
|
||||
|
||||
content: ContainerWidgetComponentModel;
|
||||
|
||||
constructor(public formService: FormService) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
onExpanderClicked() {
|
||||
if (this.content && this.content.isCollapsible()) {
|
||||
this.content.isExpanded = !this.content.isExpanded;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field) {
|
||||
this.content = new ContainerWidgetComponentModel(this.field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes column fields
|
||||
*/
|
||||
get fields(): FormFieldModel[] {
|
||||
const fields = [];
|
||||
|
||||
let rowContainsElement = true,
|
||||
rowIndex = 0;
|
||||
|
||||
while (rowContainsElement) {
|
||||
rowContainsElement = false;
|
||||
for (let i = 0; i < this.content.columns.length; i++ ) {
|
||||
let field = this.content.columns[i].fields[rowIndex];
|
||||
if (field) {
|
||||
rowContainsElement = true;
|
||||
}
|
||||
|
||||
fields.push(field);
|
||||
}
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the column width based on the numberOfColumns and current field's colspan property
|
||||
*
|
||||
* @param field
|
||||
*/
|
||||
getColumnWith(field: FormFieldModel): string {
|
||||
const colspan = field ? field.colspan : 1;
|
||||
return (100 / this.content.json.numberOfColumns) * colspan + '%';
|
||||
}
|
||||
}
|
22
lib/core/form/components/widgets/content/content.widget.html
Normal file
22
lib/core/form/components/widgets/content/content.widget.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<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}}</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>
|
15
lib/core/form/components/widgets/content/content.widget.scss
Normal file
15
lib/core/form/components/widgets/content/content.widget.scss
Normal file
@@ -0,0 +1,15 @@
|
||||
.adf {
|
||||
&-img-upload-widget {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid rgba(117, 117, 117, 0.57);
|
||||
box-shadow: 1px 1px 2px #dddddd;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
&-content-widget-preview-text {
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
310
lib/core/form/components/widgets/content/content.widget.spec.ts
Normal file
310
lib/core/form/components/widgets/content/content.widget.spec.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DebugElement, SimpleChange } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TranslationService, ContentService } from '../../../../services';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
|
||||
import { EcmModelService } from '../../../services/ecm-model.service';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { ProcessContentService } from '../../../services/process-content.service';
|
||||
import { ContentLinkModel } from '../index';
|
||||
import { ContentWidgetComponent } from './content.widget';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('ContentWidgetComponent', () => {
|
||||
|
||||
let component: ContentWidgetComponent;
|
||||
let fixture: ComponentFixture<ContentWidgetComponent>;
|
||||
let debug: DebugElement;
|
||||
let element: HTMLElement;
|
||||
|
||||
let serviceForm: FormService;
|
||||
let processContentService: ProcessContentService;
|
||||
let serviceContent: ContentService;
|
||||
|
||||
function createFakeImageBlob() {
|
||||
let data = atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
|
||||
return new Blob([data], {type: 'image/png'});
|
||||
}
|
||||
|
||||
function createFakePdfBlob(): Blob {
|
||||
let 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'});
|
||||
}
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
ContentWidgetComponent
|
||||
],
|
||||
providers: [
|
||||
FormService,
|
||||
EcmModelService,
|
||||
ContentService,
|
||||
ProcessContentService
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
serviceForm = TestBed.get(FormService);
|
||||
serviceContent = TestBed.get(ContentService);
|
||||
processContentService = TestBed.get(ProcessContentService);
|
||||
|
||||
let translateService = TestBed.get(TranslationService);
|
||||
spyOn(translateService, 'addTranslationFolder').and.stub();
|
||||
spyOn(translateService, 'get').and.callFake((key) => {
|
||||
return Observable.of(key);
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ContentWidgetComponent);
|
||||
component = fixture.componentInstance;
|
||||
debug = fixture.debugElement;
|
||||
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();
|
||||
|
||||
let content = fixture.debugElement.query(By.css('div.upload-widget__content-thumbnail'));
|
||||
expect(content).toBeDefined();
|
||||
});
|
||||
|
||||
it('should load the thumbnail preview of the png image', (done) => {
|
||||
let blob = createFakeImageBlob();
|
||||
spyOn(processContentService, 'getFileRawContent').and.returnValue(Observable.of(blob));
|
||||
|
||||
component.thumbnailLoaded.subscribe((res) => {
|
||||
fixture.detectChanges();
|
||||
expect(res).toBeDefined();
|
||||
expect(res.changingThisBreaksApplicationSecurity).toBeDefined();
|
||||
expect(res.changingThisBreaksApplicationSecurity).toContain('blob');
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let thumbnailPreview: any = element.querySelector('#thumbnailPreview');
|
||||
expect(thumbnailPreview.src).toContain('blob');
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
let contentId = 1;
|
||||
let 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: 'dasdas', 'lastName': 'dasads', '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', (done) => {
|
||||
let blob = createFakePdfBlob();
|
||||
spyOn(processContentService, 'getContentThumbnailUrl').and.returnValue(Observable.of(blob));
|
||||
|
||||
component.thumbnailLoaded.subscribe((res) => {
|
||||
fixture.detectChanges();
|
||||
expect(res).toBeDefined();
|
||||
expect(res.changingThisBreaksApplicationSecurity).toBeDefined();
|
||||
expect(res.changingThisBreaksApplicationSecurity).toContain('blob');
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let thumbnailPreview: any = element.querySelector('#thumbnailPreview');
|
||||
expect(thumbnailPreview.src).toContain('blob');
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
let contentId = 1;
|
||||
let 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: 'dasdas', 'lastName': 'dasads', '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', (done) => {
|
||||
|
||||
let contentId = 1;
|
||||
let change = new SimpleChange(null, contentId, true);
|
||||
component.ngOnChanges({'id': change});
|
||||
|
||||
component.contentLoaded.subscribe((res) => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let thumbnailPreview: any = element.querySelector('#unsupported-thumbnail');
|
||||
expect(thumbnailPreview).toBeDefined();
|
||||
expect(element.querySelector('div.upload-widget__content-text').innerHTML).toEqual('FakeBlob.zip');
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: {
|
||||
id: 4004,
|
||||
name: 'FakeBlob.zip',
|
||||
created: 1490354907883,
|
||||
createdBy: {
|
||||
id: 2,
|
||||
firstName: 'dasdas', 'lastName': 'dasads', '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', (done) => {
|
||||
let blob = createFakePdfBlob();
|
||||
spyOn(processContentService, 'getFileRawContent').and.returnValue(Observable.of(blob));
|
||||
|
||||
component.content = new ContentLinkModel({
|
||||
id: 4004,
|
||||
name: 'FakeBlob.pdf',
|
||||
created: 1490354907883,
|
||||
createdBy: {
|
||||
id: 2,
|
||||
firstName: 'dasdas', 'lastName': 'dasads', 'email': 'administrator@admin.com'
|
||||
},
|
||||
relatedContent: false,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'application/pdf',
|
||||
simpleType: 'pdf',
|
||||
previewStatus: 'created',
|
||||
thumbnailStatus: 'created'
|
||||
});
|
||||
|
||||
component.contentClick.subscribe((content) => {
|
||||
expect(content.contentBlob).toBe(blob);
|
||||
expect(content.mimeType).toBe('application/pdf');
|
||||
expect(content.name).toBe('FakeBlob.pdf');
|
||||
done();
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
let viewButton: any = element.querySelector('#view');
|
||||
viewButton.click();
|
||||
});
|
||||
|
||||
it('should download the pdf when the download button is clicked', () => {
|
||||
let blob = createFakePdfBlob();
|
||||
spyOn(processContentService, 'getFileRawContent').and.returnValue(Observable.of(blob));
|
||||
spyOn(serviceContent, 'downloadBlob').and.callThrough();
|
||||
|
||||
component.content = new ContentLinkModel({
|
||||
id: 4004,
|
||||
name: 'FakeBlob.pdf',
|
||||
created: 1490354907883,
|
||||
createdBy: {
|
||||
id: 2,
|
||||
firstName: 'dasdas', 'lastName': 'dasads', 'email': 'administrator@admin.com'
|
||||
},
|
||||
relatedContent: false,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'application/pdf',
|
||||
simpleType: 'pdf',
|
||||
previewStatus: 'created',
|
||||
thumbnailStatus: 'created'
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
let downloadButton: any = element.querySelector('#download');
|
||||
downloadButton.click();
|
||||
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(serviceContent.downloadBlob).toHaveBeenCalledWith(blob, 'FakeBlob.pdf');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
131
lib/core/form/components/widgets/content/content.widget.ts
Normal file
131
lib/core/form/components/widgets/content/content.widget.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ContentService, LogService } from '../../../../services';
|
||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
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 {
|
||||
|
||||
@Input()
|
||||
id: string;
|
||||
|
||||
@Input()
|
||||
showDocumentContent: boolean = true;
|
||||
|
||||
@Output()
|
||||
contentClick = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
thumbnailLoaded: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@Output()
|
||||
contentLoaded: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@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.getContentThumbnailUrl(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 {
|
||||
this.processContentService.getFileRawContent(content.id).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);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { FormFieldModel } from './form-field.model';
|
||||
|
||||
export class ContainerColumnModel {
|
||||
|
||||
size: number = 12;
|
||||
fields: FormFieldModel[] = [];
|
||||
colspan: number = 1;
|
||||
rowspan: number = 1;
|
||||
|
||||
hasFields(): boolean {
|
||||
return this.fields && this.fields.length > 0;
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ContainerModel } from './container.model';
|
||||
import { FormFieldModel } from './form-field.model';
|
||||
import { FormModel } from './form.model';
|
||||
|
||||
describe('ContainerModel', () => {
|
||||
|
||||
it('should store the form reference', () => {
|
||||
let form = new FormModel();
|
||||
let model = new ContainerModel(new FormFieldModel(form));
|
||||
expect(model.form).toBe(form);
|
||||
});
|
||||
|
||||
});
|
39
lib/core/form/components/widgets/core/container.model.ts
Normal file
39
lib/core/form/components/widgets/core/container.model.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { FormFieldModel } from './form-field.model';
|
||||
import { FormWidgetModel } from './form-widget.model';
|
||||
|
||||
export class ContainerModel extends FormWidgetModel {
|
||||
|
||||
field: FormFieldModel;
|
||||
|
||||
get isVisible(): boolean {
|
||||
return this.field.isVisible;
|
||||
}
|
||||
|
||||
constructor(field: FormFieldModel) {
|
||||
super(field.form, field.json);
|
||||
|
||||
if (field) {
|
||||
this.field = field;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
76
lib/core/form/components/widgets/core/content-link.model.ts
Normal file
76
lib/core/form/components/widgets/core/content-link.model.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { RelatedContentRepresentation } from 'alfresco-js-api';
|
||||
|
||||
export class ContentLinkModel implements RelatedContentRepresentation {
|
||||
|
||||
contentAvailable: boolean;
|
||||
created: Date;
|
||||
createdBy: any;
|
||||
id: number;
|
||||
link: boolean;
|
||||
mimeType: string;
|
||||
name: string;
|
||||
previewStatus: string;
|
||||
relatedContent: boolean;
|
||||
simpleType: string;
|
||||
thumbnailUrl: string;
|
||||
contentRawUrl: string;
|
||||
contentBlob: Blob;
|
||||
thumbnailStatus: string;
|
||||
|
||||
constructor(obj?: any) {
|
||||
this.contentAvailable = obj && obj.contentAvailable;
|
||||
this.created = obj && obj.created;
|
||||
this.createdBy = obj && obj.createdBy || {};
|
||||
this.id = obj && obj.id;
|
||||
this.link = obj && obj.link;
|
||||
this.mimeType = obj && obj.mimeType;
|
||||
this.name = obj && obj.name;
|
||||
this.previewStatus = obj && obj.previewStatus;
|
||||
this.relatedContent = obj && obj.relatedContent;
|
||||
this.simpleType = obj && obj.simpleType;
|
||||
this.thumbnailStatus = obj && obj.thumbnailStatus;
|
||||
}
|
||||
|
||||
hasPreviewStatus(): boolean {
|
||||
return this.previewStatus === 'supported' ? true : false;
|
||||
}
|
||||
|
||||
isTypeImage(): boolean {
|
||||
return this.simpleType === 'image' ? true : false;
|
||||
}
|
||||
|
||||
isTypePdf(): boolean {
|
||||
return this.simpleType === 'pdf' ? true : false;
|
||||
}
|
||||
|
||||
isTypeDoc(): boolean {
|
||||
return this.simpleType === 'word' || this.simpleType === 'content' ? true : false;
|
||||
}
|
||||
|
||||
isThumbnailReady(): boolean {
|
||||
return this.thumbnailStatus === 'created';
|
||||
}
|
||||
|
||||
isThumbnailSupported(): boolean {
|
||||
return this.isTypeImage() || ((this.isTypePdf() || this.isTypeDoc()) && this.isThumbnailReady());
|
||||
}
|
||||
}
|
45
lib/core/form/components/widgets/core/error-message.model.ts
Normal file
45
lib/core/form/components/widgets/core/error-message.model.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
export class ErrorMessageModel {
|
||||
|
||||
message: string = '';
|
||||
attributes: Map<string, string> = null;
|
||||
|
||||
constructor(obj?: any) {
|
||||
this.message = obj && obj.message ? obj.message : '';
|
||||
this.attributes = new Map();
|
||||
}
|
||||
|
||||
isActive() {
|
||||
return this.message ? true : false;
|
||||
}
|
||||
|
||||
getAttributesAsJsonObj() {
|
||||
let result = {};
|
||||
if (this.attributes.size > 0) {
|
||||
let obj = Object.create(null);
|
||||
this.attributes.forEach((value, key) => {
|
||||
obj[key] = value;
|
||||
});
|
||||
result = JSON.stringify(obj);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
export interface ExternalContentLink {
|
||||
contentAvailable: boolean;
|
||||
created: string;
|
||||
createdBy: any;
|
||||
id: number;
|
||||
link: boolean;
|
||||
mimeType: string;
|
||||
name: string;
|
||||
previewStatus: string;
|
||||
relatedContent: boolean;
|
||||
simpleType: string;
|
||||
source: string;
|
||||
sourceId: string;
|
||||
thumbnailStatus: string;
|
||||
}
|
25
lib/core/form/components/widgets/core/external-content.ts
Normal file
25
lib/core/form/components/widgets/core/external-content.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
export interface ExternalContent {
|
||||
folder: boolean;
|
||||
id: string;
|
||||
simpleType: string;
|
||||
title: string;
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { FormFieldSelectedFolder } from './form-field-selected-folder';
|
||||
|
||||
export interface FormFieldFileSource {
|
||||
metadataAllowed: boolean;
|
||||
name: string;
|
||||
selectedFolder: FormFieldSelectedFolder;
|
||||
serviceId: string;
|
||||
}
|
26
lib/core/form/components/widgets/core/form-field-metadata.ts
Normal file
26
lib/core/form/components/widgets/core/form-field-metadata.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { FormFieldFileSource } from './form-field-file-source';
|
||||
|
||||
export interface FormFieldMetadata {
|
||||
[key: string]: any;
|
||||
fileSource?: FormFieldFileSource;
|
||||
link?: boolean;
|
||||
}
|
23
lib/core/form/components/widgets/core/form-field-option.ts
Normal file
23
lib/core/form/components/widgets/core/form-field-option.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
export interface FormFieldOption {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
export interface FormFieldSelectedFolder {
|
||||
accountId: string;
|
||||
folderTree: [any];
|
||||
path: string;
|
||||
pathId: string;
|
||||
site: string;
|
||||
siteId: string;
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
export interface FormFieldTemplates {
|
||||
[key: string]: string;
|
||||
}
|
54
lib/core/form/components/widgets/core/form-field-types.ts
Normal file
54
lib/core/form/components/widgets/core/form-field-types.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
export class FormFieldTypes {
|
||||
static CONTAINER: string = 'container';
|
||||
static GROUP: string = 'group';
|
||||
static DYNAMIC_TABLE: string = 'dynamic-table';
|
||||
static TEXT: string = 'text';
|
||||
static MULTILINE_TEXT: string = 'multi-line-text';
|
||||
static DROPDOWN: string = 'dropdown';
|
||||
static HYPERLINK: string = 'hyperlink';
|
||||
static RADIO_BUTTONS: string = 'radio-buttons';
|
||||
static DISPLAY_VALUE: string = 'readonly';
|
||||
static READONLY_TEXT: string = 'readonly-text';
|
||||
static UPLOAD: string = 'upload';
|
||||
static TYPEAHEAD: string = 'typeahead';
|
||||
static FUNCTIONAL_GROUP: string = 'functional-group';
|
||||
static PEOPLE: string = 'people';
|
||||
static BOOLEAN: string = 'boolean';
|
||||
static NUMBER: string = 'integer';
|
||||
static DATE: string = 'date';
|
||||
static AMOUNT: string = 'amount';
|
||||
static DOCUMENT: string = 'document';
|
||||
|
||||
static READONLY_TYPES: string[] = [
|
||||
FormFieldTypes.HYPERLINK,
|
||||
FormFieldTypes.DISPLAY_VALUE,
|
||||
FormFieldTypes.READONLY_TEXT
|
||||
];
|
||||
|
||||
static isReadOnlyType(type: string) {
|
||||
return FormFieldTypes.READONLY_TYPES.indexOf(type) > -1;
|
||||
}
|
||||
|
||||
static isContainerType(type: string) {
|
||||
return type === FormFieldTypes.CONTAINER || type === FormFieldTypes.GROUP;
|
||||
}
|
||||
}
|
@@ -0,0 +1,591 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ErrorMessageModel } from './error-message.model';
|
||||
import { FormFieldOption } from './form-field-option';
|
||||
import { FormFieldTypes } from './form-field-types';
|
||||
import {
|
||||
FixedValueFieldValidator,
|
||||
MaxLengthFieldValidator,
|
||||
MaxValueFieldValidator,
|
||||
MinLengthFieldValidator,
|
||||
MinValueFieldValidator,
|
||||
NumberFieldValidator,
|
||||
RegExFieldValidator,
|
||||
RequiredFieldValidator
|
||||
} from './form-field-validator';
|
||||
import { FormFieldModel } from './form-field.model';
|
||||
import { FormModel } from './form.model';
|
||||
|
||||
describe('FormFieldValidator', () => {
|
||||
|
||||
describe('RequiredFieldValidator', () => {
|
||||
|
||||
let validator: RequiredFieldValidator;
|
||||
|
||||
beforeEach(() => {
|
||||
validator = new RequiredFieldValidator();
|
||||
});
|
||||
|
||||
it('should require [required] setting', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT,
|
||||
value: '<value>'
|
||||
});
|
||||
|
||||
field.required = false;
|
||||
expect(validator.isSupported(field)).toBeFalsy();
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
|
||||
field.required = true;
|
||||
expect(validator.isSupported(field)).toBeTruthy();
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should skip unsupported type', () => {
|
||||
let field = new FormFieldModel(new FormModel(), { type: 'wrong-type' });
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should fail for dropdown with empty value', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.DROPDOWN,
|
||||
value: '<empty>',
|
||||
hasEmptyValue: true,
|
||||
required: true
|
||||
});
|
||||
|
||||
field.emptyOption = <FormFieldOption> { id: '<empty>' };
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
|
||||
field.value = '<non-empty>';
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should fail for radio buttons', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.RADIO_BUTTONS,
|
||||
required: true,
|
||||
value: 'one',
|
||||
options: [{ id: 'two', name: 'two' }]
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should succeed for radio buttons', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.RADIO_BUTTONS,
|
||||
required: true,
|
||||
value: 'two',
|
||||
options: [{ id: 'two', name: 'two' }]
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should fail for upload', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
value: null,
|
||||
required: true
|
||||
});
|
||||
|
||||
field.value = null;
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
|
||||
field.value = [];
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should succeed for upload', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
value: [{}],
|
||||
required: true
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should fail for text', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT,
|
||||
value: null,
|
||||
required: true
|
||||
});
|
||||
|
||||
field.value = null;
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
|
||||
field.value = '';
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should succeed for date', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.DATE,
|
||||
value: '2016-12-31',
|
||||
required: true
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should fail for date', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.DATE,
|
||||
value: null,
|
||||
required: true
|
||||
});
|
||||
|
||||
field.value = null;
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
|
||||
field.value = '';
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should succeed for text', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT,
|
||||
value: '<value>',
|
||||
required: true
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('NumberFieldValidator', () => {
|
||||
|
||||
let validator: NumberFieldValidator;
|
||||
|
||||
beforeEach(() => {
|
||||
validator = new NumberFieldValidator();
|
||||
});
|
||||
|
||||
it('should verify number', () => {
|
||||
expect(NumberFieldValidator.isNumber('1')).toBeTruthy();
|
||||
expect(NumberFieldValidator.isNumber('1.0')).toBeTruthy();
|
||||
expect(NumberFieldValidator.isNumber('-1')).toBeTruthy();
|
||||
expect(NumberFieldValidator.isNumber(1)).toBeTruthy();
|
||||
expect(NumberFieldValidator.isNumber(0)).toBeTruthy();
|
||||
expect(NumberFieldValidator.isNumber(-1)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not verify number', () => {
|
||||
expect(NumberFieldValidator.isNumber(null)).toBeFalsy();
|
||||
expect(NumberFieldValidator.isNumber(undefined)).toBeFalsy();
|
||||
expect(NumberFieldValidator.isNumber('')).toBeFalsy();
|
||||
expect(NumberFieldValidator.isNumber('one')).toBeFalsy();
|
||||
expect(NumberFieldValidator.isNumber('1q')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should allow empty number value', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: null
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow number value', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: 44
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow zero number value', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: 0
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should fail for wrong number value', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: '<value>'
|
||||
});
|
||||
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
expect(field.validationSummary).not.toBeNull();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('MinLengthFieldValidator', () => {
|
||||
|
||||
let validator: MinLengthFieldValidator;
|
||||
|
||||
beforeEach(() => {
|
||||
validator = new MinLengthFieldValidator();
|
||||
});
|
||||
|
||||
it('should require minLength defined', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT
|
||||
});
|
||||
|
||||
expect(validator.isSupported(field)).toBeFalsy();
|
||||
|
||||
field.minLength = 10;
|
||||
expect(validator.isSupported(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow empty values', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT,
|
||||
minLength: 10,
|
||||
value: null
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should succeed text validation', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT,
|
||||
minLength: 3,
|
||||
value: '1234'
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should fail text validation', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT,
|
||||
minLength: 3,
|
||||
value: '12'
|
||||
});
|
||||
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
expect(field.validationSummary).not.toBeNull();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('MaxLengthFieldValidator', () => {
|
||||
|
||||
let validator: MaxLengthFieldValidator;
|
||||
|
||||
beforeEach(() => {
|
||||
validator = new MaxLengthFieldValidator();
|
||||
});
|
||||
|
||||
it('should require maxLength defined', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT
|
||||
});
|
||||
|
||||
expect(validator.isSupported(field)).toBeFalsy();
|
||||
|
||||
field.maxLength = 10;
|
||||
expect(validator.isSupported(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow empty values', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT,
|
||||
maxLength: 10,
|
||||
value: null
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should succeed text validation', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT,
|
||||
maxLength: 3,
|
||||
value: '123'
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should fail text validation', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT,
|
||||
maxLength: 3,
|
||||
value: '1234'
|
||||
});
|
||||
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
expect(field.validationSummary).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('MinValueFieldValidator', () => {
|
||||
|
||||
let validator: MinValueFieldValidator;
|
||||
|
||||
beforeEach(() => {
|
||||
validator = new MinValueFieldValidator();
|
||||
});
|
||||
|
||||
it('should require minValue defined', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER
|
||||
});
|
||||
expect(validator.isSupported(field)).toBeFalsy();
|
||||
|
||||
field.minValue = '1';
|
||||
expect(validator.isSupported(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should support numeric widgets only', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER,
|
||||
minValue: '1'
|
||||
});
|
||||
|
||||
expect(validator.isSupported(field)).toBeTruthy();
|
||||
|
||||
field.type = FormFieldTypes.TEXT;
|
||||
expect(validator.isSupported(field)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should allow empty values', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: null,
|
||||
minValue: '1'
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should succeed for unsupported types', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should succeed validating value', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: '10',
|
||||
minValue: '10'
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should fail validating value', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: '9',
|
||||
minValue: '10'
|
||||
});
|
||||
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
expect(field.validationSummary).not.toBeNull();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('MaxValueFieldValidator', () => {
|
||||
|
||||
let validator: MaxValueFieldValidator;
|
||||
|
||||
beforeEach(() => {
|
||||
validator = new MaxValueFieldValidator();
|
||||
});
|
||||
|
||||
it('should require maxValue defined', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER
|
||||
});
|
||||
expect(validator.isSupported(field)).toBeFalsy();
|
||||
|
||||
field.maxValue = '1';
|
||||
expect(validator.isSupported(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should support numeric widgets only', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER,
|
||||
maxValue: '1'
|
||||
});
|
||||
|
||||
expect(validator.isSupported(field)).toBeTruthy();
|
||||
|
||||
field.type = FormFieldTypes.TEXT;
|
||||
expect(validator.isSupported(field)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should allow empty values', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: null,
|
||||
maxValue: '1'
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should succeed for unsupported types', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should succeed validating value', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: '10',
|
||||
maxValue: '10'
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should fail validating value', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: '11',
|
||||
maxValue: '10'
|
||||
});
|
||||
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
expect(field.validationSummary).not.toBeNull();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('RegExFieldValidator', () => {
|
||||
|
||||
let validator: RegExFieldValidator;
|
||||
|
||||
beforeEach(() => {
|
||||
validator = new RegExFieldValidator();
|
||||
});
|
||||
|
||||
it('should require regex pattern to be defined', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT
|
||||
});
|
||||
expect(validator.isSupported(field)).toBeFalsy();
|
||||
|
||||
field.regexPattern = '<pattern>';
|
||||
expect(validator.isSupported(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow empty values', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT,
|
||||
value: null,
|
||||
regexPattern: 'pattern'
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should succeed validating regex', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT,
|
||||
value: 'pattern',
|
||||
regexPattern: 'pattern'
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should fail validating regex', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT,
|
||||
value: 'some value',
|
||||
regexPattern: 'pattern'
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('FixedValueFieldValidator', () => {
|
||||
|
||||
let validator: FixedValueFieldValidator;
|
||||
|
||||
beforeEach(() => {
|
||||
validator = new FixedValueFieldValidator();
|
||||
});
|
||||
|
||||
it('should support only typeahead field', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TEXT
|
||||
});
|
||||
expect(validator.isSupported(field)).toBeFalsy();
|
||||
|
||||
field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TYPEAHEAD
|
||||
});
|
||||
|
||||
expect(validator.isSupported(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow empty values', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TYPEAHEAD,
|
||||
value: null,
|
||||
regexPattern: 'pattern'
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should succeed for a valid input value in options', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TYPEAHEAD,
|
||||
value: '1',
|
||||
options: [{id: '1', name: 'Leanne Graham'}, {id: '2', name: 'Ervin Howell'}]
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should fail for an invalid input value in options', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.TYPEAHEAD,
|
||||
value: 'Lean',
|
||||
options: [{id: '1', name: 'Leanne Graham'}, {id: '2', name: 'Ervin Howell'}]
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
432
lib/core/form/components/widgets/core/form-field-validator.ts
Normal file
432
lib/core/form/components/widgets/core/form-field-validator.ts
Normal file
@@ -0,0 +1,432 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import * as moment from 'moment';
|
||||
import { FormFieldTypes } from './form-field-types';
|
||||
import { FormFieldModel } from './form-field.model';
|
||||
|
||||
export interface FormFieldValidator {
|
||||
|
||||
isSupported(field: FormFieldModel): boolean;
|
||||
validate(field: FormFieldModel): boolean;
|
||||
|
||||
}
|
||||
|
||||
export class RequiredFieldValidator implements FormFieldValidator {
|
||||
|
||||
private supportedTypes = [
|
||||
FormFieldTypes.TEXT,
|
||||
FormFieldTypes.MULTILINE_TEXT,
|
||||
FormFieldTypes.NUMBER,
|
||||
FormFieldTypes.TYPEAHEAD,
|
||||
FormFieldTypes.DROPDOWN,
|
||||
FormFieldTypes.PEOPLE,
|
||||
FormFieldTypes.FUNCTIONAL_GROUP,
|
||||
FormFieldTypes.RADIO_BUTTONS,
|
||||
FormFieldTypes.UPLOAD,
|
||||
FormFieldTypes.AMOUNT,
|
||||
FormFieldTypes.DYNAMIC_TABLE,
|
||||
FormFieldTypes.DATE
|
||||
];
|
||||
|
||||
isSupported(field: FormFieldModel): boolean {
|
||||
return field &&
|
||||
this.supportedTypes.indexOf(field.type) > -1 &&
|
||||
field.required;
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field)) {
|
||||
|
||||
if (field.type === FormFieldTypes.DROPDOWN) {
|
||||
if (field.hasEmptyValue && field.emptyOption) {
|
||||
if (field.value === field.emptyOption.id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (field.type === FormFieldTypes.RADIO_BUTTONS) {
|
||||
let option = field.options.find(opt => opt.id === field.value);
|
||||
return !!option;
|
||||
}
|
||||
|
||||
if (field.type === FormFieldTypes.UPLOAD) {
|
||||
return field.value && field.value.length > 0;
|
||||
}
|
||||
|
||||
if (field.type === FormFieldTypes.DYNAMIC_TABLE) {
|
||||
return field.value && field.value instanceof Array && field.value.length > 0;
|
||||
}
|
||||
|
||||
if (field.value === null || field.value === undefined || field.value === '') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class NumberFieldValidator implements FormFieldValidator {
|
||||
|
||||
private supportedTypes = [
|
||||
FormFieldTypes.NUMBER,
|
||||
FormFieldTypes.AMOUNT
|
||||
];
|
||||
|
||||
static isNumber(value: any): boolean {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !isNaN(+value);
|
||||
}
|
||||
|
||||
isSupported(field: FormFieldModel): boolean {
|
||||
return field && this.supportedTypes.indexOf(field.type) > -1;
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field)) {
|
||||
if (field.value === null ||
|
||||
field.value === undefined ||
|
||||
field.value === '') {
|
||||
return true;
|
||||
}
|
||||
let valueStr = '' + field.value;
|
||||
let pattern = new RegExp(/^-?\d+$/);
|
||||
if (field.enableFractions) {
|
||||
pattern = new RegExp(/^-?[0-9]+(\.[0-9]{1,2})?$/);
|
||||
}
|
||||
if (valueStr.match(pattern)) {
|
||||
return true;
|
||||
}
|
||||
field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_NUMBER';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class DateFieldValidator implements FormFieldValidator {
|
||||
|
||||
private supportedTypes = [
|
||||
FormFieldTypes.DATE
|
||||
];
|
||||
|
||||
// Validates that the input string is a valid date formatted as <dateFormat> (default D-M-YYYY)
|
||||
static isValidDate(inputDate: string, dateFormat: string = 'D-M-YYYY'): boolean {
|
||||
if (inputDate) {
|
||||
let d = moment(inputDate, dateFormat, true);
|
||||
return d.isValid();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
isSupported(field: FormFieldModel): boolean {
|
||||
return field && this.supportedTypes.indexOf(field.type) > -1;
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.value) {
|
||||
if (DateFieldValidator.isValidDate(field.value, field.dateDisplayFormat)) {
|
||||
return true;
|
||||
}
|
||||
field.validationSummary.message = field.dateDisplayFormat;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class MinDateFieldValidator implements FormFieldValidator {
|
||||
|
||||
MIN_DATE_FORMAT = 'DD-MM-YYYY';
|
||||
|
||||
private supportedTypes = [
|
||||
FormFieldTypes.DATE
|
||||
];
|
||||
|
||||
isSupported(field: FormFieldModel): boolean {
|
||||
return field &&
|
||||
this.supportedTypes.indexOf(field.type) > -1 && !!field.minValue;
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.value) {
|
||||
const dateFormat = field.dateDisplayFormat;
|
||||
|
||||
if (!DateFieldValidator.isValidDate(field.value, dateFormat)) {
|
||||
field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_DATE';
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove time and timezone info
|
||||
let d;
|
||||
if (typeof field.value === 'string') {
|
||||
d = moment(field.value.split('T')[0], dateFormat);
|
||||
} else {
|
||||
d = field.value;
|
||||
}
|
||||
let min = moment(field.minValue, this.MIN_DATE_FORMAT);
|
||||
|
||||
if (d.isBefore(min)) {
|
||||
field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_LESS_THAN`;
|
||||
field.validationSummary.attributes.set('minValue', field.minValue.toLocaleString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class MaxDateFieldValidator implements FormFieldValidator {
|
||||
|
||||
MAX_DATE_FORMAT = 'DD-MM-YYYY';
|
||||
|
||||
private supportedTypes = [
|
||||
FormFieldTypes.DATE
|
||||
];
|
||||
|
||||
isSupported(field: FormFieldModel): boolean {
|
||||
return field &&
|
||||
this.supportedTypes.indexOf(field.type) > -1 && !!field.maxValue;
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.value) {
|
||||
const dateFormat = field.dateDisplayFormat;
|
||||
|
||||
if (!DateFieldValidator.isValidDate(field.value, dateFormat)) {
|
||||
field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_DATE';
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove time and timezone info
|
||||
let d;
|
||||
if (typeof field.value === 'string') {
|
||||
d = moment(field.value.split('T')[0], dateFormat);
|
||||
} else {
|
||||
d = field.value;
|
||||
}
|
||||
let max = moment(field.maxValue, this.MAX_DATE_FORMAT);
|
||||
|
||||
if (d.isAfter(max)) {
|
||||
field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_GREATER_THAN`;
|
||||
field.validationSummary.attributes.set('maxValue', field.maxValue.toLocaleString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class MinLengthFieldValidator implements FormFieldValidator {
|
||||
|
||||
private supportedTypes = [
|
||||
FormFieldTypes.TEXT,
|
||||
FormFieldTypes.MULTILINE_TEXT
|
||||
];
|
||||
|
||||
isSupported(field: FormFieldModel): boolean {
|
||||
return field &&
|
||||
this.supportedTypes.indexOf(field.type) > -1 &&
|
||||
field.minLength > 0;
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.value) {
|
||||
if (field.value.length >= field.minLength) {
|
||||
return true;
|
||||
}
|
||||
field.validationSummary.message = `FORM.FIELD.VALIDATOR.AT_LEAST_LONG`;
|
||||
field.validationSummary.attributes.set('minLength', field.minLength.toLocaleString());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class MaxLengthFieldValidator implements FormFieldValidator {
|
||||
|
||||
private supportedTypes = [
|
||||
FormFieldTypes.TEXT,
|
||||
FormFieldTypes.MULTILINE_TEXT
|
||||
];
|
||||
|
||||
isSupported(field: FormFieldModel): boolean {
|
||||
return field &&
|
||||
this.supportedTypes.indexOf(field.type) > -1 &&
|
||||
field.maxLength > 0;
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.value) {
|
||||
if (field.value.length <= field.maxLength) {
|
||||
return true;
|
||||
}
|
||||
field.validationSummary.message = `FORM.FIELD.VALIDATOR.NO_LONGER_THAN`;
|
||||
field.validationSummary.attributes.set('maxLength', field.maxLength.toLocaleString());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class MinValueFieldValidator implements FormFieldValidator {
|
||||
|
||||
private supportedTypes = [
|
||||
FormFieldTypes.NUMBER,
|
||||
FormFieldTypes.AMOUNT
|
||||
];
|
||||
|
||||
isSupported(field: FormFieldModel): boolean {
|
||||
return field &&
|
||||
this.supportedTypes.indexOf(field.type) > -1 &&
|
||||
NumberFieldValidator.isNumber(field.minValue);
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.value) {
|
||||
let value: number = +field.value;
|
||||
let minValue: number = +field.minValue;
|
||||
|
||||
if (value >= minValue) {
|
||||
return true;
|
||||
}
|
||||
field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_LESS_THAN`;
|
||||
field.validationSummary.attributes.set('minValue', field.minValue.toLocaleString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class MaxValueFieldValidator implements FormFieldValidator {
|
||||
|
||||
private supportedTypes = [
|
||||
FormFieldTypes.NUMBER,
|
||||
FormFieldTypes.AMOUNT
|
||||
];
|
||||
|
||||
isSupported(field: FormFieldModel): boolean {
|
||||
return field &&
|
||||
this.supportedTypes.indexOf(field.type) > -1 &&
|
||||
NumberFieldValidator.isNumber(field.maxValue);
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.value) {
|
||||
let value: number = +field.value;
|
||||
let maxValue: number = +field.maxValue;
|
||||
|
||||
if (value <= maxValue) {
|
||||
return true;
|
||||
}
|
||||
field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_GREATER_THAN`;
|
||||
field.validationSummary.attributes.set('maxValue', field.maxValue.toLocaleString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class RegExFieldValidator implements FormFieldValidator {
|
||||
|
||||
private supportedTypes = [
|
||||
FormFieldTypes.TEXT,
|
||||
FormFieldTypes.MULTILINE_TEXT
|
||||
];
|
||||
|
||||
isSupported(field: FormFieldModel): boolean {
|
||||
return field &&
|
||||
this.supportedTypes.indexOf(field.type) > -1 && !!field.regexPattern;
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.value) {
|
||||
if (field.value.length > 0 && field.value.match(new RegExp('^' + field.regexPattern + '$'))) {
|
||||
return true;
|
||||
}
|
||||
field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_VALUE';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class FixedValueFieldValidator implements FormFieldValidator {
|
||||
|
||||
private supportedTypes = [
|
||||
FormFieldTypes.TYPEAHEAD
|
||||
];
|
||||
|
||||
isSupported(field: FormFieldModel): boolean {
|
||||
return field && this.supportedTypes.indexOf(field.type) > -1;
|
||||
}
|
||||
|
||||
hasValidNameOrValidId(field: FormFieldModel): boolean {
|
||||
return this.hasValidName(field) || this.hasValidId(field);
|
||||
}
|
||||
|
||||
hasValidName(field: FormFieldModel) {
|
||||
return field.options.find(item => item.name && item.name.toLocaleLowerCase() === field.value.toLocaleLowerCase()) ? true : false;
|
||||
}
|
||||
|
||||
hasValidId(field: FormFieldModel) {
|
||||
return field.options[field.value - 1] ? true : false;
|
||||
}
|
||||
|
||||
hasStringValue(field: FormFieldModel) {
|
||||
return field.value && typeof field.value === 'string';
|
||||
}
|
||||
|
||||
hasOptions(field: FormFieldModel) {
|
||||
return field.options && field.options.length > 0;
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field)) {
|
||||
if (this.hasStringValue(field) && this.hasOptions(field) && !this.hasValidNameOrValidId(field)) {
|
||||
field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_VALUE';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export const FORM_FIELD_VALIDATORS = [
|
||||
new RequiredFieldValidator(),
|
||||
new NumberFieldValidator(),
|
||||
new MinLengthFieldValidator(),
|
||||
new MaxLengthFieldValidator(),
|
||||
new MinValueFieldValidator(),
|
||||
new MaxValueFieldValidator(),
|
||||
new RegExFieldValidator(),
|
||||
new DateFieldValidator(),
|
||||
new MinDateFieldValidator(),
|
||||
new MaxDateFieldValidator(),
|
||||
new FixedValueFieldValidator()
|
||||
];
|
378
lib/core/form/components/widgets/core/form-field.model.spec.ts
Normal file
378
lib/core/form/components/widgets/core/form-field.model.spec.ts
Normal file
@@ -0,0 +1,378 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FormFieldTypes } from './form-field-types';
|
||||
import { FormFieldModel } from './form-field.model';
|
||||
import { FormModel } from './form.model';
|
||||
|
||||
describe('FormFieldModel', () => {
|
||||
|
||||
it('should store the form reference', () => {
|
||||
let form = new FormModel();
|
||||
let model = new FormFieldModel(form);
|
||||
expect(model.form).toBe(form);
|
||||
});
|
||||
|
||||
it('should store original json', () => {
|
||||
let json = {};
|
||||
let model = new FormFieldModel(new FormModel(), json);
|
||||
expect(model.json).toBe(json);
|
||||
});
|
||||
|
||||
it('should setup with json config', () => {
|
||||
let json = {
|
||||
fieldType: '<fieldType>',
|
||||
id: '<id>',
|
||||
name: '<name>',
|
||||
type: '<type>',
|
||||
required: true,
|
||||
readOnly: true,
|
||||
overrideId: true,
|
||||
tab: '<tab>',
|
||||
restUrl: '<rest-url>',
|
||||
restResponsePath: '<rest-path>',
|
||||
restIdProperty: '<rest-id>',
|
||||
restLabelProperty: '<rest-label>',
|
||||
colspan: 1,
|
||||
options: [],
|
||||
hasEmptyValue: true,
|
||||
className: '<class>',
|
||||
optionType: '<type>',
|
||||
params: {},
|
||||
hyperlinkUrl: '<url>',
|
||||
displayText: '<text>',
|
||||
value: '<value>'
|
||||
};
|
||||
let field = new FormFieldModel(new FormModel(), json);
|
||||
Object.keys(json).forEach(key => {
|
||||
expect(field[key]).toBe(json[key]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should setup empty options collection', () => {
|
||||
let field = new FormFieldModel(new FormModel(), null);
|
||||
expect(field.options).toBeDefined();
|
||||
expect(field.options.length).toBe(0);
|
||||
|
||||
field = new FormFieldModel(new FormModel(), {options: null});
|
||||
expect(field.options).toBeDefined();
|
||||
expect(field.options.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should setup empty params', () => {
|
||||
let field = new FormFieldModel(new FormModel(), null);
|
||||
expect(field.params).toEqual({});
|
||||
|
||||
field = new FormFieldModel(new FormModel(), {params: null});
|
||||
expect(field.params).toEqual({});
|
||||
});
|
||||
|
||||
it('should update form on every value change', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {id: 'field1'});
|
||||
let value = 10;
|
||||
|
||||
spyOn(field, 'updateForm').and.callThrough();
|
||||
field.value = value;
|
||||
|
||||
expect(field.value).toBe(value);
|
||||
expect(field.updateForm).toHaveBeenCalled();
|
||||
expect(form.values['field1']).toBe(value);
|
||||
});
|
||||
|
||||
it('should get form readonly state', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, null);
|
||||
|
||||
expect(field.readOnly).toBeFalsy();
|
||||
form.readOnly = true;
|
||||
expect(field.readOnly).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should take own readonly state if form is writable', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {readOnly: true});
|
||||
|
||||
expect(form.readOnly).toBeFalsy();
|
||||
expect(field.readOnly).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should parse and leave dropdown value as is', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.DROPDOWN,
|
||||
options: [],
|
||||
value: 'deferred'
|
||||
});
|
||||
|
||||
expect(field.value).toBe('deferred');
|
||||
});
|
||||
|
||||
it('should parse the date with the default format (D-M-YYYY) if the display format is missing', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'mmddyyyy',
|
||||
name: 'MM-DD-YYYY',
|
||||
type: 'date',
|
||||
value: '2017-04-28T00:00:00.000+0000',
|
||||
required: false,
|
||||
readOnly: false,
|
||||
params: {
|
||||
field: {
|
||||
id: 'mmddyyyy',
|
||||
name: 'MM-DD-YYYY',
|
||||
type: 'date',
|
||||
value: null,
|
||||
required: false,
|
||||
readOnly: false
|
||||
}
|
||||
}
|
||||
});
|
||||
expect(field.value).toBe('28-4-2017');
|
||||
expect(form.values['mmddyyyy']).toEqual('2017-04-28T00:00:00.000Z');
|
||||
});
|
||||
|
||||
it('should parse the date with the format MM-DD-YYYY', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'mmddyyyy',
|
||||
name: 'MM-DD-YYYY',
|
||||
type: 'date',
|
||||
value: '2017-04-28T00:00:00.000+0000',
|
||||
required: false,
|
||||
readOnly: false,
|
||||
params: {
|
||||
field: {
|
||||
id: 'mmddyyyy',
|
||||
name: 'MM-DD-YYYY',
|
||||
type: 'date',
|
||||
value: null,
|
||||
required: false,
|
||||
readOnly: false
|
||||
}
|
||||
},
|
||||
dateDisplayFormat: 'MM-DD-YYYY'
|
||||
});
|
||||
expect(field.value).toBe('04-28-2017');
|
||||
expect(form.values['mmddyyyy']).toEqual('2017-04-28T00:00:00.000Z');
|
||||
});
|
||||
|
||||
it('should parse the date with the format MM-YY-DD', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'mmyydd',
|
||||
name: 'MM-YY-DD',
|
||||
type: 'date',
|
||||
value: '2017-04-28T00:00:00.000+0000',
|
||||
required: false,
|
||||
readOnly: false,
|
||||
params: {
|
||||
field: {
|
||||
id: 'mmyydd',
|
||||
name: 'MM-YY-DD',
|
||||
type: 'date',
|
||||
value: null,
|
||||
required: false,
|
||||
readOnly: false
|
||||
}
|
||||
},
|
||||
dateDisplayFormat: 'MM-YY-DD'
|
||||
});
|
||||
expect(field.value).toBe('04-17-28');
|
||||
expect(form.values['mmyydd']).toEqual('2017-04-28T00:00:00.000Z');
|
||||
});
|
||||
|
||||
it('should parse the date with the format DD-MM-YYYY', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'ddmmyyy',
|
||||
name: 'DD-MM-YYYY',
|
||||
type: 'date',
|
||||
value: '2017-04-28T00:00:00.000+0000',
|
||||
required: false,
|
||||
readOnly: false,
|
||||
params: {
|
||||
field: {
|
||||
id: 'ddmmyyy',
|
||||
name: 'DD-MM-YYYY',
|
||||
type: 'date',
|
||||
value: null,
|
||||
required: false,
|
||||
readOnly: false
|
||||
}
|
||||
},
|
||||
dateDisplayFormat: 'DD-MM-YYYY'
|
||||
});
|
||||
expect(field.value).toBe('28-04-2017');
|
||||
expect(form.values['ddmmyyy']).toEqual('2017-04-28T00:00:00.000Z');
|
||||
});
|
||||
|
||||
it('should return the label of selected dropdown value ', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.DROPDOWN,
|
||||
options: [
|
||||
{id: 'fake-option-1', name: 'fake label 1'},
|
||||
{id: 'fake-option-2', name: 'fake label 2'},
|
||||
{id: 'fake-option-3', name: 'fake label 3'}
|
||||
],
|
||||
value: 'fake-option-2'
|
||||
});
|
||||
expect(field.getOptionName()).toBe('fake label 2');
|
||||
});
|
||||
|
||||
it('should parse and resolve radio button value', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.RADIO_BUTTONS,
|
||||
options: [
|
||||
{id: 'opt1', value: 'Option 1'},
|
||||
{id: 'opt2', value: 'Option 2'}
|
||||
],
|
||||
value: 'opt2'
|
||||
});
|
||||
|
||||
expect(field.value).toBe('opt2');
|
||||
});
|
||||
|
||||
it('should parse and leave radio button value as is', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.RADIO_BUTTONS,
|
||||
options: [],
|
||||
value: 'deferred-radio'
|
||||
});
|
||||
expect(field.value).toBe('deferred-radio');
|
||||
});
|
||||
|
||||
it('should update form with empty dropdown value', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
id: 'dropdown-1',
|
||||
type: FormFieldTypes.DROPDOWN
|
||||
});
|
||||
|
||||
field.value = 'empty';
|
||||
expect(form.values['dropdown-1']).toEqual({});
|
||||
|
||||
field.value = '';
|
||||
expect(form.values['dropdown-1']).toEqual({});
|
||||
});
|
||||
|
||||
it('should update form with dropdown value', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
id: 'dropdown-2',
|
||||
type: FormFieldTypes.DROPDOWN,
|
||||
options: [
|
||||
{id: 'opt1', name: 'Option 1'},
|
||||
{id: 'opt2', name: 'Option 2'}
|
||||
]
|
||||
});
|
||||
|
||||
field.value = 'opt2';
|
||||
expect(form.values['dropdown-2']).toEqual(field.options[1]);
|
||||
});
|
||||
|
||||
it('should update form with radio button value', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
id: 'radio-1',
|
||||
type: FormFieldTypes.RADIO_BUTTONS,
|
||||
options: [
|
||||
{id: 'opt1', value: 'Option 1'},
|
||||
{id: 'opt2', value: 'Option 2'}
|
||||
]
|
||||
});
|
||||
|
||||
field.value = 'opt2';
|
||||
expect(form.values['radio-1']).toEqual(field.options[1]);
|
||||
});
|
||||
|
||||
it('should update form with the first radio button value', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
id: 'radio-2',
|
||||
type: FormFieldTypes.RADIO_BUTTONS,
|
||||
options: [
|
||||
{id: 'opt1', value: 'Option 1'},
|
||||
{id: 'opt2', value: 'Option 2'}
|
||||
]
|
||||
});
|
||||
|
||||
field.value = 'missing';
|
||||
expect(form.values['radio-2']).toEqual(field.options[0]);
|
||||
});
|
||||
|
||||
it('should not update form with display-only field value', () => {
|
||||
let form = new FormModel();
|
||||
|
||||
FormFieldTypes.READONLY_TYPES.forEach(typeName => {
|
||||
let field = new FormFieldModel(form, {
|
||||
id: typeName,
|
||||
type: typeName
|
||||
});
|
||||
|
||||
field.value = '<some value>';
|
||||
expect(form.values[field.id]).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to check if the field has options available', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
id: 'dropdown-happy',
|
||||
type: FormFieldTypes.DROPDOWN,
|
||||
options: [
|
||||
{id: 'opt1', name: 'Option 1'},
|
||||
{id: 'opt2', name: 'Option 2'}
|
||||
]
|
||||
});
|
||||
|
||||
expect(field.hasOptions()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false if field has no options', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
id: 'dropdown-sad',
|
||||
type: FormFieldTypes.DROPDOWN
|
||||
});
|
||||
|
||||
expect(field.hasOptions()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should calculate the columns in case of container type', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
type: FormFieldTypes.CONTAINER,
|
||||
numberOfColumns: 888
|
||||
});
|
||||
|
||||
expect(field.numberOfColumns).toBe(888);
|
||||
});
|
||||
|
||||
it('should calculate the columns in case of group type', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 999
|
||||
});
|
||||
|
||||
expect(field.numberOfColumns).toBe(999);
|
||||
});
|
||||
});
|
420
lib/core/form/components/widgets/core/form-field.model.ts
Normal file
420
lib/core/form/components/widgets/core/form-field.model.ts
Normal file
@@ -0,0 +1,420 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
import * as moment from 'moment';
|
||||
import { WidgetVisibilityModel } from '../../../models/widget-visibility.model';
|
||||
import { ContainerColumnModel } from './container-column.model';
|
||||
import { ErrorMessageModel } from './error-message.model';
|
||||
import { FormFieldMetadata } from './form-field-metadata';
|
||||
import { FormFieldOption } from './form-field-option';
|
||||
import { FormFieldTypes } from './form-field-types';
|
||||
import { NumberFieldValidator } from './form-field-validator';
|
||||
import { FormWidgetModel } from './form-widget.model';
|
||||
import { FormModel } from './form.model';
|
||||
|
||||
// Maps to FormFieldRepresentation
|
||||
export class FormFieldModel extends FormWidgetModel {
|
||||
|
||||
private _value: string;
|
||||
private _readOnly: boolean = false;
|
||||
private _isValid: boolean = true;
|
||||
private _required: boolean = false;
|
||||
|
||||
readonly defaultDateFormat: string = 'D-M-YYYY';
|
||||
|
||||
// model members
|
||||
fieldType: string;
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
overrideId: boolean;
|
||||
tab: string;
|
||||
rowspan: number = 1;
|
||||
colspan: number = 1;
|
||||
placeholder: string = null;
|
||||
minLength: number = 0;
|
||||
maxLength: number = 0;
|
||||
minValue: string;
|
||||
maxValue: string;
|
||||
regexPattern: string;
|
||||
options: FormFieldOption[] = [];
|
||||
restUrl: string;
|
||||
restResponsePath: string;
|
||||
restIdProperty: string;
|
||||
restLabelProperty: string;
|
||||
hasEmptyValue: boolean;
|
||||
className: string;
|
||||
optionType: string;
|
||||
params: FormFieldMetadata = {};
|
||||
hyperlinkUrl: string;
|
||||
displayText: string;
|
||||
isVisible: boolean = true;
|
||||
visibilityCondition: WidgetVisibilityModel = null;
|
||||
enableFractions: boolean = false;
|
||||
currency: string = null;
|
||||
dateDisplayFormat: string = this.dateDisplayFormat || this.defaultDateFormat;
|
||||
|
||||
// container model members
|
||||
numberOfColumns: number = 1;
|
||||
fields: FormFieldModel[] = [];
|
||||
columns: ContainerColumnModel[] = [];
|
||||
|
||||
// util members
|
||||
emptyOption: FormFieldOption;
|
||||
validationSummary: ErrorMessageModel;
|
||||
|
||||
get value(): any {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(v: any) {
|
||||
this._value = v;
|
||||
this.validate();
|
||||
this.updateForm();
|
||||
}
|
||||
|
||||
get readOnly(): boolean {
|
||||
if (this.form && this.form.readOnly) {
|
||||
return true;
|
||||
}
|
||||
return this._readOnly;
|
||||
}
|
||||
|
||||
set readOnly(readOnly: boolean) {
|
||||
this._readOnly = readOnly;
|
||||
this.updateForm();
|
||||
}
|
||||
|
||||
get required(): boolean {
|
||||
return this._required;
|
||||
}
|
||||
|
||||
set required(value: boolean) {
|
||||
this._required = value;
|
||||
this.updateForm();
|
||||
}
|
||||
|
||||
get isValid(): boolean {
|
||||
return this._isValid;
|
||||
}
|
||||
|
||||
markAsInvalid() {
|
||||
this._isValid = false;
|
||||
}
|
||||
|
||||
validate(): boolean {
|
||||
this.validationSummary = new ErrorMessageModel();
|
||||
|
||||
let validators = this.form.fieldValidators || [];
|
||||
for (let validator of validators) {
|
||||
if (!validator.validate(this)) {
|
||||
this._isValid = false;
|
||||
return this._isValid;
|
||||
}
|
||||
}
|
||||
|
||||
this._isValid = true;
|
||||
return this._isValid;
|
||||
}
|
||||
|
||||
constructor(form: FormModel, json?: any) {
|
||||
super(form, json);
|
||||
|
||||
if (json) {
|
||||
this.fieldType = json.fieldType;
|
||||
this.id = json.id;
|
||||
this.name = json.name;
|
||||
this.type = json.type;
|
||||
this._required = <boolean> json.required;
|
||||
this._readOnly = <boolean> json.readOnly || json.type === 'readonly';
|
||||
this.overrideId = <boolean> json.overrideId;
|
||||
this.tab = json.tab;
|
||||
this.restUrl = json.restUrl;
|
||||
this.restResponsePath = json.restResponsePath;
|
||||
this.restIdProperty = json.restIdProperty;
|
||||
this.restLabelProperty = json.restLabelProperty;
|
||||
this.colspan = <number> json.colspan;
|
||||
this.minLength = <number> json.minLength || 0;
|
||||
this.maxLength = <number> json.maxLength || 0;
|
||||
this.minValue = json.minValue;
|
||||
this.maxValue = json.maxValue;
|
||||
this.regexPattern = json.regexPattern;
|
||||
this.options = <FormFieldOption[]> json.options || [];
|
||||
this.hasEmptyValue = <boolean> json.hasEmptyValue;
|
||||
this.className = json.className;
|
||||
this.optionType = json.optionType;
|
||||
this.params = <FormFieldMetadata> json.params || {};
|
||||
this.hyperlinkUrl = json.hyperlinkUrl;
|
||||
this.displayText = json.displayText;
|
||||
this.visibilityCondition = <WidgetVisibilityModel> json.visibilityCondition;
|
||||
this.enableFractions = <boolean> json.enableFractions;
|
||||
this.currency = json.currency;
|
||||
this.dateDisplayFormat = json.dateDisplayFormat || this.defaultDateFormat;
|
||||
this._value = this.parseValue(json);
|
||||
this.validationSummary = new ErrorMessageModel();
|
||||
|
||||
if (json.placeholder && json.placeholder !== '' && json.placeholder !== 'null') {
|
||||
this.placeholder = json.placeholder;
|
||||
}
|
||||
|
||||
if (FormFieldTypes.isReadOnlyType(json.type)) {
|
||||
if (json.params && json.params.field) {
|
||||
if (form.processVariables) {
|
||||
const processVariable = this.getProcessVariableValue(json.params.field, form);
|
||||
if (processVariable) {
|
||||
this.value = processVariable;
|
||||
}
|
||||
} else if (json.params.field.responseVariable) {
|
||||
const formVariable = this.getVariablesValue(json.params.field.name, form);
|
||||
if (formVariable) {
|
||||
this.value = formVariable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FormFieldTypes.isContainerType(json.type)) {
|
||||
this.containerFactory(json, form);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hasEmptyValue && this.options && this.options.length > 0) {
|
||||
this.emptyOption = this.options[0];
|
||||
}
|
||||
|
||||
this.updateForm();
|
||||
}
|
||||
|
||||
private isTypeaHeadFieldType(type: string): boolean {
|
||||
return type === 'typeahead' ? true : false;
|
||||
}
|
||||
|
||||
private getFieldNameWithLabel(name: string): string {
|
||||
return name += '_LABEL';
|
||||
}
|
||||
|
||||
private getProcessVariableValue(field: any, form: FormModel) {
|
||||
let fieldName = field.name;
|
||||
if (this.isTypeaHeadFieldType(field.type)) {
|
||||
fieldName = this.getFieldNameWithLabel(field.id);
|
||||
}
|
||||
return this.findProcessVariableValue(fieldName, form);
|
||||
}
|
||||
|
||||
private getVariablesValue(variableName: string, form: FormModel) {
|
||||
let variable = form.json.variables.find((currentVariable) => {
|
||||
return currentVariable.name === variableName;
|
||||
});
|
||||
|
||||
if (variable) {
|
||||
if (variable.type === 'boolean') {
|
||||
return JSON.parse(variable.value);
|
||||
}
|
||||
|
||||
return variable.value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private findProcessVariableValue(variableName: string, form: FormModel) {
|
||||
if (form.processVariables) {
|
||||
const variable = form.processVariables.find((currentVariable) => {
|
||||
return currentVariable.name === variableName;
|
||||
});
|
||||
|
||||
if (variable) {
|
||||
return variable.type === 'boolean' ? JSON.parse(variable.value) : variable.value;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private containerFactory(json: any, form: FormModel): void {
|
||||
this.numberOfColumns = <number> json.numberOfColumns || 1;
|
||||
|
||||
this.fields = json.fields;
|
||||
|
||||
this.rowspan = 1;
|
||||
this.colspan = 1;
|
||||
|
||||
if (json.fields) {
|
||||
for (let currentField in json.fields) {
|
||||
if (json.fields.hasOwnProperty(currentField)) {
|
||||
let col = new ContainerColumnModel();
|
||||
|
||||
let fields: FormFieldModel[] = (json.fields[currentField] || []).map(f => new FormFieldModel(form, f));
|
||||
col.fields = fields;
|
||||
col.rowspan = json.fields[currentField].length;
|
||||
|
||||
col.fields.forEach((colFields: any) => {
|
||||
this.colspan = colFields.colspan > this.colspan ? colFields.colspan : this.colspan;
|
||||
});
|
||||
|
||||
this.rowspan = this.rowspan < col.rowspan ? col.rowspan : this.rowspan;
|
||||
this.columns.push(col);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parseValue(json: any): any {
|
||||
let value = json.value;
|
||||
|
||||
/*
|
||||
This is needed due to Activiti issue related to reading dropdown values as value string
|
||||
but saving back as object: { id: <id>, name: <name> }
|
||||
*/
|
||||
if (json.type === FormFieldTypes.DROPDOWN) {
|
||||
if (json.hasEmptyValue && json.options) {
|
||||
let options = <FormFieldOption[]> json.options || [];
|
||||
if (options.length > 0) {
|
||||
let emptyOption = json.options[0];
|
||||
if (value === '' || value === emptyOption.id || value === emptyOption.name) {
|
||||
value = emptyOption.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
This is needed due to Activiti issue related to reading radio button values as value string
|
||||
but saving back as object: { id: <id>, name: <name> }
|
||||
*/
|
||||
if (json.type === FormFieldTypes.RADIO_BUTTONS) {
|
||||
// Activiti has a bug with default radio button value where initial selection passed as `name` value
|
||||
// so try resolving current one with a fallback to first entry via name or id
|
||||
// TODO: needs to be reported and fixed at Activiti side
|
||||
let entry: FormFieldOption[] = this.options.filter(opt => opt.id === value || opt.name === value);
|
||||
if (entry.length > 0) {
|
||||
value = entry[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
This is needed due to Activiti displaying/editing dates in d-M-YYYY format
|
||||
but storing on server in ISO8601 format (i.e. 2013-02-04T22:44:30.652Z)
|
||||
*/
|
||||
if (json.type === FormFieldTypes.DATE) {
|
||||
if (value) {
|
||||
let dateValue;
|
||||
if (NumberFieldValidator.isNumber(value)) {
|
||||
dateValue = moment(value);
|
||||
} else {
|
||||
dateValue = moment(value.split('T')[0], 'YYYY-M-D');
|
||||
}
|
||||
if (dateValue && dateValue.isValid()) {
|
||||
value = dateValue.format(this.dateDisplayFormat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
updateForm() {
|
||||
if (!this.form) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this.type) {
|
||||
case FormFieldTypes.DROPDOWN:
|
||||
/*
|
||||
This is needed due to Activiti reading dropdown values as string
|
||||
but saving back as object: { id: <id>, name: <name> }
|
||||
*/
|
||||
if (this.value === 'empty' || this.value === '') {
|
||||
this.form.values[this.id] = {};
|
||||
} else {
|
||||
let entry: FormFieldOption[] = this.options.filter(opt => opt.id === this.value);
|
||||
if (entry.length > 0) {
|
||||
this.form.values[this.id] = entry[0];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FormFieldTypes.RADIO_BUTTONS:
|
||||
/*
|
||||
This is needed due to Activiti issue related to reading radio button values as value string
|
||||
but saving back as object: { id: <id>, name: <name> }
|
||||
*/
|
||||
let rbEntry: FormFieldOption[] = this.options.filter(opt => opt.id === this.value);
|
||||
if (rbEntry.length > 0) {
|
||||
this.form.values[this.id] = rbEntry[0];
|
||||
}
|
||||
break;
|
||||
case FormFieldTypes.UPLOAD:
|
||||
if (this.value && this.value.length > 0) {
|
||||
this.form.values[this.id] = this.value.map(elem => elem.id).join(',');
|
||||
} else {
|
||||
this.form.values[this.id] = null;
|
||||
}
|
||||
break;
|
||||
case FormFieldTypes.TYPEAHEAD:
|
||||
let taEntry: FormFieldOption[] = this.options.filter(opt => opt.id === this.value);
|
||||
if (taEntry.length > 0) {
|
||||
this.form.values[this.id] = taEntry[0];
|
||||
} else if (this.options.length > 0) {
|
||||
this.form.values[this.id] = null;
|
||||
}
|
||||
break;
|
||||
case FormFieldTypes.DATE:
|
||||
let dateValue = moment(this.value, this.dateDisplayFormat, true);
|
||||
if (dateValue && dateValue.isValid()) {
|
||||
this.form.values[this.id] = `${dateValue.format('YYYY-MM-DD')}T00:00:00.000Z`;
|
||||
} else {
|
||||
this.form.values[this.id] = null;
|
||||
this._value = this.value;
|
||||
}
|
||||
break;
|
||||
case FormFieldTypes.NUMBER:
|
||||
this.form.values[this.id] = parseInt(this.value, 10);
|
||||
break;
|
||||
case FormFieldTypes.AMOUNT:
|
||||
this.form.values[this.id] = this.enableFractions ? parseFloat(this.value) : parseInt(this.value, 10);
|
||||
break;
|
||||
default:
|
||||
if (!FormFieldTypes.isReadOnlyType(this.type) && !this.isInvalidFieldType(this.type)) {
|
||||
this.form.values[this.id] = this.value;
|
||||
}
|
||||
}
|
||||
|
||||
this.form.onFormFieldChanged(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip the invalid field type
|
||||
* @param type
|
||||
*/
|
||||
isInvalidFieldType(type: string) {
|
||||
if (type === 'container') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getOptionName(): string {
|
||||
let option: FormFieldOption = this.options.find(opt => opt.id === this.value);
|
||||
return option ? option.name : null;
|
||||
}
|
||||
|
||||
hasOptions() {
|
||||
return this.options && this.options.length > 0;
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { FormOutcomeModel } from './form-outcome.model';
|
||||
|
||||
export class FormOutcomeEvent {
|
||||
|
||||
private _outcome: FormOutcomeModel;
|
||||
private _defaultPrevented: boolean = false;
|
||||
|
||||
get outcome(): FormOutcomeModel {
|
||||
return this._outcome;
|
||||
}
|
||||
|
||||
get defaultPrevented() {
|
||||
return this._defaultPrevented;
|
||||
}
|
||||
|
||||
constructor(outcome: FormOutcomeModel) {
|
||||
this._outcome = outcome;
|
||||
}
|
||||
|
||||
preventDefault() {
|
||||
this._defaultPrevented = true;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FormOutcomeModel } from './form-outcome.model';
|
||||
import { FormModel } from './form.model';
|
||||
|
||||
describe('FormOutcomeModel', () => {
|
||||
|
||||
it('should setup with json config', () => {
|
||||
let json = {
|
||||
id: '<id>',
|
||||
name: '<name>'
|
||||
};
|
||||
let model = new FormOutcomeModel(null, json);
|
||||
expect(model.id).toBe(json.id);
|
||||
expect(model.name).toBe(json.name);
|
||||
});
|
||||
|
||||
it('should store the form reference', () => {
|
||||
let form = new FormModel();
|
||||
let model = new FormOutcomeModel(form);
|
||||
expect(model.form).toBe(form);
|
||||
});
|
||||
|
||||
it('should store original json', () => {
|
||||
let json = {};
|
||||
let model = new FormOutcomeModel(null, json);
|
||||
expect(model.json).toBe(json);
|
||||
});
|
||||
|
||||
});
|
40
lib/core/form/components/widgets/core/form-outcome.model.ts
Normal file
40
lib/core/form/components/widgets/core/form-outcome.model.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { FormWidgetModel } from './form-widget.model';
|
||||
import { FormModel } from './form.model';
|
||||
|
||||
export class FormOutcomeModel extends FormWidgetModel {
|
||||
|
||||
static SAVE_ACTION: string = 'Save'; // Activiti 'Save' action name
|
||||
static COMPLETE_ACTION: string = 'Complete'; // Activiti 'Complete' action name
|
||||
static START_PROCESS_ACTION: string = 'Start Process'; // Activiti 'Start Process' action name
|
||||
|
||||
isSystem: boolean = false;
|
||||
isSelected: boolean = false;
|
||||
|
||||
constructor(form: FormModel, json?: any) {
|
||||
super(form, json);
|
||||
|
||||
if (json) {
|
||||
this.isSystem = json.isSystem ? true : false;
|
||||
this.isSelected = form && json.name === form.selectedOutcome ? true : false;
|
||||
}
|
||||
}
|
||||
}
|
24
lib/core/form/components/widgets/core/form-values.ts
Normal file
24
lib/core/form/components/widgets/core/form-values.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
/* tslint:disable */
|
||||
import { FormFieldMetadata } from './form-field-metadata';
|
||||
|
||||
export interface FormValues extends FormFieldMetadata {
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FormWidgetModel } from './form-widget.model';
|
||||
import { FormModel } from './form.model';
|
||||
|
||||
describe('FormWidgetModel', () => {
|
||||
|
||||
class FormWidgetModelMock extends FormWidgetModel {
|
||||
constructor(form: FormModel, json: any) {
|
||||
super(form, json);
|
||||
}
|
||||
}
|
||||
|
||||
it('should store the form reference', () => {
|
||||
let form = new FormModel();
|
||||
let model = new FormWidgetModelMock(form, null);
|
||||
expect(model.form).toBe(form);
|
||||
});
|
||||
|
||||
it('should store original json', () => {
|
||||
let json = {};
|
||||
let model = new FormWidgetModelMock(null, json);
|
||||
expect(model.json).toBe(json);
|
||||
});
|
||||
|
||||
});
|
49
lib/core/form/components/widgets/core/form-widget.model.ts
Normal file
49
lib/core/form/components/widgets/core/form-widget.model.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { FormModel } from './form.model';
|
||||
|
||||
export abstract class FormWidgetModel {
|
||||
|
||||
readonly fieldType: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly type: string;
|
||||
readonly tab: string;
|
||||
|
||||
readonly form: FormModel;
|
||||
readonly json: any;
|
||||
|
||||
constructor(form: FormModel, json: any) {
|
||||
this.form = form;
|
||||
this.json = json;
|
||||
|
||||
if (json) {
|
||||
this.fieldType = json.fieldType;
|
||||
this.id = json.id;
|
||||
this.name = json.name;
|
||||
this.type = json.type;
|
||||
this.tab = json.tab;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface FormWidgetModelCache<T extends FormWidgetModel> {
|
||||
[key: string]: T;
|
||||
}
|
450
lib/core/form/components/widgets/core/form.model.spec.ts
Normal file
450
lib/core/form/components/widgets/core/form.model.spec.ts
Normal file
@@ -0,0 +1,450 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ValidateFormFieldEvent } from './../../../events/validate-form-field.event';
|
||||
import { ValidateFormEvent } from './../../../events/validate-form.event';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { ContainerModel } from './container.model';
|
||||
import { FormFieldTypes } from './form-field-types';
|
||||
import { FORM_FIELD_VALIDATORS, FormFieldValidator } from './form-field-validator';
|
||||
import { FormFieldModel } from './form-field.model';
|
||||
import { FormOutcomeModel } from './form-outcome.model';
|
||||
import { FormModel } from './form.model';
|
||||
import { TabModel } from './tab.model';
|
||||
|
||||
describe('FormModel', () => {
|
||||
|
||||
let formService: FormService;
|
||||
|
||||
beforeEach(() => {
|
||||
formService = new FormService(null, null, null);
|
||||
});
|
||||
|
||||
it('should store original json', () => {
|
||||
let json = {};
|
||||
let form = new FormModel(json);
|
||||
expect(form.json).toBe(json);
|
||||
});
|
||||
|
||||
it('should setup properties with json', () => {
|
||||
let json = {
|
||||
id: '<id>',
|
||||
name: '<name>',
|
||||
taskId: '<task-id>',
|
||||
taskName: '<task-name>'
|
||||
};
|
||||
let form = new FormModel(json);
|
||||
|
||||
Object.keys(json).forEach(key => {
|
||||
expect(form[key]).toEqual(form[key]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should take form name when task name is missing', () => {
|
||||
let json = {
|
||||
id: '<id>',
|
||||
name: '<name>'
|
||||
};
|
||||
let form = new FormModel(json);
|
||||
expect(form.taskName).toBe(json.name);
|
||||
});
|
||||
|
||||
it('should use fallback value for task name', () => {
|
||||
let form = new FormModel({});
|
||||
expect(form.taskName).toBe(FormModel.UNSET_TASK_NAME);
|
||||
});
|
||||
|
||||
it('should set readonly state from params', () => {
|
||||
let form = new FormModel({}, null, true);
|
||||
expect(form.readOnly).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should check tabs', () => {
|
||||
let form = new FormModel();
|
||||
|
||||
form.tabs = null;
|
||||
expect(form.hasTabs()).toBeFalsy();
|
||||
|
||||
form.tabs = [];
|
||||
expect(form.hasTabs()).toBeFalsy();
|
||||
|
||||
form.tabs = [new TabModel(null)];
|
||||
expect(form.hasTabs()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should check fields', () => {
|
||||
let form = new FormModel();
|
||||
|
||||
form.fields = null;
|
||||
expect(form.hasFields()).toBeFalsy();
|
||||
|
||||
form.fields = [];
|
||||
expect(form.hasFields()).toBeFalsy();
|
||||
|
||||
let field = new FormFieldModel(form);
|
||||
form.fields = [new ContainerModel(field)];
|
||||
expect(form.hasFields()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should check outcomes', () => {
|
||||
let form = new FormModel();
|
||||
|
||||
form.outcomes = null;
|
||||
expect(form.hasOutcomes()).toBeFalsy();
|
||||
|
||||
form.outcomes = [];
|
||||
expect(form.hasOutcomes()).toBeFalsy();
|
||||
|
||||
form.outcomes = [new FormOutcomeModel(null)];
|
||||
expect(form.hasOutcomes()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should parse tabs', () => {
|
||||
let json = {
|
||||
tabs: [
|
||||
{ id: 'tab1' },
|
||||
{ id: 'tab2' }
|
||||
]
|
||||
};
|
||||
|
||||
let form = new FormModel(json);
|
||||
expect(form.tabs.length).toBe(2);
|
||||
expect(form.tabs[0].id).toBe('tab1');
|
||||
expect(form.tabs[1].id).toBe('tab2');
|
||||
});
|
||||
|
||||
it('should parse fields', () => {
|
||||
let json = {
|
||||
fields: [
|
||||
{
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.CONTAINER
|
||||
},
|
||||
{
|
||||
id: 'field2',
|
||||
type: FormFieldTypes.CONTAINER
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let form = new FormModel(json);
|
||||
expect(form.fields.length).toBe(2);
|
||||
expect(form.fields[0].id).toBe('field1');
|
||||
expect(form.fields[1].id).toBe('field2');
|
||||
});
|
||||
|
||||
it('should parse fields from the definition', () => {
|
||||
let json = {
|
||||
fields: null,
|
||||
formDefinition: {
|
||||
fields: [
|
||||
{
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.CONTAINER
|
||||
},
|
||||
{
|
||||
id: 'field2',
|
||||
type: FormFieldTypes.CONTAINER
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
let form = new FormModel(json);
|
||||
expect(form.fields.length).toBe(2);
|
||||
expect(form.fields[0].id).toBe('field1');
|
||||
expect(form.fields[1].id).toBe('field2');
|
||||
});
|
||||
|
||||
it('should convert missing fields to empty collection', () => {
|
||||
let json = {
|
||||
fields: null
|
||||
};
|
||||
|
||||
let form = new FormModel(json);
|
||||
expect(form.fields).toBeDefined();
|
||||
expect(form.fields.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should put fields into corresponding tabs', () => {
|
||||
let json = {
|
||||
tabs: [
|
||||
{ id: 'tab1' },
|
||||
{ id: 'tab2' }
|
||||
],
|
||||
fields: [
|
||||
{ id: 'field1', tab: 'tab1', type: FormFieldTypes.CONTAINER },
|
||||
{ id: 'field2', tab: 'tab2', type: FormFieldTypes.CONTAINER },
|
||||
{ id: 'field3', tab: 'tab1', type: FormFieldTypes.DYNAMIC_TABLE },
|
||||
{ id: 'field4', tab: 'missing-tab', type: FormFieldTypes.DYNAMIC_TABLE }
|
||||
]
|
||||
};
|
||||
|
||||
let form = new FormModel(json);
|
||||
expect(form.tabs.length).toBe(2);
|
||||
expect(form.fields.length).toBe(4);
|
||||
|
||||
let tab1 = form.tabs[0];
|
||||
expect(tab1.fields.length).toBe(2);
|
||||
expect(tab1.fields[0].id).toBe('field1');
|
||||
expect(tab1.fields[1].id).toBe('field3');
|
||||
|
||||
let tab2 = form.tabs[1];
|
||||
expect(tab2.fields.length).toBe(1);
|
||||
expect(tab2.fields[0].id).toBe('field2');
|
||||
});
|
||||
|
||||
it('should create standard form outcomes', () => {
|
||||
let json = {
|
||||
fields: [
|
||||
{ id: 'container1' }
|
||||
]
|
||||
};
|
||||
|
||||
let form = new FormModel(json);
|
||||
expect(form.outcomes.length).toBe(3);
|
||||
|
||||
expect(form.outcomes[0].id).toBe(FormModel.SAVE_OUTCOME);
|
||||
expect(form.outcomes[0].isSystem).toBeTruthy();
|
||||
|
||||
expect(form.outcomes[1].id).toBe(FormModel.COMPLETE_OUTCOME);
|
||||
expect(form.outcomes[1].isSystem).toBeTruthy();
|
||||
|
||||
expect(form.outcomes[2].id).toBe(FormModel.START_PROCESS_OUTCOME);
|
||||
expect(form.outcomes[2].isSystem).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should create outcomes only when fields available', () => {
|
||||
let json = {
|
||||
fields: null
|
||||
};
|
||||
let form = new FormModel(json);
|
||||
expect(form.outcomes.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should use custom form outcomes', () => {
|
||||
let json = {
|
||||
fields: [
|
||||
{ id: 'container1' }
|
||||
],
|
||||
outcomes: [
|
||||
{ id: 'custom-1', name: 'custom 1' }
|
||||
]
|
||||
};
|
||||
|
||||
let form = new FormModel(json);
|
||||
expect(form.outcomes.length).toBe(2);
|
||||
|
||||
expect(form.outcomes[0].id).toBe(FormModel.SAVE_OUTCOME);
|
||||
expect(form.outcomes[0].isSystem).toBeTruthy();
|
||||
|
||||
expect(form.outcomes[1].id).toBe('custom-1');
|
||||
expect(form.outcomes[1].isSystem).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should raise validation event when validating form', (done) => {
|
||||
const form = new FormModel({}, null, false, formService);
|
||||
|
||||
formService.validateForm.subscribe(() => done());
|
||||
form.validateForm();
|
||||
});
|
||||
|
||||
it('should raise validation event when validating field', (done) => {
|
||||
const form = new FormModel({}, null, false, formService);
|
||||
const field = jasmine.createSpyObj('FormFieldModel', ['validate']);
|
||||
|
||||
formService.validateFormField.subscribe(() => done());
|
||||
form.validateField(field);
|
||||
});
|
||||
|
||||
it('should skip form validation when default behaviour prevented', () => {
|
||||
const form = new FormModel({}, null, false, formService);
|
||||
|
||||
let prevented = false;
|
||||
|
||||
formService.validateForm.subscribe((event: ValidateFormEvent) => {
|
||||
event.isValid = false;
|
||||
event.preventDefault();
|
||||
prevented = true;
|
||||
});
|
||||
|
||||
const field = jasmine.createSpyObj('FormFieldModel', ['validate']);
|
||||
spyOn(form, 'getFormFields').and.returnValue([field]);
|
||||
|
||||
form.validateForm();
|
||||
|
||||
expect(prevented).toBeTruthy();
|
||||
expect(form.isValid).toBeFalsy();
|
||||
expect(field.validate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should skip field validation when default behaviour prevented', () => {
|
||||
const form = new FormModel({}, null, false, formService);
|
||||
|
||||
let prevented = false;
|
||||
|
||||
formService.validateFormField.subscribe((event: ValidateFormFieldEvent) => {
|
||||
event.isValid = false;
|
||||
event.preventDefault();
|
||||
prevented = true;
|
||||
});
|
||||
|
||||
const field = jasmine.createSpyObj('FormFieldModel', ['validate']);
|
||||
form.validateField(field);
|
||||
|
||||
expect(prevented).toBeTruthy();
|
||||
expect(form.isValid).toBeFalsy();
|
||||
expect(field.validate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should validate fields when form validation not prevented', () => {
|
||||
const form = new FormModel({}, null, false, formService);
|
||||
|
||||
let validated = false;
|
||||
|
||||
formService.validateForm.subscribe((event: ValidateFormEvent) => {
|
||||
validated = true;
|
||||
});
|
||||
|
||||
const field = jasmine.createSpyObj('FormFieldModel', ['validate']);
|
||||
spyOn(form, 'getFormFields').and.returnValue([field]);
|
||||
|
||||
form.validateForm();
|
||||
|
||||
expect(validated).toBeTruthy();
|
||||
expect(field.validate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should validate field when field validation not prevented', () => {
|
||||
const form = new FormModel({}, null, false, formService);
|
||||
|
||||
let validated = false;
|
||||
|
||||
formService.validateFormField.subscribe((event: ValidateFormFieldEvent) => {
|
||||
validated = true;
|
||||
});
|
||||
|
||||
const field = jasmine.createSpyObj('FormFieldModel', ['validate']);
|
||||
form.validateField(field);
|
||||
|
||||
expect(validated).toBeTruthy();
|
||||
expect(field.validate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should validate form when field validation not prevented', () => {
|
||||
const form = new FormModel({}, null, false, formService);
|
||||
spyOn(form, 'validateForm').and.stub();
|
||||
|
||||
let validated = false;
|
||||
|
||||
formService.validateFormField.subscribe((event: ValidateFormFieldEvent) => {
|
||||
validated = true;
|
||||
});
|
||||
|
||||
const field: any = {
|
||||
validate() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
form.validateField(field);
|
||||
|
||||
expect(validated).toBeTruthy();
|
||||
expect(form.validateForm).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not validate form when field validation prevented', () => {
|
||||
const form = new FormModel({}, null, false, formService);
|
||||
spyOn(form, 'validateForm').and.stub();
|
||||
|
||||
let prevented = false;
|
||||
|
||||
formService.validateFormField.subscribe((event: ValidateFormFieldEvent) => {
|
||||
event.preventDefault();
|
||||
prevented = true;
|
||||
});
|
||||
|
||||
const field = jasmine.createSpyObj('FormFieldModel', ['validate']);
|
||||
form.validateField(field);
|
||||
|
||||
expect(prevented).toBeTruthy();
|
||||
expect(field.validate).not.toHaveBeenCalled();
|
||||
expect(form.validateForm).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should get field by id', () => {
|
||||
const form = new FormModel({}, null, false, formService);
|
||||
const field: any = { id: 'field1' };
|
||||
spyOn(form, 'getFormFields').and.returnValue([field]);
|
||||
|
||||
const result = form.getFieldById('field1');
|
||||
expect(result).toBe(field);
|
||||
});
|
||||
|
||||
it('should use custom field validator', () => {
|
||||
const form = new FormModel({}, null, false, formService);
|
||||
const testField = new FormFieldModel(form, {
|
||||
id: 'test-field-1'
|
||||
});
|
||||
|
||||
spyOn(form, 'getFormFields').and.returnValue([testField]);
|
||||
|
||||
let validator = <FormFieldValidator> {
|
||||
isSupported(field: FormFieldModel): boolean {
|
||||
return true;
|
||||
},
|
||||
validate(field: FormFieldModel): boolean {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
spyOn(validator, 'validate').and.callThrough();
|
||||
|
||||
form.fieldValidators = [validator];
|
||||
form.validateForm();
|
||||
|
||||
expect(validator.validate).toHaveBeenCalledWith(testField);
|
||||
});
|
||||
|
||||
it('should re-validate the field when required attribute changes', () => {
|
||||
const form = new FormModel({}, null, false, formService);
|
||||
const testField = new FormFieldModel(form, {
|
||||
id: 'test-field-1',
|
||||
required: false
|
||||
});
|
||||
|
||||
spyOn(form, 'getFormFields').and.returnValue([testField]);
|
||||
spyOn(form, 'onFormFieldChanged').and.callThrough();
|
||||
spyOn(form, 'validateField').and.callThrough();
|
||||
|
||||
testField.required = true;
|
||||
|
||||
expect(testField.required).toBeTruthy();
|
||||
expect(form.onFormFieldChanged).toHaveBeenCalledWith(testField);
|
||||
expect(form.validateField).toHaveBeenCalledWith(testField);
|
||||
});
|
||||
|
||||
it('should not change default validators export', () => {
|
||||
const form = new FormModel({}, null, false, formService);
|
||||
const defaultLength = FORM_FIELD_VALIDATORS.length;
|
||||
|
||||
expect(form.fieldValidators.length).toBe(defaultLength);
|
||||
form.fieldValidators.push(<any> {});
|
||||
|
||||
expect(form.fieldValidators.length).toBe(defaultLength + 1);
|
||||
expect(FORM_FIELD_VALIDATORS.length).toBe(defaultLength);
|
||||
});
|
||||
});
|
280
lib/core/form/components/widgets/core/form.model.ts
Normal file
280
lib/core/form/components/widgets/core/form.model.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { FormFieldEvent, ValidateFormEvent, ValidateFormFieldEvent } from './../../../events/index';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { ContainerModel } from './container.model';
|
||||
import { FormFieldTemplates } from './form-field-templates';
|
||||
import { FormFieldTypes } from './form-field-types';
|
||||
import { FormFieldModel } from './form-field.model';
|
||||
import { FormOutcomeModel } from './form-outcome.model';
|
||||
import { FormValues } from './form-values';
|
||||
import { FormWidgetModel, FormWidgetModelCache } from './form-widget.model';
|
||||
import { TabModel } from './tab.model';
|
||||
|
||||
import {
|
||||
FORM_FIELD_VALIDATORS,
|
||||
FormFieldValidator
|
||||
} from './form-field-validator';
|
||||
|
||||
export class FormModel {
|
||||
|
||||
static UNSET_TASK_NAME: string = 'Nameless task';
|
||||
static SAVE_OUTCOME: string = '$save';
|
||||
static COMPLETE_OUTCOME: string = '$complete';
|
||||
static START_PROCESS_OUTCOME: string = '$startProcess';
|
||||
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly taskId: string;
|
||||
readonly taskName: string = FormModel.UNSET_TASK_NAME;
|
||||
processDefinitionId: string;
|
||||
private _isValid: boolean = true;
|
||||
|
||||
get isValid(): boolean {
|
||||
return this._isValid;
|
||||
}
|
||||
|
||||
className: string;
|
||||
readOnly: boolean = false;
|
||||
tabs: TabModel[] = [];
|
||||
/** Stores root containers */
|
||||
fields: FormWidgetModel[] = [];
|
||||
outcomes: FormOutcomeModel[] = [];
|
||||
customFieldTemplates: FormFieldTemplates = {};
|
||||
fieldValidators: FormFieldValidator[] = [...FORM_FIELD_VALIDATORS];
|
||||
readonly selectedOutcome: string;
|
||||
|
||||
values: FormValues = {};
|
||||
processVariables: any;
|
||||
|
||||
readonly json: any;
|
||||
|
||||
hasTabs(): boolean {
|
||||
return this.tabs && this.tabs.length > 0;
|
||||
}
|
||||
|
||||
hasFields(): boolean {
|
||||
return this.fields && this.fields.length > 0;
|
||||
}
|
||||
|
||||
hasOutcomes(): boolean {
|
||||
return this.outcomes && this.outcomes.length > 0;
|
||||
}
|
||||
|
||||
constructor(json?: any, data?: FormValues, readOnly: boolean = false, protected formService?: FormService) {
|
||||
this.readOnly = readOnly;
|
||||
|
||||
if (json) {
|
||||
this.json = json;
|
||||
|
||||
this.id = json.id;
|
||||
this.name = json.name;
|
||||
this.taskId = json.taskId;
|
||||
this.taskName = json.taskName || json.name || FormModel.UNSET_TASK_NAME;
|
||||
this.processDefinitionId = json.processDefinitionId;
|
||||
this.customFieldTemplates = json.customFieldTemplates || {};
|
||||
this.selectedOutcome = json.selectedOutcome || {};
|
||||
this.className = json.className || '';
|
||||
|
||||
let tabCache: FormWidgetModelCache<TabModel> = {};
|
||||
|
||||
this.processVariables = json.processVariables;
|
||||
|
||||
this.tabs = (json.tabs || []).map(t => {
|
||||
let model = new TabModel(this, t);
|
||||
tabCache[model.id] = model;
|
||||
return model;
|
||||
});
|
||||
|
||||
this.fields = this.parseRootFields(json);
|
||||
|
||||
if (data) {
|
||||
this.loadData(data);
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.fields.length; i++) {
|
||||
let field = this.fields[i];
|
||||
if (field.tab) {
|
||||
let tab = tabCache[field.tab];
|
||||
if (tab) {
|
||||
tab.fields.push(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (json.fields) {
|
||||
let saveOutcome = new FormOutcomeModel(this, { id: FormModel.SAVE_OUTCOME, name: 'Save', isSystem: true });
|
||||
let completeOutcome = new FormOutcomeModel(this, { id: FormModel.COMPLETE_OUTCOME, name: 'Complete', isSystem: true });
|
||||
let startProcessOutcome = new FormOutcomeModel(this, { id: FormModel.START_PROCESS_OUTCOME, name: 'Start Process', isSystem: true });
|
||||
|
||||
let customOutcomes = (json.outcomes || []).map(obj => new FormOutcomeModel(this, obj));
|
||||
|
||||
this.outcomes = [saveOutcome].concat(
|
||||
customOutcomes.length > 0 ? customOutcomes : [completeOutcome, startProcessOutcome]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.validateForm();
|
||||
}
|
||||
|
||||
onFormFieldChanged(field: FormFieldModel) {
|
||||
this.validateField(field);
|
||||
if (this.formService) {
|
||||
this.formService.formFieldValueChanged.next(new FormFieldEvent(this, field));
|
||||
}
|
||||
}
|
||||
|
||||
getFieldById(fieldId: string): FormFieldModel {
|
||||
return this.getFormFields().find(field => field.id === fieldId);
|
||||
}
|
||||
|
||||
// TODO: consider evaluating and caching once the form is loaded
|
||||
getFormFields(): FormFieldModel[] {
|
||||
let result: FormFieldModel[] = [];
|
||||
|
||||
for (let i = 0; i < this.fields.length; i++) {
|
||||
let field = this.fields[i];
|
||||
|
||||
if (field instanceof ContainerModel) {
|
||||
let container = <ContainerModel> field;
|
||||
result.push(container.field);
|
||||
|
||||
container.field.columns.forEach((column) => {
|
||||
result.push(...column.fields);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
markAsInvalid() {
|
||||
this._isValid = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates entire form and all form fields.
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof FormModel
|
||||
*/
|
||||
validateForm(): void {
|
||||
const validateFormEvent = new ValidateFormEvent(this);
|
||||
|
||||
if (this.formService) {
|
||||
this.formService.validateForm.next(validateFormEvent);
|
||||
}
|
||||
|
||||
this._isValid = validateFormEvent.isValid;
|
||||
|
||||
if (validateFormEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (validateFormEvent.isValid) {
|
||||
let fields = this.getFormFields();
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
if (!fields[i].validate()) {
|
||||
this._isValid = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a specific form field, triggers form validation.
|
||||
*
|
||||
* @param {FormFieldModel} field Form field to validate.
|
||||
* @returns {void}
|
||||
* @memberof FormModel
|
||||
*/
|
||||
validateField(field: FormFieldModel): void {
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
|
||||
const validateFieldEvent = new ValidateFormFieldEvent(this, field);
|
||||
|
||||
if (this.formService) {
|
||||
this.formService.validateFormField.next(validateFieldEvent);
|
||||
}
|
||||
|
||||
if (!validateFieldEvent.isValid) {
|
||||
this._isValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (validateFieldEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!field.validate()) {
|
||||
this._isValid = false;
|
||||
return;
|
||||
}
|
||||
this.validateForm();
|
||||
}
|
||||
|
||||
// Activiti supports 3 types of root fields: container|group|dynamic-table
|
||||
private parseRootFields(json: any): FormWidgetModel[] {
|
||||
let fields = [];
|
||||
|
||||
if (json.fields) {
|
||||
fields = json.fields;
|
||||
} else if (json.formDefinition && json.formDefinition.fields) {
|
||||
fields = json.formDefinition.fields;
|
||||
}
|
||||
|
||||
let result: FormWidgetModel[] = [];
|
||||
|
||||
for (let field of fields) {
|
||||
if (field.type === FormFieldTypes.DISPLAY_VALUE) {
|
||||
// workaround for dynamic table on a completed/readonly form
|
||||
if (field.params) {
|
||||
let originalField = field.params['field'];
|
||||
if (originalField.type === FormFieldTypes.DYNAMIC_TABLE) {
|
||||
result.push(new ContainerModel(new FormFieldModel(this, field)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.push(new ContainerModel(new FormFieldModel(this, field)));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Loads external data and overrides field values
|
||||
// Typically used when form definition and form data coming from different sources
|
||||
private loadData(data: FormValues) {
|
||||
for (let field of this.getFormFields()) {
|
||||
if (data[field.id]) {
|
||||
field.json.value = data[field.id];
|
||||
field.value = field.parseValue(field.json);
|
||||
if (field.type === FormFieldTypes.DROPDOWN ||
|
||||
field.type === FormFieldTypes.RADIO_BUTTONS) {
|
||||
field.value = data[field.id].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
38
lib/core/form/components/widgets/core/group.model.ts
Normal file
38
lib/core/form/components/widgets/core/group.model.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
export class GroupModel {
|
||||
|
||||
externalId: string;
|
||||
groups: any;
|
||||
id: string;
|
||||
name: string;
|
||||
status: string;
|
||||
|
||||
constructor(json?: any) {
|
||||
if (json) {
|
||||
this.externalId = json.externalId;
|
||||
this.groups = json.groups;
|
||||
this.id = json.id;
|
||||
this.name = json.name;
|
||||
this.status = json.status;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
35
lib/core/form/components/widgets/core/index.ts
Normal file
35
lib/core/form/components/widgets/core/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
export * from './form-field-metadata';
|
||||
export * from './form-values';
|
||||
export * from './form-field-types';
|
||||
export * from './form-field-option';
|
||||
export * from './form-field-templates';
|
||||
export * from './form-widget.model';
|
||||
export * from './form-field.model';
|
||||
export * from './form.model';
|
||||
export * from './container.model';
|
||||
export * from './container-column.model';
|
||||
export * from './tab.model';
|
||||
export * from './form-outcome.model';
|
||||
export * from './form-outcome-event.model';
|
||||
export * from './form-field-validator';
|
||||
export * from './content-link.model';
|
||||
export * from './error-message.model';
|
74
lib/core/form/components/widgets/core/tab.model.spec.ts
Normal file
74
lib/core/form/components/widgets/core/tab.model.spec.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ContainerModel } from './container.model';
|
||||
import { FormModel } from './form.model';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { TabModel } from './tab.model';
|
||||
|
||||
describe('TabModel', () => {
|
||||
|
||||
it('should setup with json config', () => {
|
||||
let json = {
|
||||
id: '<id>',
|
||||
title: '<title>',
|
||||
visibilityCondition: '<condition>'
|
||||
};
|
||||
|
||||
let model = new TabModel(null, json);
|
||||
expect(model.id).toBe(json.id);
|
||||
expect(model.title).toBe(json.title);
|
||||
expect(model.isVisible).toBe(true);
|
||||
});
|
||||
|
||||
it('should not setup with json config', () => {
|
||||
let model = new TabModel(null, null);
|
||||
expect(model.id).toBeUndefined();
|
||||
expect(model.title).toBeUndefined();
|
||||
expect(model.isVisible).toBeDefined();
|
||||
expect(model.isVisible).toBe(true);
|
||||
expect(model.visibilityCondition).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should evaluate content based on fields', () => {
|
||||
let model = new TabModel(null, null);
|
||||
|
||||
model.fields = null;
|
||||
expect(model.hasContent()).toBeFalsy();
|
||||
|
||||
model.fields = [];
|
||||
expect(model.hasContent()).toBeFalsy();
|
||||
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form);
|
||||
model.fields = [new ContainerModel(field)];
|
||||
expect(model.hasContent()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should store the form reference', () => {
|
||||
let form = new FormModel();
|
||||
let model = new TabModel(form);
|
||||
expect(model.form).toBe(form);
|
||||
});
|
||||
|
||||
it('should store original json', () => {
|
||||
let json = {};
|
||||
let model = new TabModel(null, json);
|
||||
expect(model.json).toBe(json);
|
||||
});
|
||||
|
||||
});
|
44
lib/core/form/components/widgets/core/tab.model.ts
Normal file
44
lib/core/form/components/widgets/core/tab.model.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { WidgetVisibilityModel } from '../../../models/widget-visibility.model';
|
||||
import { FormWidgetModel } from './form-widget.model';
|
||||
import { FormModel } from './form.model';
|
||||
|
||||
export class TabModel extends FormWidgetModel {
|
||||
|
||||
title: string;
|
||||
isVisible: boolean = true;
|
||||
visibilityCondition: WidgetVisibilityModel;
|
||||
|
||||
fields: FormWidgetModel[] = [];
|
||||
|
||||
hasContent(): boolean {
|
||||
return this.fields && this.fields.length > 0;
|
||||
}
|
||||
|
||||
constructor(form: FormModel, json?: any) {
|
||||
super(form, json);
|
||||
|
||||
if (json) {
|
||||
this.title = json.title;
|
||||
this.visibilityCondition = <WidgetVisibilityModel> json.visibilityCondition;
|
||||
}
|
||||
}
|
||||
}
|
21
lib/core/form/components/widgets/date/date.widget.html
Normal file
21
lib/core/form/components/widgets/date/date.widget.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<div class="{{field.className}}" *ngIf="field?.isVisible" id="data-widget" [class.adf-invalid]="!field.isValid || field.validationSummary.message">
|
||||
<mat-form-field class="adf-date-widget">
|
||||
<label class="adf-label" [attr.for]="field.id">{{field.name}} ({{field.dateDisplayFormat}})<span *ngIf="isRequired()">*</span></label>
|
||||
<input matInput
|
||||
[id]="field.id"
|
||||
[matDatepicker]="datePicker"
|
||||
[(ngModel)]="displayDate"
|
||||
[required]="isRequired()"
|
||||
[disabled]="field.readOnly"
|
||||
[min]="minDate"
|
||||
[max]="maxDate"
|
||||
(focusout)="onDateChanged($event.srcElement.value)"
|
||||
(dateChange)="onDateChanged($event)"
|
||||
placeholder="{{field.placeholder}}">
|
||||
<mat-datepicker-toggle matSuffix [for]="datePicker" [disabled]="field.readOnly" ></mat-datepicker-toggle>
|
||||
</mat-form-field>
|
||||
<error-widget [error]="field.validationSummary"></error-widget>
|
||||
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||
<mat-datepicker #datePicker [touchUi]="true" [startAt]="startAt" ></mat-datepicker>
|
||||
</div>
|
||||
|
34
lib/core/form/components/widgets/date/date.widget.scss
Normal file
34
lib/core/form/components/widgets/date/date.widget.scss
Normal file
@@ -0,0 +1,34 @@
|
||||
@import '../form';
|
||||
|
||||
.adf {
|
||||
|
||||
&-date-widget {
|
||||
.mat-input-suffix {
|
||||
text-align: right;
|
||||
position: absolute;
|
||||
margin-top: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-date-widget-button {
|
||||
position: relative;
|
||||
float: right;
|
||||
}
|
||||
|
||||
&-date-input {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
&-grid-date-widget {
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&-date-widget-button__cell {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
232
lib/core/form/components/widgets/date/date.widget.spec.ts
Normal file
232
lib/core/form/components/widgets/date/date.widget.spec.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import * as moment from 'moment';
|
||||
import { ActivitiContentService } from '../../../services/activiti-alfresco.service';
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { ErrorWidgetComponent } from '../error/error.component';
|
||||
import { EcmModelService } from './../../../services/ecm-model.service';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { FormModel } from './../core/form.model';
|
||||
import { DateWidgetComponent } from './date.widget';
|
||||
|
||||
describe('DateWidgetComponent', () => {
|
||||
|
||||
let widget: DateWidgetComponent;
|
||||
let fixture: ComponentFixture<DateWidgetComponent>;
|
||||
let nativeElement: any;
|
||||
let element: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
DateWidgetComponent,
|
||||
ErrorWidgetComponent
|
||||
],
|
||||
providers: [
|
||||
FormService,
|
||||
ActivitiContentService,
|
||||
EcmModelService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
nativeElement = {
|
||||
querySelector: function () {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
fixture = TestBed.createComponent(DateWidgetComponent);
|
||||
|
||||
element = fixture.nativeElement;
|
||||
widget = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should setup min value for date picker', () => {
|
||||
let minValue = '13-03-1982';
|
||||
widget.field = new FormFieldModel(null, {
|
||||
id: 'date-id',
|
||||
name: 'date-name',
|
||||
minValue: minValue
|
||||
});
|
||||
|
||||
widget.ngOnInit();
|
||||
|
||||
let expected = moment(minValue, widget.field.dateDisplayFormat);
|
||||
expect(widget.minDate.isSame(expected)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should date field be present', () => {
|
||||
let minValue = '13-03-1982';
|
||||
widget.field = new FormFieldModel(null, {
|
||||
minValue: minValue
|
||||
});
|
||||
|
||||
widget.ngOnInit();
|
||||
|
||||
expect(element.querySelector('#dropdown-id')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should setup max value for date picker', () => {
|
||||
let maxValue = '31-03-1982';
|
||||
widget.field = new FormFieldModel(null, {
|
||||
maxValue: maxValue
|
||||
});
|
||||
widget.ngOnInit();
|
||||
|
||||
let expected = moment(maxValue, widget.field.dateDisplayFormat);
|
||||
expect(widget.maxDate.isSame(expected)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should eval visibility on date changed', () => {
|
||||
spyOn(widget, 'checkVisibility').and.callThrough();
|
||||
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
id: 'date-field-id',
|
||||
name: 'date-name',
|
||||
value: '9-9-9999',
|
||||
type: 'date',
|
||||
readOnly: 'false'
|
||||
});
|
||||
|
||||
widget.field = field;
|
||||
|
||||
widget.onDateChanged({ value: moment('12/12/2012') });
|
||||
expect(widget.checkVisibility).toHaveBeenCalledWith(field);
|
||||
});
|
||||
|
||||
describe('template check', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
widget.field = new FormFieldModel(new FormModel(), {
|
||||
id: 'date-field-id',
|
||||
name: 'date-name',
|
||||
value: '9-9-9999',
|
||||
type: 'date',
|
||||
readOnly: 'false'
|
||||
});
|
||||
widget.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should show visible date widget', async(() => {
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(element.querySelector('#date-field-id')).toBeDefined();
|
||||
expect(element.querySelector('#date-field-id')).not.toBeNull();
|
||||
let dateElement: any = element.querySelector('#date-field-id');
|
||||
expect(dateElement.value).toContain('9-9-9999');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should check correctly the min value with different formats', async(() => {
|
||||
widget.field.value = '11-30-9999';
|
||||
widget.field.dateDisplayFormat = 'MM-DD-YYYY';
|
||||
widget.field.minValue = '30-12-9999';
|
||||
widget.ngOnInit();
|
||||
widget.field.validate();
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(element.querySelector('#date-field-id')).toBeDefined();
|
||||
expect(element.querySelector('#date-field-id')).not.toBeNull();
|
||||
let dateElement: any = element.querySelector('#date-field-id');
|
||||
expect(dateElement.value).toContain('11-30-9999');
|
||||
expect(element.querySelector('.adf-error-text').textContent).toBe('FORM.FIELD.VALIDATOR.NOT_LESS_THAN');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should show the correct format type', async(() => {
|
||||
widget.field.value = '12-30-9999';
|
||||
widget.field.dateDisplayFormat = 'MM-DD-YYYY';
|
||||
widget.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(element.querySelector('#date-field-id')).toBeDefined();
|
||||
expect(element.querySelector('#date-field-id')).not.toBeNull();
|
||||
let dateElement: any = element.querySelector('#date-field-id');
|
||||
expect(dateElement.value).toContain('12-30-9999');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should hide not visible date widget', async(() => {
|
||||
widget.field.isVisible = false;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#data-widget')).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should become visibile if the visibility change to true', async(() => {
|
||||
widget.field.isVisible = false;
|
||||
fixture.detectChanges();
|
||||
widget.fieldChanged.subscribe((field) => {
|
||||
field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(element.querySelector('#date-field-id')).toBeDefined();
|
||||
expect(element.querySelector('#date-field-id')).not.toBeNull();
|
||||
let dateElement: any = element.querySelector('#date-field-id');
|
||||
expect(dateElement.value).toContain('9-9-9999');
|
||||
});
|
||||
});
|
||||
widget.checkVisibility(widget.field);
|
||||
}));
|
||||
|
||||
it('should be hided if the visibility change to false', async(() => {
|
||||
widget.fieldChanged.subscribe((field) => {
|
||||
field.isVisible = false;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(element.querySelector('#data-widget')).toBeNull();
|
||||
});
|
||||
});
|
||||
widget.checkVisibility(widget.field);
|
||||
}));
|
||||
|
||||
it('should disable date button when is readonly', async(() => {
|
||||
widget.field.readOnly = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
let dateButton = <HTMLButtonElement> element.querySelector('button');
|
||||
expect(dateButton.disabled).toBeFalsy();
|
||||
|
||||
widget.field.readOnly = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
dateButton = <HTMLButtonElement> element.querySelector('button');
|
||||
expect(dateButton.disabled).toBeTruthy();
|
||||
}));
|
||||
});
|
||||
});
|
82
lib/core/form/components/widgets/date/date.widget.ts
Normal file
82
lib/core/form/components/widgets/date/date.widget.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { UserPreferencesService } from '../../../../services';
|
||||
import { MOMENT_DATE_FORMATS, MomentDateAdapter } from '../../../../utils';
|
||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material';
|
||||
import * as moment from 'moment';
|
||||
import { Moment } from 'moment';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { baseHost, WidgetComponent } from './../widget.component';
|
||||
|
||||
@Component({
|
||||
selector: 'date-widget',
|
||||
providers: [
|
||||
{ provide: DateAdapter, useClass: MomentDateAdapter },
|
||||
{ provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS }],
|
||||
templateUrl: './date.widget.html',
|
||||
styleUrls: ['./date.widget.scss'],
|
||||
host: baseHost,
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class DateWidgetComponent extends WidgetComponent implements OnInit {
|
||||
|
||||
minDate: Moment;
|
||||
maxDate: Moment;
|
||||
|
||||
displayDate: Moment;
|
||||
|
||||
constructor(public formService: FormService,
|
||||
private dateAdapter: DateAdapter<Moment>,
|
||||
private preferences: UserPreferencesService) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.preferences.locale$.subscribe((locale) => {
|
||||
this.dateAdapter.setLocale(locale);
|
||||
});
|
||||
let momentDateAdapter = <MomentDateAdapter> this.dateAdapter;
|
||||
momentDateAdapter.overrideDisplyaFormat = this.field.dateDisplayFormat;
|
||||
|
||||
if (this.field) {
|
||||
if (this.field.minValue) {
|
||||
this.minDate = moment(this.field.minValue, 'DD/MM/YYYY');
|
||||
}
|
||||
|
||||
if (this.field.maxValue) {
|
||||
this.maxDate = moment(this.field.maxValue, 'DD/MM/YYYY');
|
||||
}
|
||||
}
|
||||
this.displayDate = moment(this.field.value, this.field.dateDisplayFormat);
|
||||
}
|
||||
|
||||
onDateChanged(newDateValue) {
|
||||
if (newDateValue && newDateValue.value) {
|
||||
this.field.value = newDateValue.value.format(this.field.dateDisplayFormat);
|
||||
} else if (newDateValue) {
|
||||
this.field.value = newDateValue;
|
||||
} else {
|
||||
this.field.value = null;
|
||||
}
|
||||
this.checkVisibility(this.field);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1 @@
|
||||
<div class="adf-display-text-widget {{field.className}}">{{field.value}}</div>
|
@@ -0,0 +1,3 @@
|
||||
.adf-display-text-widget {
|
||||
white-space: pre-wrap;
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { Component, ViewEncapsulation } from '@angular/core';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { baseHost , WidgetComponent } from './../widget.component';
|
||||
|
||||
@Component({
|
||||
selector: 'display-text-widget',
|
||||
templateUrl: './display-text.widget.html',
|
||||
styleUrls: ['./display-text.widget.scss'],
|
||||
host: baseHost,
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class DisplayTextWidgetComponentComponent extends WidgetComponent {
|
||||
|
||||
constructor(public formService: FormService) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
<div class="adf-form-document-widget {{field.className}}">
|
||||
<ng-container *ngIf="hasFile">
|
||||
<adf-content [id]="fileId" [showDocumentContent]="true"></adf-content>
|
||||
</ng-container>
|
||||
</div>
|
50
lib/core/form/components/widgets/document/document.widget.ts
Normal file
50
lib/core/form/components/widgets/document/document.widget.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { baseHost , WidgetComponent } from './../widget.component';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-form-document-widget',
|
||||
templateUrl: 'document.widget.html',
|
||||
host: baseHost,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<div class="adf-dropdown-widget {{field.className}}"
|
||||
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly" *ngIf="field?.isVisible">
|
||||
<label class="adf-label" [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label>
|
||||
<mat-form-field>
|
||||
<mat-select class="adf-select"
|
||||
[id]="field.id"
|
||||
[(ngModel)]="field.value"
|
||||
[disabled]="field.readOnly"
|
||||
(ngModelChange)="checkVisibility(field)">
|
||||
<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="isInvalidFieldRequired()"
|
||||
required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||
</div>
|
@@ -0,0 +1,27 @@
|
||||
@import '../form';
|
||||
|
||||
.adf {
|
||||
&-dropdown-widget {
|
||||
width: 100%;
|
||||
margin-top: 13px;
|
||||
|
||||
.adf-select{
|
||||
padding-top: 0 !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mat-select-value-text{
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-dropdown-required-message .adf-error-text-container{
|
||||
margin-top: 1px !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,312 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { EcmModelService } from '../../../services/ecm-model.service';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { ErrorWidgetComponent } from '../error/error.component';
|
||||
import { FormFieldOption } from './../core/form-field-option';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { FormModel } from './../core/form.model';
|
||||
import { DropdownWidgetComponent } from './dropdown.widget';
|
||||
|
||||
describe('DropdownWidgetComponent', () => {
|
||||
|
||||
function openSelect() {
|
||||
const dropdown = fixture.debugElement.query(By.css('[class="mat-select-trigger"]'));
|
||||
dropdown.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
let formService: FormService;
|
||||
let widget: DropdownWidgetComponent;
|
||||
let visibilityService: WidgetVisibilityService;
|
||||
let fixture: ComponentFixture<DropdownWidgetComponent>;
|
||||
let element: HTMLElement;
|
||||
|
||||
let fakeOptionList: FormFieldOption[] = [
|
||||
{id: 'opt_1', name: 'option_1'},
|
||||
{id: 'opt_2', name: 'option_2'},
|
||||
{id: 'opt_3', name: 'option_3'}];
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [DropdownWidgetComponent, ErrorWidgetComponent],
|
||||
providers: [FormService, EcmModelService, WidgetVisibilityService]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(DropdownWidgetComponent);
|
||||
widget = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
formService = TestBed.get(FormService);
|
||||
visibilityService = TestBed.get(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>';
|
||||
|
||||
let form = new FormModel({
|
||||
taskId: taskId
|
||||
});
|
||||
|
||||
widget.field = new FormFieldModel(form, {
|
||||
id: fieldId,
|
||||
restUrl: '<url>'
|
||||
});
|
||||
|
||||
spyOn(formService, 'getRestFieldValues').and.returnValue(
|
||||
Observable.create(observer => {
|
||||
observer.next(null);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
widget.ngOnInit();
|
||||
expect(formService.getRestFieldValues).toHaveBeenCalledWith(taskId, fieldId);
|
||||
});
|
||||
|
||||
it('should preserve empty option when loading fields', () => {
|
||||
let restFieldValue: FormFieldOption = <FormFieldOption> {id: '1', name: 'Option1'};
|
||||
spyOn(formService, 'getRestFieldValues').and.callFake(() => {
|
||||
return Observable.create(observer => {
|
||||
observer.next([restFieldValue]);
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
let form = new FormModel({taskId: '<id>'});
|
||||
let emptyOption: FormFieldOption = <FormFieldOption> {id: 'empty', name: 'Empty'};
|
||||
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 template is ready', () => {
|
||||
|
||||
describe('and dropdown is populated via taskId', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
spyOn(visibilityService, 'refreshVisibility').and.stub();
|
||||
spyOn(formService, 'getRestFieldValues').and.callFake(() => {
|
||||
return Observable.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();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let 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();
|
||||
|
||||
openSelect();
|
||||
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let dropDownElement: any = element.querySelector('#dropdown-id');
|
||||
expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('empty');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should be not visible when isVisible is false', async(() => {
|
||||
widget.field.isVisible = false;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let dropDownElement: HTMLSelectElement = <HTMLSelectElement> element.querySelector('#dropdown-id');
|
||||
expect(dropDownElement).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should became visible when isVisible is true', async(() => {
|
||||
widget.field.isVisible = false;
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#dropdown-id')).toBeNull();
|
||||
widget.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(element.querySelector('#dropdown-id')).not.toBeNull();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('and dropdown is populated via processDefinitionId', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
spyOn(visibilityService, 'refreshVisibility').and.stub();
|
||||
spyOn(formService, 'getRestFieldValuesByProcessId').and.callFake(() => {
|
||||
return Observable.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', 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();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let 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();
|
||||
|
||||
openSelect();
|
||||
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let 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();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let dropDownElement: HTMLSelectElement = <HTMLSelectElement> element.querySelector('#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.whenStable()
|
||||
.then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#dropdown-id')).not.toBeNull();
|
||||
const option = fixture.debugElement.query(By.css('.mat-option')).nativeElement;
|
||||
expect(option.innerText).toEqual('FakeValue');
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
112
lib/core/form/components/widgets/dropdown/dropdown.widget.ts
Normal file
112
lib/core/form/components/widgets/dropdown/dropdown.widget.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { LogService } from '../../../../services';
|
||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
import { FormFieldOption } from './../core/form-field-option';
|
||||
import { baseHost , WidgetComponent } from './../widget.component';
|
||||
|
||||
@Component({
|
||||
selector: 'dropdown-widget',
|
||||
templateUrl: './dropdown.widget.html',
|
||||
styleUrls: ['./dropdown.widget.scss'],
|
||||
host: baseHost,
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class DropdownWidgetComponent extends WidgetComponent implements OnInit {
|
||||
|
||||
constructor(public formService: FormService,
|
||||
private visibilityService: WidgetVisibilityService,
|
||||
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(
|
||||
(result: FormFieldOption[]) => {
|
||||
let options = [];
|
||||
if (this.field.emptyOption) {
|
||||
options.push(this.field.emptyOption);
|
||||
}
|
||||
this.field.options = options.concat((result || []));
|
||||
this.field.updateForm();
|
||||
},
|
||||
err => this.handleError(err)
|
||||
);
|
||||
}
|
||||
|
||||
getValuesByProcessDefinitionId() {
|
||||
this.formService
|
||||
.getRestFieldValuesByProcessId(
|
||||
this.field.form.processDefinitionId,
|
||||
this.field.id
|
||||
)
|
||||
.subscribe(
|
||||
(result: FormFieldOption[]) => {
|
||||
let options = [];
|
||||
if (this.field.emptyOption) {
|
||||
options.push(this.field.emptyOption);
|
||||
}
|
||||
this.field.options = options.concat((result || []));
|
||||
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;
|
||||
}
|
||||
|
||||
checkVisibility() {
|
||||
this.visibilityService.refreshVisibility(this.field.form);
|
||||
}
|
||||
|
||||
handleError(error: any) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
|
||||
isReadOnlyType(): boolean {
|
||||
return this.field.type === 'readonly' ? true : false;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable: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;
|
||||
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import * as 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)) {
|
||||
let value = row.value[column.id];
|
||||
let dateValue = moment(value, 'D-M-YYYY');
|
||||
if (!dateValue.isValid()) {
|
||||
if (summary) {
|
||||
summary.isValid = false;
|
||||
summary.text = `Invalid '${column.name}' format.`;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
export interface DynamicRowValidationSummary {
|
||||
|
||||
isValid: boolean;
|
||||
text: string;
|
||||
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
// maps to: com.activiti.model.editor.form.OptionRepresentation
|
||||
export interface DynamicTableColumnOption {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable: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;
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
export interface DynamicTableRow {
|
||||
isNew: boolean;
|
||||
selected: boolean;
|
||||
value: any;
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
<div class="{{field.className}}"
|
||||
[class.adf-invalid]="!isValid()" *ngIf="field?.isVisible">
|
||||
<div class="adf-label">{{content.name}}<span *ngIf="isRequired()">*</span></div>
|
||||
|
||||
<div *ngIf="!editMode">
|
||||
<div class="adf-table-container">
|
||||
<table class="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)">
|
||||
{{ getCellValue(row, column) }}
|
||||
</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"
|
||||
[column]="column"
|
||||
(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>
|
@@ -0,0 +1,203 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import * as 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 _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 => <DynamicTableRow> {selected: false, value: obj});
|
||||
}
|
||||
}
|
||||
|
||||
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 => <DynamicTableColumn> obj);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
flushValue() {
|
||||
if (this.field) {
|
||||
this.field.value = this.rows.map(r => r.value);
|
||||
this.field.updateForm();
|
||||
}
|
||||
}
|
||||
|
||||
moveRow(row: DynamicTableRow, offset: number) {
|
||||
let 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;
|
||||
}
|
||||
|
||||
let 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;
|
||||
}
|
||||
let 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 = <DynamicRowValidationSummary> {
|
||||
isValid: true,
|
||||
text: 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 (let col of this.columns) {
|
||||
for (let validator of this._validators) {
|
||||
if (!validator.validate(row, col, summary)) {
|
||||
return summary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
getCellValue(row: DynamicTableRow, column: DynamicTableColumn): any {
|
||||
let result = row.value[column.id];
|
||||
|
||||
if (column.type === 'Dropdown') {
|
||||
if (result) {
|
||||
return result.name;
|
||||
}
|
||||
}
|
||||
|
||||
if (column.type === 'Boolean') {
|
||||
return result ? true : false;
|
||||
}
|
||||
|
||||
if (column.type === 'Date') {
|
||||
if (result) {
|
||||
return moment(result.split('T')[0], 'YYYY-MM-DD').format('DD-MM-YYYY');
|
||||
}
|
||||
}
|
||||
|
||||
return result || '';
|
||||
}
|
||||
|
||||
getDisplayText(column: DynamicTableColumn): string {
|
||||
let result = column.name;
|
||||
if (column.type === 'Amount') {
|
||||
let currency = column.amountCurrency || '$';
|
||||
result = `${column.name} (${currency})`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,161 @@
|
||||
@import '../form';
|
||||
|
||||
@mixin mat-dynamic-table-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
$dynamic-table-font-size: 14px !default;
|
||||
$dynamic-table-header-font-size: 12px !default;
|
||||
$dynamic-table-header-sort-icon-size: 16px !default;
|
||||
$dynamic-table-header-color: mat-color($foreground, text) !default;
|
||||
$dynamic-table-header-sorted-color: mat-color($foreground, text) !default;
|
||||
$dynamic-table-header-sorted-icon-hover-color: mat-color($foreground, disabled-text) !default;
|
||||
$dynamic-table-divider-color: mat-color($foreground, text, .07) !default;
|
||||
$dynamic-table-hover-color: #eeeeee !default;
|
||||
$dynamic-table-selection-color: #e0f7fa !default;
|
||||
$dynamic-table-dividers: 1px solid $dynamic-table-divider-color !default;
|
||||
$dynamic-table-row-height: 56px !default;
|
||||
$dynamic-table-column-spacing: 36px !default;
|
||||
$dynamic-table-column-padding: $dynamic-table-column-spacing / 2;
|
||||
$dynamic-table-card-padding: 24px !default;
|
||||
$dynamic-table-cell-top: $dynamic-table-card-padding / 2;
|
||||
$dynamic-table-drag-border: 1px dashed rgb(68, 138, 255);
|
||||
|
||||
.adf {
|
||||
|
||||
&-dynamic-table {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
border: $dynamic-table-dividers;
|
||||
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;
|
||||
}
|
||||
|
||||
&.is-selected, &.is-selected:hover {
|
||||
background-color: $dynamic-table-selection-color;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline-offset: -1px;
|
||||
outline-width: 1px;
|
||||
outline-color: rgb(68, 138, 255);
|
||||
outline-style: solid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: mat-color($foreground, text);
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
height: $dynamic-table-row-height;
|
||||
border-top: $dynamic-table-dividers;
|
||||
border-bottom: $dynamic-table-dividers;
|
||||
padding-top: $dynamic-table-cell-top;
|
||||
box-sizing: border-box;
|
||||
|
||||
@include no-select;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
th {
|
||||
@include 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: $dynamic-table-header-color;
|
||||
padding-bottom: 8px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.sortable {
|
||||
@include no-select;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&.adf-dynamic-table__header--sorted-asc,
|
||||
&.adf-dynamic-table__header--sorted-desc {
|
||||
color: $dynamic-table-header-sorted-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: $dynamic-table-header-sorted-icon-hover-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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,390 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { LogService } from '../../../../services';
|
||||
import { ActivitiContentService } from '../../../services/activiti-alfresco.service';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { ErrorWidgetComponent } from '../error/error.component';
|
||||
import { EcmModelService } from './../../../services/ecm-model.service';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { FormFieldModel, FormFieldTypes, FormModel } from './../core/index';
|
||||
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 { BooleanEditorComponent } from './editors/boolean/boolean.editor';
|
||||
import { DateEditorComponent } from './editors/date/date.editor';
|
||||
import { DropdownEditorComponent } from './editors/dropdown/dropdown.editor';
|
||||
import { RowEditorComponent } from './editors/row.editor';
|
||||
import { TextEditorComponent } from './editors/text/text.editor';
|
||||
|
||||
let 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;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [DynamicTableWidgetComponent, RowEditorComponent,
|
||||
DropdownEditorComponent, DateEditorComponent, BooleanEditorComponent,
|
||||
TextEditorComponent, ErrorWidgetComponent],
|
||||
providers: [
|
||||
FormService,
|
||||
LogService,
|
||||
ActivitiContentService,
|
||||
EcmModelService,
|
||||
WidgetVisibilityService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
const field = new FormFieldModel(new FormModel());
|
||||
logService = TestBed.get(LogService);
|
||||
formService = TestBed.get(FormService);
|
||||
table = new DynamicTableModel(field, formService);
|
||||
let changeDetectorSpy = jasmine.createSpyObj('cd', ['detectChanges']);
|
||||
let nativeElementSpy = jasmine.createSpyObj('nativeElement', ['querySelector']);
|
||||
changeDetectorSpy.nativeElement = nativeElementSpy;
|
||||
let elementRefSpy = jasmine.createSpyObj('elementRef', ['']);
|
||||
elementRefSpy.nativeElement = nativeElementSpy;
|
||||
|
||||
fixture = TestBed.createComponent(DynamicTableWidgetComponent);
|
||||
element = fixture.nativeElement;
|
||||
widget = fixture.componentInstance;
|
||||
widget.content = table;
|
||||
|
||||
});
|
||||
|
||||
it('should select row on click', () => {
|
||||
let row = <DynamicTableRow> {selected: false};
|
||||
widget.onRowClicked(row);
|
||||
|
||||
expect(row.selected).toBeTruthy();
|
||||
expect(widget.content.selectedRow).toBe(row);
|
||||
});
|
||||
|
||||
it('should requre table to select clicked row', () => {
|
||||
let row = <DynamicTableRow> {selected: false};
|
||||
widget.content = null;
|
||||
widget.onRowClicked(row);
|
||||
|
||||
expect(row.selected).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should reset selected row', () => {
|
||||
let row = <DynamicTableRow> {selected: false};
|
||||
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', () => {
|
||||
let row = <DynamicTableRow> {selected: false};
|
||||
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', () => {
|
||||
let row1 = <DynamicTableRow> {};
|
||||
let row2 = <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', () => {
|
||||
let row1 = <DynamicTableRow> {};
|
||||
let row2 = <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', () => {
|
||||
let row = <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();
|
||||
|
||||
let row = <DynamicTableRow> {value: true};
|
||||
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', () => {
|
||||
let row = <DynamicTableRow> {value: {opt: {key: '1', value: 1}}};
|
||||
let 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>';
|
||||
let row = <DynamicTableRow> {value: {key: value}};
|
||||
let column = <DynamicTableColumn> {id: 'key'};
|
||||
|
||||
expect(widget.getCellValue(row, column)).toBe(value);
|
||||
});
|
||||
|
||||
it('should save changes and add new row', () => {
|
||||
let row = <DynamicTableRow> {isNew: true, value: {key: 'value'}};
|
||||
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', () => {
|
||||
let row = <DynamicTableRow> {isNew: false, value: {key: 'value'}};
|
||||
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 = <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', () => {
|
||||
let form = new FormModel();
|
||||
let 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', () => {
|
||||
let row = <DynamicTableRow> {value: {key: '100'}};
|
||||
let column = <DynamicTableColumn> {id: 'key', type: 'Amount'};
|
||||
let actual = widget.getCellValue(row, column);
|
||||
expect(actual).toBe('$ 100');
|
||||
});
|
||||
|
||||
it('should prepend custom currency for amount columns', () => {
|
||||
let row = <DynamicTableRow> {value: {key: '100'}};
|
||||
let column = <DynamicTableColumn> {id: 'key', type: 'Amount', amountCurrency: 'GBP'};
|
||||
let 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(() => {
|
||||
let 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).toBeFalsy();
|
||||
|
||||
let event: any = new Event('keyup');
|
||||
event.keyCode = 32;
|
||||
rowElement.dispatchEvent(event);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
let selectedRow = element.querySelector('#fake-dynamic-table-row-0');
|
||||
expect(selectedRow.className).toBe('adf-dynamic-table-widget__row-selected');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should focus on add button when a new row is saved', async(() => {
|
||||
let addNewRowButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#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();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(document.activeElement.id).toBe('fake-dynamic-table-add-row');
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
@@ -0,0 +1,207 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { LogService } from '../../../../services';
|
||||
import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { baseHost, 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: baseHost,
|
||||
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();
|
||||
let buttonAddRow = <HTMLButtonElement> 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 result = true;
|
||||
|
||||
if (this.content && this.content.field) {
|
||||
result = this.content.field.isValid;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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 = <DynamicTableRow> {
|
||||
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) {
|
||||
let result = this.content.getCellValue(row, column);
|
||||
if (column.type === 'Amount') {
|
||||
return (column.amountCurrency || '$') + ' ' + (result || 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
onSaveChanges() {
|
||||
if (this.content) {
|
||||
if (this.editRow.isNew) {
|
||||
let 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 <DynamicTableRow> {
|
||||
value: this.copyObject(row.value)
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
<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>
|
@@ -0,0 +1,11 @@
|
||||
.adf {
|
||||
|
||||
&-checkbox-label {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { 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', () => {
|
||||
let row = <DynamicTableRow> { value: {} };
|
||||
let column = <DynamicTableColumn> { id: 'key' };
|
||||
let event = { checked: true } ;
|
||||
|
||||
component.onValueChanged(row, column, event);
|
||||
expect(row.value[column.id]).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,46 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable: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) {
|
||||
let value: boolean = (<HTMLInputElement> event).checked;
|
||||
row.value[column.id] = value;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
<div>
|
||||
<mat-form-field class="adf-date-editor">
|
||||
<label [attr.for]="column.id">{{column.name}} (d-M-yyyy)</label>
|
||||
<input matInput
|
||||
id="dateInput"
|
||||
type="text"
|
||||
[matDatepicker]="datePicker"
|
||||
[value]="value"
|
||||
[id]="column.id"
|
||||
[required]="column.required"
|
||||
[disabled]="!column.editable"
|
||||
(focusout)="onDateChanged($event.srcElement.value)">
|
||||
<mat-datepicker-toggle *ngIf="column.editable" matSuffix [for]="datePicker" class="adf-date-editor-button" ></mat-datepicker-toggle>
|
||||
</mat-form-field>
|
||||
<mat-datepicker #datePicker (dateChange)="onDateChanged($event)" [touchUi]="true"></mat-datepicker>
|
||||
</div>
|
@@ -0,0 +1,10 @@
|
||||
.adf {
|
||||
&-date-editor {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-date-editor-button {
|
||||
position: relative;
|
||||
top: 25px;
|
||||
}
|
||||
}
|
@@ -0,0 +1,97 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import * as moment from 'moment';
|
||||
import { MaterialModule } from '../../../../../../material.module';
|
||||
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';
|
||||
|
||||
describe('DateEditorComponent', () => {
|
||||
let debugElement: DebugElement;
|
||||
let element: HTMLElement;
|
||||
let component: DateEditorComponent;
|
||||
let fixture: ComponentFixture<DateEditorComponent>;
|
||||
let row: DynamicTableRow;
|
||||
let column: DynamicTableColumn;
|
||||
let table: DynamicTableModel;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
DateEditorComponent
|
||||
],
|
||||
imports: [
|
||||
MaterialModule
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DateEditorComponent);
|
||||
component = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
debugElement = fixture.debugElement;
|
||||
|
||||
row = <DynamicTableRow> { value: { date: '1879-03-14T00:00:00.000Z' } };
|
||||
column = <DynamicTableColumn> { id: 'date', type: 'Date' };
|
||||
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 create instance of DateEditorComponent', () => {
|
||||
expect(fixture.componentInstance instanceof DateEditorComponent).toBe(true, 'should create DateEditorComponent');
|
||||
});
|
||||
|
||||
it('should update fow value on change', () => {
|
||||
component.ngOnInit();
|
||||
let newDate = moment('14-03-1879', 'DD-MM-YYYY');
|
||||
component.onDateChanged(newDate);
|
||||
expect(row.value[column.id]).toBe('1879-03-14T00:00:00.000Z');
|
||||
});
|
||||
|
||||
it('should update row value upon user input', () => {
|
||||
const input = '14-03-2016';
|
||||
|
||||
component.ngOnInit();
|
||||
component.onDateChanged(input);
|
||||
|
||||
let 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 = '14-03-2016';
|
||||
|
||||
component.ngOnInit();
|
||||
component.onDateChanged(input);
|
||||
|
||||
expect(table.flushValue).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,83 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { UserPreferencesService } from '../../../../../../services';
|
||||
import { MOMENT_DATE_FORMATS, MomentDateAdapter } from '../../../../../../utils';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material';
|
||||
import * as moment from 'moment';
|
||||
import { Moment } from 'moment';
|
||||
import { DynamicTableColumn } from './../../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from './../../dynamic-table-row.model';
|
||||
import { DynamicTableModel } from './../../dynamic-table.widget.model';
|
||||
|
||||
@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 {
|
||||
|
||||
DATE_FORMAT: string = 'DD-MM-YYYY';
|
||||
|
||||
value: any;
|
||||
|
||||
@Input()
|
||||
table: DynamicTableModel;
|
||||
|
||||
@Input()
|
||||
row: DynamicTableRow;
|
||||
|
||||
@Input()
|
||||
column: DynamicTableColumn;
|
||||
|
||||
minDate: Moment;
|
||||
maxDate: Moment;
|
||||
|
||||
constructor(private dateAdapter: DateAdapter<Moment>,
|
||||
private preferences: UserPreferencesService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.preferences.locale$.subscribe((locale) => {
|
||||
this.dateAdapter.setLocale(locale);
|
||||
});
|
||||
let momentDateAdapter = <MomentDateAdapter> this.dateAdapter;
|
||||
momentDateAdapter.overrideDisplyaFormat = this.DATE_FORMAT;
|
||||
|
||||
this.value = moment(this.table.getCellValue(this.row, this.column), this.DATE_FORMAT);
|
||||
}
|
||||
|
||||
onDateChanged(newDateValue) {
|
||||
if (newDateValue) {
|
||||
let momentDate = moment(newDateValue, this.DATE_FORMAT, true);
|
||||
|
||||
if (!momentDate.isValid()) {
|
||||
this.row.value[this.column.id] = '';
|
||||
} else {
|
||||
this.row.value[this.column.id] = `${momentDate.format('YYYY-MM-DD')}T00:00:00.000Z`;
|
||||
this.table.flushValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
<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"
|
||||
(change)="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>
|
@@ -0,0 +1,5 @@
|
||||
.adf {
|
||||
&-dropdown-editor-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@@ -0,0 +1,308 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { EcmModelService } from '../../../../../services/ecm-model.service';
|
||||
import { MaterialModule } from '../../../../../../material.module';
|
||||
import { FormService } from './../../../../../services/form.service';
|
||||
import { FormFieldModel, FormModel } from './../../../core/index';
|
||||
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';
|
||||
|
||||
describe('DropdownEditorComponent', () => {
|
||||
|
||||
let component: DropdownEditorComponent;
|
||||
let formService: FormService;
|
||||
let form: FormModel;
|
||||
let table: DynamicTableModel;
|
||||
let column: DynamicTableColumn;
|
||||
let row: DynamicTableRow;
|
||||
|
||||
beforeEach(() => {
|
||||
formService = new FormService(null, null, null);
|
||||
|
||||
row = <DynamicTableRow> {value: {dropdown: 'one'}};
|
||||
column = <DynamicTableColumn> {
|
||||
id: 'dropdown',
|
||||
options: [
|
||||
<DynamicTableColumnOption> {id: '1', name: 'one'},
|
||||
<DynamicTableColumnOption> {id: '2', name: 'two'}
|
||||
]
|
||||
};
|
||||
|
||||
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';
|
||||
|
||||
let restResults = [
|
||||
<DynamicTableColumnOption> {id: '11', name: 'eleven'},
|
||||
<DynamicTableColumnOption> {id: '12', name: 'twelve'}
|
||||
];
|
||||
|
||||
spyOn(formService, 'getRestFieldValuesColumn').and.returnValue(
|
||||
Observable.create(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(
|
||||
Observable.create(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 gettig options with task id', () => {
|
||||
column.optionType = 'rest';
|
||||
const error = 'error';
|
||||
|
||||
spyOn(formService, 'getRestFieldValuesColumn').and.returnValue(
|
||||
Observable.throw(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';
|
||||
let procForm = new FormModel({processDefinitionId: '<process-definition-id>'});
|
||||
let procTable = new DynamicTableModel(new FormFieldModel(procForm, {id: '<field-id>'}), formService);
|
||||
component.table = procTable;
|
||||
const error = 'error';
|
||||
|
||||
spyOn(formService, 'getRestFieldValuesColumnByProcessId').and.returnValue(
|
||||
Observable.throw(error)
|
||||
);
|
||||
spyOn(component, 'handleError').and.stub();
|
||||
|
||||
component.ngOnInit();
|
||||
expect(component.handleError).toHaveBeenCalledWith(error);
|
||||
});
|
||||
|
||||
it('should update row on value change', () => {
|
||||
let event = {value: 'two'};
|
||||
component.onValueChanged(row, column, event);
|
||||
expect(row.value[column.id]).toBe(column.options[1]);
|
||||
});
|
||||
|
||||
describe('when template is ready', () => {
|
||||
|
||||
function openSelect() {
|
||||
const dropdown = fixture.debugElement.query(By.css('[class="mat-select-trigger"]'));
|
||||
dropdown.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
let dropDownEditorComponent: DropdownEditorComponent;
|
||||
let fixture: ComponentFixture<DropdownEditorComponent>;
|
||||
let element: HTMLElement;
|
||||
let stubFormService;
|
||||
let fakeOptionList: DynamicTableColumnOption[] = [{
|
||||
id: 'opt_1',
|
||||
name: 'option_1'
|
||||
}, {
|
||||
id: 'opt_2',
|
||||
name: 'option_2'
|
||||
}, {id: 'opt_3', name: 'option_3'}];
|
||||
let dynamicTable: DynamicTableModel;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [MaterialModule],
|
||||
declarations: [DropdownEditorComponent],
|
||||
providers: [FormService, EcmModelService]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(DropdownEditorComponent);
|
||||
dropDownEditorComponent = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
describe('and dropdown is populated via taskId', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
stubFormService = fixture.debugElement.injector.get(FormService);
|
||||
spyOn(stubFormService, 'getRestFieldValuesColumn').and.returnValue(Observable.of(fakeOptionList));
|
||||
row = <DynamicTableRow> {value: {dropdown: 'one'}};
|
||||
column = <DynamicTableColumn> {
|
||||
id: 'column-id',
|
||||
optionType: 'rest',
|
||||
options: [
|
||||
<DynamicTableColumnOption> {id: '1', name: 'one'},
|
||||
<DynamicTableColumnOption> {id: '2', name: 'two'}
|
||||
]
|
||||
};
|
||||
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', async(() => {
|
||||
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(async(() => {
|
||||
stubFormService = fixture.debugElement.injector.get(FormService);
|
||||
spyOn(stubFormService, 'getRestFieldValuesColumnByProcessId').and.returnValue(Observable.of(fakeOptionList));
|
||||
row = <DynamicTableRow> {value: {dropdown: 'one'}};
|
||||
column = <DynamicTableColumn> {
|
||||
id: 'column-id',
|
||||
optionType: 'rest',
|
||||
options: [
|
||||
<DynamicTableColumnOption> {id: '1', name: 'one'},
|
||||
<DynamicTableColumnOption> {id: '2', name: 'two'}
|
||||
]
|
||||
};
|
||||
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', async(() => {
|
||||
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();
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,110 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { LogService } from '../../../../../../services';
|
||||
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() {
|
||||
let 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(
|
||||
(result: DynamicTableColumnOption[]) => {
|
||||
this.column.options = result || [];
|
||||
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(
|
||||
(result: DynamicTableColumnOption[]) => {
|
||||
this.column.options = result || [];
|
||||
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 = (<HTMLInputElement> event).value;
|
||||
value = column.options.find(opt => opt.name === value);
|
||||
row.value[column.id] = value;
|
||||
}
|
||||
|
||||
handleError(error: any) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
.row-editor {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.row-editor__validation-summary {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.row-editor__invalid .row-editor__validation-summary {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
color: #d50000;
|
||||
visibility: visible;
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
<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="'Boolean'">
|
||||
<adf-boolean-editor
|
||||
[table]="table"
|
||||
[row]="row"
|
||||
[column]="column">
|
||||
</adf-boolean-editor>
|
||||
</div>
|
||||
<div *ngSwitchDefault>
|
||||
<adf-text-editor
|
||||
[table]="table"
|
||||
[row]="row"
|
||||
[column]="column">
|
||||
</adf-text-editor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<error-widget [error]="validationSummary.text"></error-widget>
|
||||
<div>
|
||||
<button mat-button (click)="onCancelChanges()">Cancel</button>
|
||||
<button mat-button (click)="onSaveChanges()">Save</button>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,82 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FormFieldModel, FormModel } from '../../core';
|
||||
import { FormService } from './../../../../services/form.service';
|
||||
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';
|
||||
import { RowEditorComponent } from './row.editor';
|
||||
|
||||
describe('RowEditorComponent', () => {
|
||||
|
||||
let component: RowEditorComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
component = new RowEditorComponent();
|
||||
const field = new FormFieldModel(new FormModel());
|
||||
component.table = new DynamicTableModel(field, new FormService(null, null, null));
|
||||
component.row = <DynamicTableRow> {};
|
||||
component.column = <DynamicTableColumn> {};
|
||||
});
|
||||
|
||||
it('should be valid upon init', () => {
|
||||
expect(component.validationSummary.isValid).toBeTruthy();
|
||||
expect(component.validationSummary.text).toBeNull();
|
||||
});
|
||||
|
||||
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(
|
||||
<DynamicRowValidationSummary> {isValid: true, text: null}
|
||||
);
|
||||
component.save.subscribe(e => {
|
||||
expect(e.table).toBe(component.table);
|
||||
expect(e.row).toBe(component.row);
|
||||
expect(e.column).toBe(component.column);
|
||||
done();
|
||||
});
|
||||
component.onSaveChanges();
|
||||
});
|
||||
|
||||
it('should not emit [save] event for invalid row', () => {
|
||||
spyOn(component.table, 'validateRow').and.returnValue(
|
||||
<DynamicRowValidationSummary> {isValid: false, text: 'error'}
|
||||
);
|
||||
let raised = false;
|
||||
component.save.subscribe(e => raised = true);
|
||||
component.onSaveChanges();
|
||||
expect(raised).toBeFalsy();
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,77 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable: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 = <DynamicRowValidationSummary> { isValid: true, text: null };
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
<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>
|
@@ -0,0 +1,6 @@
|
||||
|
||||
.adf {
|
||||
&-text-editor {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { 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', () => {
|
||||
let row = <DynamicTableRow> { value: {} };
|
||||
let column = <DynamicTableColumn> { id: 'key' };
|
||||
|
||||
const value = '<value>';
|
||||
let event = { target: { value } };
|
||||
|
||||
editor.onValueChanged(row, column, event);
|
||||
expect(row.value[column.id]).toBe(value);
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,52 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable: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) {
|
||||
let value: any = (<HTMLInputElement> event.target).value;
|
||||
row.value[column.id] = value;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable: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)) {
|
||||
let value = row.value[column.id];
|
||||
if (value === null ||
|
||||
value === undefined ||
|
||||
value === '' ||
|
||||
this.isNumber(value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (summary) {
|
||||
summary.isValid = false;
|
||||
summary.text = `Field '${column.name}' must be a number.`;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable: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)) {
|
||||
let value = row.value[column.id];
|
||||
if (column.required) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
if (summary) {
|
||||
summary.isValid = false;
|
||||
summary.text = `Field '${column.name}' is required.`;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
<div class="adf-error-text-container">
|
||||
<div *ngIf="error.isActive()" [@transitionMessages]="_subscriptAnimationState">
|
||||
<div class="adf-error-text">{{error.message | translate:translateParameters}}</div>
|
||||
<mat-icon class="adf-error-icon">warning</mat-icon>
|
||||
</div>
|
||||
<div *ngIf="required" [@transitionMessages]="_subscriptAnimationState">
|
||||
<div class="adf-error-text">{{required}}</div>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,4 @@
|
||||
.adf-error-text{
|
||||
width: 85%;
|
||||
}
|
||||
|
71
lib/core/form/components/widgets/error/error.component.ts
Normal file
71
lib/core/form/components/widgets/error/error.component.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { Component, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core';
|
||||
import { ErrorMessageModel } from '../core/index';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { baseHost , WidgetComponent } from './../widget.component';
|
||||
|
||||
@Component({
|
||||
selector: 'error-widget',
|
||||
templateUrl: './error.component.html',
|
||||
styleUrls: ['./error.component.scss'],
|
||||
animations: [
|
||||
trigger('transitionMessages', [
|
||||
state('enter', style({opacity: 1, transform: 'translateY(0%)'})),
|
||||
transition('void => enter', [
|
||||
style({opacity: 0, transform: 'translateY(-100%)'}),
|
||||
animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')
|
||||
])
|
||||
])
|
||||
],
|
||||
host: baseHost,
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class ErrorWidgetComponent extends WidgetComponent implements OnChanges {
|
||||
|
||||
@Input()
|
||||
error: ErrorMessageModel;
|
||||
|
||||
@Input()
|
||||
required: string;
|
||||
|
||||
translateParameters: any = null;
|
||||
|
||||
_subscriptAnimationState: string = '';
|
||||
|
||||
constructor(public formService: FormService) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['required']) {
|
||||
this.required = changes.required.currentValue;
|
||||
this._subscriptAnimationState = 'enter';
|
||||
}
|
||||
if (changes['error']) {
|
||||
if (changes.error.currentValue.isActive()) {
|
||||
this.error = changes.error.currentValue;
|
||||
this.translateParameters = this.error.getAttributesAsJsonObj();
|
||||
this._subscriptAnimationState = 'enter';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
71
lib/core/form/components/widgets/form.scss
Normal file
71
lib/core/form/components/widgets/form.scss
Normal file
@@ -0,0 +1,71 @@
|
||||
@import './hyperlink/hyperlink.widget';
|
||||
@import './container/container.widget';
|
||||
@import './people/people.widget';
|
||||
|
||||
@mixin adf-form-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$warn: map-get($theme, warn);
|
||||
|
||||
@include mat-hyperlink-widget-theme($theme);
|
||||
|
||||
ul > li > form-field > .adf-focus {
|
||||
.adf-label {
|
||||
color: mat-color($primary);
|
||||
}
|
||||
}
|
||||
|
||||
.adf {
|
||||
|
||||
&-error-text-container {
|
||||
height: 20px;
|
||||
margin-top: -12px;
|
||||
}
|
||||
|
||||
&-error-text {
|
||||
padding: 1px;
|
||||
height: 16px;
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
float: left;
|
||||
color: mat-color($warn);
|
||||
}
|
||||
|
||||
&-error-icon {
|
||||
float: right;
|
||||
font-size: 17px;
|
||||
color: mat-color($warn);
|
||||
}
|
||||
|
||||
&-label {
|
||||
color: rgb(186, 186, 186);;
|
||||
}
|
||||
|
||||
&-invalid {
|
||||
|
||||
.mat-input-underline {
|
||||
background-color: #f44336 !important;
|
||||
}
|
||||
|
||||
.adf-file {
|
||||
border-color: mat-color($warn);
|
||||
}
|
||||
|
||||
.mat-input-prefix {
|
||||
color: mat-color($warn);
|
||||
}
|
||||
|
||||
.adf-input {
|
||||
border-color: mat-color($warn);
|
||||
}
|
||||
|
||||
.adf-label {
|
||||
color: mat-color($warn);
|
||||
&:after {
|
||||
background-color: mat-color($warn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user