/* ********************************************************************************************************************* * MetaData models for Form Fields * ------------------------------- * Keep in one file for now, but maybe split if this grows too large ******************************************************************************************************************** */ import { TemplateRef } from '@angular/core'; import { ValidatorFn, AsyncValidatorFn } from '@angular/forms'; import { ValueTransformer } from './../interfaces'; import { standardModifiers, standardTransformer } from './../utils'; interface SimpleFieldMetaData { name: string; // The FormControl name origin?: string; // Location in API-returned model - defaults to name type?: string; // The component type e.g. BasicInput, Checkbutton, Timepicker, etc label?: string; // The field label - defaults to unCamelCased name if not supplied value?: any; // The field value - defaults to empty string if not supplied default?: any; // Default value placeholder?: string; // Optional placeholder text class?: string | Array; // CSS classes to apply id?: string; // CSS id to apply isDisabled?: boolean; // Whether field is initially disabled validators?: Array; // Array of validator functions - following Angular FormControl API asyncValidators?: Array; // Array of async validator functions - following Angular FormControl API valFailureMsgs?: StringMap; // Validation failure messages - display appropriate message if validation fails } interface Option { label: string; value: string | number | boolean; } interface OptionsFieldMetaData extends SimpleFieldMetaData { options; // Array of Options - for select, radio-button-group and other 'multiple-choice' types horizontal?: boolean; // Whether to arrang radio buttons or checkboxes horizontally (default false) } // For components that include links to other pages interface Link { label: string; route: any[] | string; } interface DropdownModifiedInputFieldMetaData extends SimpleFieldMetaData { modifiers: string[]; transform: ValueTransformer; } interface TimePickerFieldMetaData extends SimpleFieldMetaData { // TODO: Tighhten up on types value: Date; format: string; steps: StringMap; } // Utility to unCamelCase const unCamelCase = (str: string): string => str.replace(/([A-Z])/g, ' $1') .replace(/(\w)(\d)/g, '$1 $2') .replace(/^./, s => s.toUpperCase()); // --------------------------------------------------------------------------------------------------------------------- // Form Field MetaData Models // --------------------------------------------------------------------------------------------------------------------- // Base Implementations abstract class SimpleField { type: string; name: string; origin?: string; label?: string; value; default = ''; placeholder = ''; class?: string | Array; id?: string; isDisabled = false; validators: Array = []; asyncValidators: Array = []; valFailureMsgs: StringMap = {}; constructor(meta: SimpleFieldMetaData) { Object.assign(this, meta); if (!this.origin) { // If origin is not supplied it's the same as the name this.origin = this.name; } if (!this.label) { // If label is not supplied set it to the unCamelCased'n'Spaced name // e.g. supervisorCardNumber --> Supervisor Card Number this.label = unCamelCase(this.name); } } } class Option { // Can take a simple string value, a value-label pair [value, label], // or an Option of the form { label: string, value: string } constructor(opt: string | string[] | Option) { if (typeof opt === 'object') { if (Array.isArray(opt)) { this.label = opt[1]; this.value = opt[0]; } else { this.label = opt.label; this.value = opt.value; } } else if (typeof opt === 'string') { this.label = opt; this.value = opt; } } } abstract class OptionsField extends SimpleField { options: Option[] = []; constructor(meta: OptionsFieldMetaData) { super(meta); if (Array.isArray(meta.options)) { this.options = meta.options.reduce((acc, opt) => { acc.push(new Option(opt)); return acc; }, []); } else { this.options = [ new Option({ label: 'Yes', value: true }), new Option({ label: 'No', value: false }) ]; } } } // --------------------------------------------------------------------------------------------------------------------- // Concrete Implementations - Native Form Components class TextField extends SimpleField { type = 'Text'; link?: Link; } class TextareaField extends SimpleField { type = 'Textarea'; } class PasswordField extends SimpleField { type = 'Password'; } class SelectField extends OptionsField { type = 'Select'; link?: Link; } class RadioField extends OptionsField { type = 'Radio'; } class HiddenField extends SimpleField { type = 'Hidden'; } // --------------------------------------------------------------------------------------------------------------------- // Concrete Implementations - Custom Form Components class CheckbuttonField extends SimpleField { type = 'Checkbutton'; default: any = false; } class DropdownModifiedInputField extends SimpleField { type = 'DropdownModifiedInput'; modifiers: string[] = standardModifiers; transform: ValueTransformer = standardTransformer; constructor(meta: DropdownModifiedInputFieldMetaData) { super(meta); } } // --------------------------------------------------------------------------------------------------------------------- // Concrete Implementations - Custom FormGroup Components (which render a group of FormControls) class CheckbuttonGroup { type = 'CheckbuttonGroup'; name: string; label?: string; groupName: string; firstEnablesRest?; showAllOrNone?; meta: CheckbuttonField[] | { [key: string]: CheckbuttonField }; constructor(groupmeta: any) { Object.assign(this, groupmeta); if (!this.label) { // If label is not supplied set it to the unCamelCased'n'Spaced name // e.g. supervisorCardNumber --> Supervisor Card Number this.label = unCamelCase(this.name); } // Can render as a FormArray or FormGroup depending on input data if (Array.isArray(groupmeta.meta)) { const arrayMembers = groupmeta.meta; this.meta = arrayMembers.map(cb => new CheckbuttonField(cb)); } else { const groupMembers = groupmeta.meta; this.meta = Object.entries(groupMembers) .map( ([key, cb]) => [key, new CheckbuttonField(cb as SimpleFieldMetaData)] ) .reduce((res, [key, cbf]) => { res[key] = cbf; return res; }, {}); } } } // --------------------------------------------------------------------------------------------------------------------- // Concrete Implementations - Kendo Form Components class TimepickerField extends SimpleField { type = 'Timepicker'; value: Date = new Date(); format = 'hh:mm a'; steps = { hour: 1, minute: 15 }; } class DatepickerField extends SimpleField { type = 'Datepicker'; value: Date = new Date(); } // --------------------------------------------------------------------------------------------------------------------- // Container class Container { type = 'Container'; name: string; label = ''; template?: TemplateRef; meta: StringMap; // TODO: Tighten up on type with union type constructor(meta: StringMap) { console.log(meta); meta.meta ? Object.assign(this, meta) : this.meta = meta; if (!this.label) { this.label = unCamelCase(this.name); } } } // --------------------------------------------------------------------------------------------------------------------- // Button Group interface ButtonInterface { label: string; fnId: string; class?: string; icon?: string; } class Button implements ButtonInterface { label; fnId; class: string = 'btn-primary'; constructor(buttonProps) { Object.assign(this, buttonProps); } } class ButtonGroup { type = 'ButtonGroup'; name: string; label = ''; meta: Button[]; readonly noFormControls = true; // Indicates this has no FormControls associated with it constructor(meta) { Object.assign(this, meta); this.meta = this.meta.map(b => b instanceof Button ? b : new Button(b)); } } // --------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------- // Exports export { SimpleField, TextField, TextareaField, PasswordField, SelectField, RadioField, HiddenField, CheckbuttonField, DropdownModifiedInputField, CheckbuttonGroup, TimepickerField, DatepickerField, Container, ButtonGroup };