UNPKG

@formql/core

Version:

FormQL - A framework for building dynamic forms

1,177 lines (1,164 loc) 103 kB
import { __decorate, __metadata, __param } from 'tslib'; import { EventEmitter, Input, Output, Component, Injectable, ComponentFactoryResolver, ɵɵdefineInjectable, ɵɵinject, Inject, Directive, ViewChild, ViewContainerRef, ChangeDetectionStrategy, ElementRef, Renderer2, TemplateRef, HostListener, forwardRef, NgModule } from '@angular/core'; import { FormGroup, FormControl, FormBuilder, NG_VALUE_ACCESSOR, NG_VALIDATORS, Validators, ReactiveFormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; import { UUID } from 'angular2-uuid'; import { Subject } from 'rxjs'; import { map, concatMap, takeUntil } from 'rxjs/operators'; import { TextMaskModule } from 'angular2-text-mask'; import { createAutoCorrectedDatePipe, createNumberMask } from 'text-mask-addons'; var ContainerType; (function (ContainerType) { ContainerType[ContainerType["Section"] = 1] = "Section"; ContainerType[ContainerType["Component"] = 2] = "Component"; ContainerType[ContainerType["Page"] = 3] = "Page"; })(ContainerType || (ContainerType = {})); var FormQLMode; (function (FormQLMode) { FormQLMode[FormQLMode["View"] = 0] = "View"; FormQLMode[FormQLMode["Edit"] = 1] = "Edit"; })(FormQLMode || (FormQLMode = {})); let PlainLayoutComponent = class PlainLayoutComponent { constructor() { this.submit = new EventEmitter(); } onSubmitTriggered() { this.submit.emit(null); } }; PlainLayoutComponent.componentName = 'PlainLayoutComponent'; __decorate([ Input(), __metadata("design:type", Object) ], PlainLayoutComponent.prototype, "form", void 0); __decorate([ Input(), __metadata("design:type", FormGroup) ], PlainLayoutComponent.prototype, "reactiveForm", void 0); __decorate([ Input(), __metadata("design:type", Number) ], PlainLayoutComponent.prototype, "mode", void 0); __decorate([ Output(), __metadata("design:type", Object) ], PlainLayoutComponent.prototype, "submit", void 0); PlainLayoutComponent = __decorate([ Component({ selector: 'formql-plain-layout', template: `<ng-container *ngIf="form && form.pages && form.pages.length > 0"> <form [formGroup]="reactiveForm" (ngSubmit)="onSubmitTriggered()"> <div formql-page-wrapper *ngIf="form.pages" [(page)]="form.pages[0]" [reactivePage]="reactiveForm.controls[form.pages[0].pageId]" [formGroupName]="form.pages[0].pageId" [mode]="mode"></div> </form> </ng-container>` }) ], PlainLayoutComponent); let DndService = class DndService { synchroniseSectionModel(page, event) { let sourceSection = page.sections.find(s => s.sectionId === event.sourceWrapperId); let sourceComponent = null; if (event.sourceObjectId === 'new') { sourceComponent = this.newComponent(); event.sourceObjectId = sourceComponent.componentId; sourceSection = page.sections.find(s => s.sectionId === event.targetWrapperId); if (sourceSection) sourceSection.components.push(sourceComponent); else return page; // this should never happen } else sourceComponent = sourceSection.components.find(c => c.componentId === event.sourceObjectId); if (!sourceComponent) return; // has the component been moved across position type (header/body)? if (sourceComponent.position.type !== event.positionType) sourceComponent.position.type = event.positionType; // has the component been placed in a differnet CSS grid area? if (sourceComponent.position.id !== event.targetPositionId) sourceComponent.position.id = event.targetPositionId; if (event.sourceWrapperId !== event.targetWrapperId) { let targetSection = this.transferComponent(page, sourceSection, event); targetSection.template.reRender = true; targetSection = this.reorderComponents(targetSection, sourceComponent, event); } else sourceSection = this.reorderComponents(sourceSection, sourceComponent, event); sourceSection.template.reRender = true; return page; } synchronisePageModel(page, event) { let sourceSection = null; if (event.sourceObjectId === 'new') { sourceSection = this.newSection(); page.sections.push(sourceSection); } else sourceSection = page.sections.find(s => s.sectionId === event.sourceObjectId); // can't find the source section, model must be in different page if (sourceSection != null) { if (sourceSection.position.id !== event.targetPositionId) sourceSection.position.id = event.targetPositionId; page.template.reRender = true; page = this.reorderSections(page, sourceSection, event); } return page; } reorderComponents(section, sourceCompoment, event) { let components = section.components.filter(c => c.position.id === sourceCompoment.position.id); const targetComponent = components.find(c => c.componentId === event.targetIndexId); if (targetComponent) { sourceCompoment.position.index = targetComponent.position.index; targetComponent.position.index = targetComponent.position.index + 0.5; } components = components.sort((left, right) => { return left.position.index - right.position.index; }); for (let i = 0; i < components.length; i++) { components[i].position.index = i; } return section; } reorderSections(page, sourceSection, event) { let sections = page.sections.filter(c => c.position.id === sourceSection.position.id); const targetSection = sections.find(c => c.sectionId === event.targetIndexId); if (targetSection) { sourceSection.position.index = targetSection.position.index; targetSection.position.index = targetSection.position.index + 0.5; } sections = sections.sort((left, right) => { return left.position.index - right.position.index; }); for (let i = 0; i < sections.length; i++) sections[i].position.index = i; return page; } transferComponent(page, sourceSection, event) { const targetSection = page.sections.find(s => s.sectionId === event.targetWrapperId); if (!targetSection) return null; const component = sourceSection.components.find(c => c.componentId === event.sourceObjectId); if (!component) return null; const index = sourceSection.components.findIndex(s => s.componentId === event.sourceObjectId); targetSection.components.push(component); sourceSection.components.splice(index, 1); return targetSection; } newSection() { return { sectionId: UUID.UUID(), sectionName: 'New section', components: [], template: { header: { gridTemplateColumns: '1fr', gridTemplateRows: '1fr', gridTemplateAreas: '"ID1_1"' }, body: { gridTemplateColumns: '1fr', gridTemplateRows: '1fr', gridTemplateAreas: '"ID1_1"' } }, position: { id: '-1', index: 0 } }; } newComponent() { return { componentId: UUID.UUID(), label: 'New Component', componentName: 'FormQLLabelComponent', position: { id: '-1', index: 0 } }; } }; DndService = __decorate([ Injectable() ], DndService); var GridPositionType; (function (GridPositionType) { GridPositionType[GridPositionType["Header"] = 1] = "Header"; GridPositionType[GridPositionType["Body"] = 2] = "Body"; GridPositionType[GridPositionType["Footer"] = 3] = "Footer"; })(GridPositionType || (GridPositionType = {})); var InternalEventType; (function (InternalEventType) { // Editing events InternalEventType[InternalEventType["EditingComponent"] = 1] = "EditingComponent"; InternalEventType[InternalEventType["EditingSection"] = 2] = "EditingSection"; InternalEventType[InternalEventType["EditingPage"] = 3] = "EditingPage"; InternalEventType[InternalEventType["EditingForm"] = 4] = "EditingForm"; InternalEventType[InternalEventType["RemoveComponent"] = 5] = "RemoveComponent"; InternalEventType[InternalEventType["RemoveSection"] = 6] = "RemoveSection"; InternalEventType[InternalEventType["RemovePage"] = 7] = "RemovePage"; // Form changes InternalEventType[InternalEventType["DndFormChanged"] = 8] = "DndFormChanged"; })(InternalEventType || (InternalEventType = {})); let ComponentResolverService = class ComponentResolverService { constructor(componentFactoryResolver) { this.componentFactoryResolver = componentFactoryResolver; this.componentRegister = {}; } resolveComponent(componentName) { if (!this.componentRegister) { return null; } const component = this.componentRegister[componentName]; if (!component) { console.log(`Component ${componentName} not found.`); return null; } const resolvedComponent = this.componentFactoryResolver.resolveComponentFactory(component); return resolvedComponent; } addComponents(...components) { if (Array.isArray(components)) { components.forEach(c => this.componentRegister[c.componentName] = c); } } addComponent(component) { this.componentRegister[component.componentName] = component; } getComponentArray() { return Array.from(Object.keys(this.componentRegister), x => this.componentRegister[x]); } }; ComponentResolverService = __decorate([ Injectable(), __metadata("design:paramtypes", [ComponentFactoryResolver]) ], ComponentResolverService); var HelperService_1; let HelperService = HelperService_1 = class HelperService { static evaluateCondition(condition, data) { let response = { value: false, error: null }; if (condition && condition.trim() !== '' && condition !== 'false') { if (condition === 'true') { response.value = true; return response; } if (!data) return response; response = Object.assign({}, this.evaluate(condition, data)); if (response.value !== true) response.value = false; } return response; } static evaluateValue(path, data) { let response = { value: null, error: null }; if (!data) return response; response = Object.assign({}, this.evaluate(path, data)); if (Number.isNaN(response.value) || response.value === Infinity) response.value = null; else response.value = this.deepCopy(response.value); return response; } static evaluate(path, data) { const response = { value: null, error: null }; const props = Object.keys(data); const params = []; for (let i = 0; i < props.length; i++) params.push(data[props[i]]); params.push(path); props.push('path'); const expression = ` 'use strict' let window = undefined; let document = undefined; let alert = undefined; let a = undefined; return ${path}; `; props.push(expression); try { const evalFunc = new Function(...props); response.value = evalFunc(...params); } catch (err) { response.error = err; } return response; } static setValue(schema, value, data) { if (value === undefined) value = null; if (schema) { if (!data) data = {}; let key = schema; if (schema.indexOf('.') !== -1) { const arr = schema.split('.'); let item = data; for (let i = 0; i <= arr.length - 1; i++) { key = arr[i]; if (!item[key]) item[key] = {}; if (i !== arr.length - 1) item = item[key]; } item[key] = value; } else data[key] = value; } return data; } static getValue(schema, data, type) { if (!schema || !data || (data && Object.keys(data).length === 0 && data.constructor === Object)) return; try { const evalFunc = new Function('data', `return data.${schema};`); return HelperService_1.resolveType(evalFunc(data), type); } catch (err) { return null; } } static setValidators(componentResolverService, component, control) { const componentRef = componentResolverService.resolveComponent(component.componentName); if (!componentRef) return control; const type = componentRef.componentType; if (type && (!type['validators'] || (type['validators'] && type['validators'].length === 0))) return control; const validators = []; const rules = component.rules; if (rules != null) { const FormValidators = type['validators']; Object.keys(rules).forEach((key) => { const item = rules[key]; if (item.value && item.key !== 'readonly' && item.key !== 'hidden' && item.key !== 'value') { const validator = FormValidators.find((x) => x.key === item.key); if (validator && validator.validator) validators.push(validator.validator); } else if (item.value && item.key === 'readonly' && control.enabled) control.disable(); }); if (control.disabled && (!rules || (rules && !rules.readonly) || (rules && rules.readonly && !rules.readonly.value))) control.enable(); } if (validators.length > 0) control.setValidators(validators); return control; } static createReactiveFormStructure(form, initialiseData = true, data = null) { const formControls = {}; const components = {}; const pageGroup = new FormGroup({}); form.pages.forEach((page) => { const sectionGroup = {}; if (page.sections != null) page.sections.forEach((section) => { const componentGroup = {}; if (section.components != null) section.components.forEach((component) => { components[component.componentId] = component; const singleComponentGroup = new FormControl(); formControls[component.componentId] = singleComponentGroup; componentGroup[component.componentId] = singleComponentGroup; if (initialiseData) { if (!data) data = {}; data = HelperService_1.instantiateData(data, component.schema); try { const value = this.getValue(component.schema, data, component.type); if (value) { formControls[component.componentId].setValue(value); component.value = value; } } catch (err) { throw err; } } }); sectionGroup[section.sectionId] = new FormGroup(componentGroup); }); pageGroup[page.pageId] = new FormGroup(sectionGroup); }); return { pageGroup: pageGroup, formControls: formControls, components: components, data: data }; } static instantiateData(data, schema) { if (schema && schema.indexOf('.') !== -1) { const arr = schema.split('.'); let item = data; let key = ''; for (let i = 0; i <= arr.length - 2; i++) { key = arr[i]; if (!item[key]) item[key] = {}; if (i !== arr.length - 2) item = item[key]; } } return data; } static deepCopy(oldObj, ignoreProperty = null) { let newObj = oldObj; if (oldObj && typeof oldObj === 'object') { newObj = Object.prototype.toString.call(oldObj) === '[object Array]' ? [] : {}; for (const i in oldObj) if (!ignoreProperty || (ignoreProperty && !ignoreProperty.find((p) => p === i))) newObj[i] = this.deepCopy(oldObj[i]); } return newObj; } static propertyCopy(source, target, ignoreProperties = null) { if (source && typeof source === 'object') for (const i in source) if (!ignoreProperties || (ignoreProperties && !ignoreProperties.find((p) => p === i))) if (source[i] && typeof source[i] === 'object') { if (!target[i]) target[i] = {}; target[i] = this.propertyCopy(source[i], target[i]); } else target[i] = source[i]; else console.log(`propertyCopy function doesn't support primitives`); return target; } static formatForGraphQl(obj) { const updatedData = this.deepCopy(obj); if (updatedData['__typename']) delete updatedData['__typename']; let dataForQuery = ''; Object.keys(updatedData).forEach((fieldName) => { if (updatedData[fieldName] == null) dataForQuery += fieldName + ': null,'; else if (typeof updatedData[fieldName] === 'object') dataForQuery += this.formatForGraphQl(updatedData[fieldName]); else if (typeof updatedData[fieldName] === 'number' || typeof updatedData[fieldName] === 'boolean') dataForQuery += fieldName + `:${updatedData[fieldName]},`; else dataForQuery += fieldName + `:\"${updatedData[fieldName]},`; }); dataForQuery = `{${dataForQuery.slice(0, -1)}}`; return dataForQuery; } static formatError(error) { if (!error) return; if (error.error && error.error.message) error.message = error.error.message; return error; } static resolveType(value, type) { if (value === null || value === undefined || value === '') return null; else if (Number.isNaN(value)) return 0; switch (type) { case 'number': if (typeof value === 'string') value = value.replace(/[^\d\.]/g, ''); return Number(value); default: return value; } } static maskToArray(mask) { const result = []; if (mask) { const maskTrimmed = mask.trim().substring(1).slice(0, -1).replace('\\\\', '\\'); const arry = maskTrimmed.split(','); arry.forEach((item) => { result.push(item.trim().replace(/\"/g, '').replace(/\'/g, '')); }); } return result; } static updateTemplates(form) { form.pages.forEach((page) => { page.template.reRender = false; page.template = HelperService_1.deepCopy(page.template); page.sections.forEach((section) => { section.template.reRender = false; section.template = HelperService_1.deepCopy(section.template); }); }); return form; } static resetValidators(components, formControls, componentResolverService) { if (components && Object.keys(components).length > 0) Object.keys(components).forEach((key) => { const component = components[key]; if (component) { let componentControl = formControls[component.componentId]; if (componentControl) componentControl = HelperService_1.setValidators(componentResolverService, component, componentControl); } }); return formControls; } static validateForm(formGroup) { Object.keys(formGroup.controls).forEach((field) => { const control = formGroup.get(field); if (control instanceof FormControl) control.markAsTouched({ onlySelf: true }); else if (control instanceof FormGroup) this.validateForm(control); }); } }; HelperService.ɵprov = ɵɵdefineInjectable({ factory: function HelperService_Factory() { return new HelperService(); }, token: HelperService, providedIn: "root" }); HelperService = HelperService_1 = __decorate([ Injectable({ providedIn: 'root' }) ], HelperService); class RuleLogic { constructor() { this.evalFunctions = ['GET', 'SUM']; } /* Perform a condition evaluation */ doEval(condition, conditionFunctions) { const conditionFunctionsDeclares = Object.keys(conditionFunctions).map(x => `let ${x} = conditionFunctions.${x}; `).join(''); if (condition.trim() === '') return; if (this.evalFunctions.indexOf(condition.trim()) !== -1) throw Error(`Funcitons need a parameter (e.g. GET('contact.firstName') )`); const props = []; const params = []; params.push(condition); params.push(conditionFunctions); props.push('condition'); props.push('conditionFunctions'); const expression = ` 'use strict' ${conditionFunctionsDeclares} let window = undefined; let document = undefined; let alert = undefined; let a = undefined; return ${condition}; `; props.push(expression); try { const evalFunc = new Function(...props); const response = evalFunc(...params); return response; } catch (err) { debugger; // intentionally left to help troubleshooting issues console.log(err); throw err; } } /* Reset all dependancies for any given condition in a component */ resetDependancies(formState, condition, component) { const self = this; const registerFunctions = { GET(schema) { formState.components = self.setDependents(formState.components, schema, component.componentId); }, SUM(...schemas) { schemas.forEach(schema => formState.components = self.setDependents(formState.components, schema, component.componentId)); } }; return this.doEval(condition, registerFunctions); } /* Evaluetes the value of any given condition */ evaluate(data, condition) { const self = this; const evalFunctions = { GET(schema) { const result = self.getSchemaValue(data, schema); if (result !== undefined && result !== null) return result; else return ''; }, SUM(...schemas) { let total = 0; schemas.forEach(schema => { const value = self.getSchemaValue(data, schema); if (value && !isNaN(value)) total += value; }); return total; } }; return this.doEval(condition, evalFunctions); } evaluateCondition(data, condition) { const response = { value: false, error: null }; if (condition && condition.trim() !== '' && condition !== 'false') { if (condition === 'true') { response.value = true; return response; } if (!data) return response; try { response.value = this.evaluate(data, condition); } catch (err) { response.error = err; } if (response.value !== true) response.value = false; } return response; } evaluateValue(data, expression) { const response = { value: null, error: null }; if (!data) return response; try { response.value = this.evaluate(data, expression); } catch (err) { response.error = err; return response; } if (Number.isNaN(response.value) || response.value === Infinity) response.value = null; else response.value = HelperService.deepCopy(response.value); return response; } getSchemaValue(data, schema) { const evalFunc = new Function('data', 'schema', `return data.${schema}`); return evalFunc(data, schema); } setDependents(components, schema, componentId) { Object.keys(components).forEach((key) => { const component = components[key]; if (component.schema === schema) { if (!component.dependents) component.dependents = [componentId]; else if (component.dependents.indexOf(componentId) === -1) component.dependents.push(componentId); } }); return components; } } let FormService = class FormService { constructor(srv, componentResolverService, formBuilder) { this.componentResolverService = componentResolverService; this.formBuilder = formBuilder; this.ruleLogic = new RuleLogic(); this.injectedService = srv; } getFormAndData(formName, ids) { if (ids) return this.injectedService.getForm(formName).pipe(map((response) => response), concatMap((model) => this.injectedService.getData(model.dataSource, ids).pipe(map((data) => this.initialiseFormState(model, data))))); else return this.injectedService.getForm(formName).pipe(map((model) => this.initialiseFormState(model, null))); } /* Invokes the form save in the injected service (see constructor for service) */ saveForm(name, form) { // remove all transactional data const updateForm = HelperService.deepCopy(form); updateForm.pages.forEach((page) => { page.sections.forEach((section) => { section.components.forEach((component) => { Object.keys(component) .filter((key) => component[key] === null) .forEach((key) => delete component[key]); delete component.value; if (component.rules) if (Object.keys(component.rules).length === 0 && component.rules.constructor === Object) delete component.rules; else Object.keys(component.rules).forEach((p) => { delete component.rules[p].value; }); }); }); }); return this.injectedService.saveForm(name, updateForm).pipe(map((response) => { return response; })); } /* Invokes the data save in the injected service (see constructor for service) */ saveData(dataSource, ids, data) { return this.injectedService.saveData(dataSource, ids, data).pipe(map((result) => { return result; })); } /* Updates a component value and recalculates all dependents If reset is set to true, it will recalculate all dependents, this is when a rules as been modified in the FormQL Editor */ updateComponent(component, formState, reset = false) { const value = HelperService.resolveType(component.value, component.type); formState.data = HelperService.setValue(component.schema, value, formState.data); if (reset) this.resetComponentDependents(formState); // refresh any dependent components if (component.dependents) component.dependents.forEach((key) => { formState.components[key] = this.resolveComponentRules(formState.components[key], formState); }); // set the value on any components that have the same schema Object.keys(formState.components).forEach((key) => { const c = formState.components[key]; if (c.schema === component.schema) try { c.value = HelperService.getValue(c.schema, formState.data, c.type); } catch (err) { throw err; } }); return formState; } /* Initialises Form State */ initialiseFormState(form, data) { const reactiveFormStructure = HelperService.createReactiveFormStructure(form, true, data); const formState = { components: reactiveFormStructure.components, data: Object.assign({}, reactiveFormStructure.data), form: form, formControls: reactiveFormStructure.formControls, reactiveForm: this.formBuilder.group(reactiveFormStructure.pageGroup) }; return this.resolveConditions(formState); } /* Resets all component dependents, it should only be called when a user modified a question in the formql editor */ resetComponentDependents(formState) { if (formState.components) { Object.keys(formState.components).forEach((componentKey) => delete formState.components[componentKey].dependents); Object.keys(formState.components).forEach((componentKey) => { const component = formState.components[componentKey]; if (component.rules) Object.keys(component.rules).forEach((ruleKey) => { const rule = component.rules[ruleKey]; this.ruleLogic.resetDependancies(formState, rule.condition, component); }); }); Object.keys(formState.components).forEach((componentKey) => (formState.components[componentKey] = this.resolveComponentRules(formState.components[componentKey], formState))); } } getData(query, ids) { return this.injectedService.getData(query, ids).pipe(map((data) => { if (data) return data; else return {}; })); } getForms() { return this.injectedService.getForms().pipe(map((data) => { return data; })); } getForm(name) { return this.injectedService.getForm(name).pipe(map((data) => { return data; })); } /* Resolve all rules for any given component */ resolveComponentRules(component, formState) { if (component.rules) { let resetValidator = false; Object.keys(component.rules).forEach((key) => { const property = component.rules[key]; if (property.condition) { resetValidator = true; const evaluatedValue = this.ruleLogic.evaluate(formState.data, property.condition); if (key === 'value') { const value = HelperService.resolveType(evaluatedValue, component.type); if (component.value !== value) { component.value = value; formState = this.updateComponent(component, formState); } } property.value = evaluatedValue; } else delete component.rules[key]; }); if (resetValidator) formState.formControls[component.componentId] = HelperService.setValidators(this.componentResolverService, component, formState.formControls[component.componentId]); } return component; } /* Resolves all conditions in each component, used when initialising the form */ resolveConditions(formState) { if (formState.components) { const components = formState.components; Object.keys(components).forEach((componentKey) => (components[componentKey] = this.resolveComponentRules(components[componentKey], formState))); } return formState; } }; FormService.ɵprov = ɵɵdefineInjectable({ factory: function FormService_Factory() { return new FormService(ɵɵinject("FormQLService"), ɵɵinject(ComponentResolverService), ɵɵinject(FormBuilder)); }, token: FormService, providedIn: "root" }); FormService = __decorate([ Injectable({ providedIn: 'root' }), __param(0, Inject('FormQLService')), __metadata("design:paramtypes", [Object, ComponentResolverService, FormBuilder]) ], FormService); let StoreService = class StoreService { constructor(formService, componentResolverService, formBuilder) { this.formService = formService; this.componentResolverService = componentResolverService; this.formBuilder = formBuilder; this.data$ = new Subject(); this.formState$ = new Subject(); this.serviceDestroyed = new Subject(); } // private formControls: FormControls; ngOnDestroy() { this.data$.complete(); this.data$.unsubscribe(); } getData() { return this.data$.asObservable(); } getFormState() { return this.formState$.asObservable(); } updateComponent(component) { this.formState = this.formService.updateComponent(component, this.formState, false); this.data$.next(Object.assign({}, this.formState.data)); this.formState$.next(Object.assign({}, this.formState)); } setComponent(component) { this.formState = this.formService.updateComponent(component, this.formState, true); // this.formControls = HelperService.resetValidators(this.formState.components, this.formControls, this.componentResolverService); this.data$.next(Object.assign({}, this.formState.data)); this.formState$.next(Object.assign({}, this.formState)); } getAll(formName, ids, mode) { this.formService.getFormAndData(formName, ids).pipe(takeUntil(this.serviceDestroyed)).subscribe(response => { this.formState = Object.assign({}, response); this.formState.ids = ids; this.formState.mode = mode; this.data$.next(Object.assign({}, response.data)); this.formState$.next(this.formState); }, error => { this.formState$.next({ form: { error: HelperService.formatError({ title: 'Error loading form or data', error: error }) } }); }); } saveForm() { this.formService.saveForm(this.formState.form.formName, this.formState.form); } saveData() { return this.formService.saveData(this.formState.form.dataSource, this.formState.ids, this.formState.data); } validateForm() { HelperService.validateForm(this.formState.reactiveForm); } isFormValid() { return this.formState.reactiveForm.valid; } unsubscribeAll() { this.serviceDestroyed.next(); this.serviceDestroyed.complete(); } reSetForm(eventType, event) { switch (eventType) { case InternalEventType.EditingForm: this.populateReactiveForm(); break; case InternalEventType.DndFormChanged: const pageId = event.pageId; const indexDnd = this.formState.form.pages.findIndex(p => p.pageId === pageId); if (indexDnd >= 0) this.formState.form.pages[indexDnd] = event; this.populateReactiveForm(); break; case InternalEventType.RemoveComponent: const componentId = event.componentId; let updateSectionId = ''; this.formState.form.pages.forEach(page => { page.sections.forEach(section => { const indexComponent = section.components.findIndex(c => c.componentId === componentId); if (indexComponent >= 0) { section.components.splice(indexComponent, 1); updateSectionId = section.sectionId; } }); }); this.populateReactiveForm(); break; case InternalEventType.RemoveSection: const sectionId = event.sectionId; let updatePageId = ''; this.formState.form.pages.forEach(page => { const indexSection = page.sections.findIndex(c => c.sectionId === sectionId); if (indexSection >= 0) { page.sections.splice(indexSection, 1); updatePageId = page.pageId; } }); this.populateReactiveForm(); break; } this.formState$.next(Object.assign({}, this.formState)); } populateReactiveForm() { if (this.formState.form.pages != null && this.formState.form.pages.length > 0) { // get reactive structure -> formControls, pageGroup and components if it's an update const reactiveFormStructure = HelperService.createReactiveFormStructure(this.formState.form, true, this.formState.data); this.formState.formControls = reactiveFormStructure.formControls; // if it's an update, refresh reactive form, set all form controls, validators this.formState.form.pages.forEach(page => { this.formState.reactiveForm.setControl(page.pageId, reactiveFormStructure.pageGroup[page.pageId]); }); this.formState.form = HelperService.updateTemplates(this.formState.form); if (reactiveFormStructure.components != null && Object.keys(reactiveFormStructure.components).length > 0) this.formState.formControls = HelperService.resetValidators(reactiveFormStructure.components, this.formState.formControls, this.componentResolverService); this.formState = this.formService.resolveConditions(this.formState); } } }; StoreService.ɵprov = ɵɵdefineInjectable({ factory: function StoreService_Factory() { return new StoreService(ɵɵinject(FormService), ɵɵinject(ComponentResolverService), ɵɵinject(FormBuilder)); }, token: StoreService, providedIn: "root" }); StoreService = __decorate([ Injectable({ providedIn: 'root' }), __metadata("design:paramtypes", [FormService, ComponentResolverService, FormBuilder]) ], StoreService); let PageWrapperComponent = class PageWrapperComponent { constructor(dndService, storeService) { this.dndService = dndService; this.storeService = storeService; this.FormQLMode = FormQLMode; this.ContainerType = ContainerType; this.ComponentPositionType = GridPositionType; } ngOnInit() { this.sections = this.createSections(this.page); } synchroniseModel($event, positionId) { const dndEvent = { sourceObjectId: $event.sourceObjectId, sourceWrapperId: $event.sourceWrapperId, targetPositionId: positionId, targetWrapperId: this.page.pageId, targetIndexId: $event.targetIndexId }; this.page = this.dndService.synchronisePageModel(this.page, dndEvent); this.sections = this.createSections(this.page); this.storeService.reSetForm(InternalEventType.DndFormChanged, this.page); } createSections(page) { const sections = {}; if (page && page.sections) page.sections.forEach(section => { if (sections && sections[section.position.id]) sections[section.position.id].push(section); else sections[section.position.id] = [section]; }); return sections; } trackByFn(index, item) { return item.id; } resetSections() { if (this.sections) this.sections = this.createSections(this.page); } }; __decorate([ Input(), __metadata("design:type", Object) ], PageWrapperComponent.prototype, "page", void 0); __decorate([ Input(), __metadata("design:type", FormGroup) ], PageWrapperComponent.prototype, "reactivePage", void 0); __decorate([ Input(), __metadata("design:type", Number) ], PageWrapperComponent.prototype, "mode", void 0); PageWrapperComponent = __decorate([ Component({ // tslint:disable-next-line: component-selector selector: '[formql-page-wrapper]', template: ` <div class="fql-page-body"> <ng-template formqlGdConfig [formqlGdConfigOf]="page.template.body" let-bodyitem let-i="index" (resetItems)="resetSections()"> <div [ngClass]="{'fql-page-container': mode === FormQLMode.Edit}" [ngStyle]="bodyitem.style"> <div formqlDndDrop [type]="ContainerType.Section" [mode]="mode" [ngClass]="{'fql-page-container': (mode === FormQLMode.Edit)}" (synchronise)="synchroniseModel($event, bodyitem.id)"> <ng-container *ngFor="let section of sections[bodyitem.id]; trackBy: trackByFn"> <ng-container *ngTemplateOutlet="templateRef; context: {section: section}"> </ng-container> </ng-container> </div> </div> </ng-template> </div> <ng-template #templateRef let-section="section"> <div [formGroup]="reactivePage"> <div formql-section-wrapper [page]="page" [section]="section" [formGroupName]="section.sectionId" [reactiveSection]="reactivePage.controls[section.sectionId]" [mode]="mode"> </div> </div> </ng-template>`, providers: [DndService], styles: [".fql-bundle-field-input{width:100%;box-sizing:border-box;-webkit-box-sizing:border-box;-moz-box-sizing:border-box}.fql-bundle-checkbox-input{cursor:pointer;box-sizing:border-box;-webkit-box-sizing:border-box;-moz-box-sizing:border-box}.fql-bundle-label-required:after{content:\" *\";color:red}.fql-bundle-field-radio{cursor:pointer}.fql-error-message{text-align:center;padding:20px}.fql-dnd-container-separator{box-shadow:0 -2px 0 #00f}.fql-dnd-container-drop-area{outline:dashed 3px}.fql-page-body{display:-ms-grid;display:grid;width:100%;min-height:30px}.fql-page-container{display:table;height:100%;width:100%;min-height:15px}.fql-page-container:hover{outline:solid 2px}"] }), __metadata("design:paramtypes", [DndService, StoreService]) ], PageWrapperComponent); let InternalEventHandlerService = class InternalEventHandlerService { constructor() { this.event = new EventEmitter(); } send(eventType, event) { const eventHandler = { event: event, eventType: eventType }; this.event.emit(eventHandler); } }; InternalEventHandlerService.ɵprov = ɵɵdefineInjectable({ factory: function InternalEventHandlerService_Factory() { return new InternalEventHandlerService(); }, token: InternalEventHandlerService, providedIn: "root" }); __decorate([ Output(), __metadata("design:type", EventEmitter) ], InternalEventHandlerService.prototype, "event", void 0); InternalEventHandlerService = __decorate([ Directive(), Injectable({ providedIn: 'root' }) ], InternalEventHandlerService); let SectionWrapperComponent = class SectionWrapperComponent { constructor(internalEventHandlerService, componentResolverService, viewContainerRef, dndService, storeService) { this.internalEventHandlerService = internalEventHandlerService; this.componentResolverService = componentResolverService; this.viewContainerRef = viewContainerRef; this.dndService = dndService; this.storeService = storeService; this.FormQLMode = FormQLMode; this.ContainerType = ContainerType; this.ComponentPositionType = GridPositionType; } ngOnInit() { this.components = this.createComponents(this.section); if (this.mode === FormQLMode.Edit) { const tooltip = this.viewContainerRef.createComponent(this.componentResolverService.resolveComponent('TooltipComponent')); tooltip.instance.wrapper = this.wrapper; tooltip.instance.type = ContainerType.Section; tooltip.instance.object = this.section; this.tooltip.insert(tooltip.hostView); } } editField() { if (this.mode === FormQLMode.Edit) this.internalEventHandlerService.send(InternalEventType.EditingSection, this.section); } synchroniseModel($event, positionId, positionType) { const dndEvent = { sourceObjectId: $event.sourceObjectId, sourceWrapperId: $event.sourceWrapperId, targetPositionId: positionId, targetWrapperId: this.section.sectionId, targetIndexId: $event.targetIndexId, positionType: positionType }; this.page = this.dndService.synchroniseSectionModel(this.page, dndEvent); this.storeService.reSetForm(InternalEventType.DndFormChanged, this.page); } trackByFn(index, item) { return item.id; } createComponents(section) { const components = {}; if (section && section.components) section.components.sort((left, right) => { return left.position.index - right.position.index; }); section.components.forEach(component => { if (components && components[component.position.type + '_' + component.position.id]) components[component.position.type + '_' + component.position.id].push(component); else components[component.position.type + '_' + component.position.id] = [component]; }); return components; } }; __decorate([ ViewChild('wrapper', { read: ViewContainerRef, static: true }), __metadata("design:type", ViewContainerRef) ], SectionWrapperComponent.prototype, "wrapper", void 0); __decorate([ ViewChild('tooltip', { read: ViewContainerRef, static: true }), __metadata("design:type", ViewContainerRef) ], SectionWrapperComponent.prototype, "tooltip", void 0); __decorate([ Input(), __metadata("design:type", Object) ], SectionWrapperComponent.prototype, "section", void 0); __decorate([ Input(), __metadata("design:type", FormGroup) ], SectionWrapperComponent.prototype, "reactiveSection", void 0); __decorate([ Input(), __metadata("design:type", Object) ], SectionWrapperComponent.prototype, "page", void 0); __decorate([ Input(), __metadata("design:type", Number) ], SectionWrapperComponent.prototype, "mode", void 0); SectionWrapperComponent = __decorate([ Component({ // tslint:disable-next-line: component-selector selector: '[formql-section-wrapper]', template: ` <div #wrapper for