123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- /* *********************************************************************************************************************
- * 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 ISimpleFieldMetaData {
- name: string; // The FormControl name
- source?: 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 | string[]; // CSS classes to apply
- id?: string; // CSS id to apply
- before?: string; // Ordering instruction - move before <name of another key in group>
- after?: string; // Ordering instruction - move after <name of another key in group>
- disabled?: boolean; // Whether field is initially disabled
- validators?: ValidatorFn[]; // Array of validator functions - following Angular FormControl API
- asyncValidators?: AsyncValidatorFn[]; // Array of async validator functions - following Angular FormControl API
- valFailureMsgs?: StringMap; // Validation failure messages - display appropriate message if validation fails
- onChange?: (val) => {}; // Function to call when field's value changes
- }
- interface IOption {
- label: string;
- value: string | number | boolean;
- }
- interface IOptionsFieldMetaData extends ISimpleFieldMetaData {
- 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 ILink {
- label: string;
- route: any[] | string;
- }
- interface IDropdownModifiedInputFieldMetaData extends ISimpleFieldMetaData {
- modifiers: string[];
- transform: ValueTransformer;
- }
- interface ITimePickerFieldMetaData extends ISimpleFieldMetaData {
- value: Date | string;
- 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())
- .replace(/([A-Z]) /g, '$1')
- // .replace(/ ([A-Z][a-z])/g, s => s.toLowerCase()) lowercase all but first word and acronyms
- ;
- // ---------------------------------------------------------------------------------------------------------------------
- // Form Field MetaData Models
- // ---------------------------------------------------------------------------------------------------------------------
- // Base Implementations
- abstract class SimpleField {
- type: string;
- name: string;
- source?: string;
- label?: string;
- value;
- default = '';
- placeholder = '';
- class?: string | string[];
- id?: string;
- disabled = false;
- validators: ValidatorFn[] = [];
- asyncValidators: AsyncValidatorFn[] = [];
- valFailureMsgs: StringMap = {};
- constructor(meta: ISimpleFieldMetaData) {
- if (meta.type === 'Multiline') {
- console.log(meta);
- }
- Object.assign(this, meta);
- if (!this.source) {
- // If source is not supplied it's the same as the name
- this.source = this.name;
- }
- if (typeof this.label === 'undefined') {
- // 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 implements IOption {
- // Can take a simple string value, a value-label pair [value, label],
- // or an Option of the form { label: string, value: string }
- label: string;
- value: string | number | boolean;
- 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: IOptionsFieldMetaData) {
- 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?: ILink;
- }
- class TextareaField extends SimpleField {
- type = 'Textarea';
- }
- class PasswordField extends SimpleField {
- type = 'Password';
- }
- class SelectField extends OptionsField {
- type = 'Select';
- link?: ILink;
- }
- 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;
- isChecked: boolean;
- checkedValue: boolean | string = true;
- rowLabel: null;
- }
- class DropdownModifiedInputField extends SimpleField {
- type = 'DropdownModifiedInput';
- modifiers: string[] = standardModifiers;
- transform: ValueTransformer = standardTransformer;
- constructor(meta: IDropdownModifiedInputFieldMetaData) {
- super(meta);
- }
- }
- class MultilineField extends SimpleField {
- type = 'Multiline';
- lines: number;
- maxLineLength: number;
- }
- // ---------------------------------------------------------------------------------------------------------------------
- // Concrete Implementations - Custom FormGroup Components (which render a group of FormControls)
- class CheckbuttonGroup {
- type = 'CheckbuttonGroup';
- name: string;
- label?: string;
- groupName: string;
- firstEnablesRest?: boolean;
- showAllOrNone?: boolean;
- meta: CheckbuttonField[] | { [key: string]: CheckbuttonField };
- constructor(groupmeta: any) {
- Object.assign(this, groupmeta);
- if (typeof this.label === 'undefined') {
- // 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 ISimpleFieldMetaData)] )
- .reduce((res, [key, cbf]) => { res[key as string] = cbf; return res; }, {});
- }
- }
- }
- // ---------------------------------------------------------------------------------------------------------------------
- // Concrete Implementations - Kendo Form Components
- class TimepickerField extends SimpleField {
- type = 'Timepicker';
- value: Date | string;
- format = 'hh:mm a';
- steps = { hour: 1, minute: 15 };
- constructor(meta) {
- super(meta);
- if (typeof this.value === 'string') {
- const [hh, mm, ss] = this.value.split(':');
- this.value = new Date(2000, 6, 1, +hh || 0, +mm || 0, +ss || 0);
- }
- if (!(this.value instanceof Date)) {
- this.value = new Date();
- }
- }
- }
- class ClrDatepickerField extends SimpleField {
- type = 'ClrDatepicker';
- value: Date = new Date();
- }
- // ---------------------------------------------------------------------------------------------------------------------
- // Containers
- class Container {
- type = 'Container';
- name: string;
- label = '';
- seed: StringMap;
- template?: TemplateRef<any>;
- button: string;
- focussed: boolean = true;
- meta: StringMap; // TODO: Tighten up on type with union type
- constructor(containerMeta: StringMap) {
- Object.assign(this, containerMeta);
- if (typeof this.label === 'undefined') {
- this.label = unCamelCase(this.name);
- }
- }
- }
- class RepeatingContainer {
- type = 'RepeatingContainer';
- name: string;
- prefix: string = 'group';
- label = '';
- seed: StringMap;
- template?: TemplateRef<any>;
- meta: Container[]; // An array of Containers
- minRepeat: number = 1;
- maxRepeat: number = 10;
- initialRepeat: number;
- showAddControl: boolean = true;
- showDeleteControl: boolean = true;
- primaryField: string = '';
- display: string = 'SINGLE'; // Display strategy to use - ALL or SINGLE - All at once, or one at a time (with a switcher)
- constructor(containerMeta: StringMap) {
- Object.assign(this, containerMeta);
- if (typeof this.label === 'undefined') {
- this.label = unCamelCase(this.name);
- }
- if (!containerMeta.initialRepeat) {
- this.initialRepeat = containerMeta.meta.length;
- }
- this.meta = containerMeta.meta.map((m, i) => new Container({
- name: `${this.prefix}${i+1}`,
- meta: m,
- button: unCamelCase(`${this.prefix}${i+1}`),
- focussed: this.display === 'SINGLE' ? i === 0 : true
- }));
- }
- }
- // ---------------------------------------------------------------------------------------------------------------------
- // Button Group
- interface IButtonInterface {
- label: string;
- fnId: string;
- class?: string;
- icon?: string;
- }
- class Button implements IButtonInterface {
- label;
- fnId;
- class = '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));
- }
- }
- // ---------------------------------------------------------------------------------------------------------------------
- // Heading
- class Heading {
- type = 'Heading';
- text = 'Missing Heading Text';
- level = 3;
- readonly noFormControls = true; // Indicates this has no FormControls associated with it
- readonly noLabel = true; // Indicates this has no label, so don't create normal form row
- constructor(meta) {
- Object.assign(this, meta);
- }
- }
- // ---------------------------------------------------------------------------------------------------------------------
- // Display
- class DisplayField {
- value: string;
- link?: ILink;
- readonly noFormControls = true; // Indicates this has no FormControls associated with it
- constructor(meta) {
- Object.assign(this, meta);
- }
- }
- // ---------------------------------------------------------------------------------------------------------------------
- // ---------------------------------------------------------------------------------------------------------------------
- // ---------------------------------------------------------------------------------------------------------------------
- // Exports
- export {
- SimpleField,
- TextField, TextareaField, PasswordField, SelectField, RadioField, HiddenField,
- CheckbuttonField, DropdownModifiedInputField, MultilineField,
- CheckbuttonGroup,
- TimepickerField, ClrDatepickerField,
- Container, RepeatingContainer,
- ButtonGroup, Heading, DisplayField
- };
|