UNPKG

novo-elements

Version:

1,250 lines (1,222 loc) 2.06 MB
import * as i0 from '@angular/core'; import { EventEmitter, Injectable, Component, HostListener, Directive, LOCALE_ID, Output, Input, Inject, ChangeDetectionStrategy, ViewEncapsulation, forwardRef, ElementRef, ViewChild, Optional, ViewContainerRef, HostBinding, Self, ViewChildren, ContentChildren, NgModule } from '@angular/core'; import * as i1 from 'novo-elements/services'; import { NovoTemplateService } from 'novo-elements/services'; import { Helpers, notify, formatZonedTime, DateUtil, OutsideClick, COUNTRIES, findByCountryId, getStates } from 'novo-elements/utils'; import { Subject } from 'rxjs'; import { map, debounceTime, distinctUntilChanged, takeUntil, filter } from 'rxjs/operators'; import * as i4$1 from '@angular/common/http'; import * as i1$1 from 'novo-elements/elements/modal'; import { NovoModalModule } from 'novo-elements/elements/modal'; import * as i4$3 from 'novo-elements/elements/picker'; import { EntityPickerResults, EntityPickerResult, NovoPickerModule } from 'novo-elements/elements/picker'; import * as i1$2 from 'novo-elements/elements/toast'; import { startOfToday } from 'date-fns'; import * as i3$1 from '@angular/forms'; import { Validators, FormControl, UntypedFormGroup, NG_VALUE_ACCESSOR, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { listTimeZones, findTimeZone, getZonedTime } from 'timezone-support'; import * as i3 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i4 from 'novo-elements/elements/button'; import { NovoButtonModule } from 'novo-elements/elements/button'; import * as i5 from 'novo-elements/elements/common'; import { NovoOverlayTemplateComponent, mixinErrorState, NovoTemplate, NovoOverlayModule, NovoCommonModule } from 'novo-elements/elements/common'; import * as i4$2 from 'novo-elements/elements/tooltip'; import { NovoTooltipModule } from 'novo-elements/elements/tooltip'; import * as i5$1 from 'novo-elements/elements/tip-well'; import { NovoTipWellModule } from 'novo-elements/elements/tip-well'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import * as i4$4 from 'novo-elements/elements/radio'; import { NovoRadioModule } from 'novo-elements/elements/radio'; import * as i5$2 from 'novo-elements/elements/tiles'; import { NovoTilesModule } from 'novo-elements/elements/tiles'; import * as i6$2 from 'novo-elements/elements/select'; import { NovoSelectModule } from 'novo-elements/elements/select'; import * as i8$1 from 'novo-elements/elements/chips'; import { NovoChipsModule } from 'novo-elements/elements/chips'; import * as i9 from 'novo-elements/elements/date-picker'; import { NovoDatePickerModule } from 'novo-elements/elements/date-picker'; import * as i10 from 'novo-elements/elements/time-picker'; import { NovoTimePickerModule } from 'novo-elements/elements/time-picker'; import * as i11 from 'novo-elements/elements/date-time-picker'; import { NovoDateTimePickerModule } from 'novo-elements/elements/date-time-picker'; import * as i12 from 'novo-elements/addons/ckeditor'; import { NovoNovoCKEditorModule } from 'novo-elements/addons/ckeditor'; import * as i6 from 'novo-elements/elements/places'; import { NOVO_ADDRESS_CONFIG, PlacesListComponent, GooglePlacesModule } from 'novo-elements/elements/places'; import * as i7 from '@angular/cdk/drag-drop'; import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop'; import * as i5$3 from 'novo-elements/elements/field'; import { NovoFieldControl, NovoFieldModule } from 'novo-elements/elements/field'; import * as i6$1 from 'novo-elements/elements/loading'; import { NovoLoadingModule } from 'novo-elements/elements/loading'; import * as i8 from 'novo-elements/pipes'; import { NovoPipesModule } from 'novo-elements/pipes'; import * as i15 from 'novo-elements/elements/quick-note'; import { NovoQuickNoteModule } from 'novo-elements/elements/quick-note'; import * as i17 from 'novo-elements/elements/popover'; import { NovoPopOverModule } from 'novo-elements/elements/popover'; import * as i18 from 'angular-imask'; import { IMaskModule } from 'angular-imask'; import * as i19 from 'novo-elements/addons/code-editor'; import { NovoCodeEditorModule } from 'novo-elements/addons/code-editor'; import * as i21 from 'novo-elements/elements/checkbox'; import { NovoCheckboxModule } from 'novo-elements/elements/checkbox'; import * as i22 from 'novo-elements/elements/switch'; import { NovoSwitchModule } from 'novo-elements/elements/switch'; import * as i2 from 'novo-elements/elements/icon'; import { NovoIconModule } from 'novo-elements/elements/icon'; import * as i4$5 from 'novo-elements/elements/flex'; import { NovoFlexModule } from 'novo-elements/elements/flex'; import { OverlayModule } from '@angular/cdk/overlay'; import { NovoHeaderModule } from 'novo-elements/elements/header'; const MAX_INTEGER = 2147483647; const MIN_YEAR = 1753; class FormValidators { showStateRequiredFlag(subfield, control) { return (subfield === 'state' && !Helpers.isEmpty(control.config.state) && control.config.state.required && Helpers.isBlank(control.value.state) && control.config.state.updated && !Helpers.isBlank(control.value.countryName) && control.config.state.pickerConfig && control.config.state.pickerConfig.defaultOptions && control.config.state.pickerConfig.defaultOptions.length > 0); } // Makes sure the control value does not exceed the max integer value static maxInteger(control) { return control.value < MAX_INTEGER ? null : { integerTooLarge: true }; } // Makes sure the control value is above the minimum year static minYear(control) { if (!control.value) { return null; } return control.value >= MIN_YEAR ? null : { minYear: true }; } // Makes sure the control value does not exceed the max number value static maxDouble(control) { return control.value < Number.MAX_SAFE_INTEGER ? null : { doubleTooLarge: true }; } // Make sure the control value is an email static isEmail(control) { const EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; return !control.value || EMAIL_REGEXP.test(control.value) ? null : { invalidEmail: true }; } // Makes sure the control value is a valid address static isValidAddress(control) { const fieldList = ['address1', 'address2', 'city', 'state', 'zip', 'countryID']; const invalidAddressFields = []; const maxlengthFields = []; let returnVal = null; let maxlengthError = false; const showCountryRequiredFlag = (subfield, ctrl) => { return (subfield === 'countryID' && !Helpers.isEmpty(ctrl.config.countryID) && ctrl.config.countryID.required && Helpers.isBlank(ctrl.value.countryName) && ctrl.config.countryID.updated); }; const showStateRequiredFlag = (subfield, ctrl) => { return (subfield === 'state' && !Helpers.isEmpty(ctrl.config.state) && ctrl.config.state.required && Helpers.isBlank(ctrl.value.state) && ctrl.config.state.updated && !Helpers.isBlank(ctrl.value.countryName) && ctrl.config.state.pickerConfig && ctrl.config.state.pickerConfig.defaultOptions && ctrl.config.state.pickerConfig.defaultOptions.length > 0); }; if (control.value && control.config) { let valid = true; let formValidity = true; fieldList.forEach((subfield) => { if (!Helpers.isEmpty(control.config[subfield])) { if ((['countryID', 'state'].indexOf(subfield) === -1 && control.config[subfield].required && !Helpers.isBlank(control.value[subfield]) && Helpers.isEmpty(control.value[subfield])) || showCountryRequiredFlag(subfield, control) || showStateRequiredFlag(subfield, control)) { valid = false; invalidAddressFields.push(control.config[subfield].label); } if (((subfield !== 'countryID' && control.config[subfield].required && Helpers.isEmpty(control.value[subfield])) || (subfield === 'countryID' && !Helpers.isEmpty(control.config.countryID) && control.config.countryID.required && Helpers.isEmpty(control.value.countryName))) && !(subfield === 'state' && !Helpers.isBlank(control.value.countryName) && control.config.state.pickerConfig && control.config.state.pickerConfig.defaultOptions && control.config.state.pickerConfig.defaultOptions.length === 0)) { formValidity = false; } if (!Helpers.isEmpty(control.config[subfield].maxlength) && !Helpers.isEmpty(control.value[subfield]) && control.value[subfield].length > control.config[subfield].maxlength) { maxlengthError = true; maxlengthFields.push(subfield); formValidity = false; } } }); if (!valid || !formValidity || maxlengthError) { returnVal = {}; } if (!valid) { returnVal.invalidAddress = true; returnVal.invalidAddressFields = invalidAddressFields; } if (!formValidity) { returnVal.invalidAddressForForm = true; } if (maxlengthError) { returnVal.maxlength = true; returnVal.maxlengthFields = maxlengthFields; } return returnVal; } return null; } } // NG2 class ControlConfig { constructor() { this.isEmbedded = false; this.isInlineEmbedded = false; this.highlighted = false; } } class BaseControl extends ControlConfig { constructor(type = 'BaseControl', config = {}) { super(); this.__type = 'BaseControl'; this.__type = type; this.__config = config; this.alwaysActive = config.alwaysActive; this.validators = config.validators || []; this.asyncValidators = config.asyncValidators || []; this.value = config.value; this.key = config.key || ''; this.label = config.label || ''; this.checkboxLabel = config.checkboxLabel; this.name = config.name || ''; this.required = !!config.required; this.hidden = !!config.hidden; this.encrypted = !!config.encrypted; this.sortOrder = config.sortOrder === undefined ? 1 : config.sortOrder; this.controlType = config.controlType || ''; this.type = config.type; this.subType = config.subType; this.metaType = config.metaType; this.placeholder = config.placeholder || ''; this.config = config.config || null; this.dirty = !!(config.value !== undefined && config.value !== null); this.multiple = !!config.multiple; this.headerConfig = config.headerConfig || null; this.currencyFormat = config.currencyFormat || null; this.associatedEntity = config.associatedEntity || null; this.optionsType = config.optionsType || null; this.options = config.options || []; this.forceClear = new EventEmitter(); this.readOnly = !!config.readOnly || !!config.disabled; this.disabled = !!config.disabled; this.enabled = true; this.layoutOptions = config.layoutOptions || {}; this.military = !!config.military; this.dateFormat = config.dateFormat; this.textMaskEnabled = config.textMaskEnabled; this.maskOptions = config.maskOptions; this.allowInvalidDate = config.allowInvalidDate; this.startDate = config.startDate; this.endDate = config.endDate; this.restrictFieldInteractions = !!config.restrictFieldInteractions; this.highlighted = !!config.highlighted; if (!Helpers.isEmpty(config.warning)) { this.warning = config.warning; } if (this.required) { this.validators.push(Validators.required); } if (!Helpers.isBlank(config.maxlength)) { this.maxlength = config.maxlength; this.validators.push(Validators.maxLength(this.maxlength)); } if (!Helpers.isBlank(config.minlength)) { this.minlength = config.minlength; this.validators.push(Validators.minLength(this.minlength)); } this.closeOnSelect = !!config.closeOnSelect; this.interactions = config.interactions; this.dataSpecialization = config.dataSpecialization; this.dataType = config.dataType; this.appendToBody = !!config.appendToBody; if (this.appendToBody) { notify('\'appendToBody\' has been deprecated. Please remove this attribute.'); } this.parentScrollSelector = config.parentScrollSelector; this.description = config.description; if (config.tooltip) { this.tooltip = config.tooltip; this.tooltipPosition = config.tooltipPosition; this.tooltipSize = config.tooltipSize; this.tooltipPreline = config.tooltipPreline; this.removeTooltipArrow = config.removeTooltipArrow; this.tooltipAutoPosition = config.tooltipAutoPosition; } this.popoverAlways = config.popoverAlways; this.popoverAnimation = config.popoverAnimation; this.popoverContent = config.popoverContent; this.popoverDisabled = config.popoverDisabled; this.popoverDismissTimeout = config.popoverDismissTimeout; this.popoverHtmlContent = config.popoverHtmlContent; this.popoverOnHover = config.popoverOnHover; this.popoverPlacement = config.popoverPlacement; this.popoverTitle = config.popoverTitle; this.template = config.template; this.customControlConfig = config.customControlConfig; this.tipWell = config.tipWell; this.width = config.width; this.startupFocus = !!config.startupFocus; if (config.fileBrowserImageUploadUrl) { this.fileBrowserImageUploadUrl = config.fileBrowserImageUploadUrl; } if (config.isEmpty) { this.isEmpty = config.isEmpty; } this.weekStart = config.weekStart || 0; this.disabledDateMessage = config.disabledDateMessage; } } // APP class AddressControl extends BaseControl { constructor(config) { super('AddressControl', config); this.controlType = 'address'; this.validators.push(FormValidators.isValidAddress); } } // APP class CheckListControl extends BaseControl { constructor(config) { super('CheckListControl', config); this.controlType = 'checklist'; this.options = config.options || []; } } // APP class CheckboxControl extends BaseControl { constructor(config) { super('CheckboxControl', config); this.controlType = 'checkbox'; } } // APP class CodeEditorControl extends BaseControl { constructor(config) { super('CodeEditorControl', config); this.controlType = 'code-editor'; } } // APP class CustomControl extends BaseControl { constructor(config) { super(config.template, config); this.controlType = 'custom'; this.controlType = config.template; } } // APP class DateControl extends BaseControl { constructor(config) { super('DateControl', config); this.controlType = 'date'; } } // APP class DateTimeControl extends BaseControl { constructor(config) { super('DateTimeControl', config); this.controlType = 'date-time'; } } // APP class EditorControl extends BaseControl { constructor(config) { super('EditorControl', config); this.controlType = 'editor'; this.minimal = false; } } // APP class FileControl extends BaseControl { constructor(config) { super('FileControl', config); this.controlType = 'file'; // TODO - translate this.placeholder = config.placeholder; this.multiple = config.multiple; } } class GroupedControl { constructor(config) { this.__type = 'GroupedControl'; Object.keys(config).forEach((key) => (this[key] = config[key])); } } // APP class NativeSelectControl extends BaseControl { constructor(config) { super('NativeSelectControl', config); this.controlType = 'native-select'; this.options = []; this.options = config.options || []; } } // APP class PickerControl extends BaseControl { constructor(config) { super('PickerControl', config); this.controlType = 'picker'; this.options = []; this.options = config.options || []; } } class TablePickerControl extends PickerControl { constructor(config) { super(Object.assign(config, { parentScrollSelector: '.table-container' })); this.__type = 'TablePickerControl'; } } // APP class QuickNoteControl extends BaseControl { constructor(config) { super('QuickNoteControl', config); this.controlType = 'quick-note'; this.options = []; this.options = config.options || []; } } // APP class RadioControl extends BaseControl { constructor(config) { super('RadioControl', config); this.controlType = 'radio'; this.options = []; this.options = config.options || []; } } // APP class ReadOnlyControl extends BaseControl { constructor(config) { super('ReadOnlyControl', config); this.controlType = 'read-only'; config.readOnly = true; } } // APP class SelectControl extends BaseControl { constructor(config) { super('SelectControl', config); this.controlType = 'select'; this.options = []; this.options = config.options || []; this.placeholder = config.placeholder || ''; } } // APP class SwitchControl extends BaseControl { constructor(config) { super('SwitchControl', config); this.controlType = 'switch'; } } // APP class TextAreaControl extends BaseControl { constructor(config) { super('TextAreaControl', config); this.controlType = 'text-area'; } } // APP class TextBoxControl extends BaseControl { constructor(config) { super('TextBoxControl', config); this.controlType = 'textbox'; this.type = this.getTextboxType(config.type) || ''; this.subType = config.type || ''; this.setValidators(this.subType); } setValidators(type) { switch (type) { case 'email': this.validators.push(FormValidators.isEmail); break; case 'number': case 'currency': this.validators.push(FormValidators.maxInteger); break; case 'float': case 'percentage': this.validators.push(FormValidators.maxDouble); break; case 'year': this.validators.push(FormValidators.minYear); break; default: break; } } getTextboxType(type) { switch (type) { case 'percentage': case 'currency': case 'float': case 'year': return 'number'; default: return type; } } } // APP class TilesControl extends BaseControl { constructor(config) { super('TilesControl', config); this.controlType = 'tiles'; this.options = []; this.options = config.options || []; } } // APP class TimeControl extends BaseControl { constructor(config) { super('TimeControl', config); this.controlType = 'time'; } } // APP class TimezoneControl extends BaseControl { constructor(config) { super('TimezoneControl', config); this.controlType = 'timezone'; this.options = []; this.buildTimezones = (compareDate) => { const timezones = listTimeZones() .map((zone) => { const timezone = findTimeZone(zone); const zonedTime = getZonedTime(compareDate, timezone); const formatted = formatZonedTime(zonedTime, `z - [${zone}] ([GMT] Z)`).replace('_', ' '); const option = { value: zone, label: formatted, offset: zonedTime.zone.offset, }; return option; }) // Formats 'noisy' timezones without a letter acronym. .map((option) => { const rgx = /(^(\+|-)\d+\s- )/; const matches = option.label.match(rgx); if (matches) { const prefix = matches[0]; option.label = option.label.split(prefix)[1]; } return option; }) // Sorts W -> E, prioritizes america. could be more nuanced based on system tz but simple for now .sort((a, b) => { const offsetDelta = b.offset - a.offset; if (offsetDelta !== 0) { return offsetDelta; } if (a.label < b.label) { return -1; } if (a.label > b.label) { return 1; } return 0; }); return timezones; }; this.options = this.buildTimezones(new Date()); this.placeholder = config.placeholder || ''; // current timezone const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; this.value = tz; } } class ControlFactory { static create(type, config) { switch (type) { case 'AddressControl': return new AddressControl(config); case 'CheckboxControl': return new CheckboxControl(config); case 'CheckListControl': return new CheckListControl(config); case 'DateTimeControl': return new DateTimeControl(config); case 'EditorControl': return new EditorControl(config); case 'FileControl': return new FileControl(config); case 'NativeSelectControl': return new NativeSelectControl(config); case 'PickerControl': return new PickerControl(config); case 'TablePickerControl': return new TablePickerControl(config); case 'QuickNoteControl': return new QuickNoteControl(config); case 'RadioControl': return new RadioControl(config); case 'ReadOnlyControl': return new ReadOnlyControl(config); case 'TextAreaControl': return new TextAreaControl(config); case 'TextBoxControl': return new TextBoxControl(config); case 'SelectControl': return new SelectControl(config); case 'SwitchControl': return new SwitchControl(config); case 'TilesControl': return new TilesControl(config); case 'TimeControl': return new TimeControl(config); default: console.warn('[ControlFactory] - unable to find control for type. Make sure to set "editorType" and "editorConfig" on your column', type); return null; } } } // NG2 class NovoFormControl extends FormControl { constructor(value, control) { super(value, control.validators, control.asyncValidators); this.displayValueChanges = new EventEmitter(); this.valueHistory = []; this.validators = control.validators; this.initialValue = value; this.valueHistory.push(value); this.key = control.key; this.label = control.label; this.readOnly = control.readOnly; this.hidden = control.hidden; this.encrypted = control.encrypted; this.config = control.config; this.type = control.type; this.subType = control.subType; this.required = control.required; this.hasRequiredValidator = this.required; this.tooltip = control.tooltip; this.tooltipPosition = control.tooltipPosition; this.tooltipSize = control.tooltipSize; this.tooltipPreline = control.tooltipPreline; this.removeTooltipArrow = control.removeTooltipArrow; this.tooltipAutoPosition = control.tooltipAutoPosition; this.label = control.label; this.name = control.name; this.required = control.required; this.sortOrder = control.sortOrder; this.controlType = control.controlType; this.placeholder = control.placeholder; this.minimal = control.minimal; this.multiple = control.multiple; this.headerConfig = control.headerConfig; this.optionsType = control.optionsType; this.readOnly = control.readOnly; this.layoutOptions = control.layoutOptions; this.military = control.military; this.dateFormat = control.dateFormat; this.currencyFormat = control.currencyFormat; this.startDate = control.startDate; this.endDate = control.endDate; this.weekStart = control.weekStart; this.textMaskEnabled = control.textMaskEnabled; this.maskOptions = control.maskOptions; this.allowInvalidDate = control.allowInvalidDate; this.maxlength = control.maxlength; this.minlength = control.minlength; this.closeOnSelect = control.closeOnSelect; this.interactions = control.interactions; this.checkboxLabel = control.checkboxLabel; this.restrictFieldInteractions = control.restrictFieldInteractions; this.appendToBody = control.appendToBody; if (this.appendToBody) { notify('\'appendToBody\' has been deprecated. Please remove this attribute.'); } this.parentScrollSelector = control.parentScrollSelector; this.description = control.description; this.options = control.options; this.tipWell = control.tipWell; this.customControlConfig = control.customControlConfig; this.warning = control.warning; this.disabledDateMessage = control.disabledDateMessage; // Reactive Form, need to enable/disable, can't bind to [disabled] if (this.readOnly) { this.disable(); } else { this.enable(); } } /** * @param clearValue - flag to reset the control's value */ hide(clearValue = true) { this.hidden = true; if (clearValue) { this.setValue(null); } } show() { this.hidden = false; } setRequired(isRequired) { this.required = isRequired; // Update validators to have the required if (this.required && !this.hasRequiredValidator) { const validators = [...this.validators]; validators.push(Validators.required); // TODO: duplicated below this.setValidators(validators); this.updateValueAndValidity({ emitEvent: false }); this.hasRequiredValidator = this.required; } else if (!this.required && this.hasRequiredValidator) { let validators = [...this.validators]; validators = validators.filter((val) => val !== Validators.required); // TODO: duplicated above this.setValidators(validators); this.updateValueAndValidity({ emitEvent: false }); this.hasRequiredValidator = this.required; } } setValue(value, { onlySelf, emitEvent, emitModelToViewChange, emitViewToModelChange, } = {}) { this.markAsDirty(); this.markAsTouched(); this.displayValueChanges.emit(value); super.setValue(value, { onlySelf, emitEvent, emitModelToViewChange, emitViewToModelChange }); // History clearTimeout(this.historyTimeout); this.historyTimeout = setTimeout(() => { this.valueHistory.push(value); }, 300); } setReadOnly(isReadOnly) { this.readOnly = isReadOnly; if (this.readOnly) { this.disable(); } else { this.enable(); } } /** * Disables the control. This means the control will be exempt from validation checks and * excluded from the aggregate value of any parent. Its status is `DISABLED`. * * If the control has children, all children will be disabled to maintain the model. */ disable(opts = { emitEvent: false }) { if (typeof opts.emitEvent === 'undefined') { opts.emitEvent = false; } super.disable(opts); } enable(opts = { emitEvent: false }) { if (typeof opts.emitEvent === 'undefined') { opts.emitEvent = false; } super.enable(opts); } markAsInvalid(message) { this.markAsDirty(); this.markAsTouched(); this.setErrors(Object.assign({}, this.errors, { custom: message })); } markAsValid() { this.setErrors(null); } } // NG class NovoFormGroup extends UntypedFormGroup { constructor() { super(...arguments); this.fieldInteractionEvents = new EventEmitter(); } enableAllControls() { for (const key in this.controls) { if (this.controls[key].readOnly) { this.controls[key].readOnly = false; this.controls[key].enable(); } } } disableAllControls() { for (const key in this.controls) { if (!this.controls[key].readOnly) { this.controls[key].readOnly = true; this.controls[key].disable(); } } } } // NG class FormUtils { constructor(labels, optionsService) { this.labels = labels; this.optionsService = optionsService; this.ASSOCIATED_ENTITY_LIST = [ 'Candidate', 'ClientContact', 'ClientCorporation', 'CorporationDepartment', 'Lead', 'Opportunity', 'JobOrder', 'CorporateUser', 'Person', 'Placement', 'JobShift', ]; this.ENTITY_PICKER_LIST = [ 'Candidate', 'CandidateText', 'Client', 'ClientText', 'ClientContact', 'ClientContactText', 'ClientCorporation', 'ClientCorporationText', 'Lead', 'LeadText', 'Opportunity', 'OpportunityText', 'JobOrder', 'JobOrderText', 'CorporateUser', 'CorporateUserText', 'Person', 'PersonText', 'Placement', 'JobShift', ]; } toFormGroup(controls) { const group = {}; controls.forEach((control) => { const value = Helpers.isBlank(control.value) ? '' : control.value; group[control.key] = new NovoFormControl(value, control); }); return new NovoFormGroup(group); } emptyFormGroup() { return new NovoFormGroup({}); } addControls(formGroup, controls) { controls.forEach((control) => { const value = Helpers.isBlank(control.value) ? '' : control.value; const formControl = new NovoFormControl(value, control); formGroup.addControl(control.key, formControl); }); } removeControls(formGroup, controls) { controls.forEach((control) => { formGroup.removeControl(control.key); }); } toFormGroupFromFieldset(fieldsets) { const controls = []; fieldsets.forEach((fieldset) => { controls.push(...fieldset.controls); }); return this.toFormGroup(controls); } hasAssociatedEntity(field) { return !!(field.associatedEntity && ~this.ASSOCIATED_ENTITY_LIST.indexOf(field.associatedEntity.entity)); } determineInputType(field) { let type; const dataSpecializationTypeMap = { DATETIME: 'datetime', TIME: 'time', MONEY: 'currency', PERCENTAGE: 'percentage', HTML: 'editor', 'HTML-MINIMAL': 'editor-minimal', YEAR: 'year', WORKFLOW_OPTIONS: 'select', SPECIALIZED_OPTIONS: 'select', ALL_WORKFLOW_OPTIONS: 'select', WorkflowOptionsLookup: 'select', SpecializedOptionsLookup: 'select', SimplifiedOptionsLookup: 'select', AllWorkflowOptionsLookup: 'select', }; const dataTypeToTypeMap = { Timestamp: 'date', Date: 'date', Boolean: 'tiles', // eslint-disable-line }; const inputTypeToTypeMap = { CHECKBOX: 'radio', RADIO: 'radio', SELECT: 'select', TILES: 'tiles', }; const inputTypeMultiToTypeMap = { CHECKBOX: 'checklist', RADIO: 'checklist', SELECT: 'chips', }; const typeToTypeMap = { file: 'file', COMPOSITE: 'address', }; const numberDataTypeToTypeMap = { Double: 'float', BigDecimal: 'float', Integer: 'number', }; if (field.type === 'TO_MANY') { if (this.hasAssociatedEntity(field)) { if (field.multiValue === false) { type = 'entitypicker'; } else { type = 'entitychips'; } } else { if (field.multiValue === false) { type = 'picker'; } else { type = 'chips'; } } } else if (field.type === 'TO_ONE') { if ('SYSTEM' === field.dataSpecialization && ['WorkflowOptionsLookup', 'SpecializedOptionsLookup', 'AllWorkflowOptionsLookup'].includes(field.dataType)) { type = dataSpecializationTypeMap[field.dataType]; } else if (['WORKFLOW_OPTIONS', 'SPECIALIZED_OPTIONS', 'ALL_WORKFLOW_OPTIONS'].includes(field.dataSpecialization)) { type = dataSpecializationTypeMap[field.dataSpecialization]; } else if (['SimplifiedOptionsLookup', 'SpecializedOptionsLookup'].includes(field.dataType)) { if (field.options && Object.keys(inputTypeToTypeMap).indexOf(field.inputType) > -1 && !field.multiValue) { type = inputTypeToTypeMap[field.inputType]; } else if (field.options && Object.keys(inputTypeMultiToTypeMap).indexOf(field.inputType) > -1 && field.multiValue) { type = inputTypeMultiToTypeMap[field.inputType]; } else { type = dataSpecializationTypeMap[field.dataType]; } } else if (this.hasAssociatedEntity(field)) { type = 'entitypicker'; // TODO! } else { type = 'picker'; } } else if (field.optionsUrl && field.inputType === 'SELECT') { if (field.optionsType && ~this.ENTITY_PICKER_LIST.indexOf(field.optionsType)) { type = 'entitypicker'; // TODO! } else { type = 'picker'; } } else if (Object.keys(dataSpecializationTypeMap).indexOf(field.dataSpecialization) > -1) { type = dataSpecializationTypeMap[field.dataSpecialization]; } else if (Object.keys(dataTypeToTypeMap).indexOf(field.dataType) > -1) { type = dataTypeToTypeMap[field.dataType]; } else if (field.inputType === 'TEXTAREA') { type = 'textarea'; } else if (field.options && Object.keys(inputTypeToTypeMap).indexOf(field.inputType) > -1 && !field.multiValue) { type = inputTypeToTypeMap[field.inputType]; } else if (field.options && Object.keys(inputTypeMultiToTypeMap).indexOf(field.inputType) > -1 && field.multiValue) { type = inputTypeMultiToTypeMap[field.inputType]; } else if (Object.keys(typeToTypeMap).indexOf(field.type) > -1) { type = typeToTypeMap[field.type]; } else if (Object.keys(numberDataTypeToTypeMap).indexOf(field.dataType) > -1) { type = numberDataTypeToTypeMap[field.dataType]; } /* else { throw new Error('FormUtils: This field type is unsupported.'); }*/ return type; } isFieldEncrypted(key) { return key.indexOf('customEncrypted') > -1; } getControlForField(field, http, config, overrides, forTable = false, fieldData) { // TODO: if field.type overrides `determineInputType` we should use it in that method or use this method // TODO: (cont.) as the setter of the field argument let type = this.determineInputType(field) || field.type; let control; const controlConfig = { metaType: field.type, type, key: field.name, label: field.label, placeholder: field.hint || '', required: field.required || field.systemRequired, hidden: !field.required, encrypted: this.isFieldEncrypted(field.name ? field.name.toString() : ''), value: field.value || field.defaultValue, sortOrder: field.sortOrder, associatedEntity: field.associatedEntity, optionsType: field.optionsType, multiple: field.multiValue, readOnly: !!field.disabled || !!field.readOnly, disabled: field.disabled, maxlength: field.maxLength, interactions: field.interactions, dataSpecialization: field.dataSpecialization, dataType: field.dataType, description: field.description || '', tooltip: field.tooltip, tooltipPosition: field.tooltipPosition, customControl: field.customControl, template: field.template, customControlConfig: field.customControlConfig, restrictFieldInteractions: field.restrictFieldInteractions, validators: field.validators, warning: field.warning, config: field.config || {}, closeOnSelect: field.closeOnSelect, layoutOptions: field.layoutOptions, }; this.inferDateRange(controlConfig, field); // TODO: getControlOptions should always return the correct format const optionsConfig = this.getControlOptions(field, http, config, fieldData); if (Array.isArray(optionsConfig) && !(type === 'chips' || type === 'picker')) { controlConfig.options = optionsConfig; } else if (Array.isArray(optionsConfig) && (type === 'chips' || type === 'picker')) { controlConfig.config = { options: optionsConfig, }; } else if (optionsConfig) { controlConfig.config = { ...optionsConfig, ...(controlConfig && controlConfig.config), }; } if (type === 'year') { controlConfig.maxlength = 4; } // TODO: Overrides should be an iterable of all properties (potentially a private method) let overrideResultsTemplate; let overridePreviewTemplate; if (overrides && overrides[field.name]) { if (overrides[field.name].resultsTemplate) { overrideResultsTemplate = overrides[field.name].resultsTemplate; controlConfig.config.resultsTemplate = overrideResultsTemplate; delete overrides[field.name].resultsTemplate; } if (overrides[field.name].overridePreviewTemplate) { overrideResultsTemplate = overrides[field.name].overridePreviewTemplate; controlConfig.config.overridePreviewTemplate = overrideResultsTemplate; delete overrides[field.name].overridePreviewTemplate; } if (overrides[field.name].pickerCallback) { controlConfig.config.callback = overrides[field.name].pickerCallback; } if (overrides[field.name].type) { type = overrides[field.name].type; } if (overrides[field.name].columns) { controlConfig.config.columns = overrides[field.name].columns; controlConfig.closeOnSelect = true; delete controlConfig.label; } if (overrides[field.name].warning) { controlConfig.warning = overrides[field.name].warning; } Object.assign(controlConfig, overrides[field.name]); } switch (type) { case 'entitychips': // TODO: This doesn't belong in this codebase controlConfig.multiple = true; controlConfig.config.resultsTemplate = overrideResultsTemplate || EntityPickerResults; controlConfig.config.previewTemplate = overridePreviewTemplate || EntityPickerResult; // TODO: When appendToBody picker works better in table/form control = new PickerControl(controlConfig); break; case 'chips': controlConfig.multiple = true; // TODO: When appendToBody picker works better in table/form control = new PickerControl(controlConfig); break; case 'entitypicker': // TODO: This doesn't belong in this codebase controlConfig.config.resultsTemplate = overrideResultsTemplate || EntityPickerResults; // TODO: When appendToBody picker works better in table/form control = new PickerControl(controlConfig); break; case 'picker': // TODO: When appendToBody picker works better in table/form control = new PickerControl(controlConfig); break; case 'datetime': controlConfig.military = config ? !!config.military : false; controlConfig.weekStart = config && config.weekStart ? config.weekStart : 0; control = new DateTimeControl(controlConfig); break; case 'date': controlConfig.dateFormat = field.dateFormat; controlConfig.textMaskEnabled = field.textMaskEnabled; controlConfig.allowInvalidDate = field.allowInvalidDate; controlConfig.military = config ? !!config.military : false; controlConfig.weekStart = config && config.weekStart ? config.weekStart : 0; control = new DateControl(controlConfig); break; case 'time': controlConfig.military = config ? !!config.military : false; control = new TimeControl(controlConfig); break; case 'native-time': case 'native-date': case 'native-week': case 'native-year': case 'native-datetime-local': case 'native-tel': case 'native-email': case 'native-url': case 'native-number': control = new CustomControl({ ...controlConfig, template: 'native-input', type: type.replace('native-', ''), alwaysActive: true }); break; case 'timezone': control = new TimezoneControl(controlConfig); break; case 'currency': case 'money': case 'email': case 'percentage': case 'float': case 'number': case 'year': // TODO: Only types from `determineInputType` should be used in this class if (type === 'money') { type = 'currency'; } controlConfig.type = type; control = new TextBoxControl(controlConfig); break; case 'text': control = new TextBoxControl(controlConfig); break; case 'textarea': control = new TextAreaControl(controlConfig); break; case 'editor': control = new EditorControl(controlConfig); break; case 'editor-minimal': control = new EditorControl(controlConfig); control.minimal = true; break; case 'tiles': control = new TilesControl(controlConfig); break; case 'checkbox': controlConfig.checkboxLabel = field.checkboxLabel; control = new CheckboxControl(controlConfig); break; case 'switch': control = new SwitchControl(controlConfig); break; case 'checklist': control = new CheckListControl(controlConfig); break; case 'radio': control = new RadioControl(controlConfig); break; case 'select': control = new SelectControl(controlConfig); break; case 'address': controlConfig.required = field.required || false; if (Helpers.isBlank(controlConfig.config)) { controlConfig.config = {}; } controlConfig.config.required = field.required; controlConfig.config.readOnly = controlConfig.readOnly; if (field.fields && field.fields.length) { for (const subfield of field.fields) { controlConfig.config[subfield.name] = { required: !!subfield.required, hidden: !!subfield.readOnly, }; if (!Helpers.isEmpty(subfield.label)) { controlConfig.config[subfield.name].label = subfield.label; } if (!Helpers.isEmpty(subfield.maxLength)) { controlConfig.config[subfield.name].maxlength = subfield.maxLength; } controlConfig.required = controlConfig.required || subfield.required; if (subfield.defaultValue) { if (Helpers.isBlank(controlConfig.value)) { controlConfig.value = {}; } controlConfig.value[subfield.name] = subfield.defaultValue; } else if (subfield.name === 'countryID') { if (Helpers.isBlank(controlConfig.value)) { controlConfig.value = {}; } controlConfig.value[subfield.name] = 1; } if (subfield.name === 'state' || subfield.name === 'countryID') { if (subfield.name === 'countryID') { subfield.optionsType = 'Country'; } if (!subfield.optionsUrl) { subfield.optionsUrl = `options/${subfield.optionsType}`; } controlConfig.config[subfield.name].pickerConfig = this.getControlOptions(subfield, http, config, fieldData); } } } controlConfig.isEmpty = this.isAddressEmpty; control = new AddressControl(controlConfig); break; case 'file': control = new FileControl(controlConfig); break; case 'custom': control = new CustomControl(controlConfig); break; default: control = new TextBoxControl(controlConfig); break; } return control; } shouldCreateControl(field) { if (field.systemRequired) { field.readOnly = false; } return (field.name !== 'id' && (!['SYSTEM', 'SECTION_HEADER'].includes(field.dataSpecialization) || ['address', 'billingAddress', 'secondaryAddress