diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.spec.ts
new file mode 100644
index 0000000000..c2653378b6
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.spec.ts
@@ -0,0 +1,209 @@
+/*!
+ * @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 { DynamicTableWidget } from './dynamic-table.widget';
+import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './../core/index';
+
+describe('DynamicTableWidget', () => {
+
+ let widget: DynamicTableWidget;
+ let table: DynamicTableModel;
+
+ beforeEach(() => {
+ table = new DynamicTableModel(null);
+ widget = new DynamicTableWidget(null);
+ widget.content = table;
+ });
+
+ it('should select row on click', () => {
+ let row = { 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 = { selected: false };
+ widget.content = null;
+ widget.onRowClicked(row);
+
+ expect(row.selected).toBeFalsy();
+ });
+
+ it('should reset selected row', () => {
+ let row = { 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 = { 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 = {};
+ let row2 = {};
+ 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 = { };
+ let row2 = { };
+ 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 = {};
+ 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 = { 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 = { 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 = '';
+ let row = { value: { key: value } };
+ let column = { id: 'key' };
+
+ expect(widget.getCellValue(row, column)).toBe(value);
+ });
+
+ it('should save changes and add new row', () => {
+ let row = { 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 = { 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(console, 'log').and.stub();
+ widget.editMode = true;
+ widget.content = null;
+ widget.onSaveChanges();
+
+ expect(widget.editMode).toBeFalsy();
+ expect(console.log).toHaveBeenCalledWith(widget.ERROR_MODEL_NOT_FOUND);
+ });
+
+ it('should cancel changes', () => {
+ widget.editMode = true;
+ widget.editRow = {};
+ widget.onCancelChanges();
+
+ expect(widget.editMode).toBeFalsy();
+ expect(widget.editRow).toBeNull();
+ });
+
+});
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.ts
new file mode 100644
index 0000000000..47ff3e2a0f
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.ts
@@ -0,0 +1,147 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, Input, ElementRef } from '@angular/core';
+import { WidgetComponent } from './../widget.component';
+import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './../core/index';
+
+@Component({
+ moduleId: module.id,
+ selector: 'dynamic-table-widget',
+ templateUrl: './dynamic-table.widget.html',
+ styleUrls: ['./dynamic-table.widget.css']
+})
+export class DynamicTableWidget extends WidgetComponent {
+
+ ERROR_MODEL_NOT_FOUND = 'Table model not found';
+
+ @Input()
+ content: DynamicTableModel;
+
+ editMode: boolean = false;
+ editRow: DynamicTableRow = null;
+
+ constructor(private elementRef: ElementRef) {
+ super();
+ }
+
+ onRowClicked(row: DynamicTableRow) {
+ if (this.content) {
+ this.content.selectedRow = row;
+ }
+ }
+
+ hasSelection(): boolean {
+ return !!(this.content && this.content.selectedRow);
+ }
+
+ moveSelectionUp(): boolean {
+ if (this.content) {
+ this.content.moveRow(this.content.selectedRow, -1);
+ return true;
+ }
+ return false;
+ }
+
+ moveSelectionDown(): boolean {
+ if (this.content) {
+ this.content.moveRow(this.content.selectedRow, 1);
+ return true;
+ }
+ return false;
+ }
+
+ deleteSelection(): boolean {
+ if (this.content) {
+ this.content.deleteRow(this.content.selectedRow);
+ return true;
+ }
+ return false;
+ }
+
+ addNewRow(): boolean {
+ if (this.content) {
+ this.editRow = {
+ isNew: true,
+ selected: false,
+ value: {}
+ };
+ this.editMode = true;
+ return true;
+ }
+ return false;
+ }
+
+ editSelection(): boolean {
+ if (this.content) {
+ this.editRow = this.copyRow(this.content.selectedRow);
+ this.editMode = true;
+ return true;
+ }
+ return false;
+ }
+
+ getCellValue(row: DynamicTableRow, column: DynamicTableColumn): any {
+ if (this.content) {
+ return this.content.getCellValue(row, column);
+ }
+ 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.handleError(this.ERROR_MODEL_NOT_FOUND);
+ }
+ this.editMode = false;
+ }
+
+ onCancelChanges() {
+ this.editMode = false;
+ this.editRow = null;
+ }
+
+ copyRow(row: DynamicTableRow): DynamicTableRow {
+ return {
+ 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;
+ }
+}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.css b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.css
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.html b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.html
new file mode 100644
index 0000000000..b92d6f30ba
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.html
@@ -0,0 +1,11 @@
+
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.spec.ts
new file mode 100644
index 0000000000..5899693d6c
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.spec.ts
@@ -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.
+ */
+
+import { BooleanEditorComponent } from './boolean.editor';
+import { DynamicTableRow, DynamicTableColumn } from './../../../core/index';
+
+describe('BooleanEditorComponent', () => {
+
+ let component: BooleanEditorComponent;
+
+ beforeEach(() => {
+ component = new BooleanEditorComponent();
+ });
+
+ it('should update row value on change', () => {
+ let row = { value: {} };
+ let column = { id: 'key' };
+ let event = { srcElement: { checked: true } };
+
+ component.onValueChanged(row, column, event);
+ expect(row.value[column.id]).toBeTruthy();
+ });
+
+});
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.ts
new file mode 100644
index 0000000000..d13846dbea
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.ts
@@ -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.
+ */
+
+import { Component } from '@angular/core';
+import { CellEditorComponent } from './../cell.editor';
+import { DynamicTableRow, DynamicTableColumn } from './../../../core/index';
+
+@Component({
+ moduleId: module.id,
+ selector: 'alf-boolean-editor',
+ templateUrl: './boolean.editor.html',
+ styleUrls: ['./boolean.editor.css']
+})
+export class BooleanEditorComponent extends CellEditorComponent {
+
+ onValueChanged(row: DynamicTableRow, column: DynamicTableColumn, event: any) {
+ let value: boolean = (event.srcElement).checked;
+ row.value[column.id] = value;
+ }
+
+}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/cell.editor.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/cell.editor.spec.ts
new file mode 100644
index 0000000000..239ce25eee
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/cell.editor.spec.ts
@@ -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 { CellEditorComponent } from './cell.editor';
+
+describe('CellEditorComponent', () => {
+
+ class CustomEditor extends CellEditorComponent {
+ onError(error: any) {
+ this.handleError(error);
+ }
+ }
+
+ let component: CustomEditor;
+
+ beforeEach(() => {
+ component = new CustomEditor();
+ });
+
+ it('should handle error', () => {
+ const error = 'error';
+ spyOn(console, 'error').and.stub();
+
+ component.onError(error);
+ expect(console.error).toHaveBeenCalledWith(error);
+ });
+
+});
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/cell.editor.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/cell.editor.ts
new file mode 100644
index 0000000000..ed3bbab266
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/cell.editor.ts
@@ -0,0 +1,36 @@
+/*!
+ * @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 { Input } from '@angular/core';
+import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './../../core/index';
+
+export abstract class CellEditorComponent {
+
+ @Input()
+ table: DynamicTableModel;
+
+ @Input()
+ row: DynamicTableRow;
+
+ @Input()
+ column: DynamicTableColumn;
+
+ handleError(error: any) {
+ console.error(error);
+ }
+
+}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.css b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.css
new file mode 100644
index 0000000000..9101330a83
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.css
@@ -0,0 +1,7 @@
+.date-editor {
+ width: 100%;
+}
+
+.date-editor--button {
+ margin-top: 15px;
+}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.html b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.html
new file mode 100644
index 0000000000..b4beaa2463
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.spec.ts
new file mode 100644
index 0000000000..dedc853952
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.spec.ts
@@ -0,0 +1,138 @@
+/*!
+ * @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 { ElementRef } from '@angular/core';
+import { DateEditorComponent } from './date.editor';
+import { DynamicTableModel, DynamicTableRow, DynamicTableColumn/*, DynamicRowValidationSummary*/ } from './../../../core/index';
+
+describe('DateEditorComponent', () => {
+
+ let nativeElement: any;
+ let elementRef: ElementRef;
+ let component: DateEditorComponent;
+ let row: DynamicTableRow;
+ let column: DynamicTableColumn;
+ let table: DynamicTableModel;
+
+ beforeEach(() => {
+ nativeElement = {
+ querySelector: function () { return null; }
+ };
+
+ row = { value: { date: '1879-03-14T00:00:00.000Z' } };
+ column = { id: 'date', type: 'Date' };
+ table = new DynamicTableModel(null);
+ table.rows.push(row);
+ table.columns.push(column);
+
+ elementRef = new ElementRef(nativeElement);
+ component = new DateEditorComponent(elementRef);
+ component.table = table;
+ component.row = row;
+ component.column = column;
+ });
+
+ it('should setup date picker on init', () => {
+ let trigger = {};
+ spyOn(nativeElement, 'querySelector').and.returnValue(trigger);
+
+ component.ngOnInit();
+
+ let settings = component.settings;
+ expect(settings.type).toBe('date');
+ expect(settings.future.year()).toBe(moment().year() + 21);
+ expect(settings.init.isSame(moment('14-03-1879', component.DATE_FORMAT))).toBeTruthy();
+ expect(component.datePicker.trigger).toBe(trigger);
+ });
+
+ it('should require cell value to setup initial date', () => {
+ row.value[column.id] = null;
+ component.ngOnInit();
+ expect(component.settings.init).toBeUndefined();
+ });
+
+ it('should require dom element to setup trigger', () => {
+ component = new DateEditorComponent(null);
+ component.table = table;
+ component.row = row;
+ component.column = column;
+ component.ngOnInit();
+ expect(component.datePicker.trigger).toBeFalsy();
+ });
+
+ it('should update fow value on change', () => {
+ component.ngOnInit();
+ component.datePicker.time = moment('14-03-1879', 'DD-MM-YYYY');
+ component.onDateSelected(null);
+ expect(row.value[column.id]).toBe('1879-03-14T00:00:00.000Z');
+ });
+
+ it('should update material textfield on date selected', () => {
+ component.ngOnInit();
+ component.datePicker.time = moment('14-03-1879', 'DD-MM-YYYY');
+ spyOn(component, 'updateMaterialTextField').and.stub();
+ component.onDateSelected(null);
+ expect(component.updateMaterialTextField).toHaveBeenCalled();
+ });
+
+ it('should require dom element to update material textfield on change', () => {
+ component = new DateEditorComponent(null);
+ component.table = table;
+ component.row = row;
+ component.column = column;
+ component.ngOnInit();
+
+ component.datePicker.time = moment('14-03-1879', 'DD-MM-YYYY');
+ spyOn(component, 'updateMaterialTextField').and.stub();
+ component.onDateSelected(null);
+ expect(component.updateMaterialTextField).not.toHaveBeenCalled();
+ });
+
+ it('should require dom element to update material textfield', () => {
+ let result = component.updateMaterialTextField(null, 'value');
+ expect(result).toBeFalsy();
+ });
+
+ it('should require native dom element to update material textfield', () => {
+ elementRef.nativeElement = null;
+ let result = component.updateMaterialTextField(elementRef, 'value');
+ expect(result).toBeFalsy();
+ });
+
+ it('should require input element to update material textfield', () => {
+ spyOn(nativeElement, 'querySelector').and.returnValue(null);
+ let result = component.updateMaterialTextField(elementRef, 'value');
+ expect(result).toBeFalsy();
+ });
+
+ it('should update material textfield with new value', () => {
+ let called = false;
+ const value = '';
+
+ spyOn(nativeElement, 'querySelector').and.returnValue({
+ MaterialTextfield: {
+ change: function (val) {
+ called = true;
+ expect(val).toBe(value);
+ }
+ }
+ });
+ component.updateMaterialTextField(elementRef, value);
+ expect(called).toBeTruthy();
+ });
+
+});
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.ts
new file mode 100644
index 0000000000..5b2b2c2ecc
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.ts
@@ -0,0 +1,79 @@
+/*!
+ * @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, ElementRef } from '@angular/core';
+import { CellEditorComponent } from './../cell.editor';
+
+@Component({
+ moduleId: module.id,
+ selector: 'alf-date-editor',
+ templateUrl: './date.editor.html',
+ styleUrls: ['./date.editor.css']
+})
+export class DateEditorComponent extends CellEditorComponent implements OnInit {
+
+ DATE_FORMAT: string = 'DD-MM-YYYY';
+
+ datePicker: any;
+ settings: any;
+
+ constructor(private elementRef: ElementRef) {
+ super();
+ }
+
+ ngOnInit() {
+ this.settings = {
+ type: 'date',
+ future: moment().add(21, 'years')
+ };
+
+ let value = this.table.getCellValue(this.row, this.column);
+ if (value) {
+ this.settings.init = moment(value, this.DATE_FORMAT);
+ }
+
+ this.datePicker = new mdDateTimePicker.default(this.settings);
+ if (this.elementRef) {
+ this.datePicker.trigger = this.elementRef.nativeElement.querySelector('#dateInput');
+ }
+ }
+
+ onDateSelected(event: CustomEvent) {
+ let newValue = this.datePicker.time.format('YYYY-MM-DD');
+ this.row.value[this.column.id] = newValue + 'T00:00:00.000Z';
+ this.table.flushValue();
+
+ if (this.elementRef) {
+ this.updateMaterialTextField(this.elementRef, newValue);
+ }
+ }
+
+ updateMaterialTextField(elementRef: ElementRef, value: string): boolean {
+ if (elementRef) {
+ let el = elementRef.nativeElement;
+ if (el) {
+ let container = el.querySelector('.mdl-textfield');
+ if (container) {
+ container.MaterialTextfield.change(value);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.css b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.css
new file mode 100644
index 0000000000..0db743bfa6
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.css
@@ -0,0 +1,3 @@
+.dropdown-editor__select {
+ width: 100%;
+}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.html b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.html
new file mode 100644
index 0000000000..026f9cd555
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.spec.ts
new file mode 100644
index 0000000000..f2c5d7e89d
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.spec.ts
@@ -0,0 +1,154 @@
+/*!
+ * @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 { Observable } from 'rxjs/Rx';
+import { DropdownEditorComponent } from './dropdown.editor';
+import {
+ DynamicTableModel,
+ DynamicTableRow,
+ DynamicTableColumn,
+ DynamicTableColumnOption,
+ FormFieldModel,
+ FormModel
+} from './../../../core/index';
+import { FormService } from './../../../../../services/form.service';
+
+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);
+
+ row = { value: { dropdown: 'one' } };
+ column = {
+ id: 'dropdown',
+ options: [
+ { id: '1', name: 'one' },
+ { id: '2', name: 'two' }
+ ]
+ };
+
+ table = new DynamicTableModel(null);
+ form = new FormModel({ taskId: '' });
+ table.field = new FormFieldModel(form, { id: '' });
+ table.rows.push(row);
+ table.columns.push(column);
+
+ component = new DropdownEditorComponent(formService);
+ 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 = [
+ { id: '11', name: 'eleven' },
+ { 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', () => {
+ 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 update row on value change', () => {
+ let event = { srcElement: { value: 'two' } };
+ component.onValueChanged(row, column, event);
+ expect(row.value[column.id]).toBe(column.options[1]);
+ });
+
+});
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.ts
new file mode 100644
index 0000000000..88e86bada5
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.ts
@@ -0,0 +1,68 @@
+/*!
+ * @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 } from '@angular/core';
+import { CellEditorComponent } from './../cell.editor';
+import { DynamicTableRow, DynamicTableColumn, DynamicTableColumnOption } from './../../../core/index';
+import { FormService } from './../../../../../services/form.service';
+
+@Component({
+ moduleId: module.id,
+ selector: 'alf-dropdown-editor',
+ templateUrl: './dropdown.editor.html',
+ styleUrls: ['./dropdown.editor.css']
+})
+export class DropdownEditorComponent extends CellEditorComponent implements OnInit {
+
+ value: any = null;
+ options: DynamicTableColumnOption[] = [];
+
+ constructor(private formService: FormService) {
+ super();
+ }
+
+ ngOnInit() {
+ let field = this.table.field;
+ if (field) {
+ if (this.column.optionType === 'rest') {
+ 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)
+ );
+ } else {
+ this.options = this.column.options || [];
+ this.value = this.table.getCellValue(this.row, this.column);
+ }
+ }
+ }
+
+ onValueChanged(row: DynamicTableRow, column: DynamicTableColumn, event: any) {
+ let value: any = (event.srcElement).value;
+ value = column.options.find(opt => opt.name === value);
+ row.value[column.id] = value;
+ }
+}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.css b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.css
new file mode 100644
index 0000000000..d72e472c43
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.css
@@ -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;
+}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.html b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.html
new file mode 100644
index 0000000000..4db9fed239
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.html
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{validationSummary.text}}
+
+
+
+
+
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.spec.ts
new file mode 100644
index 0000000000..c460d25fc5
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.spec.ts
@@ -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.
+ */
+
+import { RowEditorComponent } from './row.editor';
+import { DynamicTableModel, DynamicTableRow, DynamicTableColumn, DynamicRowValidationSummary } from './../../core/index';
+
+describe('RowEditorComponent', () => {
+
+ let component: RowEditorComponent;
+
+ beforeEach(() => {
+ component = new RowEditorComponent();
+ component.table = new DynamicTableModel(null);
+ component.row = {};
+ component.column = {};
+ });
+
+ 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(
+ { 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(
+ { isValid: false, text: 'error' }
+ );
+ let raised = false;
+ component.save.subscribe(e => raised = true);
+ component.onSaveChanges();
+ expect(raised).toBeFalsy();
+ });
+
+});
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.ts
new file mode 100644
index 0000000000..6ca74f59b8
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.ts
@@ -0,0 +1,73 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { DynamicTableModel, DynamicTableRow, DynamicTableColumn, DynamicRowValidationSummary } from './../../core/index';
+
+@Component({
+ moduleId: module.id,
+ 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 = new EventEmitter();
+
+ @Output()
+ cancel: EventEmitter = new EventEmitter();
+
+ validationSummary: 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);
+ }
+
+}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.css b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.css
new file mode 100644
index 0000000000..81665765ce
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.css
@@ -0,0 +1,3 @@
+.text-editor {
+ width: 100%;
+}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.html b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.html
new file mode 100644
index 0000000000..69308bcd02
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.html
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.spec.ts
new file mode 100644
index 0000000000..cad08cc0ab
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.spec.ts
@@ -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 { TextEditorComponent } from './text.editor';
+import { DynamicTableRow, DynamicTableColumn } from './../../../core/index';
+
+describe('TextEditorComponent', () => {
+
+ let editor: TextEditorComponent;
+
+ beforeEach(() => {
+ editor = new TextEditorComponent();
+ });
+
+ it('should update row value on change', () => {
+ let row = { value: {} };
+ let column = { id: 'key' };
+
+ const value = '';
+ let event = { srcElement: { value } };
+
+ editor.onValueChanged(row, column, event);
+ expect(row.value[column.id]).toBe(value);
+ });
+
+});
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.ts
new file mode 100644
index 0000000000..dc76cf90fd
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.ts
@@ -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.
+ */
+
+import { Component } from '@angular/core';
+import { CellEditorComponent } from './../cell.editor';
+import { DynamicTableRow, DynamicTableColumn } from './../../../core/index';
+
+@Component({
+ moduleId: module.id,
+ selector: 'alf-text-editor',
+ templateUrl: './text.editor.html',
+ styleUrls: ['./text.editor.css']
+})
+export class TextEditorComponent extends CellEditorComponent {
+
+ onValueChanged(row: DynamicTableRow, column: DynamicTableColumn, event: any) {
+ let value: any = (event.srcElement).value;
+ row.value[column.id] = value;
+ }
+
+}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/index.ts b/ng2-components/ng2-activiti-form/src/components/widgets/index.ts
index 200ecf6631..493174ea37 100644
--- a/ng2-components/ng2-activiti-form/src/components/widgets/index.ts
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/index.ts
@@ -34,6 +34,12 @@ import { FunctionalGroupWidget } from './functional-group/functional-group.widge
import { PeopleWidget } from './people/people.widget';
import { DateWidget } from './date/date.widget';
import { AmountWidget } from './amount/amount.widget';
+import { DynamicTableWidget } from './dynamic-table/dynamic-table.widget';
+import { DateEditorComponent } from './dynamic-table/editors/date/date.editor';
+import { DropdownEditorComponent } from './dynamic-table/editors/dropdown/dropdown.editor';
+import { BooleanEditorComponent } from './dynamic-table/editors/boolean/boolean.editor';
+import { TextEditorComponent } from './dynamic-table/editors/text/text.editor';
+import { RowEditorComponent } from './dynamic-table/editors/row.editor';
// core
export * from './widget.component';
@@ -60,6 +66,14 @@ export * from './functional-group/functional-group.widget';
export * from './people/people.widget';
export * from './date/date.widget';
export * from './amount/amount.widget';
+export * from './dynamic-table/dynamic-table.widget';
+
+// editors (dynamic table)
+export * from './dynamic-table/editors/row.editor';
+export * from './dynamic-table/editors/date/date.editor';
+export * from './dynamic-table/editors/dropdown/dropdown.editor';
+export * from './dynamic-table/editors/boolean/boolean.editor';
+export * from './dynamic-table/editors/text/text.editor';
export const WIDGET_DIRECTIVES: any[] = [
TabsWidget,
@@ -79,5 +93,12 @@ export const WIDGET_DIRECTIVES: any[] = [
FunctionalGroupWidget,
PeopleWidget,
DateWidget,
- AmountWidget
+ AmountWidget,
+
+ DynamicTableWidget,
+ DateEditorComponent,
+ DropdownEditorComponent,
+ BooleanEditorComponent,
+ TextEditorComponent,
+ RowEditorComponent
];
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.html
index 4d9d3e7680..0b09df138e 100644
--- a/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.html
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.html
@@ -12,10 +12,22 @@
class="mdl-tabs__panel"
[class.is-active]="isFirst"
[attr.id]="tab.id">
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ UNKNOWN WIDGET TYPE: {{field.type}}
+
+
+
diff --git a/ng2-components/ng2-activiti-form/src/services/form.service.ts b/ng2-components/ng2-activiti-form/src/services/form.service.ts
index bcd020e02b..20311caacd 100644
--- a/ng2-components/ng2-activiti-form/src/services/form.service.ts
+++ b/ng2-components/ng2-activiti-form/src/services/form.service.ts
@@ -208,7 +208,12 @@ export class FormService {
getRestFieldValues(taskId: string, field: string): Observable {
let alfrescoApi = this.apiService.getInstance();
- return Observable.fromPromise(alfrescoApi.activiti.taskFormsApi.getRestFieldValues(taskId, field));
+ return Observable.fromPromise(alfrescoApi.activiti.taskApi.getRestFieldValues(taskId, field));
+ }
+
+ getRestFieldValuesColumn(taskId: string, field: string, column?: string): Observable {
+ let alfrescoApi = this.apiService.getInstance();
+ return Observable.fromPromise(alfrescoApi.activiti.taskApi.getRestFieldValuesColumn(taskId, field, column));
}
// TODO: uses private webApp api
diff --git a/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.spec.ts b/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.spec.ts
index 076b047b0d..ffd7a8e177 100644
--- a/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.spec.ts
+++ b/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.spec.ts
@@ -27,7 +27,7 @@ import { WidgetVisibilityService } from './widget-visibility.service';
import { AlfrescoSettingsService, AlfrescoAuthenticationService, AlfrescoApiService } from 'ng2-alfresco-core';
import { TaskProcessVariableModel } from '../models/task-process-variable.model';
import { WidgetVisibilityModel } from '../models/widget-visibility.model';
-import { FormModel, FormFieldModel, TabModel, ContainerModel } from '../components/widgets/core/index';
+import { FormModel, FormFieldModel, TabModel, ContainerModel, FormFieldTypes } from '../components/widgets/core/index';
declare let jasmine: any;
@@ -620,13 +620,18 @@ describe('WidgetVisibilityService', () => {
visibilityObjTest.leftFormFieldId = 'FIELD_TEST';
visibilityObjTest.operator = '!=';
visibilityObjTest.rightFormFieldId = 'RIGHT_FORM_FIELD_ID';
- fakeFormWithField.fields[0].columns[0].fields[0].visibilityCondition = visibilityObjTest;
+
+ let container = fakeFormWithField.fields[0];
+ let column0 = container.columns[0];
+ let column1 = container.columns[1];
+
+ column0.fields[0].visibilityCondition = visibilityObjTest;
service.refreshVisibility(fakeFormWithField);
- expect(fakeFormWithField.fields[0].columns[0].fields[0].isVisible).toBeFalsy();
- expect(fakeFormWithField.fields[0].columns[0].fields[1].isVisible).toBeTruthy();
- expect(fakeFormWithField.fields[0].columns[0].fields[2].isVisible).toBeTruthy();
- expect(fakeFormWithField.fields[0].columns[1].fields[0].isVisible).toBeTruthy();
+ expect(column0.fields[0].isVisible).toBeFalsy();
+ expect(column0.fields[1].isVisible).toBeTruthy();
+ expect(column0.fields[2].isVisible).toBeTruthy();
+ expect(column1.fields[0].isVisible).toBeTruthy();
});
it('should refresh the visibility for tab in forms', () => {
@@ -652,20 +657,21 @@ describe('WidgetVisibilityService', () => {
expect(tab.isVisible).toBeFalsy();
});
- it('should refresh the visibility for container in forms', () => {
+ xit('should refresh the visibility for container in forms', () => {
visibilityObjTest.leftFormFieldId = 'FIELD_TEST';
visibilityObjTest.operator = '!=';
- visibilityObjTest.rightFormFieldId = 'RIGHT_FORM_FIELD_ID';
+ visibilityObjTest.rightFormFieldId = 'LEFT_FORM_FIELD_ID';
let contModel = new ContainerModel(fakeFormWithField, {
id: 'fake-container-id',
+ type: FormFieldTypes.GROUP,
name: 'fake-container-name',
- isVisible: true
+ isVisible: true,
+ visibilityCondition: visibilityObjTest
});
- contModel.visibilityCondition = visibilityObjTest;
- fakeFormWithField.fields[0].visibilityCondition = visibilityObjTest;
- service.refreshVisibility(fakeFormWithField);
- expect(fakeFormWithField.fields[0].isVisible).toBeFalsy();
+ fakeFormWithField.fields.push(contModel);
+ service.refreshVisibility(fakeFormWithField);
+ expect(contModel.isVisible).toBeFalsy();
});
it('should refresh the visibility for single container', () => {
@@ -674,12 +680,12 @@ describe('WidgetVisibilityService', () => {
visibilityObjTest.rightFormFieldId = 'RIGHT_FORM_FIELD_ID';
let contModel = new ContainerModel(fakeFormWithField, {
id: 'fake-container-id',
+ type: FormFieldTypes.GROUP,
name: 'fake-container-name',
- isVisible: true
+ isVisible: true,
+ visibilityCondition: visibilityObjTest
});
- contModel.visibilityCondition = visibilityObjTest;
- service.refreshEntityVisibility(contModel);
-
+ service.refreshEntityVisibility(contModel.field);
expect(contModel.isVisible).toBeFalsy();
});
});
diff --git a/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.ts b/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.ts
index 1b735ffa7e..1bc357e65a 100644
--- a/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.ts
+++ b/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.ts
@@ -19,7 +19,7 @@ import { Injectable } from '@angular/core';
import { Response, Http, Headers, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import { AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core';
-import { FormModel, FormFieldModel, TabModel, ContainerModel } from '../components/widgets/core/index';
+import { FormModel, FormFieldModel, TabModel } from '../components/widgets/core/index';
import { WidgetVisibilityModel } from '../models/widget-visibility.model';
import { TaskProcessVariableModel } from '../models/task-process-variable.model';
@@ -37,16 +37,13 @@ export class WidgetVisibilityService {
if (form && form.tabs && form.tabs.length > 0) {
form.tabs.map(tabModel => this.refreshEntityVisibility(tabModel));
}
- if (form && form.fields.length > 0) {
- form.fields.map(contModel => {
- this.refreshEntityVisibility(contModel);
- contModel.columns.map(contColModel =>
- contColModel.fields.map(field => this.refreshEntityVisibility(field)));
- });
+
+ if (form) {
+ form.getFormFields().map(field => this.refreshEntityVisibility(field));
}
}
- refreshEntityVisibility(element: FormFieldModel | ContainerModel | TabModel) {
+ refreshEntityVisibility(element: FormFieldModel | TabModel) {
element.isVisible = this.evaluateVisibility(element.form, element.visibilityCondition);
}
diff --git a/ng2-components/ng2-alfresco-core/src/components/material/index.ts b/ng2-components/ng2-alfresco-core/src/components/material/index.ts
index 8760c82be1..84f30ce07b 100644
--- a/ng2-components/ng2-alfresco-core/src/components/material/index.ts
+++ b/ng2-components/ng2-alfresco-core/src/components/material/index.ts
@@ -19,15 +19,18 @@ import { MDL } from './MaterialDesignLiteUpgradeElement';
import { AlfrescoMdlButtonDirective } from './mdl-button.directive';
import { AlfrescoMdlMenuDirective } from './mdl-menu.directive';
import { AlfrescoMdlTabsDirective } from './mdl-tabs.directive';
+import { AlfrescoMdlTextFieldDirective } from './mdl-textfield.directive';
export * from './MaterialDesignLiteUpgradeElement';
export * from './mdl-button.directive';
export * from './mdl-menu.directive';
export * from './mdl-tabs.directive';
+export * from './mdl-textfield.directive';
export const MATERIAL_DESIGN_DIRECTIVES: [any] = [
MDL,
AlfrescoMdlButtonDirective,
AlfrescoMdlMenuDirective,
- AlfrescoMdlTabsDirective
+ AlfrescoMdlTabsDirective,
+ AlfrescoMdlTextFieldDirective
];
diff --git a/ng2-components/ng2-alfresco-core/src/components/material/mdl-textfield.directive.ts b/ng2-components/ng2-alfresco-core/src/components/material/mdl-textfield.directive.ts
new file mode 100644
index 0000000000..d8e0a1be0b
--- /dev/null
+++ b/ng2-components/ng2-alfresco-core/src/components/material/mdl-textfield.directive.ts
@@ -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.
+ */
+
+import { Directive, ElementRef, AfterViewInit } from '@angular/core';
+
+declare var componentHandler;
+
+@Directive({
+ selector: '[alfresco-mdl-textfield]'
+})
+export class AlfrescoMdlTextFieldDirective implements AfterViewInit {
+
+ constructor(private element: ElementRef) {}
+
+ ngAfterViewInit() {
+ if (componentHandler) {
+ let el = this.element.nativeElement;
+ el.classList.add('mdl-textfield');
+ el.classList.add('mdl-js-textfield');
+ el.classList.add('mdl-textfield--floating-label');
+ componentHandler.upgradeElement(el, 'MaterialTextfield');
+ }
+ }
+}