UNPKG

@ng-flexy/form

Version:

Flexy components and tools to build Angular 8+ applications

1,306 lines (1,295 loc) 72.9 kB
import * as i0 from '@angular/core'; import { Directive, Renderer2, ElementRef, Input, ViewContainerRef, ComponentFactoryResolver, InjectionToken, Injectable, Optional, Inject, EventEmitter, Component, ChangeDetectorRef, Output, TemplateRef, Pipe, NgModule } from '@angular/core'; import * as jsonata_ from 'jsonata'; import * as i3 from '@angular/forms'; import { FormControl, FormArray, Validators, FormGroup, AbstractControl, FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms'; import * as i2 from '@ng-flexy/layout'; import { FlexyLayout, FlexyLayoutJsonMapperService, FlexyLayoutModule } from '@ng-flexy/layout'; import { uniq, set, merge, has, get, cloneDeep, template } from 'lodash'; import * as i4 from '@ng-flexy/core'; import { FlexyLoggerService } from '@ng-flexy/core'; import { BehaviorSubject } from 'rxjs'; import { skip, debounceTime, map } from 'rxjs/operators'; import { __awaiter } from 'tslib'; import { TranslateService, TranslateModule } from '@ngx-translate/core'; import * as i1 from '@angular/common/http'; import { HttpClient } from '@angular/common/http'; import { CommonModule } from '@angular/common'; const jsonata$4 = jsonata_; function bindAttributes(schema, nativeEl, renderer, data) { if (nativeEl && renderer && schema.attributes) { Object.keys(schema.attributes).forEach(attrKey => { if (typeof schema.attributes[attrKey] === 'object') { const attrValues = []; Object.keys(schema.attributes[attrKey]).forEach(oKey => { if (schema.attributes[attrKey][oKey]) { try { const is = jsonata$4(schema.attributes[attrKey][oKey]).evaluate(data); if (is) { attrValues.push(oKey); } } catch (e) { // do nothing } } }); renderer.setAttribute(nativeEl, attrKey, attrValues.join(' ')); } else if (typeof schema.attributes[attrKey] === 'string') { renderer.setAttribute(nativeEl, attrKey, schema.attributes[attrKey]); } }); } } class FlexyFormAttributesDirective { constructor(renderer, el) { this.renderer = renderer; this.el = el; } ngOnInit() { if (this.componentSchema && this.componentSchema.attributes) { bindAttributes(this.componentSchema, this.el.nativeElement, this.renderer, this.flexyForm.currentData); this._changesSubscription = this.flexyForm.currentData$.subscribe(data => { bindAttributes(this.componentSchema, this.el.nativeElement, this.renderer, data); }); } } ngOnDestroy() { if (this._changesSubscription) { this._changesSubscription.unsubscribe(); } } } FlexyFormAttributesDirective.decorators = [ { type: Directive, args: [{ selector: '[flexyFormAttributes]' },] } ]; FlexyFormAttributesDirective.ctorParameters = () => [ { type: Renderer2 }, { type: ElementRef } ]; FlexyFormAttributesDirective.propDecorators = { flexyForm: [{ type: Input }], componentSchema: [{ type: Input }] }; const LAYOUT_SCHEMA_KEY = 'layoutSchema'; const LAYOUT_FORM_KEY = 'form'; class FlexyFormContainerDirective { constructor(vc, resolver, renderer) { this.vc = vc; this.resolver = resolver; this.renderer = renderer; } set componentSchema(schema) { this._schema = schema; } get componentSchema() { return this._schema; } ngOnInit() { if (!this._schema) { return; } if (!this._schema.componentType) { return; } const componentFactory = this.resolver.resolveComponentFactory(this._schema.componentType); const viewContainerRef = this.vc; viewContainerRef.clear(); const componentRef = viewContainerRef.createComponent(componentFactory); componentRef.instance[LAYOUT_SCHEMA_KEY] = this._schema; componentRef.instance[LAYOUT_FORM_KEY] = this.flexyForm; this._schema.componentRef = componentRef; if (this._schema.componentInputs) { Object.keys(this._schema.componentInputs).forEach(key => { componentRef.instance[key] = this._schema.componentInputs[key]; }); } this._componentRef = componentRef; if (this._schema.attributes) { bindAttributes(this._schema, this._componentRef.location.nativeElement, this.renderer, this.flexyForm.currentData); } this._changesSubscription = this.flexyForm.currentData$.subscribe(data => { if (this._componentRef) { bindAttributes(this.componentSchema, this._componentRef.location.nativeElement, this.renderer, data); } }); } ngOnDestroy() { if (this._changesSubscription) { this._changesSubscription.unsubscribe(); } } } FlexyFormContainerDirective.decorators = [ { type: Directive, args: [{ selector: '[flexyFormContainer]' },] } ]; FlexyFormContainerDirective.ctorParameters = () => [ { type: ViewContainerRef }, { type: ComponentFactoryResolver }, { type: Renderer2 } ]; FlexyFormContainerDirective.propDecorators = { flexyForm: [{ type: Input }], componentSchema: [{ type: Input }] }; const COMPLEX_TYPE_INDEX_MARKER = '{%}'; var FlexyFormFieldType; (function (FlexyFormFieldType) { FlexyFormFieldType["String"] = "string"; FlexyFormFieldType["Number"] = "number"; FlexyFormFieldType["Boolean"] = "boolean"; FlexyFormFieldType["Array"] = "array"; FlexyFormFieldType["Group"] = "group"; })(FlexyFormFieldType || (FlexyFormFieldType = {})); var FlexyFormsValidators; (function (FlexyFormsValidators) { function notEmptyValidator(control) { if (!control) { return null; } if (FlexyFormsValidators.isEmpty(control)) { return { 'not-empty': true }; } return null; } FlexyFormsValidators.notEmptyValidator = notEmptyValidator; function noWhitespaceValidator(control) { if (!control) { return null; } if ((control.value || '').trim().length === 0) { return { whitespace: true }; } return null; } FlexyFormsValidators.noWhitespaceValidator = noWhitespaceValidator; function emailValidator(control) { if (!control) { return null; } const re = new RegExp([ '^(([^<>()\\[\\]\\\\.,;:!#\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:!#\\s@"]+)*)|(".+"))@', '((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)', '+[a-zA-Z]{2,})|([a-zA-Z\\-0-9]+))$' ].join('')); if (!FlexyFormsValidators.isEmpty(control) && !re.test(control.value)) { return { 'invalid-email': { currentValue: control.value } }; } return null; } FlexyFormsValidators.emailValidator = emailValidator; function booleanValidator(control) { if (!control) { return null; } if (!FlexyFormsValidators.isEmpty(control) && typeof control.value !== 'boolean') { return { 'invalid-boolean': { currentValue: control.value } }; } return null; } FlexyFormsValidators.booleanValidator = booleanValidator; function integerValidator(control) { if (!control) { return null; } const re = /^-?\d+$/; if (!FlexyFormsValidators.isEmpty(control) && !re.test(control.value)) { return { 'invalid-integer': { currentValue: control.value } }; } return null; } FlexyFormsValidators.integerValidator = integerValidator; function minValidator(min) { return (control) => { if (!control) { return null; } if (!(min || min === 0)) { return { 'invalid-min': { wrongConfiguration: true } }; } const notNumber = FlexyFormsValidators.numberValidator(control); if (notNumber) { return notNumber; } if ((!FlexyFormsValidators.isEmpty(control) || control.value === 0) && control.value < min) { return { 'invalid-min': { minimumValue: min, currentValue: control.value } }; } return null; }; } FlexyFormsValidators.minValidator = minValidator; function maxValidator(max) { return (control) => { if (!control) { return null; } if (!(max || max === 0)) { return { 'invalid-max': { wrongConfiguration: true } }; } const notNumber = FlexyFormsValidators.numberValidator(control); if (notNumber) { return notNumber; } if ((!FlexyFormsValidators.isEmpty(control) || control.value === 0) && control.value > max) { return { 'invalid-max': { maximumValue: max, currentValue: control.value } }; } return null; }; } FlexyFormsValidators.maxValidator = maxValidator; function numberValidator(control) { if (!control) { return null; } const re = /^-?(\d+\.?\d*)$|^(\d*\.?\d+)$/; if (!FlexyFormsValidators.isEmpty(control) && !re.test(control.value)) { return { 'invalid-number': { currentValue: control.value } }; } return null; } FlexyFormsValidators.numberValidator = numberValidator; function minLengthArray(min) { return (control) => { if (!control) { return null; } if (!(min || min === 0)) { return { 'min-length-array': { wrongConfiguration: true } }; } if (control.value && Array.isArray(control.value) && control.value.length >= min) { return null; } return { 'min-length-array': { minimumLength: min, currentLength: control.value } }; }; } FlexyFormsValidators.minLengthArray = minLengthArray; function maxLengthArray(max) { return (control) => { if (!control) { return null; } if (!(max || max === 0)) { return { 'max-length-array': { wrongConfiguration: true } }; } if (control.value && control.value.length <= max) { return null; } return { 'max-length-array': { maximumLength: max, currentLength: control.value } }; }; } FlexyFormsValidators.maxLengthArray = maxLengthArray; function isEmpty(control) { if (!control) { return null; } if (!control.value || (Array.isArray(control.value) && control.value.length === 0)) { return true; } else { return false; } } FlexyFormsValidators.isEmpty = isEmpty; function urlValidator(control) { if (!control) { return null; } const re = new RegExp([ '((([A-Za-z]{3,9}:(?:\\/\\/)?)(?:[\\-;:&=\\+\\$,\\w]+@)?[A-Za-z0-9\\.\\-]', '+|(?:www\\.|[\\-;:&=\\+\\$,\\w]+@)[A-Za-z0-9\\.\\-]+)((?:\\/[\\+~%\\/\\.\\w\\-_]*)?\\??', '(?:[\\-\\+=&;%@\\.\\w_]*)#?(?:[\\.\\!\\/\\\\\\w]*))?)' ].join('')); if (!FlexyFormsValidators.isEmpty(control) && !re.test(control.value)) { return { 'invalid-url': { currentValue: control.value } }; } return null; } FlexyFormsValidators.urlValidator = urlValidator; function crossFieldValidator(fields) { return (control) => { if (!control) { return null; } if (!(fields && fields.lower && fields.greater)) { return { 'cross-field-invalid': { wrongConfiguration: true } }; } const lower = getControl(fields.lower.path, control); const greater = getControl(fields.greater.path, control); if (!(greater && greater.valid && lower && lower.valid && greater.value < lower.value)) { return null; } return { 'cross-field-invalid': { greater: fields.greater.name, greaterPath: fields.greater.path, greaterValue: greater && greater.value, lower: fields.lower.name, lowerPath: fields.lower.path, lowerValue: lower && lower.value } }; }; } FlexyFormsValidators.crossFieldValidator = crossFieldValidator; function crossFieldMinValidator(minPath) { return (control) => { if (!control) { return null; } if (!minPath) { return { 'invalid-min': { wrongConfiguration: true } }; } const min = getControl(minPath, control); if (!(min && control.valid && control.value < min.value)) { return null; } return { 'invalid-min': { minimumValue: min.value, currentValue: control.value } }; }; } FlexyFormsValidators.crossFieldMinValidator = crossFieldMinValidator; function crossFieldMaxValidator(maxPath) { return (control) => { if (!control) { return null; } if (!maxPath) { return { 'invalid-max': { wrongConfiguration: true } }; } const max = getControl(maxPath, control); if (!(max && control.valid && control.value > max.value)) { return null; } return { 'invalid-max': { maximumValue: max.value, currentValue: control.value } }; }; } FlexyFormsValidators.crossFieldMaxValidator = crossFieldMaxValidator; function crossFieldAbsoluteMinValidator(minPath) { return (control) => { if (!control) { return null; } if (!minPath) { return { 'absolute-min-invalid': { wrongConfiguration: true } }; } const min = getControl(minPath, control); if (min && control.valid && min.valid && Math.abs(control.value) >= min.value) { return null; } return { 'absolute-min-invalid': { min: min.value, currentValue: control.value } }; }; } FlexyFormsValidators.crossFieldAbsoluteMinValidator = crossFieldAbsoluteMinValidator; function forbiddenValuesValidator(forbiddenValues) { return (control) => { if (!control) { return null; } if (!forbiddenValues) { return { 'forbidden-value': { wrongConfiguration: true } }; } if (!forbiddenValues.includes(control.value)) { return null; } return { 'forbidden-value': { value: control.value } }; }; } FlexyFormsValidators.forbiddenValuesValidator = forbiddenValuesValidator; function arrayUniqueFieldsValidator(data) { return (control) => { if (!control || !control.controls) { return null; } if (!(data && data.path)) { return { 'value-duplicate': { wrongConfiguration: true } }; } const comparedValues = []; control.controls.forEach(item => { const compared = getControl(data.path, item); if (compared) { comparedValues.push(compared.value); } }); if (uniq(comparedValues).length === comparedValues.length) { return null; } return { 'value-duplicate': { field: data.fieldName } }; }; } FlexyFormsValidators.arrayUniqueFieldsValidator = arrayUniqueFieldsValidator; function getControl(path, control) { let arrayPath = []; if (Array.isArray(path)) { arrayPath = path; } else if (path) { const parents = ('' + path).indexOf('../') !== -1 ? path.split('../') : [path]; const sPath = '' + parents.pop(); const stringPath = sPath.split('.').map(i => { if (i.match(/^[0-9]+$/)) { return parseInt(i, 10); } else { return i; } }); parents.fill('../'); parents.push(...stringPath); arrayPath = parents; } arrayPath.forEach(i => { if (control && control.parent && i === '../') { control = control.parent; } else if (control && control.controls) { control = Number.isInteger(i) ? control.get(Object.keys(control.controls)[i]) : control.controls[i]; } }); return control; } })(FlexyFormsValidators || (FlexyFormsValidators = {})); const HIDDEN_IF_GROUP_NAME = '__if__'; const HIDDEN_CALC_GROUP_NAME = '__calc__'; function parseFormJson(json) { if (Array.isArray(json)) { return parseFormVersion1(json); } else if (json.schemaVersion === 1) { return parseFormVersion1(json.schema); } else { const schema = json.schema; assignHiddenNames(schema); checkSchema(schema); return json.schema; } } function checkSchema(schema) { if (schema && Array.isArray(schema)) { schema.forEach((jsonItem, index) => { if (jsonItem.if && (jsonItem.name || jsonItem.type)) { console.warn('Wrong if schema', jsonItem); } if (jsonItem.children) { checkSchema(jsonItem.children); } }); } } function assignHiddenNames(schema) { if (schema && Array.isArray(schema)) { schema.forEach((jsonItem, index) => { // if ((jsonItem as FlexyFormIfJsonSchema).if && !(jsonItem as FlexyFormFieldLayoutJsonSchema).type) { // (jsonItem as FlexyFormFieldLayoutJsonSchema).type = FlexyFormFieldType.Group; // } if (jsonItem.calc && !jsonItem.name) { jsonItem.name = HIDDEN_CALC_GROUP_NAME + '.' + (jsonItem.id ? jsonItem.id : 'ui-' + Math.random() .toString(36) .substr(2, 9)); } if (jsonItem.children) { assignHiddenNames(jsonItem.children); } }); } } function parseFormVersion1(json) { const parsed = []; json.forEach(item => { parsed.push(parseFormVersion1Item(item)); }); return parsed; } function parseFormVersion1Item(item) { const schema = {}; if (item.properties && item.properties.class) { if (!schema.attributes) { schema.attributes = {}; } schema.attributes.class = item.properties.class; } if (item.component) { Object.assign(schema, { component: item.component, properties: item.componentInputs ? item.componentInputs : {} }); } if (item.controlGroupName) { Object.assign(schema, { name: item.controlGroupName, type: FlexyFormFieldType.Group, validators: item.validators, groupKey: item.groupKey, items: item.items ? parseFormVersion1Item(item.items) : void 0, indexDef: item.itemKeyDef, indexPattern: item.itemKeyPattern, indexGenPattern: item.itemKeyGen }); } else if (item.controlArrayName) { Object.assign(schema, { name: item.controlArrayName, type: FlexyFormFieldType.Array, validators: item.validators, items: item.items ? parseFormVersion1Item(item.items) : void 0, indexDef: item.itemIndexDef }); } else if (item.controlName) { Object.assign(schema, { name: item.controlName, validators: item.validators }); } if (item.children) { schema.children = parseFormVersion1(item.children); } return schema; } function replaceMarker(s, marker, key) { return s.split(marker).join('' + key); } const jsonata$3 = jsonata_; const ifExpressionsCache = {}; const calculatedExpresionCache = {}; var FlexyFormDataMode; (function (FlexyFormDataMode) { FlexyFormDataMode["All"] = "all"; FlexyFormDataMode["Dirty"] = "dirty"; FlexyFormDataMode["Touched"] = "toched"; })(FlexyFormDataMode || (FlexyFormDataMode = {})); function findErrors(schema, currentData) { const errors = {}; for (const item of schema) { if (checkIf(item, currentData) && item.items) { item.items.forEach((aItem, index) => { const arrItemError = findErrors([aItem], currentData); if (arrItemError && Object.values(arrItemError).length) { errors[item.formName + '.' + index] = arrItemError; } }); } else if (checkIf(item, currentData) && item.formName && item.formControl && item.formControl.invalid) { errors[item.formName] = item.formControl.errors; } if (checkIf(item, currentData) && item.children) { Object.assign(errors, findErrors(item.children, currentData)); } } return errors; } function findSchema(fieldName, schema) { for (const item of schema) { if (item.formName && item.formName === fieldName) { return item; } else if (item.children) { const childSchema = findSchema(fieldName, item.children); if (childSchema) { return childSchema; } } } return null; } function calculate(calcExp, data) { let value; try { if (!calculatedExpresionCache[calcExp]) { calculatedExpresionCache[calcExp] = jsonata$3(calcExp); } value = calculatedExpresionCache[calcExp].evaluate(data); } catch (e) { // console.error(e); value = null; } return value; } function getSchemaData$1(schemas, currentData, mode = FlexyFormDataMode.All) { let data = {}; if (schemas) { schemas.forEach(schema => { const fieldSchema = schema; if (checkIf(fieldSchema, currentData)) { const isFormControl = fieldSchema.formControl && fieldSchema.formName; if (isFormControl && fieldSchema.formControl instanceof FormControl) { if (checkSchemaData(fieldSchema.formControl, mode)) { set(data, fieldSchema.formName, fieldSchema.formControl.value); } } else if (isFormControl && fieldSchema.formControl instanceof FormArray) { const arrayData = getArrayData(fieldSchema, currentData, mode, data); if (mode === FlexyFormDataMode.All) { set(data, fieldSchema.formName, Object.values(arrayData)); } else if (!isInputEmpty(arrayData)) { set(data, fieldSchema.formName, arrayData); } } if (checkIf(fieldSchema, currentData)) { data = merge(data, getSchemaData$1(fieldSchema.children, currentData, mode)); } } }); } return data; } function findRemoved(allData, originalData) { const removed = {}; if (originalData) { Object.keys(originalData).forEach(key => { const path = key; if (!isInputEmpty(originalData[key]) && isInputEmpty(allData[key])) { set(removed, path, null); } else if (originalData[key] && Array.isArray(originalData[key])) { originalData[key].forEach((item, index) => { if (!isInputEmpty(item) && isInputEmpty(allData[key][index])) { if (!has(removed, path)) { set(removed, path, {}); } const v = get(removed, path); v['' + index] = null; } else if (item && isObject(item)) { const founded = findRemoved(allData[key][index], item); if (founded && !isInputEmpty(founded)) { if (!has(removed, path)) { set(removed, path, {}); } const v = get(removed, path); v['' + index] = founded; } } }); } else if (originalData[key] && isObject(originalData[key])) { const founded = findRemoved(allData[key], originalData[key]); if (founded && !isInputEmpty(founded)) { set(removed, path, founded); } } }); } return removed; } function clearEmptyArrayAndObjects(data) { if (data) { if (isObject(data)) { Object.keys(data).forEach(key => { if (isEmptyStructure(data[key])) { delete data[key]; } else if (isObject(data[key])) { clearEmptyArrayAndObjects(data[key]); } }); } } } function isObject(a) { return !!a && a.constructor === Object; } function checkIf(fieldSchema, currentData) { if (!fieldSchema.if) { return true; } const ifName = 'IF_' + fieldSchema.if; let ret; try { if (!ifExpressionsCache[ifName]) { ifExpressionsCache[ifName] = jsonata$3(fieldSchema.if); } ret = ifExpressionsCache[ifName].evaluate(currentData ? currentData : {}); } catch (e) { // console.error(e); ret = null; } return !!ret; } function getArrayData(fieldSchema, currentData, mode, data) { const arrayData = {}; fieldSchema.items.forEach((item, index) => { const itemFormControl = item.formControl; if (!itemFormControl || checkSchemaData(itemFormControl, mode)) { if (item.children) { const itemData = getSchemaData$1(item.children, currentData, mode); if (!isInputEmpty(itemData)) { arrayData['' + index] = itemData; } } else { arrayData['' + index] = item.formControl.value; } } }); return arrayData; } function checkSchemaData(control, mode) { return (control && (mode === FlexyFormDataMode.All || (mode === FlexyFormDataMode.Dirty && control.dirty) || (mode === FlexyFormDataMode.Touched && control.touched))); } function isInputEmpty(v) { return v === void 0 || v === ''; } function isEmptyStructure(data) { let ret = true; if (Array.isArray(data)) { if (data.length > 0) { data.forEach(item => { ret = ret && isEmptyStructure(item); }); } } else if (isObject(data)) { if (Object.keys(data).length > 0) { Object.keys(data).forEach(key => { ret = ret && isEmptyStructure(data[key]); }); } } else { return isInputEmpty(data); } return ret; } class FlexyForm extends FlexyLayout { constructor(formGroup, schema, data) { super(schema); this.isStarted = false; this._calculatedRefs = {}; this._currentDataSubject = new BehaviorSubject(data); this.currentData$ = this._currentDataSubject.asObservable(); this.formGroup = formGroup; this.schema = schema; this._initCalculatedRefs(schema); this._originalData = cloneDeep(data); this._setCurrentData(); // refresh attributes this._setCurrentData(); // jump to next tick // setTimeout(() => { this._subscribeChangesAndCalculate(); // }); } get valid() { return !this.getAllErrors(); } getAllData() { const data = cloneDeep(getSchemaData$1(this.schema, this.currentData)); this._clearHiddenData(data); return data; } // @deprecated getDirtyData() { const data = cloneDeep(getSchemaData$1(this.schema, this.currentData, FlexyFormDataMode.Dirty)); this._clearHiddenData(data); clearEmptyArrayAndObjects(data); const allData = this.getAllData(); const removed = findRemoved(allData, this._originalData); clearEmptyArrayAndObjects(removed); return merge(data, removed); } getAllErrors() { return this._lastErrors; } containsFieldSchema(fieldName) { return !!findSchema(fieldName, this.schema); } getFieldSchema(fieldName) { return findSchema(fieldName, this.schema); } getFieldInstance(fieldName) { const schema = findSchema(fieldName, this.schema); if (schema && schema.componentRef) { return schema.componentRef.instance; } return null; } _subscribeChangesAndCalculate() { this._setCurrentData(); this.isStarted = true; // this._setCurrentData(); this._changesSubscription = this.formGroup.valueChanges.subscribe(data => { const hash = this.currentDataHash; this._setCurrentData(); if (hash !== this.currentDataHash) { this._currentDataSubject.next(this.currentData); } }); this._currentDataSubject.next(this.currentData); } _setCurrentData() { this.currentData = getSchemaData$1(this.schema, this.currentData); this.currentDataHash = JSON.stringify(this.currentData); this._calculate(); this.currentData = getSchemaData$1(this.schema, this.currentData); this.currentDataHash = JSON.stringify(this.currentData); const errors = findErrors(this.schema, this.currentData); this._lastErrors = errors && Object.keys(errors).length ? errors : null; } _initCalculatedRefs(schema) { if (schema) { schema.forEach((schemaItem) => { if (schemaItem.formName && schemaItem.formControl && schemaItem.calc) { this._calculatedRefs[schemaItem.formName] = { calc: schemaItem.calc, control: schemaItem.formControl }; } if (schemaItem.children) { this._initCalculatedRefs(schemaItem.children); } }); } } _calculate() { if (this._calculatedRefs) { Object.values(this._calculatedRefs).forEach(calc => { const value = calculate(calc.calc, this.currentData); if (value !== calc.control.value) { calc.control.setValue(value); calc.control.markAsDirty(); } }); } } _clearHiddenData(data) { if (data[HIDDEN_CALC_GROUP_NAME]) { delete data[HIDDEN_CALC_GROUP_NAME]; } } } const FLEXY_FORM_VALIDATORS = new InjectionToken('FLEXY_FORM_VALIDATORS'); const INPUTS_READONLY_KEY = 'readonly'; const SCHEMA_CONTROL_NAME_KEY = 'name'; const SCHEMA_GROUP_KEY = 'groupKey'; const SCHEMA_COMPONENT_INPUTS_KEY = 'properties'; const SCHEMA_DEFAULT_KEY = 'default'; const ɵ0 = () => Validators.required, ɵ1 = data => Validators.maxLength(data), ɵ2 = data => Validators.minLength(data), ɵ3 = data => FlexyFormsValidators.minValidator(data), ɵ4 = data => FlexyFormsValidators.maxValidator(data), ɵ5 = () => FlexyFormsValidators.numberValidator, ɵ6 = () => FlexyFormsValidators.integerValidator, ɵ7 = () => FlexyFormsValidators.booleanValidator, ɵ8 = () => FlexyFormsValidators.emailValidator, ɵ9 = () => FlexyFormsValidators.noWhitespaceValidator, ɵ10 = () => FlexyFormsValidators.notEmptyValidator, ɵ11 = data => Validators.pattern(data), ɵ12 = data => FlexyFormsValidators.crossFieldValidator(data), ɵ13 = data => FlexyFormsValidators.crossFieldMinValidator(data), ɵ14 = data => FlexyFormsValidators.crossFieldMaxValidator(data), ɵ15 = data => FlexyFormsValidators.crossFieldAbsoluteMinValidator(data), ɵ16 = data => FlexyFormsValidators.forbiddenValuesValidator(data), ɵ17 = data => FlexyFormsValidators.arrayUniqueFieldsValidator(data), ɵ18 = data => FlexyFormsValidators.minLengthArray(data), ɵ19 = data => FlexyFormsValidators.maxLengthArray(data); const DEFAULT_VALIDATORS_MAP = { required: ɵ0, maxLength: ɵ1, minLength: ɵ2, min: ɵ3, max: ɵ4, number: ɵ5, integer: ɵ6, boolean: ɵ7, email: ɵ8, noWhitespace: ɵ9, notEmpty: ɵ10, pattern: ɵ11, crossField: ɵ12, crossFieldMin: ɵ13, crossFieldMax: ɵ14, crossFieldAbsoluteMin: ɵ15, forbiddenValues: ɵ16, arrayUniqueFields: ɵ17, minItems: ɵ18, maxItems: ɵ19 }; class FlexyFormJsonMapperService { constructor(validatorsMap, jsonLayoutMapper, formBuilder, logger) { this.jsonLayoutMapper = jsonLayoutMapper; this.formBuilder = formBuilder; this.logger = logger; this._validatorsMap = DEFAULT_VALIDATORS_MAP; if (validatorsMap) { Object.assign(this._validatorsMap, validatorsMap); } } get supportedValidators() { return Object.keys(this._validatorsMap); } createForm(json, readonlyMode = false, formData) { this.logger.debug('createForm'); FlexyFormJsonMapperService.controlCounter = 0; const jsonSchema = parseFormJson(json); const rootFormGroup = new FormGroup({}); const dynamicSchema = this.map(jsonSchema, readonlyMode, formData, rootFormGroup); this.logger.debug('countOfControls', FlexyFormJsonMapperService.controlCounter); return new FlexyForm(rootFormGroup, dynamicSchema, formData); } createItemControl(itemsSchema, readonlyMode, value) { let control; if (itemsSchema.children) { control = new FormGroup({}); this.mapItem(itemsSchema, readonlyMode, value, control); } else { control = this.formBuilder.control(value ? value : get(itemsSchema, 'componentInputs.default'), itemsSchema.validators ? this.mapValidators(itemsSchema.validators) : []); } return control; } createArrayItemSchema(control, items, itemKeyDef, parentName, readonlyMode, formData, value, index, parentSchema = null) { const citems = cloneDeep(items); this.populateComplexTypeIndexMarker([citems], index + 1, value, itemKeyDef); const isComplex = citems && !!citems.children; let schema; if (isComplex) { const withRootValues = Object.assign({}, value); const groupSchema = this._jsonLayoutItemMap({}, '' + index, parentSchema); groupSchema.children = this.map([citems], readonlyMode, withRootValues, control, groupSchema); schema = groupSchema; } else { schema = this.jsonLayoutMapper.map([citems])[0]; schema.formControl = control; control.setValue(value); schema.id = parentSchema.id + ':' + index; if (!schema.componentInputs) { schema.componentInputs = {}; } schema.componentInputs[INPUTS_READONLY_KEY] = readonlyMode; } return schema; } createGroupItemSchema(control, items, itemKeyDef, parentName, readonlyMode, formData, value, key, parentSchema = null) { const citems = cloneDeep(items); this.populateComplexTypeIndexMarker([citems], key, value, itemKeyDef); const isComplex = citems && !!citems.children; let schema; if (isComplex) { const withRootValues = Object.assign({}, value); const groupSchema = this.map([citems], readonlyMode, withRootValues, control, null)[0]; schema = groupSchema; } else { schema = this.jsonLayoutMapper.map([citems])[0]; schema.formControl = control; control.setValue(value); if (!schema.componentInputs) { schema.componentInputs = {}; } schema.componentInputs[INPUTS_READONLY_KEY] = readonlyMode; schema.formName = citems.name; } schema.id = (parentSchema && parentSchema.id ? parentSchema.id + ':' : '') + key; schema.groupKey = key; return schema; } createSchema(json, readonlyMode = false, formData = {}, parentFormGroup, parentControlGroupName, parentSchema = null) { const schema = []; if (json && Array.isArray(json)) { json.forEach((jsonItem, index) => { const schemaItem = this.mapItem(jsonItem, readonlyMode, formData, parentFormGroup, parentControlGroupName, parentSchema, '' + index); let itemParentFormGroup = parentFormGroup; let itemParentControlGroupName = parentControlGroupName; if (schemaItem.formControl instanceof FormGroup) { itemParentFormGroup = schemaItem.formControl; if ([FlexyFormFieldType.Group, FlexyFormFieldType.Array].includes(jsonItem.type)) { itemParentControlGroupName = this.controlComplexName(jsonItem, itemParentControlGroupName); } } if (jsonItem.children) { schemaItem.children = this.createSchema(jsonItem.children, readonlyMode, formData, itemParentFormGroup, itemParentControlGroupName, schemaItem); } schema.push(schemaItem); }); } return schema; } map(json, readonlyMode = false, formData, parentFormGroup, parentSchema = null) { const dynamicSchema = this.createSchema(json, readonlyMode, formData, parentFormGroup, null, parentSchema); return dynamicSchema; } createControl(config) { return config instanceof AbstractControl ? config : config[1] ? this.formBuilder.control(config[0], config[1]) : this.formBuilder.control(config); } controlComplexName(jsonItem, parentName) { return (parentName && jsonItem.name[0] === '.' ? parentName : '') + jsonItem.name; } mapItem(jsonItem, readonlyMode = false, formData = {}, parentFormGroup, parentControlGroupName = null, parentSchema = null, schemaId = '') { const formSchemaItem = this._jsonLayoutItemMap(jsonItem, schemaId, parentSchema); if (readonlyMode) { if (!formSchemaItem.componentInputs) { formSchemaItem.componentInputs = {}; } formSchemaItem.componentInputs[INPUTS_READONLY_KEY] = readonlyMode; } let controlName = ''; if (jsonItem.name && jsonItem.type !== FlexyFormFieldType.Group && jsonItem.type !== FlexyFormFieldType.Array) { controlName = jsonItem.name; this.mapItemSetFieldControl(formSchemaItem, jsonItem, parentControlGroupName, formData); } else if (jsonItem.items && jsonItem.type === FlexyFormFieldType.Group) { const groupJsonItem = jsonItem; controlName = groupJsonItem.name; this.mapItemSetGroupControl(formSchemaItem, jsonItem, parentControlGroupName, readonlyMode); const formGroupName = (parentControlGroupName && groupJsonItem.name[0] === '.' ? parentControlGroupName : '') + groupJsonItem.name; const formGroupData = has(formData, formGroupName) ? get(formData, formGroupName) : void 0; if (formGroupData) { jsonItem.children = []; const isComplex = !!groupJsonItem.items.children; Object.keys(formGroupData).forEach(key => { const schemaJson = cloneDeep(groupJsonItem.items); schemaJson.name = '.' + key; if (isComplex) { schemaJson.type = FlexyFormFieldType.Group; } schemaJson[SCHEMA_GROUP_KEY] = key; this.populateComplexTypeIndexMarker([schemaJson], key, formGroupData[key], groupJsonItem.indexDef); groupJsonItem.children.push(schemaJson); }); } } else if (jsonItem.items && jsonItem.type === FlexyFormFieldType.Array) { const arrayJsonItem = jsonItem; controlName = arrayJsonItem.name; this.mapItemSetArrayControl(formSchemaItem, arrayJsonItem, parentControlGroupName, formData, readonlyMode); } else { controlName = jsonItem.type === FlexyFormFieldType.Group ? jsonItem.name : formSchemaItem.id ? 'g-' + formSchemaItem.id : 'd-' + Date.now(); formSchemaItem.groupKey = jsonItem.groupKey; // TODO to think: form control is required also for non FlexyFormFieldLayoutSchema formSchemaItem.formControl = new FormGroup({}, this.mapValidators(jsonItem.validators)); } const control = formSchemaItem.formControl; if (control) { parentFormGroup.addControl(this.unifyName(controlName, parentControlGroupName ? this.unifyName(parentControlGroupName) : ''), control); } return formSchemaItem; } _jsonLayoutItemMap(jsonItem, schemaId, parentSchema) { const schema = this.jsonLayoutMapper.mapItem(jsonItem, schemaId, parentSchema); // const SCHEMA_GROUP_KEY = 'groupKey'; const SCHEMA_IF = 'if'; const SCHEMA_CALC = 'calc'; // TODO tothink is problem with populate form group controls from external domain [SCHEMA_GROUP_KEY, SCHEMA_IF, SCHEMA_CALC].forEach(key => { if (jsonItem[key]) { schema[key] = jsonItem[key]; } }); return schema; } mapItemSetArrayControl(formSchemaItem, jsonItem, parentControlGroupName, formData, readonlyMode) { let formName = (parentControlGroupName && jsonItem.name[0] === '.' ? parentControlGroupName : '') + jsonItem.name; // its possible in array in group if (formName.endsWith('.')) { formName = formName.slice(0, -1); } const val = has(formData, formName) ? get(formData, formName) : void 0; const formControl = this.createArrayControl(jsonItem, readonlyMode, val); formSchemaItem.formControl = formControl; formSchemaItem.formName = formName; if (jsonItem.items) { formSchemaItem.items = this.createArrayItems(jsonItem.items, jsonItem.indexDef, formControl, formName, readonlyMode, val, formData, formSchemaItem); formSchemaItem.componentInputs = Object.assign(Object.assign({}, formSchemaItem.componentInputs), { jsonSchema: jsonItem, readonly: readonlyMode }); } } mapItemSetGroupControl(formSchemaItem, jsonItem, parentControlGroupName, readonlyMode) { const formGroupName = (parentControlGroupName && jsonItem.name[0] === '.' ? parentControlGroupName : '') + jsonItem.name; formSchemaItem.formControl = new FormGroup({}); if (jsonItem.items) { formSchemaItem.componentInputs = Object.assign(Object.assign({}, formSchemaItem.componentInputs), { jsonSchema: jsonItem, parentGroupName: formGroupName, readonly: readonlyMode }); } } mapItemSetFieldControl(formSchemaItem, jsonItem, parentControlGroupName, formData) { FlexyFormJsonMapperService.controlCounter++; const formName = (parentControlGroupName && jsonItem.name[0] === '.' ? parentControlGroupName : '') + jsonItem.name; const val = has(formData, formName) ? get(formData, formName) : void 0; const formControl = this.createControl(this.createControlConfig(jsonItem, val)); formSchemaItem.formName = formName; formSchemaItem.formControl = formControl; } createArrayItems(items, itemKeyDef, arrayControl, parentName, readonlyMode = false, values, formData, parentSchema = null) { const formArray = arrayControl; const formSchema = []; formArray.controls.forEach((control, index) => { let value; let key; if (values && Array.isArray(values)) { value = values[index]; key = '' + index; } else if (values && typeof values === 'object') { key = Object.keys(values)[index]; value = values[key]; } const schema = this.createArrayItemSchema(control, items, itemKeyDef, parentName, readonlyMode, formData, value, index, parentSchema); schema.formName = key; formSchema.push(schema); }); return formSchema; } populateComplexTypeIndexMarker(items, key, value, marker = COMPLEX_TYPE_INDEX_MARKER) { items.forEach(item => { if (item.name) { item.name = replaceMarker(item.name, marker, key); } if (item.if) { item.if = replaceMarker(item.if, marker, key); } if (item.calc) { item.calc = replaceMarker(item.calc, marker, key); } if (item.attributes) { Object.keys(item.attributes).forEach(attr => { if (typeof item.attributes[attr] === 'string') { item.attributes[attr] = replaceMarker('' + item.attributes[attr], marker, key); } else if (typeof item.attributes[attr] === 'object') { Object.keys(item.attributes[attr]).forEach(attrVal => { const replaced = replaceMarker(attrVal, marker, key); item.attributes[attr][attrVal] = replaceMarker('' + item.attributes[attr][attrVal], marker, key); if (replaced !== attrVal) { item.attributes[attr][replaced] = item.attributes[attr][attrVal]; delete item.attributes[attr][attrVal]; } }); } }); } if (item[SCHEMA_COMPONENT_INPUTS_KEY]) { const componentInputs = ['title', 'label', 'legend']; componentInputs.forEach(propName => { if (item[SCHEMA_COMPONENT_INPUTS_KEY][propName]) { item[SCHEMA_COMPONENT_INPUTS_KEY][propName] = replaceMarker(item[SCHEMA_COMPONENT_INPUTS_KEY][propName], marker, key); }