import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { TranslationResources } from '@core/resources/translation.resources'; import { PortalDeterminationService } from '@core/services/portal-determination.service'; import { SpinnerService } from '@core/services/spinner.service'; import { TranslationService } from '@core/services/translation.service'; import { FooterState } from '@core/states/footer.state'; import { AdHocReportingUI } from '@core/typings/ui/ad-hoc-reporting.typing'; import { ClientSettingsService } from '@features/client-settings/client-settings.service'; import { BaseApplicationForLogic, Form, FormDefinitionComponent, FormDefinitionForUi, FormType, FormTypes, MAX_COMPONENTS_PER_PAGE, SaveForm, SaveFormOptions } from '@features/configure-forms/form.typing'; import { FormsService } from '@features/configure-forms/services/forms/forms.service'; import { ExternalAPIService } from '@features/external-api/external-api.service'; import { QuickAddModalComponent } from '@features/formio/form-builder/quick-add-modal/quick-add-modal.component'; import { FormBuilderService } from '@features/formio/form-builder/services/form-builder/form-builder.service'; import { ComponentHelperService } from '@features/formio/services/component-helper/component-helper.service'; import { FormHelperService } from '@features/formio/services/form-helper/form-helper.service'; import { FormLogicService } from '@features/formio/services/form-logic/form-logic.service'; import { ConditionalLogicBuilderModalComponent } from '@features/logic-builder/conditional-logic-builder-modal/conditional-logic-builder-modal.component'; import { LogicBuilderService } from '@features/logic-builder/logic-builder.service'; import { EvaluationType, GlobalLogicGroup } from '@features/logic-builder/logic-builder.typing'; import { StandardProductConfigurationService } from '@features/platform-admin/standard-product-configuration/standard-product-configuration.service'; import { ProgramService } from '@features/programs/program.service'; import { ReferenceFieldsService } from '@features/reference-fields/services/reference-fields.service'; import { AdHocReportingService } from '@features/reporting/services/ad-hoc-reporting.service'; import { WorkflowLevelAutomationService } from '@features/workflow-automation/workflow-level-automation.service'; import { Tab, TabAction, TypeaheadSelectOption } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { ConfirmationModalComponent, ModalFactory } from '@yourcause/common/modals'; import { NotifierService } from '@yourcause/common/notifier'; import { cloneDeep, orderBy } from 'lodash'; import { v4 } from 'uuid'; import { AddEditFormTabModalComponent } from '../add-edit-form-tab-modal/add-edit-form-tab-modal.component'; import { CreateEditFormModalComponent, CreateEditFormModalResponse } from '../create-edit-form-modal/create-edit-form-modal.component'; import { SaveFormModalComponent } from '../save-form-modal/save-form-modal.component'; // TODO: reset logic on tabs that rely on comps on the same page // TODO: reset logic on tabs that no longer have fields on other pages // TODO: figure out what to do about logic based on components that have been removed @Component({ selector: 'gc-edit-form-page', templateUrl: './edit-form-page.component.html', styleUrls: ['./edit-form-page.component.scss'] }) export class EditFormPageComponent implements OnInit, OnDestroy { // TODO: remove useNewNavTabsComp when comp migration is complete useNewNavTabsComp = false; formTypeOptions = this.formService.getFormTypeOptions(); revisionOptions: TypeaheadSelectOption[] = []; form: Form; isPublished = false; TypesForm = FormTypes; formTypes: FormType[]; id: number; typeAudienceMap = this.formService.typeAudienceMap; duplicateStandardComponents: string[] = []; duplicateKeyComponents: FormDefinitionComponent[] = []; missingKeyComponents: FormDefinitionComponent[] = []; langKeys = this.clientSettingsService.get('selectedLanguages'); hasInternational = this.clientSettingsService.clientSettings.hasInternational; allComps: FormDefinitionComponent[] = []; latestRevisionId: number; currentRevisionId: number; currentVersionName: string; correctionsNeeded = this.i18n.translate( 'CONFIG:textCorrectionsNeeded', {}, 'Corrections Needed' ); proceedToChanges = this.i18n.translate( 'CONFIG:textProceedToChanges', {}, 'Proceed to changes' ); tabs: Tab[] = []; tabIndex = 0; numberOfCompsTracker: Record = {}; allowedMoreCompsThanMax = false; doNotAllowUpdateCurrentRevision = false; FormTypes = FormTypes; requireSignatureHelp = this.i18n.translate( 'common:textRequireSignatureFormHelp', {}, `It is recommended to review this feature internally to ensure it meets your organization's legal and / or audit requirements.` ); originalFormDef: FormDefinitionForUi[]; hasRevisionChanges = false; ready = false; constructor ( private formHelper: FormHelperService, private logicBuilderService: LogicBuilderService, private referenceFieldService: ReferenceFieldsService, private modalFactory: ModalFactory, private i18n: I18nService, private footerState: FooterState, private formService: FormsService, private determinationService: PortalDeterminationService, private activatedRoute: ActivatedRoute, private programService: ProgramService, private clientSettingsService: ClientSettingsService, private spinnerService: SpinnerService, private workflowLevelAutomationService: WorkflowLevelAutomationService, private notifier: NotifierService, private translationService: TranslationService, private translationResources: TranslationResources, private adHocReportingService: AdHocReportingService, private externalApiService: ExternalAPIService, private standardProductService: StandardProductConfigurationService, private componentHelper: ComponentHelperService, private formBuilderService: FormBuilderService, private formLogicService: FormLogicService ) { } get isRootZone () { return this.clientSettingsService.clientSettings.isRootClient; } async ngOnInit () { this.spinnerService.startSpinner(); this.formBuilderService.setInFormBuilder(true); this.isPublished = location.pathname.includes('published'); this.id = +this.activatedRoute.snapshot.params.id; this.latestRevisionId = this.formService.getLatestRevisionId(this.id, this.isPublished); this.doNotAllowUpdateCurrentRevision = this.standardProductService.getDoNotAllowUpdateCurrentRevision( this.id, this.isPublished ); this.revisionOptions = this.formService.getFormRevisionOptions( this.id, this.isPublished, this.doNotAllowUpdateCurrentRevision ); this.setCurrentRevisionId(this.latestRevisionId); await this.setFormDetail(); this.setTabs(); if (this.formBuilderService.openQuickAddModal) { setTimeout(() => { this.openQuickAddModal(this.tabIndex); this.formBuilderService.setOpenQuickAddModal(false); }); } this.ready = true; this.spinnerService.stopSpinner(); } async setFormDetail () { if (this.id && this.currentRevisionId) { const form = await this.formLogicService.fetchFormDetail( this.id, this.currentRevisionId ); this.adaptAndSetForm(form); await this.formHelper.prepareComponentsForRenderForm( [this.form.formDefinition], [this.form.id] ); this.componentHelper.guessKeys(this.form.formDefinition); } else { this.form = {} as Form; } } setCurrentRevisionId (revisionId: number) { this.currentRevisionId = revisionId; this.currentVersionName = this.revisionOptions.find((opt) => { return opt.value === this.currentRevisionId; })?.label || 'v1'; } adaptAndSetForm (form: Form) { const formDefinition = form.formDefinition.map((def, index) => { return { ...def, index }; }); this.form = { ...form, formDefinition }; this.originalFormDef = cloneDeep(formDefinition); this.setCurrentFormBuilderAttrs(); } setCurrentFormBuilderAttrs () { this.formBuilderService.setCurrentFormBuilderFormId(this.id); this.formBuilderService.setCurrentFormBuilderFormAudience(this.typeAudienceMap[this.form.formType]); this.setCurrentDefinition(); this.setCurrentIndex(); } setCurrentDefinition () { this.formBuilderService.setCurrentFormBuilderDefinition(this.form.formDefinition); } setCurrentIndex () { this.formBuilderService.setCurrentFormBuilderIndex(this.tabIndex); } async formDetailClicked () { const modalResponse = await this.modalFactory.open( CreateEditFormModalComponent, { formId: this.form.id, revisionId: this.currentRevisionId, formName: this.form.name, description: this.form.description, defaultFormLang: this.getDefaultFormLang(), formType: this.form.formType, requireSignature: this.form.requireSignature, signatureDescription: this.form.signatureDescription, revisionOptions: this.revisionOptions, hasRevisionChanges: this.hasRevisionChanges } ); if (modalResponse) { const details = modalResponse.formGroup; await this.startSave(this.originalFormDef, details); this.form.name = details.formName; this.form.description = details.description; this.form.requireSignature = details.requireSignature; this.form.signatureDescription = details.signatureDescription; const revisionChanged = modalResponse.formGroup.revisionId !== this.currentRevisionId; if (revisionChanged) { this.spinnerService.startSpinner(); this.setCurrentRevisionId(modalResponse.formGroup.revisionId); this.form = undefined; await this.setFormDetail(); this.activeTabChanged(0); this.setTabs(); this.spinnerService.stopSpinner(); } } } setNumberOfCompsTracker (tracker: Record = {}) { this.numberOfCompsTracker = tracker; this.setFooterInfoText(); } setTabs () { this.tabs = this.form.formDefinition.map((tab, index) => { return { order: index, label: tab.tabName, active: this.tabIndex === index, actions: this.getFormTabActions(index), context: tab }; }); } getFormTabActions (index: number): TabAction[] { const numberOfTabs = this.form.formDefinition.length; const canMoveLeft = index > 0; const canMoveRight = index !== numberOfTabs -1; const actions = [{ labelKey: 'common:textRename', label: 'Rename', icon: 'font', iconClass: 'text-link', showPrimaryButton: false, action: () => { this.addEditPage(index); } }, { labelKey: 'common:textQuickAdd', label: 'Quick add', icon: 'pencil', iconClass: 'text-link', showPrimaryButton: true, action: () => { this.openQuickAddModal(index); } }, canMoveLeft ? { labelKey: 'common:textMoveLeft', label: 'Move left', icon: 'arrow-left', iconClass: 'text-link', showPrimaryButton: false, action: () => { this.moveTabLeft(index); } } : undefined, canMoveRight ? { labelKey: 'common:textMoveRight', label: 'Move right', icon: 'arrow-right', iconClass: 'text-link', showPrimaryButton: false, action: () => { this.moveTabRight(index); } } : undefined, numberOfTabs !== 1 ? { labelKey: 'FORMS:hdrShowHide', label: 'Show/Hide', icon: 'bolt', iconClass: 'text-link', showPrimaryButton: false, action: () => { this.openTabLogicModal(index); } } : undefined, numberOfTabs !== 1 ? { labelKey: 'common:btnDelete', label: 'Delete', icon: 'times', iconClass: 'text-danger', showPrimaryButton: false, action: () => { this.deletePage(index); } } : undefined ].filter((item) => !!item); const primaryActions = actions.filter((ta) => !!ta.showPrimaryButton); const secondaryActions = actions.filter((ta) => !ta.showPrimaryButton); const sortedActions = [ ...primaryActions, ...secondaryActions ]; return sortedActions; } async openQuickAddModal (pageIndex: number) { const tab = this.tabs[pageIndex]; const result = await this.modalFactory.open( QuickAddModalComponent, { pageName: tab.label, currentFormRefFields: this.referenceFieldService.currentFormRefFields, numberOfComps: this.numberOfCompsTracker[this.tabIndex] || 0, numberAllowed: MAX_COMPONENTS_PER_PAGE, formType: this.form.formType, formAudience: this.formBuilderService.currentFormBuilderFormAudience }, { class: 'modal-full-size' }); if (result) { result.components.forEach((quickAddComp) => { const componentToAdd = cloneDeep(quickAddComp); const required = result.requiredMap[componentToAdd.key]; const label = result.labelMap[componentToAdd.key]; let key = componentToAdd.key; let type = componentToAdd.type; let reportCompConfig = {}; if ('referenceFieldId' in componentToAdd) { type = `referenceFields-${componentToAdd.key}`; } if ('reportFieldConfigType' in componentToAdd) { reportCompConfig = { displayType: AdHocReportingUI.DisplayTypes.TextField, reportFieldDataOptions: componentToAdd.reportFieldConfigType }; key = 'reportField_' + componentToAdd.reportFieldConfigType.reportFieldDisplay; } const comp: FormDefinitionComponent = { ...reportCompConfig, key, type, validate: { required }, label }; const comps = this.componentHelper.getAllComponents(this.form.formDefinition); // If this key already exists on the form, update to unique key comp.key = this.componentHelper.guessKey(comp, comps); this.componentHelper.insertFormComponent( comp, this.form.formDefinition[this.tabIndex] ); }); this.hasRevisionChanges = true; this.form = { ...this.form, formDefinition: [ ...this.form.formDefinition ] }; } } async openTabLogicModal (index: number) { const tab = this.tabs[index]; const existingDef = this.form.formDefinition[index]; const existingEvaluationType = existingDef.logic?.evaluationType; const isAlwaysTrueOrFalse = [ EvaluationType.AlwaysFalse, EvaluationType.AlwaysTrue ].includes(existingEvaluationType); if (isAlwaysTrueOrFalse) { existingDef.logic = this.logicBuilderService.getDefaultConditionalLogic(); existingDef.logic.evaluationType = existingEvaluationType; } const existingLogic: GlobalLogicGroup = existingDef.logic ?? this.logicBuilderService.getDefaultConditionalLogic(); const result = await this.modalFactory.open< ConditionalLogicBuilderModalComponent >( ConditionalLogicBuilderModalComponent, { builderName: this.i18n.translate( 'FORMS:hdrShowHidePage', {}, 'Show/Hide Page' ), evaluationTypeOptions: this.logicBuilderService.getEvaluationTypeOptionsForFormTabs(), currentColumnName: tab.label, availableColumns: this.formLogicService.getAvailableColumnsForLogicModal( this.form.formDefinition, index, this.typeAudienceMap[this.form.formType], false, false ), logic: existingLogic } ); if (result) { this.replacePage(index, { ...existingDef, logic: result }); } } // When we manually update the tab index, we have to update "active" attr on tabs updateTabIndex (index: number) { this.tabs.forEach((tab, idx) => { tab.active = idx === index; }); this.activeTabChanged(index); } activeTabChanged (index: number) { this.tabIndex = index; this.setCurrentIndex(); this.setFooterInfoText(); } moveTabLeft (index: number) { this.tabs.forEach((tab, tabIndex) => { if (index === tabIndex) { tab.order = tabIndex - 1; } else if ((index - 1) === tabIndex) { tab.order = tabIndex + 1; } }); this.activeTabChanged(index - 1); this.updateTabActionsAndOrder(); } moveTabRight (index: number) { this.tabs.forEach((tab, tabIndex) => { if (index === tabIndex) { tab.order = tabIndex + 1; } else if ((index + 1) === tabIndex) { tab.order = tabIndex - 1; } }); this.activeTabChanged(index + 1); this.updateTabActionsAndOrder(); } updateTabActionsAndOrder () { this.tabs = orderBy(this.tabs, ['order'], ['asc']); this.tabs.forEach((tab, tabIndex) => { tab.actions = this.getFormTabActions(tabIndex); }); this.updateFormDefinitionOrder(); this.setFooterInfoText(); } updateFormDefinitionOrder () { this.tabs.forEach((tab, index) => { const definition = this.form.formDefinition.find((def) => { return tab.context.uniqueId === def.uniqueId; }); if (definition) { definition.index = index; } }); const sortedDefinitions = orderBy(this.form.formDefinition, ['index'], ['asc']); this.hasRevisionChanges = true; this.form = { ...this.form, formDefinition: [ ...sortedDefinitions ] }; } getNumberOfComps (components: FormDefinitionComponent[]) { let numberOfComponents = 0; this.componentHelper.eachComponent(components, (component) => { if (component.type !== 'button') { numberOfComponents = numberOfComponents + 1; } }); return numberOfComponents; } async addEditPage (index?: number) { const isEdit = index || index === 0; const tabName = isEdit ? this.tabs[index].label : ''; const existingFormComps = this.componentHelper.getAllComponents( this.form.formDefinition ); const response = await this.modalFactory.open( AddEditFormTabModalComponent, { tabName, currentFormId: this.form.id, currentFormType: this.form.formType, existingFormComps } ); if (response) { let newFormDefinition: FormDefinitionForUi; const newTabIndex = this.tabs.length; if (isEdit) { const existingDefinition = this.form.formDefinition[index]; // Update existing tab newFormDefinition = { ...existingDefinition, tabName: response.formGroup.tabName }; this.replacePage(index, newFormDefinition); this.tabs = [ ...this.tabs.slice(0, index), { ...this.tabs[index], label: response.formGroup.tabName }, ...this.tabs.slice(index + 1) ]; } else { let components: FormDefinitionComponent[] = []; if (response.formGroup.pageTemplateId) { const formId = response.formGroup.formId; const formDetail = this.formLogicService.getFormDetail( formId, this.formService.getLatestRevisionId( formId, true ) ); const formDefinition = formDetail.formDefinition.find((tab) => { return tab.uniqueId === response.formGroup.pageTemplateId; }); // Clear out dependent logic when starting from a template this.componentHelper.eachComponent(formDefinition.components, (comp) => { if (comp.conditionalValue) { comp.conditionalValue = null; } if (comp.formula?.step) { comp.formula = null; } if (comp.conditionalLogic) { comp.conditionalLogic = null; } }); components = formDefinition.components; } // Add new tab newFormDefinition = { tabName: response.formGroup.tabName, logic: this.logicBuilderService.getDefaultConditionalLogic(), components, index: this.form.formDefinition.length, uniqueId: v4() }; this.hasRevisionChanges = true; this.form = { ...this.form, formDefinition: [ ...this.form.formDefinition, newFormDefinition ] }; this.activeTabChanged(newTabIndex); // update number of comps tracker and filter out submit button const numberOfCompsForThisTab = this.getNumberOfComps(components); const updatedTracker = { ...this.numberOfCompsTracker, [newTabIndex]: numberOfCompsForThisTab }; this.setNumberOfCompsTracker(updatedTracker); // add the new comps to tracker so they aren't available in quick add this.referenceFieldService.addCurrentFormRefFields(components); this.tabs = [ ...this.tabs.map((tab) => { tab.active = false; return tab; }), { label: response.formGroup.tabName, order: this.tabIndex, active: true, actions: [], context: newFormDefinition } ]; this.updateTabActionsAndOrder(); } this.setCurrentDefinition(); if (response.openQuickAdd) { this.openQuickAddModal(newTabIndex); } } } private replacePage (index: number, formDefinition: FormDefinitionForUi) { this.hasRevisionChanges = true; this.form = { ...this.form, formDefinition: [ ...this.form.formDefinition.slice(0, index), formDefinition, ...this.form.formDefinition.slice(index + 1) ] }; this.setCurrentDefinition(); } async deletePage (index: number) { const modalSubHeader = this.tabs[index].label; const response = await this.modalFactory.open( ConfirmationModalComponent, { modalHeader: this.i18n.translate( 'common:hdrDeletePage', {}, 'Delete Page' ), modalSubHeader, confirmText: this.i18n.translate( 'common:textAreYouSureDeleteFormPage', {}, 'Are you sure you want to delete this form page? This action cannot be undone once the form is saved.' ), confirmButtonText: this.i18n.translate( 'common:btnDelete', {}, 'Delete' ) } ); if (response) { this.hasRevisionChanges = true; this.form = { ...this.form, formDefinition: [ ...this.form.formDefinition.slice(0, index), ...this.form.formDefinition.slice(index + 1) ] }; this.setCurrentDefinition(); const needToUpdateActive = this.tabs[index].active; this.tabs = [ ...this.tabs.slice(0, index), ...this.tabs.slice(index + 1) ]; this.tabs.forEach((_tab, tabIndex) => { if (needToUpdateActive && tabIndex === 0) { _tab.active = true; } _tab.order = tabIndex; }); // if there is only one tab left, we need to remove the conditional logic and always show it if (this.tabs.length === 1) { const onlyDefinition = this.form.formDefinition[0]; onlyDefinition.logic = this.logicBuilderService.getDefaultConditionalLogic(); this.replacePage(0, onlyDefinition); } this.updateTabActionsAndOrder(); this.activeTabChanged(0); } } getDefaultFormLang (): string { const defaultLang = this.form.defaultLanguageId; if (defaultLang && this.langKeys) { const exists = this.langKeys.includes(defaultLang); return exists ? defaultLang : this.clientSettingsService.defaultLanguage; } return this.clientSettingsService.defaultLanguage; } isCorrectType (component: FormDefinitionComponent) { return ![ 'content', 'htmlelement', 'button', 'columns' ].includes(component.type.toLowerCase()); } setFooterInfoText () { if (this.allowedMoreCompsThanMax) { this.footerState.setFooterInfo(null, null, null); } else { const footerText = this.i18n.translate( 'common:textComponentsOnPage', { numberOnPage: this.numberOfCompsTracker[this.tabIndex], numberAllowed: MAX_COMPONENTS_PER_PAGE }, 'Components on page: __numberOnPage__ of __numberAllowed__' ); const tooltipText = this.i18n.translate( 'common:textMaxComponentsPerPageTooltip', { numberAllowed: MAX_COMPONENTS_PER_PAGE }, `To maintain performance when creating and filling out forms, only __numberAllowed__ components can be added to one form page. Click "New page" at the top to create a new page.` ); this.footerState.setFooterInfo(footerText, 'question-circle', tooltipText); } } formBuilderChanged (form: Form) { this.hasRevisionChanges = true; this.form = form; this.setFooterInfoText(); this.setCurrentDefinition(); } async startSave ( formDefinition = this.form.formDefinition, modalResponse?: CreateEditFormModalResponse ) { this.footerState.setPrimaryDisabled(true); const formSchema = this.getFormSchema(formDefinition); if (this.missingKeyComponents.length > 0) { this.missingKeysPrompt(); } else if (this.duplicateKeyComponents.length > 0) { this.duplicateKeysPrompt(); } else if (this.duplicateStandardComponents.length > 0) { this.duplicateStandardComponentsPrompt(); } else { const { referenceFieldIds, customDataTableGuids } = this.referenceFieldService.extractReferenceFieldsFromForm(formDefinition); const externalApiRequests = this.formLogicService.getExternalAPICalls(formDefinition); const data: SaveForm = { id: this.form.id, revisionId: this.currentRevisionId, name: modalResponse?.formName || this.form.name, description: modalResponse?.description || this.form.description, formDefinition, formSchema, formType: this.form.formType, defaultLanguageId: this.form.defaultLanguageId || this.clientSettingsService.defaultLanguage, availableForTranslation: true, picklistGuids: customDataTableGuids, referenceFieldIds, externalApiRequestIds: this.externalApiService.extractIds(externalApiRequests), requireSignature: modalResponse ? modalResponse.requireSignature : this.form.requireSignature, signatureDescription: modalResponse ? modalResponse.signatureDescription : this.form.signatureDescription }; if (this.isPublished && !modalResponse) { this.confirmSaveModal(data); } else { if (!!modalResponse) { await this.doSave(SaveFormOptions.UPDATE_CURRENT_REVISION, data, modalResponse); } else { await this.doSave(SaveFormOptions.CREATE_NEW_REVISION, data); } } } this.footerState.setPrimaryDisabled(false); } async confirmSaveModal (data: SaveForm) { const response = await this.modalFactory.open( SaveFormModalComponent, { data, revisionVersion: this.currentVersionName, updatingLatest: this.latestRevisionId === data.revisionId, doNotAllowUpdateCurrentRevision: this.doNotAllowUpdateCurrentRevision } ); if (response) { await this.doSave(response, data); } } async doSave ( option: SaveFormOptions, data: SaveForm, modalResponse?: CreateEditFormModalResponse ) { this.spinnerService.startSpinner(); let response; if (option === SaveFormOptions.CREATE_NEW_REVISION) { response = await this.formLogicService.saveFormNewRevision(data); } else { response = await this.formLogicService.saveFormExistingRevision(data); } await this.formService.refreshForms(); this.spinnerService.stopSpinner(); if (response.success) { const id = response ? response.id : data.id; await this.handleSaveSuccess(id, data, modalResponse); } else if (!modalResponse) { if (response.hasPendingFormError) { await this.pendingFormPrompt(option, data); } } } async handleSaveSuccess ( id: number, data: SaveForm, modalResponse?: CreateEditFormModalResponse ) { this.spinnerService.startSpinner(); if (!modalResponse) { // These things only need to be reset when changing the form definition. // When modalResponse is passed in, we are only updating form name / description this.workflowLevelAutomationService.resetWorkflowAutomationDetail(); this.workflowLevelAutomationService.clearWorkflowLevelForms(); this.adHocReportingService.resetFormComponentMap(id); this.programService.setOfflineProgramMap({}); await this.referenceFieldService.resetFields(); } if (this.hasInternational) { // Only sync if the actual form definition changes (no form detail modal response), // or if they updated name, description, or signature description const shouldSyncTranslations = !modalResponse || modalResponse.formName !== this.form.name || modalResponse.description !== this.form.description || modalResponse.signatureDescription !== this.form.signatureDescription; if (shouldSyncTranslations) { const { formAttributes, richTextAttributes } = await this.translationService.extractFormComponentsForTranslation( data.formDefinition, this.referenceFieldService.referenceFieldMap, this.referenceFieldService.tableColumnsMap, this.referenceFieldService.dataPointsMap ); if (formAttributes.length || richTextAttributes.length) { await this.translationResources.syncFormTranslations( id, formAttributes, richTextAttributes ); } } } this.notifier.success(this.i18n.translate( 'FORMS:textSuccessfullyUpdatedForm', {}, 'Successfully updated the form' )); this.spinnerService.stopSpinner(); if (!modalResponse) { this.determinationService.navigate('/program-setup/forms/all'); } } getFormSchema (formDefinition = this.form.formDefinition) { const formSchema: { key: string; label: string; type: string; values: any[]; }[] = []; const existingKeys: string[] = []; const standardMap: Record = {}; this.duplicateStandardComponents = []; this.duplicateKeyComponents = []; this.missingKeyComponents = []; formDefinition.forEach((tab) => { this.componentHelper.eachComponent( tab.components, (component) => { if (!component.key) { this.missingKeyComponents.push(component); } else { const needsUniqueKey = ![ 'tabs', 'form', 'htmlelement', 'button', 'content', 'columns', 'fieldset', 'panel', 'table', 'well' ].includes(component.type); if (needsUniqueKey) { if (existingKeys.includes(component.key)) { this.duplicateKeyComponents.push(component); } else { existingKeys.push(component.key); } } } const inputData = this.componentHelper.getComponentInputData(component); if (inputData) { formSchema.push(inputData); } }, true); }); Object.keys(standardMap).forEach((compType) => { const comps = standardMap[compType]; if (comps?.length > 1) { const label = comps[comps.length - 1]; this.duplicateStandardComponents.push(label); } }); return formSchema; } duplicateStandardComponentsPrompt () { const single = this.duplicateStandardComponents.length === 1; let dupeString = ''; this.duplicateStandardComponents.forEach((label) => { dupeString += `
  • ${label}
  • `; }); this.modalFactory.open(ConfirmationModalComponent, { confirmText: `
    ${this.i18n.translate( single ? 'CONFIG:textDuplicateStandardCompConfirmTextSingle' : 'CONFIG:textDuplicateStandardCompConfirmTextMultiple', {}, single ? 'There was an error saving the form. The standard component listed below was added to the form more than once. Only one instance of a standard component is allowed on a form. Please make modifications to correct this issue.' : 'There was an error saving the form. The standard components listed below were added to the form more than once. Only one instance of a standard component is allowed on a form. Please make modifications to correct this issue.' )}
    ${dupeString}
    `, modalHeader: this.correctionsNeeded, confirmButtonText: this.proceedToChanges }); } duplicateKeysPrompt () { const single = this.duplicateKeyComponents.length === 1; let dupeString = ''; this.duplicateKeyComponents.forEach((dupe) => { dupeString += `
  • ${dupe.label || dupe.key }
  • `; }); this.modalFactory.open(ConfirmationModalComponent, { confirmText: `
    ${this.i18n.translate( single ? 'CONFIG:textDuplicateKeysConfirmTextSingle' : 'CONFIG:textDuplicateKeysConfirmTextMultiple', {}, single ? 'There was an error saving the form. The component listed below does not have a unique property name. Please edit this component, go to the API tab, and enter a unique property name.' : 'There was an error saving the form. The components listed below do not have unique property names. Please edit these components, go to the API tab, and enter a unique property name.' )}
    ${dupeString}
    `, modalHeader: this.correctionsNeeded, confirmButtonText: this.proceedToChanges }); } missingKeysPrompt () { const single = this.missingKeyComponents.length === 1; let missingString = ''; this.missingKeyComponents.forEach((item) => { missingString += `
  • ${item.label}
  • `; }); this.modalFactory.open(ConfirmationModalComponent, { confirmText: `
    ${this.i18n.translate( single ? 'CONFIG:textMissingKeysConfirmTextSingle' : 'CONFIG:textMissingKeysConfirmTextMultiple', {}, single ? 'There was an error saving the form. The component listed below is missing a property name. Please edit this component, go to the API tab, and enter a value for property name.' : 'There was an error saving the form. The components listed below are missing their property names. Please edit these components, go to the API tab, and enter a value for property name.' )}
    ${missingString}
    `, modalHeader: this.correctionsNeeded, confirmButtonText: this.proceedToChanges }); } async pendingFormPrompt ( option: SaveFormOptions, data: SaveForm ) { this.spinnerService.startSpinner(); this.spinnerService.stopSpinner(); const foundForm = this.formService.published.find(({ formId }) => { return +formId === +this.id; }); const proceed = await this.modalFactory.open( ConfirmationModalComponent, { confirmText: `
    ${this.i18n.translate( 'CONFIG.removePendingFormsAndSavePrompt', {}, 'You have a draft version of this form that has not been published.' + ' If you proceed, that revision will be lost.' + ' Are you sure you want to save this new revision?' )}
    `, confirmButtonText: this.i18n.translate( 'common:btnSave', {}, 'Save' ) } ); if (proceed) { await this.formService.deleteRevision(this.id, foundForm.revisionId); await this.doSave(option, data); } } cancel () { this.determinationService.navigate('/program-setup/forms/all'); } ngOnDestroy () { this.footerState.clearAll(); this.formBuilderService.resetCopiedComponent(); this.referenceFieldService.resetCurrentFormRefFields(); this.formBuilderService.setInFormBuilder(false); } }