mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-19 17:14:57 +00:00
Merge pull request #923 from Alfresco/dev-denys-637
Dynamic Table widget for BPM Form renderer
This commit is contained in:
commit
cf8ada62c0
@ -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
|
||||
|
@ -14,7 +14,25 @@
|
||||
</div>
|
||||
|
||||
<div *ngIf="!form.hasTabs() && form.hasFields()">
|
||||
<container-widget *ngFor="let field of form.fields" [content]="field" (formValueChanged)="checkVisibility($event);"></container-widget>
|
||||
<div *ngFor="let field of form.fields">
|
||||
<div [ngSwitch]="field.type">
|
||||
<div *ngSwitchCase="'container'">
|
||||
<container-widget [content]="field" (formValueChanged)="checkVisibility($event)"></container-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'group'">
|
||||
<container-widget [content]="field" (formValueChanged)="checkVisibility($event)"></container-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'dynamic-table'">
|
||||
<dynamic-table-widget [content]="field"></dynamic-table-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'readonly'">
|
||||
<display-value-widget [field]="field.field" (fieldChanged)="checkVisibility($event)"></display-value-widget>
|
||||
</div>
|
||||
<div *ngSwitchDefault>
|
||||
<span>UNKNOWN WIDGET TYPE: {{field.type}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="form.hasOutcomes()" class="mdl-card__actions mdl-card--border">
|
||||
|
@ -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: '<id>',
|
||||
fields: [
|
||||
{ id: 'field1' }
|
||||
{ id: 'field1', type: FormFieldTypes.CONTAINER }
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -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">
|
||||
|
@ -11,7 +11,7 @@
|
||||
<span (click)="onExpanderClicked()" id="container-header-label">{{content.name}}</span>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="mdl-grid" *ngIf="content?.isExpanded">
|
||||
<div class="mdl-grid" *ngIf="content?.isVisible && content?.isExpanded">
|
||||
<div *ngFor="let col of content.columns" class="mdl-cell mdl-cell--{{col.size}}-col">
|
||||
<div class="mdl-grid" *ngIf="col.hasFields()">
|
||||
<div *ngFor="let field of col.fields" class="mdl-cell mdl-cell--12-col">
|
||||
|
@ -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(() => {
|
||||
|
@ -51,8 +51,7 @@ describe('ContainerModel', () => {
|
||||
type: '<type>',
|
||||
tab: '<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();
|
||||
});
|
||||
|
||||
|
@ -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 = <number> json.numberOfColumns;
|
||||
this.params = <FormFieldMetadata> json.params || {};
|
||||
this.visibilityCondition = <WidgetVisibilityModel> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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 => <DynamicTableColumn> obj);
|
||||
this.visibleColumns = this.columns.filter(col => col.visible);
|
||||
}
|
||||
|
||||
if (json.value) {
|
||||
this.rows = json.value.map(obj => <DynamicTableRow> { 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 = <DynamicRowValidationSummary> {
|
||||
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;
|
||||
}
|
||||
}
|
@ -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';
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 = <ContainerModel> form.fields[0];
|
||||
expect(container.columns.length).toBe(2);
|
||||
|
||||
let column1 = container.columns[0];
|
||||
|
@ -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<TabModel> = {};
|
||||
|
||||
@ -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 = <ContainerModel> field;
|
||||
result.push(...container.getFormFields());
|
||||
}
|
||||
|
||||
if (field.type === FormFieldTypes.DYNAMIC_TABLE) {
|
||||
let dynamicTable = <DynamicTableModel> 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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 = <WidgetVisibilityModel> json.visibilityCondition;
|
||||
}
|
||||
|
@ -1,3 +1,11 @@
|
||||
.display-value-widget {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.display-value-widget__dynamic-table {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.display-value-widget__dynamic-table table {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
<textarea class="mdl-textfield__input"
|
||||
type="text"
|
||||
rows= "3"
|
||||
[value]="value"
|
||||
[attr.id]="field.id"
|
||||
[(ngModel)]="value"
|
||||
[disabled]="field.readOnly">
|
||||
@ -38,6 +39,29 @@
|
||||
<a [href]="linkUrl" target="_blank" rel="nofollow">{{linkText}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngSwitchCase="'dynamic-table'">
|
||||
<div class="display-value-widget__dynamic-table">
|
||||
<div>{{field.name}}</div>
|
||||
<table class="mdl-data-table mdl-js-data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th *ngFor="let column of visibleColumns"
|
||||
class="mdl-data-table__cell--non-numeric">
|
||||
{{column.name}}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let row of rows">
|
||||
<td *ngFor="let column of visibleColumns"
|
||||
class="mdl-data-table__cell--non-numeric">
|
||||
{{ getCellValue(row, column) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngSwitchDefault
|
||||
class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label text-widget">
|
||||
<input class="mdl-textfield__input"
|
||||
|
@ -21,6 +21,8 @@ import { FormService } from '../../../services/form.service';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { FormFieldTypes } from '../core/form-field-types';
|
||||
import { FormModel } from '../core/form.model';
|
||||
import { DynamicTableRow } from './../core/dynamic-table-row';
|
||||
import { DynamicTableColumn } from './../core/dynamic-table-column';
|
||||
|
||||
describe('DisplayValueWidget', () => {
|
||||
|
||||
@ -539,4 +541,104 @@ describe('DisplayValueWidget', () => {
|
||||
expect(widget.value).toBe(value);
|
||||
});
|
||||
|
||||
it('should setup [DYNAMIC_TABLE] field', () => {
|
||||
let columns = [{ id: '1', visible: false }, { id: '2', visible: true }];
|
||||
let rows = [{}, {}];
|
||||
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.DISPLAY_VALUE,
|
||||
params: {
|
||||
field: {
|
||||
type: FormFieldTypes.DYNAMIC_TABLE
|
||||
}
|
||||
},
|
||||
columnDefinitions: columns,
|
||||
value: rows
|
||||
});
|
||||
widget.ngOnInit();
|
||||
|
||||
expect(widget.columns.length).toBe(2);
|
||||
expect(widget.columns[0].id).toBe(columns[0].id);
|
||||
expect(widget.columns[1].id).toBe(columns[1].id);
|
||||
|
||||
expect(widget.visibleColumns.length).toBe(1);
|
||||
expect(widget.visibleColumns[0].id).toBe(columns[1].id);
|
||||
|
||||
expect(widget.rows.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should setup [DYNAMIC_TABLE] field with empty schema', () => {
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.DISPLAY_VALUE,
|
||||
params: {
|
||||
field: {
|
||||
type: FormFieldTypes.DYNAMIC_TABLE
|
||||
}
|
||||
},
|
||||
columnDefinitions: null,
|
||||
value: null
|
||||
});
|
||||
widget.ngOnInit();
|
||||
|
||||
expect(widget.value).toBeNull();
|
||||
expect(widget.columns).toEqual([]);
|
||||
expect(widget.rows).toEqual([]);
|
||||
});
|
||||
|
||||
it('should retrieve default cell value', () => {
|
||||
const value = '<value>';
|
||||
let row = <DynamicTableRow> { value: { key: value } };
|
||||
let column = <DynamicTableColumn> { id: 'key' };
|
||||
|
||||
expect(widget.getCellValue(row, column)).toBe(value);
|
||||
});
|
||||
|
||||
it('should retrieve dropdown cell value', () => {
|
||||
const value = { id: '1', name: 'one' };
|
||||
let row = <DynamicTableRow> { value: { key: value } };
|
||||
let column = <DynamicTableColumn> { id: 'key', type: 'Dropdown' };
|
||||
|
||||
expect(widget.getCellValue(row, column)).toBe(value.name);
|
||||
});
|
||||
|
||||
it('should fallback to empty cell value for dropdown', () => {
|
||||
let row = <DynamicTableRow> { value: {} };
|
||||
let column = <DynamicTableColumn> { id: 'key', type: 'Dropdown' };
|
||||
|
||||
expect(widget.getCellValue(row, column)).toBe('');
|
||||
});
|
||||
|
||||
it('should retrieve boolean cell value', () => {
|
||||
let row1 = <DynamicTableRow> { value: { key: true } };
|
||||
let row2 = <DynamicTableRow> { value: { key: 'positive' } };
|
||||
let row3 = <DynamicTableRow> { value: { key: null } };
|
||||
let column = <DynamicTableColumn> { id: 'key', type: 'Boolean' };
|
||||
|
||||
expect(widget.getCellValue(row1, column)).toBe(true);
|
||||
expect(widget.getCellValue(row2, column)).toBe(true);
|
||||
expect(widget.getCellValue(row3, column)).toBe(false);
|
||||
});
|
||||
|
||||
it('should retrieve date cell value', () => {
|
||||
const value = '2016-10-04T00:00:00.000Z';
|
||||
let row = <DynamicTableRow> { value: { key: value } };
|
||||
let column = <DynamicTableColumn> { id: 'key', type: 'Date' };
|
||||
|
||||
expect(widget.getCellValue(row, column)).toBe('04-10-2016');
|
||||
});
|
||||
|
||||
it('should fallback to empty cell value for date', () => {
|
||||
let row = <DynamicTableRow> { value: {} };
|
||||
let column = <DynamicTableColumn> { id: 'key', type: 'Date' };
|
||||
|
||||
expect(widget.getCellValue(row, column)).toBe('');
|
||||
});
|
||||
|
||||
it('should retrieve empty text cell value', () => {
|
||||
let row = <DynamicTableRow> { value: {} };
|
||||
let column = <DynamicTableColumn> { id: 'key' };
|
||||
|
||||
expect(widget.getCellValue(row, column)).toBe('');
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -20,6 +20,8 @@ import { WidgetComponent } from './../widget.component';
|
||||
import { FormFieldTypes } from '../core/form-field-types';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { FormFieldOption } from './../core/form-field-option';
|
||||
import { DynamicTableColumn } from './../core/dynamic-table-column';
|
||||
import { DynamicTableRow } from './../core/dynamic-table-row';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@ -31,9 +33,16 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
|
||||
value: any;
|
||||
fieldType: string;
|
||||
|
||||
// hyperlink
|
||||
linkUrl: string;
|
||||
linkText: string;
|
||||
|
||||
// dynamic table
|
||||
rows: DynamicTableRow[] = [];
|
||||
columns: DynamicTableColumn[] = [];
|
||||
visibleColumns: DynamicTableColumn[] = [];
|
||||
|
||||
constructor(private formService: FormService) {
|
||||
super();
|
||||
}
|
||||
@ -103,6 +112,16 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
this.linkUrl = this.getHyperlinkUrl(this.field);
|
||||
this.linkText = this.getHyperlinkText(this.field);
|
||||
break;
|
||||
case FormFieldTypes.DYNAMIC_TABLE:
|
||||
let json = this.field.json;
|
||||
if (json.columnDefinitions) {
|
||||
this.columns = json.columnDefinitions.map(obj => <DynamicTableColumn> obj);
|
||||
this.visibleColumns = this.columns.filter(col => col.visible);
|
||||
}
|
||||
if (json.value) {
|
||||
this.rows = json.value.map(obj => <DynamicTableRow> { selected: false, value: obj });
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.value = this.field.value;
|
||||
break;
|
||||
@ -141,4 +160,26 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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 || '';
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
.dynamic-table-widget {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.dynamic-table-widget__row-selected,
|
||||
.dynamic-table-widget__row-selected:hover {
|
||||
background-color: #eef !important;
|
||||
}
|
||||
|
||||
.dynamic-table-widget__table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dynamic-table-widget__invalid .mdl-textfield__input {
|
||||
border-color: #d50000;
|
||||
}
|
||||
|
||||
.dynamic-table-widget__invalid .mdl-textfield__label {
|
||||
color: #d50000;
|
||||
}
|
||||
|
||||
.dynamic-table-widget__invalid .mdl-textfield__label:after {
|
||||
background-color: #d50000;
|
||||
}
|
||||
|
||||
.dynamic-table-widget__invalid .mdl-textfield__error {
|
||||
visibility: visible !important;
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
<div class="dynamic-table-widget">
|
||||
<div>{{content.name}}</div>
|
||||
|
||||
<div *ngIf="!editMode">
|
||||
<table class="mdl-data-table mdl-js-data-table dynamic-table-widget__table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th *ngFor="let column of content.visibleColumns"
|
||||
class="mdl-data-table__cell--non-numeric">
|
||||
{{column.name}}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let row of content.rows"
|
||||
[class.dynamic-table-widget__row-selected]="row.selected">
|
||||
<td *ngFor="let column of content.visibleColumns"
|
||||
class="mdl-data-table__cell--non-numeric"
|
||||
(click)="onRowClicked(row)">
|
||||
{{ getCellValue(row, column) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div>
|
||||
<button class="mdl-button mdl-js-button mdl-button--icon"
|
||||
[disabled]="!hasSelection()"
|
||||
(click)="moveSelectionUp()">
|
||||
<i class="material-icons">arrow_upward</i>
|
||||
</button>
|
||||
<button class="mdl-button mdl-js-button mdl-button--icon"
|
||||
[disabled]="!hasSelection()"
|
||||
(click)="moveSelectionDown()">
|
||||
<i class="material-icons">arrow_downward</i>
|
||||
</button>
|
||||
<button class="mdl-button mdl-js-button mdl-button--icon"
|
||||
(click)="addNewRow()">
|
||||
<i class="material-icons">add_circle_outline</i>
|
||||
</button>
|
||||
<button class="mdl-button mdl-js-button mdl-button--icon"
|
||||
[disabled]="!hasSelection()"
|
||||
(click)="deleteSelection()">
|
||||
<i class="material-icons">remove_circle_outline</i>
|
||||
</button>
|
||||
<button class="mdl-button mdl-js-button mdl-button--icon"
|
||||
[disabled]="!hasSelection()"
|
||||
(click)="editSelection()">
|
||||
<i class="material-icons">edit</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<row-editor *ngIf="editMode"
|
||||
[table]="content"
|
||||
[row]="editRow"
|
||||
[column]="column"
|
||||
(save)="onSaveChanges()"
|
||||
(cancel)="onCancelChanges()">
|
||||
</row-editor>
|
||||
|
||||
</div>
|
@ -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 = <DynamicTableRow> { selected: false };
|
||||
widget.onRowClicked(row);
|
||||
|
||||
expect(row.selected).toBeTruthy();
|
||||
expect(widget.content.selectedRow).toBe(row);
|
||||
});
|
||||
|
||||
it('should requre table to select clicked row', () => {
|
||||
let row = <DynamicTableRow> { selected: false };
|
||||
widget.content = null;
|
||||
widget.onRowClicked(row);
|
||||
|
||||
expect(row.selected).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should reset selected row', () => {
|
||||
let row = <DynamicTableRow> { selected: false };
|
||||
widget.content.rows.push(row);
|
||||
widget.content.selectedRow = row;
|
||||
expect(widget.content.selectedRow).toBe(row);
|
||||
expect(row.selected).toBeTruthy();
|
||||
|
||||
widget.onRowClicked(null);
|
||||
expect(widget.content.selectedRow).toBeNull();
|
||||
expect(row.selected).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should check selection', () => {
|
||||
let row = <DynamicTableRow> { selected: false };
|
||||
widget.content.rows.push(row);
|
||||
widget.content.selectedRow = row;
|
||||
expect(widget.hasSelection()).toBeTruthy();
|
||||
|
||||
widget.content.selectedRow = null;
|
||||
expect(widget.hasSelection()).toBeFalsy();
|
||||
|
||||
widget.content = null;
|
||||
expect(widget.hasSelection()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should require table to move selection up', () => {
|
||||
widget.content = null;
|
||||
expect(widget.moveSelectionUp()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should move selection up', () => {
|
||||
let row1 = <DynamicTableRow> {};
|
||||
let row2 = <DynamicTableRow> {};
|
||||
widget.content.rows.push(...[row1, row2]);
|
||||
widget.content.selectedRow = row2;
|
||||
|
||||
expect(widget.moveSelectionUp()).toBeTruthy();
|
||||
expect(widget.content.rows.indexOf(row2)).toBe(0);
|
||||
});
|
||||
|
||||
it('should require table to move selection down', () => {
|
||||
widget.content = null;
|
||||
expect(widget.moveSelectionDown()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should move selection down', () => {
|
||||
let row1 = <DynamicTableRow> { };
|
||||
let row2 = <DynamicTableRow> { };
|
||||
widget.content.rows.push(...[row1, row2]);
|
||||
widget.content.selectedRow = row1;
|
||||
|
||||
expect(widget.moveSelectionDown()).toBeTruthy();
|
||||
expect(widget.content.rows.indexOf(row1)).toBe(1);
|
||||
});
|
||||
|
||||
it('should require table to delete selection', () => {
|
||||
widget.content = null;
|
||||
expect(widget.deleteSelection()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should delete selected row', () => {
|
||||
let row = <DynamicTableRow> {};
|
||||
widget.content.rows.push(row);
|
||||
widget.content.selectedRow = row;
|
||||
widget.deleteSelection();
|
||||
expect(widget.content.rows.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should require table to add new row', () => {
|
||||
widget.content = null;
|
||||
expect(widget.addNewRow()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should start editing new row', () => {
|
||||
expect(widget.editMode).toBeFalsy();
|
||||
expect(widget.editRow).toBeNull();
|
||||
|
||||
expect(widget.addNewRow()).toBeTruthy();
|
||||
expect(widget.editRow).not.toBeNull();
|
||||
expect(widget.editMode).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should require table to edit selected row', () => {
|
||||
widget.content = null;
|
||||
expect(widget.editSelection()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should start editing selected row', () => {
|
||||
expect(widget.editMode).toBeFalsy();
|
||||
expect(widget.editRow).toBeFalsy();
|
||||
|
||||
let row = <DynamicTableRow> { value: true };
|
||||
widget.content.selectedRow = row;
|
||||
|
||||
expect(widget.editSelection()).toBeTruthy();
|
||||
expect(widget.editMode).toBeTruthy();
|
||||
expect(widget.editRow).not.toBeNull();
|
||||
expect(widget.editRow.value).toEqual(row.value);
|
||||
});
|
||||
|
||||
it('should copy row', () => {
|
||||
let row = <DynamicTableRow> { value: { opt: { key: '1', value: 1 } } };
|
||||
let copy = widget.copyRow(row);
|
||||
expect(copy.value).toEqual(row.value);
|
||||
});
|
||||
|
||||
it('should require table to retrieve cell value', () => {
|
||||
widget.content = null;
|
||||
expect(widget.getCellValue(null, null)).toBeNull();
|
||||
});
|
||||
|
||||
it('should retrieve cell value', () => {
|
||||
const value = '<value>';
|
||||
let row = <DynamicTableRow> { value: { key: value } };
|
||||
let column = <DynamicTableColumn> { id: 'key' };
|
||||
|
||||
expect(widget.getCellValue(row, column)).toBe(value);
|
||||
});
|
||||
|
||||
it('should save changes and add new row', () => {
|
||||
let row = <DynamicTableRow> { isNew: true, value: { key: 'value' } };
|
||||
widget.editMode = true;
|
||||
widget.editRow = row;
|
||||
|
||||
widget.onSaveChanges();
|
||||
|
||||
expect(row.isNew).toBeFalsy();
|
||||
expect(widget.content.selectedRow).toBeNull();
|
||||
expect(widget.content.rows.length).toBe(1);
|
||||
expect(widget.content.rows[0].value).toEqual(row.value);
|
||||
});
|
||||
|
||||
it('should save changes and update row', () => {
|
||||
let row = <DynamicTableRow> { isNew: false, value: { key: 'value' } };
|
||||
widget.editMode = true;
|
||||
widget.editRow = row;
|
||||
widget.content.selectedRow = row;
|
||||
|
||||
widget.onSaveChanges();
|
||||
expect(widget.content.selectedRow.value).toEqual(row.value);
|
||||
});
|
||||
|
||||
it('should require table to save changes', () => {
|
||||
spyOn(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 = <DynamicTableRow> {};
|
||||
widget.onCancelChanges();
|
||||
|
||||
expect(widget.editMode).toBeFalsy();
|
||||
expect(widget.editRow).toBeNull();
|
||||
});
|
||||
|
||||
});
|
@ -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 = <DynamicTableRow> {
|
||||
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 <DynamicTableRow> {
|
||||
value: this.copyObject(row.value)
|
||||
};
|
||||
}
|
||||
|
||||
private copyObject(obj: any): any {
|
||||
let result = obj;
|
||||
|
||||
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||
result = Object.assign({}, obj);
|
||||
Object.keys(obj).forEach(key => {
|
||||
if (typeof obj[key] === 'object') {
|
||||
result[key] = this.copyObject(obj[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" [attr.for]="column.id">
|
||||
<input
|
||||
class="mdl-checkbox__input"
|
||||
type="checkbox"
|
||||
[attr.id]="column.id"
|
||||
[checked]="table.getCellValue(row, column)"
|
||||
[required]="column.required"
|
||||
[disabled]="!column.editable"
|
||||
(change)="onValueChanged(row, column, $event)">
|
||||
<span class="mdl-checkbox__label">{{column.name}}</span>
|
||||
</label>
|
@ -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 = <DynamicTableRow> { value: {} };
|
||||
let column = <DynamicTableColumn> { id: 'key' };
|
||||
let event = { srcElement: { checked: true } };
|
||||
|
||||
component.onValueChanged(row, column, event);
|
||||
expect(row.value[column.id]).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
@ -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 = (<HTMLInputElement>event.srcElement).checked;
|
||||
row.value[column.id] = value;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
.date-editor {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.date-editor--button {
|
||||
margin-top: 15px;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell mdl-cell--11-col">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label date-editor">
|
||||
<input id="dateInput"
|
||||
class="mdl-textfield__input"
|
||||
type="text"
|
||||
[value]="table.getCellValue(row, column)"
|
||||
[attr.id]="column.id"
|
||||
[readonly]="true"
|
||||
[required]="column.required"
|
||||
[disabled]="!column.editable"
|
||||
(onOk)="onDateSelected($event)">
|
||||
<label class="mdl-textfield__label" [attr.for]="column.id">{{column.name}} (d-M-yyyy)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-cell mdl-cell--1-col">
|
||||
<button
|
||||
class="mdl-button mdl-js-button mdl-button--icon date-editor--button"
|
||||
(click)="datePicker.toggle()">
|
||||
<i class="material-icons">date_range</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
@ -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 = <DynamicTableRow> { value: { date: '1879-03-14T00:00:00.000Z' } };
|
||||
column = <DynamicTableColumn> { 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 = '<value>';
|
||||
|
||||
spyOn(nativeElement, 'querySelector').and.returnValue({
|
||||
MaterialTextfield: {
|
||||
change: function (val) {
|
||||
called = true;
|
||||
expect(val).toBe(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
component.updateMaterialTextField(elementRef, value);
|
||||
expect(called).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
.dropdown-editor__select {
|
||||
width: 100%;
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
<div class="dropdown-editor">
|
||||
<label [attr.for]="column.id">{{column.name}}</label>
|
||||
<div>
|
||||
<select
|
||||
class="dropdown-editor__select"
|
||||
[value]="value"
|
||||
[required]="column.required"
|
||||
[disabled]="!column.editable"
|
||||
(change)="onValueChanged(row, column, $event)">
|
||||
<option></option>
|
||||
<option *ngFor="let opt of options" [value]="opt.name">{{opt.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
@ -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 = <DynamicTableRow> { value: { dropdown: 'one' } };
|
||||
column = <DynamicTableColumn> {
|
||||
id: 'dropdown',
|
||||
options: [
|
||||
<DynamicTableColumnOption> { id: '1', name: 'one' },
|
||||
<DynamicTableColumnOption> { id: '2', name: 'two' }
|
||||
]
|
||||
};
|
||||
|
||||
table = new DynamicTableModel(null);
|
||||
form = new FormModel({ taskId: '<task-id>' });
|
||||
table.field = new FormFieldModel(form, { id: '<field-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 = [
|
||||
<DynamicTableColumnOption> { id: '11', name: 'eleven' },
|
||||
<DynamicTableColumnOption> { id: '12', name: 'twelve' }
|
||||
];
|
||||
|
||||
spyOn(formService, 'getRestFieldValuesColumn').and.returnValue(
|
||||
Observable.create(observer => {
|
||||
observer.next(restResults);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(formService.getRestFieldValuesColumn).toHaveBeenCalledWith(
|
||||
form.taskId,
|
||||
table.field.id,
|
||||
column.id
|
||||
);
|
||||
|
||||
expect(column.options).toEqual(restResults);
|
||||
expect(component.options).toEqual(restResults);
|
||||
expect(component.value).toBe(row.value[column.id]);
|
||||
});
|
||||
|
||||
it('should create empty options array on REST response', () => {
|
||||
column.optionType = 'rest';
|
||||
|
||||
spyOn(formService, 'getRestFieldValuesColumn').and.returnValue(
|
||||
Observable.create(observer => {
|
||||
observer.next(null);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(formService.getRestFieldValuesColumn).toHaveBeenCalledWith(
|
||||
form.taskId,
|
||||
table.field.id,
|
||||
column.id
|
||||
);
|
||||
|
||||
expect(column.options).toEqual([]);
|
||||
expect(component.options).toEqual([]);
|
||||
expect(component.value).toBe(row.value[column.id]);
|
||||
});
|
||||
|
||||
it('should handle REST error', () => {
|
||||
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]);
|
||||
});
|
||||
|
||||
});
|
@ -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 = (<HTMLInputElement>event.srcElement).value;
|
||||
value = column.options.find(opt => opt.name === value);
|
||||
row.value[column.id] = value;
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
.row-editor {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.row-editor__validation-summary {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.row-editor__invalid .row-editor__validation-summary {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
color: #d50000;
|
||||
visibility: visible;
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
<div class="row-editor mdl-shadow--2dp"
|
||||
[class.row-editor__invalid]="!validationSummary.isValid">
|
||||
<div class="mdl-grid" *ngFor="let column of table.columns">
|
||||
<div class="mdl-cell mdl-cell--6-col" [ngSwitch]="column.type">
|
||||
<div *ngSwitchCase="'Dropdown'">
|
||||
<alf-dropdown-editor
|
||||
[table]="table"
|
||||
[row]="row"
|
||||
[column]="column">
|
||||
</alf-dropdown-editor>
|
||||
</div>
|
||||
<div *ngSwitchCase="'Date'">
|
||||
<alf-date-editor
|
||||
[table]="table"
|
||||
[row]="row"
|
||||
[column]="column">
|
||||
</alf-date-editor>
|
||||
</div>
|
||||
|
||||
<div *ngSwitchCase="'Boolean'">
|
||||
<alf-boolean-editor
|
||||
[table]="table"
|
||||
[row]="row"
|
||||
[column]="column">
|
||||
</alf-boolean-editor>
|
||||
</div>
|
||||
<div *ngSwitchDefault>
|
||||
<alf-text-editor
|
||||
[table]="table"
|
||||
[row]="row"
|
||||
[column]="column">
|
||||
</alf-text-editor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-editor__validation-summary" *ngIf="!validationSummary.isValid">{{validationSummary.text}}</div>
|
||||
<div>
|
||||
<button
|
||||
class="mdl-button mdl-js-button mdl-js-ripple-effect"
|
||||
(click)="onCancelChanges()">Cancel</button>
|
||||
<button
|
||||
class="mdl-button mdl-js-button mdl-js-ripple-effect"
|
||||
(click)="onSaveChanges()">Save</button>
|
||||
</div>
|
||||
</div>
|
@ -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 = <DynamicTableRow> {};
|
||||
component.column = <DynamicTableColumn> {};
|
||||
});
|
||||
|
||||
it('should be valid upon init', () => {
|
||||
expect(component.validationSummary.isValid).toBeTruthy();
|
||||
expect(component.validationSummary.text).toBeNull();
|
||||
});
|
||||
|
||||
it('should emit [cancel] event', (done) => {
|
||||
component.cancel.subscribe(e => {
|
||||
expect(e.table).toBe(component.table);
|
||||
expect(e.row).toBe(component.row);
|
||||
expect(e.column).toBe(component.column);
|
||||
done();
|
||||
});
|
||||
component.onCancelChanges();
|
||||
});
|
||||
|
||||
it('should validate row on save', () => {
|
||||
spyOn(component.table, 'validateRow').and.callThrough();
|
||||
component.onSaveChanges();
|
||||
expect(component.table.validateRow).toHaveBeenCalledWith(component.row);
|
||||
});
|
||||
|
||||
it('should emit [save] event', (done) => {
|
||||
spyOn(component.table, 'validateRow').and.returnValue(
|
||||
<DynamicRowValidationSummary> { isValid: true, text: null }
|
||||
);
|
||||
component.save.subscribe(e => {
|
||||
expect(e.table).toBe(component.table);
|
||||
expect(e.row).toBe(component.row);
|
||||
expect(e.column).toBe(component.column);
|
||||
done();
|
||||
});
|
||||
component.onSaveChanges();
|
||||
});
|
||||
|
||||
it('should not emit [save] event for invalid row', () => {
|
||||
spyOn(component.table, 'validateRow').and.returnValue(
|
||||
<DynamicRowValidationSummary> { isValid: false, text: 'error' }
|
||||
);
|
||||
let raised = false;
|
||||
component.save.subscribe(e => raised = true);
|
||||
component.onSaveChanges();
|
||||
expect(raised).toBeFalsy();
|
||||
});
|
||||
|
||||
});
|
@ -0,0 +1,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<any> = new EventEmitter<any>();
|
||||
|
||||
@Output()
|
||||
cancel: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
validationSummary: DynamicRowValidationSummary = <DynamicRowValidationSummary> { isValid: true, text: null };
|
||||
|
||||
onCancelChanges() {
|
||||
this.cancel.emit({
|
||||
table: this.table,
|
||||
row: this.row,
|
||||
column: this.column
|
||||
});
|
||||
}
|
||||
|
||||
onSaveChanges() {
|
||||
this.validate();
|
||||
if (this.isValid()) {
|
||||
this.save.emit({
|
||||
table: this.table,
|
||||
row: this.row,
|
||||
column: this.column
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private isValid(): boolean {
|
||||
return this.validationSummary && this.validationSummary.isValid;
|
||||
}
|
||||
|
||||
private validate() {
|
||||
this.validationSummary = this.table.validateRow(this.row);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
.text-editor {
|
||||
width: 100%;
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<div alfresco-mdl-textfield class="text-editor">
|
||||
<input
|
||||
class="mdl-textfield__input"
|
||||
type="text"
|
||||
[value]="table.getCellValue(row, column)"
|
||||
(keyup)="onValueChanged(row, column, $event)"
|
||||
[required]="column.required"
|
||||
[disabled]="!column.editable"
|
||||
[attr.id]="column.id">
|
||||
<label class="mdl-textfield__label" [attr.for]="column.id">{{column.name}}</label>
|
||||
</div>
|
@ -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 = <DynamicTableRow> { value: {} };
|
||||
let column = <DynamicTableColumn> { id: 'key' };
|
||||
|
||||
const value = '<value>';
|
||||
let event = { srcElement: { value } };
|
||||
|
||||
editor.onValueChanged(row, column, event);
|
||||
expect(row.value[column.id]).toBe(value);
|
||||
});
|
||||
|
||||
});
|
@ -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 = (<HTMLInputElement>event.srcElement).value;
|
||||
row.value[column.id] = value;
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
];
|
||||
|
@ -12,10 +12,22 @@
|
||||
class="mdl-tabs__panel"
|
||||
[class.is-active]="isFirst"
|
||||
[attr.id]="tab.id">
|
||||
<container-widget
|
||||
*ngFor="let field of tab.fields"
|
||||
[content]="field" (formValueChanged)="tabChanged($event);">
|
||||
</container-widget>
|
||||
<div *ngFor="let field of tab.fields">
|
||||
<div [ngSwitch]="field.type">
|
||||
<div *ngSwitchCase="'container'">
|
||||
<container-widget [content]="field" (formValueChanged)="tabChanged($event);"></container-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'group'">
|
||||
<container-widget [content]="field" (formValueChanged)="tabChanged($event);"></container-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'dynamic-table'">
|
||||
<dynamic-table-widget [content]="field"></dynamic-table-widget>
|
||||
</div>
|
||||
<div *ngSwitchDefault>
|
||||
<span>UNKNOWN WIDGET TYPE: {{field.type}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -208,7 +208,12 @@ export class FormService {
|
||||
|
||||
getRestFieldValues(taskId: string, field: string): Observable<any> {
|
||||
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<any> {
|
||||
let alfrescoApi = this.apiService.getInstance();
|
||||
return Observable.fromPromise(alfrescoApi.activiti.taskApi.getRestFieldValuesColumn(taskId, field, column));
|
||||
}
|
||||
|
||||
// TODO: uses private webApp api
|
||||
|
@ -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 = <ContainerModel> 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();
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
];
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user