#967 rework container model structure

- now FormFieldModel provides information about columns and child fields
- greatly simplified models
- visibility engine fixes
This commit is contained in:
Denys Vuika 2016-11-15 15:13:05 +00:00 committed by Mario Romano
parent e114534bc5
commit ee56a1faa6
17 changed files with 97 additions and 135 deletions

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Component, OnInit, ViewChild, ViewContainerRef, Input, ComponentRef, ComponentFactoryResolver, Output, EventEmitter/*, Injector*/ } from '@angular/core';
import { Component, OnInit, ViewChild, ViewContainerRef, Input, ComponentRef, ComponentFactoryResolver/*,Output, EventEmitter, Injector*/ } from '@angular/core';
import { WidgetVisibilityService } from './../../services/widget-visibility.service';
import { FormRenderingService } from './../../services/form-rendering.service';
import { WidgetComponent } from './../widgets/widget.component';
@ -23,7 +23,11 @@ import { FormFieldModel/*, FormWidgetModel*/ } from './../widgets/core/index';
@Component({
selector: 'form-field',
template: `<div #container></div>`
template: `
<div [hidden]="!field.isVisible">
<div #container></div>
</div>
`
})
export class FormFieldComponent implements OnInit {
@ -33,10 +37,6 @@ export class FormFieldComponent implements OnInit {
@Input()
field: FormFieldModel = null;
/** @deprecated component handles visibilty itself */
@Output()
fieldChanged: EventEmitter<FormFieldModel> = new EventEmitter<FormFieldModel>();
private componentRef: ComponentRef<{}>;
constructor(
@ -54,12 +54,10 @@ export class FormFieldComponent implements OnInit {
this.componentRef = this.container.createComponent(factory/*, 0, this.injector*/);
let instance = <WidgetComponent>this.componentRef.instance;
instance.field = this.field;
instance.fieldChanged.subscribe(args => {
if (this.field && this.field.form) {
this.visibilityService.refreshVisibility(this.field.form);
instance.fieldChanged.subscribe(field => {
if (field && field.form) {
this.visibilityService.refreshVisibility(field.form);
}
/** @deprecated */
this.fieldChanged.emit(args);
});
}
}

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { ContainerColumnModel } from './container-column.model';
import { ContainerColumnModel } from './../core/container-column.model';
import { FormModel } from './../core/form.model';
import { FormFieldModel } from './../core/form-field.model';

View File

@ -1,3 +1,4 @@
<div>{{content?.isVisible}}</div>
<div class="container-widget">
<div *ngIf="content?.isGroup() && content?.isVisible" class="container-widget__header">
<h4 class="container-widget__header-text" id="container-header"
@ -15,7 +16,7 @@
<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">
<form-field [field]="field" [hidden]="!field.isVisible"></form-field>
<form-field [field]="field"></form-field>
</div>
</div>
</div>

View File

@ -17,52 +17,26 @@
import { ContainerWidgetModel } from './container.widget.model';
import { FormModel } from './../core/form.model';
import { FormFieldModel } from './../core/form-field.model';
import { FormFieldTypes } from './../core/form-field-types';
describe('ContainerWidgetModel', () => {
it('should store the form reference', () => {
let form = new FormModel();
let model = new ContainerWidgetModel(form);
let field = new FormFieldModel(form);
let model = new ContainerWidgetModel(field);
expect(model.form).toBe(form);
});
it('should store original json', () => {
let json = {};
let model = new ContainerWidgetModel(null, json);
expect(model.json).toBe(json);
});
it('should have 1 column layout by default', () => {
let container = new ContainerWidgetModel(null, null);
expect(container.numberOfColumns).toBe(1);
});
it('should be expanded by default', () => {
let container = new ContainerWidgetModel(null, null);
let container = new ContainerWidgetModel(null);
expect(container.isExpanded).toBeTruthy();
});
/*
it('should setup with json config', () => {
let json = {
fieldType: '<type>',
id: '<id>',
name: '<name>',
type: '<type>',
tab: '<tab>',
numberOfColumns: 2,
params: {}
};
let container = new ContainerWidgetModel(null, json);
Object.keys(json).forEach(key => {
expect(container[key]).toEqual(json[key]);
});
});
*/
it('should wrap fields into columns on setup', () => {
let form = new FormModel();
let json = {
fieldType: '<type>',
id: '<id>',
@ -83,7 +57,10 @@ describe('ContainerWidgetModel', () => {
'3': null
}
};
let container = new ContainerWidgetModel(form, json);
let field = new FormFieldModel(form, json);
let container = new ContainerWidgetModel(field);
expect(container.columns.length).toBe(3);
let col1 = container.columns[0];
@ -100,47 +77,47 @@ describe('ContainerWidgetModel', () => {
});
it('should allow collapsing only when of a group type', () => {
let container = new ContainerWidgetModel(new FormModel(), {
let container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
type: FormFieldTypes.CONTAINER,
params: {
allowCollapse: true
}
});
}));
expect(container.isCollapsible()).toBeFalsy();
container = new ContainerWidgetModel(new FormModel(), {
container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
type: FormFieldTypes.GROUP,
params: {
allowCollapse: true
}
});
}));
expect(container.isCollapsible()).toBeTruthy();
});
it('should allow collapsing only when explicitly defined in params', () => {
let container = new ContainerWidgetModel(new FormModel(), {
let container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
type: FormFieldTypes.GROUP,
params: {}
});
}));
expect(container.isCollapsible()).toBeFalsy();
container = new ContainerWidgetModel(new FormModel(), {
container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
type: FormFieldTypes.GROUP,
params: {
allowCollapse: true
}
});
}));
expect(container.isCollapsible()).toBeTruthy();
});
it('should be collapsed by default', () => {
let container = new ContainerWidgetModel(new FormModel(), {
let container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
type: FormFieldTypes.GROUP,
params: {
allowCollapse: true,
collapseByDefault: true
}
});
}));
expect(container.isCollapsedByDefault()).toBeTruthy();
});

View File

@ -16,14 +16,12 @@
*/
import { ContainerModel } from './../core/container.model';
import { FormModel } from './../core/form.model';
import { ContainerColumnModel } from './container-column.model';
import { ContainerColumnModel } from './../core/container-column.model';
import { FormFieldTypes } from './../core/form-field-types';
import { FormFieldModel } from './../core/form-field.model';
export class ContainerWidgetModel extends ContainerModel {
numberOfColumns: number = 1;
columns: ContainerColumnModel[] = [];
isExpanded: boolean = true;
@ -51,48 +49,12 @@ export class ContainerWidgetModel extends ContainerModel {
return collapseByDefault;
}
constructor(form: FormModel, json?: any) {
super(form, json);
if (json) {
this.numberOfColumns = <number> json.numberOfColumns;
let columnSize: number = 12;
if (this.numberOfColumns > 1) {
columnSize = 12 / this.numberOfColumns;
}
for (let i = 0; i < this.numberOfColumns; i++) {
let col = new ContainerColumnModel();
col.size = columnSize;
this.columns.push(col);
}
if (json.fields) {
Object.keys(json.fields).map(key => {
let fields = (json.fields[key] || []).map(f => new FormFieldModel(form, f));
let col = this.columns[parseInt(key, 10) - 1];
col.fields = fields;
});
}
constructor(field: FormFieldModel) {
super(field);
if (this.field) {
this.columns = this.field.columns || [];
this.isExpanded = !this.isCollapsedByDefault();
this.children = this.getFormFields();
}
}
private getFormFields(): FormFieldModel[] {
let result: FormFieldModel[] = [];
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;
}
}

View File

@ -53,12 +53,12 @@ describe('ContainerWidget', () => {
});
it('should toggle underlying group container', () => {
let container = new ContainerWidgetModel(new FormModel(), {
let container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
type: FormFieldTypes.GROUP,
params: {
allowCollapse: true
}
});
}));
let widget = new ContainerWidget();
widget.content = container;
@ -71,9 +71,9 @@ describe('ContainerWidget', () => {
});
it('should toggle only collapsible container', () => {
let container = new ContainerWidgetModel(new FormModel(), {
let container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
type: FormFieldTypes.GROUP
});
}));
let widget = new ContainerWidget();
widget.content = container;
@ -84,12 +84,12 @@ describe('ContainerWidget', () => {
});
it('should toggle only group container', () => {
let container = new ContainerWidgetModel(new FormModel(), {
let container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
type: FormFieldTypes.CONTAINER,
params: {
allowCollapse: true
}
});
}));
let widget = new ContainerWidget();
widget.content = container;
@ -134,18 +134,18 @@ describe('ContainerWidget', () => {
beforeEach(() => {
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
window['componentHandler'] = componentHandler;
fakeContainerVisible = new ContainerWidgetModel(new FormModel(fakeFormJson), {
fakeContainerVisible = new ContainerWidgetModel(new FormFieldModel(new FormModel(fakeFormJson), {
fieldType: FormFieldTypes.GROUP,
id: 'fake-cont-id-1',
name: 'fake-cont-1-name',
type: FormFieldTypes.GROUP
});
fakeContainerInvisible = new ContainerWidgetModel(new FormModel(fakeFormJson), {
}));
fakeContainerInvisible = new ContainerWidgetModel(new FormFieldModel(new FormModel(fakeFormJson), {
fieldType: FormFieldTypes.GROUP,
id: 'fake-cont-id-2',
name: 'fake-cont-2-name',
type: FormFieldTypes.GROUP
});
}));
fakeContainerVisible.field.isVisible = true;
fakeContainerInvisible.field.isVisible = false;
});

View File

@ -37,7 +37,7 @@ export class ContainerWidget extends WidgetComponent implements OnInit, AfterVie
ngOnInit() {
if (this.field) {
this.content = new ContainerWidgetModel(this.field.form, this.field.json);
this.content = new ContainerWidgetModel(this.field);
}
}

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { FormFieldModel } from './../core/form-field.model';
import { FormFieldModel } from './form-field.model';
export class ContainerColumnModel {

View File

@ -17,19 +17,14 @@
import { ContainerModel } from './container.model';
import { FormModel } from './form.model';
import { FormFieldModel } from './form-field.model';
describe('ContainerModel', () => {
it('should store the form reference', () => {
let form = new FormModel();
let model = new ContainerModel(form);
let model = new ContainerModel(new FormFieldModel(form));
expect(model.form).toBe(form);
});
it('should store original json', () => {
let json = {};
let model = new ContainerModel(null, json);
expect(model.json).toBe(json);
});
});

View File

@ -16,23 +16,20 @@
*/
import { FormWidgetModel } from './form-widget.model';
import { FormModel } from './form.model';
import { FormFieldModel } from './form-field.model';
export class ContainerModel extends FormWidgetModel {
field: FormFieldModel;
children: FormFieldModel[] = [];
get isVisible(): boolean {
return this.field.isVisible;
}
constructor(form: FormModel, json?: any) {
super(form, json);
if (json) {
this.field = new FormFieldModel(form, json);
constructor(field: FormFieldModel) {
if (field) {
super(field.form, field.json);
this.field = field;
}
}

View File

@ -20,6 +20,7 @@ import { FormFieldOption } from './form-field-option';
import { FormFieldTypes } from './form-field-types';
import { FormFieldMetadata } from './form-field-metadata';
import { FormModel } from './form.model';
import { ContainerColumnModel } from './container-column.model';
import { WidgetVisibilityModel } from '../../../models/widget-visibility.model';
import {
FormFieldValidator,
@ -74,7 +75,12 @@ export class FormFieldModel extends FormWidgetModel {
enableFractions: boolean = false;
currency: string = null;
// advanced members
// container model members
numberOfColumns: number = 1;
fields: FormFieldModel[] = [];
columns: ContainerColumnModel[] = [];
// util members
emptyOption: FormFieldOption;
validationSummary: string;
validators: FormFieldValidator[] = [];
@ -150,6 +156,30 @@ export class FormFieldModel extends FormWidgetModel {
this.enableFractions = <boolean>json.enableFractions;
this.currency = json.currency;
this._value = this.parseValue(json);
// <container>
this.numberOfColumns = <number> json.numberOfColumns;
let columnSize: number = 12;
if (this.numberOfColumns > 1) {
columnSize = 12 / this.numberOfColumns;
}
for (let i = 0; i < this.numberOfColumns; i++) {
let col = new ContainerColumnModel();
col.size = columnSize;
this.columns.push(col);
}
if (json.fields) {
Object.keys(json.fields).map(key => {
let fields = (json.fields[key] || []).map(f => new FormFieldModel(form, f));
let col = this.columns[parseInt(key, 10) - 1];
col.fields = fields;
this.fields.push(...fields);
});
}
// </container>
}
if (this.hasEmptyValue && this.options && this.options.length > 0) {

View File

@ -125,7 +125,7 @@ export class FormModel {
if (field instanceof ContainerModel) {
let container = <ContainerModel> field;
result.push(container.field);
result.push(...container.children);
result.push(...container.field.fields);
}
}
@ -172,11 +172,11 @@ export class FormModel {
if (field.params) {
let originalField = field.params['field'];
if (originalField.type === FormFieldTypes.DYNAMIC_TABLE) {
result.push(new ContainerModel(this, field));
result.push(new ContainerModel(new FormFieldModel(this, field)));
}
}
} else {
result.push(new ContainerModel(this, field));
result.push(new ContainerModel(new FormFieldModel(this, field)));
}
}

View File

@ -53,7 +53,7 @@ describe('TabModel', () => {
model.fields = [];
expect(model.hasContent()).toBeFalsy();
model.fields = [new ContainerModel(null, null)];
model.fields = [new ContainerModel(null)];
expect(model.hasContent()).toBeTruthy();
});

View File

@ -5,7 +5,7 @@
[attr.id]="field.id"
[attr.required]="isRequired()"
[(ngModel)]="field.value"
(ngModelChange)="checkVisibility(field)"
(ngModelChange)="onFieldChanged(field)"
[disabled]="field.readOnly">
<label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label>
<span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span>

View File

@ -81,6 +81,7 @@ export class WidgetComponent implements AfterViewInit {
/** @deprecated use onFieldChanged instead */
checkVisibility(field: FormFieldModel) {
console.log('checkVisibility is deprecated, use onFieldChanged instead');
this.fieldChanged.emit(field);
}

View File

@ -667,13 +667,13 @@ describe('WidgetVisibilityService', () => {
visibilityObjTest.leftFormFieldId = 'FIELD_TEST';
visibilityObjTest.operator = '!=';
visibilityObjTest.rightFormFieldId = 'LEFT_FORM_FIELD_ID';
let contModel = new ContainerModel(fakeFormWithField, {
let contModel = new ContainerModel(new FormFieldModel(fakeFormWithField, {
id: 'fake-container-id',
type: FormFieldTypes.GROUP,
name: 'fake-container-name',
isVisible: true,
visibilityCondition: visibilityObjTest
});
}));
fakeFormWithField.fields.push(contModel);
service.refreshVisibility(fakeFormWithField);
@ -684,13 +684,13 @@ describe('WidgetVisibilityService', () => {
visibilityObjTest.leftFormFieldId = 'FIELD_TEST';
visibilityObjTest.operator = '!=';
visibilityObjTest.rightFormFieldId = 'RIGHT_FORM_FIELD_ID';
let contModel = new ContainerModel(fakeFormWithField, {
let contModel = new ContainerModel(new FormFieldModel(fakeFormWithField, {
id: 'fake-container-id',
type: FormFieldTypes.GROUP,
name: 'fake-container-name',
isVisible: true,
visibilityCondition: visibilityObjTest
});
}));
service.refreshEntityVisibility(contModel.field);
expect(contModel.isVisible).toBeFalsy();
});

View File

@ -44,7 +44,8 @@ export class WidgetVisibilityService {
}
refreshEntityVisibility(element: FormFieldModel | TabModel) {
element.isVisible = this.evaluateVisibility(element.form, element.visibilityCondition);
let visible = this.evaluateVisibility(element.form, element.visibilityCondition);
element.isVisible = visible;
}
evaluateVisibility(form: FormModel, visibilityObj: WidgetVisibilityModel): boolean {