mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-31 17:38:48 +00:00
#726 basic form validation support
- initial validation workflow - validation for Required field (text, multiline text widgets) - disable form outcomes on failed validation
This commit is contained in:
@@ -4,8 +4,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div *ngIf="hasForm()">
|
<div *ngIf="hasForm()">
|
||||||
<div class="mdl-card mdl-shadow--2dp activiti-form-container">
|
<div class="mdl-card mdl-shadow--2dp activiti-form-container">
|
||||||
<div *ngIf="isTitleEnabled()" class="mdl-card__title">
|
<div class="mdl-card__title">
|
||||||
<h2 class="mdl-card__title-text">{{form.taskName}}</h2>
|
<i class="material-icons">{{ form.isValid ? 'event_available' : 'event_busy' }}</i>
|
||||||
|
<h2 *ngIf="isTitleEnabled()" class="mdl-card__title-text">{{form.taskName}}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="mdl-card__media">
|
<div class="mdl-card__media">
|
||||||
<div *ngIf="form.hasTabs()">
|
<div *ngIf="form.hasTabs()">
|
||||||
@@ -19,9 +20,9 @@
|
|||||||
<div *ngIf="form.hasOutcomes()" class="mdl-card__actions mdl-card--border">
|
<div *ngIf="form.hasOutcomes()" class="mdl-card__actions mdl-card--border">
|
||||||
<button *ngFor="let outcome of form.outcomes"
|
<button *ngFor="let outcome of form.outcomes"
|
||||||
alfresco-mdl-button
|
alfresco-mdl-button
|
||||||
[disabled]="readOnly"
|
[disabled]="!isOutcomeButtonEnabled(outcome)"
|
||||||
[class.mdl-button--colored]="!outcome.isSystem"
|
[class.mdl-button--colored]="!outcome.isSystem"
|
||||||
[class.activiti-form-hide-button]="!isOutcomeButtonEnabled(outcome)"
|
[class.activiti-form-hide-button]="!isOutcomeButtonVisible(outcome)"
|
||||||
(click)="onOutcomeClicked(outcome, $event)">
|
(click)="onOutcomeClicked(outcome, $event)">
|
||||||
{{outcome.name}}
|
{{outcome.name}}
|
||||||
</button>
|
</button>
|
||||||
|
@@ -98,13 +98,13 @@ describe('ActivitiForm', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not enable outcome button when model missing', () => {
|
it('should not enable outcome button when model missing', () => {
|
||||||
expect(formComponent.isOutcomeButtonEnabled(null)).toBeFalsy();
|
expect(formComponent.isOutcomeButtonVisible(null)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should enable custom outcome buttons', () => {
|
it('should enable custom outcome buttons', () => {
|
||||||
let formModel = new FormModel();
|
let formModel = new FormModel();
|
||||||
let outcome = new FormOutcomeModel(formModel, { id: 'action1', name: 'Action 1' });
|
let outcome = new FormOutcomeModel(formModel, { id: 'action1', name: 'Action 1' });
|
||||||
expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeTruthy();
|
expect(formComponent.isOutcomeButtonVisible(outcome)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -113,10 +113,10 @@ describe('ActivitiForm', () => {
|
|||||||
let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.SAVE_ACTION });
|
let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.SAVE_ACTION });
|
||||||
|
|
||||||
formComponent.showSaveButton = true;
|
formComponent.showSaveButton = true;
|
||||||
expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeTruthy();
|
expect(formComponent.isOutcomeButtonVisible(outcome)).toBeTruthy();
|
||||||
|
|
||||||
formComponent.showSaveButton = false;
|
formComponent.showSaveButton = false;
|
||||||
expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy();
|
expect(formComponent.isOutcomeButtonVisible(outcome)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow controlling [save] button visibility', () => {
|
it('should allow controlling [save] button visibility', () => {
|
||||||
@@ -124,10 +124,10 @@ describe('ActivitiForm', () => {
|
|||||||
let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.COMPLETE_ACTION });
|
let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.COMPLETE_ACTION });
|
||||||
|
|
||||||
formComponent.showCompleteButton = true;
|
formComponent.showCompleteButton = true;
|
||||||
expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeTruthy();
|
expect(formComponent.isOutcomeButtonVisible(outcome)).toBeTruthy();
|
||||||
|
|
||||||
formComponent.showCompleteButton = false;
|
formComponent.showCompleteButton = false;
|
||||||
expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy();
|
expect(formComponent.isOutcomeButtonVisible(outcome)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load form on refresh', () => {
|
it('should load form on refresh', () => {
|
||||||
|
@@ -128,7 +128,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
|
|||||||
showSaveButton: boolean = true;
|
showSaveButton: boolean = true;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
showDebugButton: boolean = false;
|
showDebugButton: boolean = true;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
readOnly: boolean = false;
|
readOnly: boolean = false;
|
||||||
@@ -175,6 +175,21 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isOutcomeButtonEnabled(outcome: FormOutcomeModel): boolean {
|
isOutcomeButtonEnabled(outcome: FormOutcomeModel): boolean {
|
||||||
|
if (this.form.readOnly) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outcome) {
|
||||||
|
// Make 'Save' button always available
|
||||||
|
if (outcome.name === FormOutcomeModel.SAVE_ACTION) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return this.form.isValid;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isOutcomeButtonVisible(outcome: FormOutcomeModel): boolean {
|
||||||
if (outcome && outcome.name) {
|
if (outcome && outcome.name) {
|
||||||
if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) {
|
if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) {
|
||||||
return this.showCompleteButton;
|
return this.showCompleteButton;
|
||||||
|
@@ -65,6 +65,17 @@ export class FormFieldModel extends FormWidgetModel {
|
|||||||
return this._readOnly;
|
return this._readOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isValid(): boolean {
|
||||||
|
|
||||||
|
if (this.required) {
|
||||||
|
if (this.type === FormFieldTypes.TEXT || this.type === FormFieldTypes.MULTILINE_TEXT) {
|
||||||
|
return this._value ? true : false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(form: FormModel, json?: any) {
|
constructor(form: FormModel, json?: any) {
|
||||||
super(form, json);
|
super(form, json);
|
||||||
|
|
||||||
@@ -129,7 +140,6 @@ export class FormFieldModel extends FormWidgetModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateForm() {
|
updateForm() {
|
||||||
|
|
||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case FormFieldTypes.DROPDOWN:
|
case FormFieldTypes.DROPDOWN:
|
||||||
/*
|
/*
|
||||||
@@ -177,5 +187,7 @@ export class FormFieldModel extends FormWidgetModel {
|
|||||||
this.form.values[this.id] = this.value;
|
this.form.values[this.id] = this.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.form.onFormFieldChanged(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,7 @@ import { FormValues } from './form-values';
|
|||||||
import { ContainerModel } from './container.model';
|
import { ContainerModel } from './container.model';
|
||||||
import { TabModel } from './tab.model';
|
import { TabModel } from './tab.model';
|
||||||
import { FormOutcomeModel } from './form-outcome.model';
|
import { FormOutcomeModel } from './form-outcome.model';
|
||||||
|
import { FormFieldModel } from './form-field.model';
|
||||||
|
|
||||||
export class FormModel {
|
export class FormModel {
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ export class FormModel {
|
|||||||
private _name: string;
|
private _name: string;
|
||||||
private _taskId: string;
|
private _taskId: string;
|
||||||
private _taskName: string = FormModel.UNSET_TASK_NAME;
|
private _taskName: string = FormModel.UNSET_TASK_NAME;
|
||||||
|
private _isValid: boolean = true;
|
||||||
|
|
||||||
get id(): string {
|
get id(): string {
|
||||||
return this._id;
|
return this._id;
|
||||||
@@ -48,6 +50,10 @@ export class FormModel {
|
|||||||
return this._taskName;
|
return this._taskName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isValid(): boolean {
|
||||||
|
return this._isValid;
|
||||||
|
}
|
||||||
|
|
||||||
readOnly: boolean = false;
|
readOnly: boolean = false;
|
||||||
tabs: TabModel[] = [];
|
tabs: TabModel[] = [];
|
||||||
fields: ContainerModel[] = [];
|
fields: ContainerModel[] = [];
|
||||||
@@ -102,7 +108,8 @@ export class FormModel {
|
|||||||
if (field.tab) {
|
if (field.tab) {
|
||||||
let tab = tabCache[field.tab];
|
let tab = tabCache[field.tab];
|
||||||
if (tab) {
|
if (tab) {
|
||||||
tab.fields.push(new ContainerModel(this, field.json));
|
// tab.fields.push(new ContainerModel(this, field.json));
|
||||||
|
tab.fields.push(field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,6 +126,48 @@ export class FormModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onFormFieldChanged(field: FormFieldModel) {
|
||||||
|
this.validateField(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: evaluate and cache once the form is loaded
|
||||||
|
private getFormFields(): FormFieldModel[] {
|
||||||
|
let result: FormFieldModel[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < this.fields.length; i++) {
|
||||||
|
let container = this.fields[i];
|
||||||
|
for (let j = 0; j < container.columns.length; j++) {
|
||||||
|
let column = container.columns[j];
|
||||||
|
for (let k = 0; k < column.fields.length; k++) {
|
||||||
|
let field = column.fields[k];
|
||||||
|
result.push(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateForm() {
|
||||||
|
this._isValid = true;
|
||||||
|
let fields = this.getFormFields();
|
||||||
|
for (let i = 0; i < fields.length; i++) {
|
||||||
|
if (!fields[i].isValid()) {
|
||||||
|
this._isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateField(field: FormFieldModel) {
|
||||||
|
if (!field) return;
|
||||||
|
if (!field.isValid()) {
|
||||||
|
this._isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.validateForm();
|
||||||
|
}
|
||||||
|
|
||||||
private parseContainerFields(json: any): ContainerModel[] {
|
private parseContainerFields(json: any): ContainerModel[] {
|
||||||
let fields = [];
|
let fields = [];
|
||||||
|
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
type="text"
|
type="text"
|
||||||
rows= "3"
|
rows= "3"
|
||||||
[attr.id]="field.id"
|
[attr.id]="field.id"
|
||||||
|
[attr.required]="isRequired()"
|
||||||
[(ngModel)]="field.value"
|
[(ngModel)]="field.value"
|
||||||
(ngModelChange)="checkVisibility(field)"
|
(ngModelChange)="checkVisibility(field)"
|
||||||
[disabled]="field.readOnly">
|
[disabled]="field.readOnly">
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
<input class="mdl-textfield__input"
|
<input class="mdl-textfield__input"
|
||||||
type="text"
|
type="text"
|
||||||
[attr.id]="field.id"
|
[attr.id]="field.id"
|
||||||
|
[attr.required]="isRequired()"
|
||||||
[(ngModel)]="field.value"
|
[(ngModel)]="field.value"
|
||||||
(ngModelChange)="checkVisibility(field)"
|
(ngModelChange)="checkVisibility(field)"
|
||||||
[disabled]="field.readOnly">
|
[disabled]="field.readOnly">
|
||||||
|
@@ -36,6 +36,13 @@ export class WidgetComponent implements AfterViewInit {
|
|||||||
return this.field ? true : false;
|
return this.field ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isRequired(): any {
|
||||||
|
if (this.field && this.field.required) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
this.setupMaterialComponents();
|
this.setupMaterialComponents();
|
||||||
this.fieldChanged.emit(this.field);
|
this.fieldChanged.emit(this.field);
|
||||||
|
Reference in New Issue
Block a user