diff --git a/ng2-components/ng2-activiti-form/README.md b/ng2-components/ng2-activiti-form/README.md index abd65ce003..2c440d58a5 100644 --- a/ng2-components/ng2-activiti-form/README.md +++ b/ng2-components/ng2-activiti-form/README.md @@ -244,22 +244,25 @@ will also be executed after your custom code.** ## Supported form widgets +Form renderer provides support for all basic widgets: + - [x] Tabs - [x] Text - [x] Multiline Text - [x] Number - [x] Checkbox -- [ ] Date +- [x] Date - Dropdown * [x] Manual * [x] REST service - * [ ] Data source - [x] Typeahead -- [ ] Amount -- [x] Radio buttons +- [x] Amount +- Radio buttons + * [x] Manual + * [x] REST service - [x] People - [x] Group of People -- [ ] Dynamic Table +- [x] Dynamic Table - [x] Hyperlink - Header * [x] Plain header diff --git a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.html b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.html index f42f0b9900..ae6aaaf3c9 100644 --- a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.html +++ b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.html @@ -14,7 +14,25 @@
- +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ UNKNOWN WIDGET TYPE: {{field.type}} +
+
+
diff --git a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.spec.ts b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.spec.ts index 04d20b3989..14ee3e96aa 100644 --- a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.spec.ts +++ b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.spec.ts @@ -18,7 +18,7 @@ import { Observable } from 'rxjs/Rx'; import { SimpleChange } from '@angular/core'; import { ActivitiForm } from './activiti-form.component'; -import { FormModel, FormOutcomeModel, FormFieldModel, FormOutcomeEvent } from './widgets/index'; +import { FormModel, FormOutcomeModel, FormFieldModel, FormOutcomeEvent, FormFieldTypes } from './widgets/index'; import { FormService } from './../services/form.service'; import { WidgetVisibilityService } from './../services/widget-visibility.service'; import { NodeService } from './../services/node.service'; @@ -554,7 +554,7 @@ describe('ActivitiForm', () => { let form = formComponent.parseForm({ id: '', fields: [ - { id: 'field1' } + { id: 'field1', type: FormFieldTypes.CONTAINER } ] }); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/checkbox/checkbox.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/checkbox/checkbox.widget.html index 1e7ba71ff5..a5ca9176e3 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/checkbox/checkbox.widget.html +++ b/ng2-components/ng2-activiti-form/src/components/widgets/checkbox/checkbox.widget.html @@ -3,6 +3,7 @@ [attr.id]="field.id" [attr.required]="isRequired()" class="mdl-checkbox__input" + [checked]="field.value" [(ngModel)]="field.value" (ngModelChange)="checkVisibility(field)" [disabled]="field.readOnly"> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.html index 877fb95363..ccab11975e 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.html +++ b/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.html @@ -11,7 +11,7 @@ {{content.name}}
-
+
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.spec.ts index 85a530f08b..e3da7ed9c2 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.spec.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.spec.ts @@ -145,8 +145,8 @@ describe('ContainerWidget', () => { name: 'fake-cont-2-name', type: FormFieldTypes.GROUP }); - fakeContainerVisible.isVisible = true; - fakeContainerInvisible.isVisible = false; + fakeContainerVisible.field.isVisible = true; + fakeContainerInvisible.field.isVisible = false; }); afterEach(() => { @@ -180,7 +180,7 @@ describe('ContainerWidget', () => { containerWidgetComponent.content = fakeContainerVisible; fixture.detectChanges(); containerWidgetComponent.formValueChanged.subscribe((res) => { - containerWidgetComponent.content.isVisible = false; + containerWidgetComponent.content.field.isVisible = false; fixture.detectChanges(); fixture.whenStable() .then(() => { @@ -194,7 +194,7 @@ describe('ContainerWidget', () => { it('should show header when it becomes visible', async(() => { containerWidgetComponent.content = fakeContainerInvisible; containerWidgetComponent.formValueChanged.subscribe((res) => { - containerWidgetComponent.content.isVisible = true; + containerWidgetComponent.content.field.isVisible = true; fixture.detectChanges(); fixture.whenStable() .then(() => { diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/container.model.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/container.model.spec.ts index 643929b392..cdea916fda 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/container.model.spec.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/container.model.spec.ts @@ -51,8 +51,7 @@ describe('ContainerModel', () => { type: '', tab: '', numberOfColumns: 2, - params: {}, - visibilityCondition: {} + params: {} }; let container = new ContainerModel(null, json); Object.keys(json).forEach(key => { @@ -107,7 +106,12 @@ describe('ContainerModel', () => { }); expect(container.isCollapsible()).toBeFalsy(); - container.type = FormFieldTypes.GROUP; + container = new ContainerModel(new FormModel(), { + type: FormFieldTypes.GROUP, + params: { + allowCollapse: true + } + }); expect(container.isCollapsible()).toBeTruthy(); }); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/container.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/container.model.ts index bf6af192f4..4de70c6642 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/container.model.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/container.model.ts @@ -21,24 +21,20 @@ import { ContainerColumnModel } from './container-column.model'; import { FormFieldTypes } from './form-field-types'; import { FormModel } from './form.model'; import { FormFieldModel } from './form-field.model'; -import { WidgetVisibilityModel } from '../../../models/widget-visibility.model'; -// TODO: inherit FormFieldModel export class ContainerModel extends FormWidgetModel { - fieldType: string; - id: string; - name: string; - type: string; - tab: string; + field: FormFieldModel; numberOfColumns: number = 1; params: FormFieldMetadata = {}; - isVisible: boolean = true; - visibilityCondition: WidgetVisibilityModel = null; columns: ContainerColumnModel[] = []; isExpanded: boolean = true; + get isVisible(): boolean { + return this.field.isVisible; + } + isGroup(): boolean { return this.type === FormFieldTypes.GROUP; } @@ -67,14 +63,9 @@ export class ContainerModel extends FormWidgetModel { super(form, json); if (json) { - this.fieldType = json.fieldType; - this.id = json.id; - this.name = json.name; - this.type = json.type; - this.tab = json.tab; + this.field = new FormFieldModel(form, json); this.numberOfColumns = json.numberOfColumns; this.params = json.params || {}; - this.visibilityCondition = json.visibilityCondition; let columnSize: number = 12; if (this.numberOfColumns > 1) { @@ -98,4 +89,22 @@ export class ContainerModel extends FormWidgetModel { this.isExpanded = !this.isCollapsedByDefault(); } } + + getFormFields(): FormFieldModel[] { + let result: FormFieldModel[] = []; + + if (this.field) { + result.push(this.field); + } + + for (let j = 0; j < this.columns.length; j++) { + let column = this.columns[j]; + for (let k = 0; k < column.fields.length; k++) { + let field = column.fields[k]; + result.push(field); + } + } + + return result; + } } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table-column.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table-column.ts new file mode 100644 index 0000000000..a5b4692f8f --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table-column.ts @@ -0,0 +1,48 @@ +/*! + * @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. + */ + +// 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; +} + +// maps to: com.activiti.model.editor.form.OptionRepresentation +export interface DynamicTableColumnOption { + id: string; + name: string; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table-row.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table-row.ts new file mode 100644 index 0000000000..ce471b13b2 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table-row.ts @@ -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. + */ + +export interface DynamicTableRow { + + isNew: boolean; + selected: boolean; + value: any; + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table.model.ts new file mode 100644 index 0000000000..175f0a2fdb --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table.model.ts @@ -0,0 +1,250 @@ +/*! + * @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'; +import { FormFieldModel } from './form-field.model'; +import { DynamicTableColumn } from './dynamic-table-column'; +import { DynamicTableRow } from './dynamic-table-row'; + +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(form: FormModel, json?: any) { + super(form, json); + + if (json) { + this.field = new FormFieldModel(form, json); + + if (json.columnDefinitions) { + this.columns = json.columnDefinitions.map(obj => obj); + this.visibleColumns = this.columns.filter(col => col.visible); + } + + if (json.value) { + this.rows = json.value.map(obj => { selected: false, value: obj }); + } + } + + this._validators = [ + new RequiredCellValidator(), + new NumberCellValidator() + ]; + } + + 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 { + let summary = { + isValid: true, + text: null + }; + + 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 || ''; + } +} + +export interface DynamicRowValidationSummary { + + isValid: boolean; + text: string; + +} + +export interface CellValidator { + + isSupported(column: DynamicTableColumn): boolean; + validate(row: DynamicTableRow, column: DynamicTableColumn, summary?: DynamicRowValidationSummary): boolean; + +} + +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; + } + +} + +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; + } +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-types.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-types.ts index f673b8cea3..9d0e23f5be 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-types.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-types.ts @@ -18,6 +18,7 @@ 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'; diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-outcome.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-outcome.model.ts index f0eb0abb05..219ddb7a41 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-outcome.model.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-outcome.model.ts @@ -23,25 +23,12 @@ export class FormOutcomeModel extends FormWidgetModel { static SAVE_ACTION: string = 'Save'; // Activiti 'Save' action name static COMPLETE_ACTION: string = 'Complete'; // Activiti 'Complete' action name - private _id: string; - private _name: string; - isSystem: boolean = false; - get id() { - return this._id; - } - - get name() { - return this._name; - } - constructor(form: FormModel, json?: any) { super(form, json); if (json) { - this._id = json.id; - this._name = json.name; this.isSystem = json.isSystem ? true : false; } } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-widget.model.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-widget.model.spec.ts index 29b0dcb6ca..18198a3cf6 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-widget.model.spec.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-widget.model.spec.ts @@ -20,15 +20,21 @@ import { FormWidgetModel } from './form-widget.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 FormWidgetModel(form, null); + let model = new FormWidgetModelMock(form, null); expect(model.form).toBe(form); }); it('should store original json', () => { let json = {}; - let model = new FormWidgetModel(null, json); + let model = new FormWidgetModelMock(null, json); expect(model.json).toBe(json); }); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-widget.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-widget.model.ts index 39415c2547..7e5c173204 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-widget.model.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-widget.model.ts @@ -17,22 +17,28 @@ import { FormModel } from './form.model'; -export class FormWidgetModel { +export abstract class FormWidgetModel { - private _form: FormModel; - private _json: any; + readonly fieldType: string; + readonly id: string; + readonly name: string; + readonly type: string; + readonly tab: string; - get form(): FormModel { - return this._form; - } - - get json(): any { - return this._json; - } + readonly form: FormModel; + readonly json: any; constructor(form: FormModel, json: any) { - this._form = form; - this._json = json; + 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; + } } } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.spec.ts index 21e1993240..7fabcd0722 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.spec.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.spec.ts @@ -20,6 +20,7 @@ import { TabModel } from './tab.model'; import { ContainerModel } from './container.model'; import { FormOutcomeModel } from './form-outcome.model'; import { FormValues } from './form-values'; +import { FormFieldTypes } from './form-field-types'; describe('FormModel', () => { @@ -118,8 +119,14 @@ describe('FormModel', () => { it('should parse fields', () => { let json = { fields: [ - { id: 'field1' }, - { id: 'field2' } + { + id: 'field1', + type: FormFieldTypes.CONTAINER + }, + { + id: 'field2', + type: FormFieldTypes.CONTAINER + } ] }; @@ -134,8 +141,14 @@ describe('FormModel', () => { fields: null, formDefinition: { fields: [ - { id: 'field1' }, - { id: 'field2' } + { + id: 'field1', + type: FormFieldTypes.CONTAINER + }, + { + id: 'field2', + type: FormFieldTypes.CONTAINER + } ] } }; @@ -163,10 +176,10 @@ describe('FormModel', () => { { id: 'tab2' } ], fields: [ - { id: 'field1', tab: 'tab1' }, - { id: 'field2', tab: 'tab2' }, - { id: 'field3', tab: 'tab1' }, - { id: 'field4', tab: 'missing-tab' } + { 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 } ] }; @@ -226,7 +239,7 @@ describe('FormModel', () => { let form = new FormModel(json, data); expect(form.fields.length).toBe(1); - let container = form.fields[0]; + let container = form.fields[0]; expect(container.columns.length).toBe(2); let column1 = container.columns[0]; diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.ts index b3079ec4d6..4a9096b759 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.ts @@ -15,12 +15,14 @@ * limitations under the License. */ -import { FormWidgetModelCache } from './form-widget.model'; +import { FormWidgetModel, FormWidgetModelCache } from './form-widget.model'; import { FormValues } from './form-values'; import { ContainerModel } from './container.model'; import { TabModel } from './tab.model'; import { FormOutcomeModel } from './form-outcome.model'; import { FormFieldModel } from './form-field.model'; +import { FormFieldTypes } from './form-field-types'; +import { DynamicTableModel } from './dynamic-table.model'; export class FormModel { @@ -28,44 +30,25 @@ export class FormModel { static SAVE_OUTCOME: string = '$save'; static COMPLETE_OUTCOME: string = '$complete'; - private _id: string; - private _name: string; - private _taskId: string; - private _taskName: string = FormModel.UNSET_TASK_NAME; + readonly id: string; + readonly name: string; + readonly taskId: string; + readonly taskName: string = FormModel.UNSET_TASK_NAME; + private _isValid: boolean = true; - get id(): string { - return this._id; - } - - get name(): string { - return this._name; - } - - get taskId(): string { - return this._taskId; - } - - get taskName(): string { - return this._taskName; - } - get isValid(): boolean { return this._isValid; } readOnly: boolean = false; tabs: TabModel[] = []; - fields: ContainerModel[] = []; + fields: FormWidgetModel[] = []; outcomes: FormOutcomeModel[] = []; values: FormValues = {}; - private _json: any; - - get json() { - return this._json; - } + readonly json: any; hasTabs(): boolean { return this.tabs && this.tabs.length > 0; @@ -81,13 +64,14 @@ export class FormModel { constructor(json?: any, data?: FormValues, readOnly: boolean = false) { 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; + 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; let tabCache: FormWidgetModelCache = {}; @@ -97,7 +81,7 @@ export class FormModel { return model; }); - this.fields = this.parseContainerFields(json); + this.fields = this.parseRootFields(json); if (data) { this.loadData(data); @@ -108,7 +92,6 @@ export class FormModel { if (field.tab) { let tab = tabCache[field.tab]; if (tab) { - // tab.fields.push(new ContainerModel(this, field.json)); tab.fields.push(field); } } @@ -131,18 +114,21 @@ export class FormModel { this.validateField(field); } - // TODO: evaluate and cache once the form is loaded - private getFormFields(): FormFieldModel[] { + // 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 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); - } + let field = this.fields[i]; + + if (field.type === FormFieldTypes.CONTAINER || field.type === FormFieldTypes.GROUP) { + let container = field; + result.push(...container.getFormFields()); + } + + if (field.type === FormFieldTypes.DYNAMIC_TABLE) { + let dynamicTable = field; + result.push(dynamicTable.field); } } @@ -171,7 +157,8 @@ export class FormModel { this.validateForm(); } - private parseContainerFields(json: any): ContainerModel[] { + // Activiti supports 2 types of root fields: 'container' and 'dynamic-table'. + private parseRootFields(json: any): FormWidgetModel[] { let fields = []; if (json.fields) { @@ -180,23 +167,34 @@ export class FormModel { fields = json.formDefinition.fields; } - return fields.map(obj => new ContainerModel(this, obj)); + let result: FormWidgetModel[] = []; + + for (let field of fields) { + if (field.type === FormFieldTypes.CONTAINER || field.type === FormFieldTypes.GROUP ) { + result.push(new ContainerModel(this, field)); + } else if (field.type === FormFieldTypes.DYNAMIC_TABLE) { + result.push(new DynamicTableModel(this, field)); + } else 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 DynamicTableModel(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 i = 0; i < this.fields.length; i++) { - let container = this.fields[i]; - for (let i = 0; i < container.columns.length; i++) { - let column = container.columns[i]; - for (let i = 0; i < column.fields.length; i++) { - let field = column.fields[i]; - if (data[field.id]) { - field.json.value = data[field.id]; - field.value = data[field.id]; - } - } + for (let field of this.getFormFields()) { + if (data[field.id]) { + field.json.value = data[field.id]; + field.value = data[field.id]; } } } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/index.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/index.ts index f2ba0588ae..902340e910 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/index.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/index.ts @@ -28,3 +28,6 @@ export * from './tab.model'; export * from './form-outcome.model'; export * from './form-outcome-event.model'; export * from './form-field-validator'; +export * from './dynamic-table.model'; +export * from './dynamic-table-column'; +export * from './dynamic-table-row'; diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/tab.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/tab.model.ts index ccc0df7466..2a6945e076 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/tab.model.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/tab.model.ts @@ -16,18 +16,16 @@ */ import { FormWidgetModel } from './form-widget.model'; -import { ContainerModel } from './container.model'; import { FormModel } from './form.model'; import { WidgetVisibilityModel } from '../../../models/widget-visibility.model'; export class TabModel extends FormWidgetModel { - id: string; title: string; isVisible: boolean = true; visibilityCondition: WidgetVisibilityModel; - fields: ContainerModel[] = []; + fields: FormWidgetModel[] = []; hasContent(): boolean { return this.fields && this.fields.length > 0; @@ -37,7 +35,6 @@ export class TabModel extends FormWidgetModel { super(form, json); if (json) { - this.id = json.id; this.title = json.title; this.visibilityCondition = json.visibilityCondition; } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.css index c3b7a552c9..ad833b2f12 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.css +++ b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.css @@ -1,3 +1,11 @@ .display-value-widget { width: 100%; } + +.display-value-widget__dynamic-table { + padding: 8px; +} + +.display-value-widget__dynamic-table table { + width: 100%; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.html index cafbe19e6d..37191eb5a4 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.html +++ b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.html @@ -23,6 +23,7 @@