import { CdkDragDrop, CdkDropList, CdkDropListGroup, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; import { Component, ElementRef, ViewChild } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; import { dialogBoxSettings } from './common/modals/settings'; import { AlertService } from './common/services/alert/alert.service'; import { ManageControlComponent } from './modals/manage-control/manage-control.component'; import { SaveFormComponent } from './modals/save-form/save-form.component'; import { IFormBuilderCell, ConstControlsList, IDynamicFormBuilder, ConstFormColor, ConstFormStyle, ConstFormAppearance, ConstFormStepperStyle, IFormBuilderPage, ConstPositionPageLabel } from './models/control'; @Component({ selector: 'dr-form-material-builder', templateUrl: './form-material-builder.component.html', styleUrls: ['./form-material-builder.component.scss'], // encapsulation: ViewEncapsulation.None }) export class FormMaterialBuilderComponent { // @ViewChild('stepper') stepper: MatHorizontalStepper; @ViewChild('stepper') stepper: ElementRef; uploadJson: Array = []; canDuplicate: boolean = true; controlsForm = new FormGroup({}); @ViewChild(CdkDropListGroup) listGroup: CdkDropListGroup; @ViewChild(CdkDropList) placeholder: CdkDropList; controlsList: IFormBuilderCell[] = [...ConstControlsList]; controlTypes: string[] = this.controlsList.map(m => m.type); formColors = ConstFormColor; formStyles = ConstFormStyle; formAppearances = ConstFormAppearance; formStepperStyles = ConstFormStepperStyle; positionPageLabel = ConstPositionPageLabel; controlNames: string[] = []; selectedPage = 0; buildingForm: IDynamicFormBuilder = { name: 'my_form', buttonColor: 'WARN', buttonStyle: 'BASIC', formColor: 'BASIC', formAppearance: 'LEGACY', buttonResetName: 'Reset', buttonSubmitName: 'Submit', buttonNextName: 'Next', buttonPrevName: 'Prev', stepperStyle: 'TABS', buttonNextAlwaysEnabled: false, buttonSubmitAlwaysPresent: false, showStepper: true, showResetButton: true, readonly: false, justified: false, pages: [{ title: 'Page 1', name: 'page_1', sections: [{ name: 'section_' + 1 + '_' + 1, rows: [], title: 'Section Title' }] }], language: navigator.language, positionPageLabel: 'CENTER BOTTOM' }; formRowsIds: string[] = []; constructor(private dialog: MatDialog, private alertService: AlertService, private snackBar: MatSnackBar) { // this.buildingForm .pages = Array.from({ length: 50 }).map((m, i) => ( // { title: 'Page ' + (i + 1), name: 'page_' + (i + 1), sections: [{ name: 'section_' + (i + 1), rows: [{ cells: [{ controlName: 'text_' + (i + 1), cellSize: 1, label: 'Text ' + 1, type: 'TEXT' }] }], title: 'Section ' + (i + 1) }] } // )); this.reRowsArray(); } onFileChange(): void { const reader = new FileReader(); // reader.readAsDataURL(file); const tmpForm = { ...this.buildingForm }; reader.readAsText(this.uploadJson[0]); reader.onload = () => { try { JSON.parse(reader.result as string); this.buildingForm = JSON.parse(reader.result as string); this.snackBar.open(`Form is '${this.buildingForm.title || this.buildingForm.name}' uploaded!`)._dismissAfter(3000); this.reRowsArray(); } catch (ex) { this.buildingForm = tmpForm; console.error('Error', ex); } this.uploadJson = []; }; reader.onerror = error => { this.buildingForm = tmpForm; console.error('Error', error); this.uploadJson = []; }; } onSelectPage(page: number): void { this.selectedPage = page; const el = this.stepper.nativeElement; const stepWidth = (el.scrollWidth / this.buildingForm.pages.length); el.scrollTo({ behavior: 'smooth', top: el.scrollTop, left: ((stepWidth * this.selectedPage) - (el.getBoundingClientRect().width / 2)) + (stepWidth * 2) }); } dropPage(event: CdkDragDrop): void { moveItemInArray(this.buildingForm.pages, event.previousIndex, event.currentIndex); this.selectedPage = event.currentIndex; } drop(event: CdkDragDrop): void { const controlTypes = [...this.controlTypes]; if (!!event.container.data) { if (event.previousContainer === event.container) { moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); } else { transferArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex); } } else { transferArrayItem(event.previousContainer.data, [], event.previousIndex, event.currentIndex); } this.controlTypes = [...controlTypes]; this.reRowsArray(); } reRowsArray(): void { this.controlNames = []; this.formRowsIds = []; const pages = [... this.buildingForm.pages]; let hasPageEmpty = true; pages.forEach((p, i) => { if ((p.sections.length === 1 && p.sections[0].rows.length === 0) || (p.sections.length === 1 && p.sections[0].rows.length === 1 && p.sections[0].rows[0].cells.length === 0)) { if (hasPageEmpty) { hasPageEmpty = false; } else { pages.splice(i, 1); } } }); pages.forEach((page, p) => { let hasSectionEmpty = true; page.sections.forEach((section, s) => { const arr = section.rows.filter(f => f.cells.length > 0); section.rows = [{ cells: [] }]; arr.forEach(a => { a.cells = a.cells.map(m => typeof (m) === 'string' ? { ...this.controlsList.find(f => f.type === m) } : m); this.controlNames = this.controlNames.concat(a.cells.map(m => m.controlName)); section.rows.push(a); section.rows.push({ cells: [] }); }); this.formRowsIds = this.formRowsIds.concat(section.rows.map((m, i) => `row_${p}_${s}_${i}`)); // section.rows = [...section.rows]; }); page.sections.forEach((s, i) => { if (s.rows.length === 1 && s.rows[0].cells.length === 0) { if (hasSectionEmpty) { hasSectionEmpty = false; } else { page.sections.splice(i, 1); } } }); }); this.buildingForm.pages = pages; } getColsSum(row: IFormBuilderCell[]): number { let cols = 0; row.forEach(r => cols += (r.cellSize || 1)); return cols; } openManageControlModal(col: IFormBuilderCell, p: number, s: number, r: number, c: number): void { this.dialog.open(ManageControlComponent, dialogBoxSettings({ cell: col, controlNames: this.controlNames, options: { canDuplicate: this.canDuplicate } })) .afterClosed().subscribe(cell => { if (!!cell) { if (col) { col = cell; this.buildingForm.pages[p].sections[s].rows[r].cells[c] = { ...cell }; } else { this.buildingForm.pages[p].sections[s] = { name: 'section_' + s, rows: [{ cells: [cell] }] }; this.openManageControlModal(cell, p, s, 0, 0); } this.reRowsArray(); } }); } addPage(): void { this.buildingForm.pages.push({ title: 'Page ' + (this.buildingForm.pages.length + 1), name: 'page_' + (this.buildingForm.pages.length + 1), sections: [{ name: ('section_' + (this.buildingForm.pages.length + 1) + '_' + (this.buildingForm.pages[this.selectedPage].sections.length - 1)), rows: [{ cells: [] }] }] }); this.reRowsArray(); setTimeout(() => this.selectedPage = (this.buildingForm.pages.length - 1)); } onAddSection(): void { this.buildingForm.pages[this.selectedPage].sections.push({ name: ('section_' + (this.selectedPage + 1) + '_' + (this.buildingForm.pages[this.selectedPage].sections.length + 1)), rows: [{ cells: [] }] }); this.reRowsArray(); } onDeletePage(): void { this.alertService.confirm('Delete page?').subscribe(confirm => { if (confirm) { this.buildingForm.pages = this.buildingForm.pages.filter((f, i) => i !== this.selectedPage); while (this.selectedPage > (this.buildingForm.pages.length - 1)) { this.selectedPage--; } this.reRowsArray(); } }); } onDeleteSection(s: number): void { this.alertService.confirm('Delete section?').subscribe(confirm => { if (confirm) { this.buildingForm.pages[this.selectedPage].sections = this.buildingForm.pages[this.selectedPage].sections.filter((f, i) => i !== s); this.reRowsArray(); } }); } onMoveSection(s: number, direction: string): void { const arr = [...this.buildingForm.pages[this.selectedPage].sections]; const j = s + (direction === 'UP' ? -1 : 1); if (j >= arr.length) { let k = j - arr.length + 1; while (k--) { arr.push(undefined); } } arr.splice(j, 0, arr.splice(s, 1)[0]); this.buildingForm.pages[this.selectedPage].sections = arr; this.reRowsArray(); } onSaveForm(): void { const endForm: IDynamicFormBuilder = JSON.parse(JSON.stringify(this.buildingForm)); endForm.pages.forEach((page, p) => { page.position = p; page.sections.forEach(section => { section.rows = section.rows.filter(f => f.cells.length > 0); }); page.sections.forEach((section, s) => { if (section.rows.length === 0 || (section.rows.length === 1 && section.rows[0].cells.length === 0)) { page.sections.splice(s, 1); } }); }); endForm.pages.forEach((page, s) => { if (page.sections.length === 0 || (page.sections.length === 1 && page.sections[0].rows.length === 0) || (page.sections.length === 1 && page.sections[0].rows.length === 1 && page.sections[0].rows[0].cells.length === 0)) { endForm.pages.splice(s, 1); } }); this.dialog.open(SaveFormComponent, { width: 'calc(100% - 20px)', height: 'calc(100% - 20px)', maxWidth: 'calc(100% - 20px)', panelClass: 'mx-0', data: endForm } ); } noReturnPredicate(): boolean { return false; } alwaysReturnPredicate(): boolean { return true; } getTooltip(col: IFormBuilderCell): string { let matTooltip = ''; if (!!col) { Object.keys(col).forEach(k => { if (col[k] ?? null) { switch (typeof (col[k])) { case 'string': case 'number': { matTooltip += `${k}: ${col[k]} ;\n`; break; } case 'object': if (Array.isArray(col[k])) { matTooltip += `${k}: (${col[k].length}) ;\n`; } break; } } }); } return matTooltip; } getErrorSaving(): string { let matTooltip = ''; const pageNames: string[] = []; const pageFilteredNames: string[] = []; const sectionNames: string[] = []; const sectionFilteredNames: string[] = []; const controlNames: string[] = []; const controlFilteredNames: string[] = []; this.buildingForm.pages.forEach(page => { pageNames.push(page.name); if (!pageFilteredNames.includes(page.name)) { pageFilteredNames.push(page.name); } page.sections.forEach(section => { sectionNames.push(section.name); if (!sectionFilteredNames.includes(section.name)) { sectionFilteredNames.push(section.name); } section.rows.forEach(row => { row.cells.forEach(cell => { controlNames.push(cell.controlName); if (!controlFilteredNames.includes(cell.controlName)) { controlFilteredNames.push(cell.controlName); } }); }); }); }); if (pageNames.length > pageFilteredNames.length) { matTooltip += `Duplicate page names.\n`; } if (sectionNames.length > sectionFilteredNames.length) { matTooltip += `Duplicate section names.\n`; } if (controlNames.length > controlFilteredNames.length && !this.canDuplicate) { matTooltip += `Duplicate form control names.\n`; } return matTooltip; } changeStep(evt: StepperSelectionEvent): void { this.selectedPage = evt.selectedIndex; } }