novo-elements
Version:
1,250 lines (1,222 loc) • 2.06 MB
JavaScript
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