field.model.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. /* *********************************************************************************************************************
  2. * MetaData models for Form Fields
  3. * -------------------------------
  4. * Keep in one file for now, but maybe split if this grows too large
  5. ******************************************************************************************************************** */
  6. import { TemplateRef } from '@angular/core';
  7. import { ValidatorFn, AsyncValidatorFn } from '@angular/forms';
  8. import { ValueTransformer } from './../interfaces';
  9. import { standardModifiers, standardTransformer } from './../utils';
  10. interface ISimpleFieldMetaData {
  11. name: string; // The FormControl name
  12. source?: string; // Location in API-returned model - defaults to name
  13. type?: string; // The component type e.g. BasicInput, Checkbutton, Timepicker, etc
  14. label?: string; // The field label - defaults to unCamelCased name if not supplied
  15. value?: any; // The field value - defaults to empty string if not supplied
  16. default?: any; // Default value
  17. placeholder?: string; // Optional placeholder text
  18. class?: string | string[]; // CSS classes to apply
  19. id?: string; // CSS id to apply
  20. before?: string; // Ordering instruction - move before <name of another key in group>
  21. after?: string; // Ordering instruction - move after <name of another key in group>
  22. disabled?: boolean; // Whether field is initially disabled
  23. validators?: ValidatorFn[]; // Array of validator functions - following Angular FormControl API
  24. asyncValidators?: AsyncValidatorFn[]; // Array of async validator functions - following Angular FormControl API
  25. valFailureMsgs?: StringMap; // Validation failure messages - display appropriate message if validation fails
  26. onChange?: (val) => {}; // Function to call when field's value changes
  27. }
  28. interface IOption {
  29. label: string;
  30. value: string | number | boolean;
  31. }
  32. interface IOptionsFieldMetaData extends ISimpleFieldMetaData {
  33. options; // Array of Options - for select, radio-button-group and other 'multiple-choice' types
  34. horizontal?: boolean; // Whether to arrang radio buttons or checkboxes horizontally (default false)
  35. }
  36. // For components that include links to other pages
  37. interface ILink {
  38. label: string;
  39. route: any[] | string;
  40. }
  41. interface IDropdownModifiedInputFieldMetaData extends ISimpleFieldMetaData {
  42. modifiers: string[];
  43. transform: ValueTransformer;
  44. }
  45. interface ITimePickerFieldMetaData extends ISimpleFieldMetaData {
  46. value: Date | string;
  47. format: string;
  48. steps: StringMap;
  49. }
  50. // Utility to unCamelCase
  51. const unCamelCase = (str: string): string => str.replace(/([A-Z])/g, ' $1')
  52. .replace(/(\w)(\d)/g, '$1 $2')
  53. .replace(/^./, s => s.toUpperCase())
  54. .replace(/([A-Z]) /g, '$1')
  55. // .replace(/ ([A-Z][a-z])/g, s => s.toLowerCase()) lowercase all but first word and acronyms
  56. ;
  57. // ---------------------------------------------------------------------------------------------------------------------
  58. // Form Field MetaData Models
  59. // ---------------------------------------------------------------------------------------------------------------------
  60. // Base Implementations
  61. abstract class SimpleField {
  62. type: string;
  63. name: string;
  64. source?: string;
  65. label?: string;
  66. value;
  67. default = '';
  68. placeholder = '';
  69. class?: string | string[];
  70. id?: string;
  71. disabled = false;
  72. validators: ValidatorFn[] = [];
  73. asyncValidators: AsyncValidatorFn[] = [];
  74. valFailureMsgs: StringMap = {};
  75. constructor(meta: ISimpleFieldMetaData) {
  76. if (meta.type === 'Multiline') {
  77. console.log(meta);
  78. }
  79. Object.assign(this, meta);
  80. if (!this.source) {
  81. // If source is not supplied it's the same as the name
  82. this.source = this.name;
  83. }
  84. if (typeof this.label === 'undefined') {
  85. // If label is not supplied set it to the unCamelCased'n'Spaced name
  86. // e.g. supervisorCardNumber --> Supervisor Card Number
  87. this.label = unCamelCase(this.name);
  88. }
  89. }
  90. }
  91. class Option implements IOption {
  92. // Can take a simple string value, a value-label pair [value, label],
  93. // or an Option of the form { label: string, value: string }
  94. label: string;
  95. value: string | number | boolean;
  96. constructor(opt: string | string[] | Option) {
  97. if (typeof opt === 'object') {
  98. if (Array.isArray(opt)) {
  99. this.label = opt[1];
  100. this.value = opt[0];
  101. } else {
  102. this.label = opt.label;
  103. this.value = opt.value;
  104. }
  105. } else if (typeof opt === 'string') {
  106. this.label = opt;
  107. this.value = opt;
  108. }
  109. }
  110. }
  111. abstract class OptionsField extends SimpleField {
  112. options: Option[] = [];
  113. constructor(meta: IOptionsFieldMetaData) {
  114. super(meta);
  115. if (Array.isArray(meta.options)) {
  116. this.options = meta.options.reduce((acc, opt) => { acc.push(new Option(opt)); return acc; }, []);
  117. } else {
  118. this.options = [
  119. new Option({ label: 'Yes', value: true }),
  120. new Option({ label: 'No', value: false })
  121. ];
  122. }
  123. }
  124. }
  125. // ---------------------------------------------------------------------------------------------------------------------
  126. // Concrete Implementations - Native Form Components
  127. class TextField extends SimpleField {
  128. type = 'Text';
  129. link?: ILink;
  130. }
  131. class TextareaField extends SimpleField {
  132. type = 'Textarea';
  133. }
  134. class PasswordField extends SimpleField {
  135. type = 'Password';
  136. }
  137. class SelectField extends OptionsField {
  138. type = 'Select';
  139. link?: ILink;
  140. }
  141. class RadioField extends OptionsField {
  142. type = 'Radio';
  143. }
  144. class HiddenField extends SimpleField {
  145. type = 'Hidden';
  146. }
  147. // ---------------------------------------------------------------------------------------------------------------------
  148. // Concrete Implementations - Custom Form Components
  149. class CheckbuttonField extends SimpleField {
  150. type = 'Checkbutton';
  151. default: any = false;
  152. isChecked: boolean;
  153. checkedValue: boolean | string = true;
  154. rowLabel: null;
  155. }
  156. class DropdownModifiedInputField extends SimpleField {
  157. type = 'DropdownModifiedInput';
  158. modifiers: string[] = standardModifiers;
  159. transform: ValueTransformer = standardTransformer;
  160. constructor(meta: IDropdownModifiedInputFieldMetaData) {
  161. super(meta);
  162. }
  163. }
  164. class MultilineField extends SimpleField {
  165. type = 'Multiline';
  166. lines: number;
  167. maxLineLength: number;
  168. }
  169. // ---------------------------------------------------------------------------------------------------------------------
  170. // Concrete Implementations - Custom FormGroup Components (which render a group of FormControls)
  171. class CheckbuttonGroup {
  172. type = 'CheckbuttonGroup';
  173. name: string;
  174. label?: string;
  175. groupName: string;
  176. firstEnablesRest?: boolean;
  177. showAllOrNone?: boolean;
  178. meta: CheckbuttonField[] | { [key: string]: CheckbuttonField };
  179. constructor(groupmeta: any) {
  180. Object.assign(this, groupmeta);
  181. if (typeof this.label === 'undefined') {
  182. // If label is not supplied set it to the unCamelCased'n'Spaced name
  183. // e.g. supervisorCardNumber --> Supervisor Card Number
  184. this.label = unCamelCase(this.name);
  185. }
  186. // Can render as a FormArray or FormGroup depending on input data
  187. if (Array.isArray(groupmeta.meta)) {
  188. const arrayMembers = groupmeta.meta;
  189. this.meta = arrayMembers.map(cb => new CheckbuttonField(cb));
  190. } else {
  191. const groupMembers = groupmeta.meta;
  192. this.meta = Object.entries(groupMembers)
  193. .map( ([key, cb]) => [key, new CheckbuttonField(cb as ISimpleFieldMetaData)] )
  194. .reduce((res, [key, cbf]) => { res[key as string] = cbf; return res; }, {});
  195. }
  196. }
  197. }
  198. // ---------------------------------------------------------------------------------------------------------------------
  199. // Concrete Implementations - Kendo Form Components
  200. class TimepickerField extends SimpleField {
  201. type = 'Timepicker';
  202. value: Date | string;
  203. format = 'hh:mm a';
  204. steps = { hour: 1, minute: 15 };
  205. constructor(meta) {
  206. super(meta);
  207. if (typeof this.value === 'string') {
  208. const [hh, mm, ss] = this.value.split(':');
  209. this.value = new Date(2000, 6, 1, +hh || 0, +mm || 0, +ss || 0);
  210. }
  211. if (!(this.value instanceof Date)) {
  212. this.value = new Date();
  213. }
  214. }
  215. }
  216. class ClrDatepickerField extends SimpleField {
  217. type = 'ClrDatepicker';
  218. value: Date = new Date();
  219. }
  220. // ---------------------------------------------------------------------------------------------------------------------
  221. // Containers
  222. class Container {
  223. type = 'Container';
  224. name: string;
  225. label = '';
  226. seed: StringMap;
  227. template?: TemplateRef<any>;
  228. button: string;
  229. focussed: boolean = true;
  230. meta: StringMap; // TODO: Tighten up on type with union type
  231. constructor(containerMeta: StringMap) {
  232. Object.assign(this, containerMeta);
  233. if (typeof this.label === 'undefined') {
  234. this.label = unCamelCase(this.name);
  235. }
  236. }
  237. }
  238. class RepeatingContainer {
  239. type = 'RepeatingContainer';
  240. name: string;
  241. prefix: string = 'group';
  242. label = '';
  243. seed: StringMap;
  244. template?: TemplateRef<any>;
  245. meta: Container[]; // An array of Containers
  246. minRepeat: number = 1;
  247. maxRepeat: number = 10;
  248. initialRepeat: number;
  249. showAddControl: boolean = true;
  250. showDeleteControl: boolean = true;
  251. primaryField: string = '';
  252. display: string = 'SINGLE'; // Display strategy to use - ALL or SINGLE - All at once, or one at a time (with a switcher)
  253. constructor(containerMeta: StringMap) {
  254. Object.assign(this, containerMeta);
  255. if (typeof this.label === 'undefined') {
  256. this.label = unCamelCase(this.name);
  257. }
  258. if (!containerMeta.initialRepeat) {
  259. this.initialRepeat = containerMeta.meta.length;
  260. }
  261. this.meta = containerMeta.meta.map((m, i) => new Container({
  262. name: `${this.prefix}${i+1}`,
  263. meta: m,
  264. button: unCamelCase(`${this.prefix}${i+1}`),
  265. focussed: this.display === 'SINGLE' ? i === 0 : true
  266. }));
  267. }
  268. }
  269. // ---------------------------------------------------------------------------------------------------------------------
  270. // Button Group
  271. interface IButtonInterface {
  272. label: string;
  273. fnId: string;
  274. class?: string;
  275. icon?: string;
  276. }
  277. class Button implements IButtonInterface {
  278. label;
  279. fnId;
  280. class = 'btn-primary';
  281. constructor(buttonProps) {
  282. Object.assign(this, buttonProps);
  283. }
  284. }
  285. class ButtonGroup {
  286. type = 'ButtonGroup';
  287. name: string;
  288. label = '';
  289. meta: Button[];
  290. readonly noFormControls = true; // Indicates this has no FormControls associated with it
  291. constructor(meta) {
  292. Object.assign(this, meta);
  293. this.meta = this.meta.map(b => b instanceof Button ? b : new Button(b));
  294. }
  295. }
  296. // ---------------------------------------------------------------------------------------------------------------------
  297. // Heading
  298. class Heading {
  299. type = 'Heading';
  300. text = 'Missing Heading Text';
  301. level = 3;
  302. readonly noFormControls = true; // Indicates this has no FormControls associated with it
  303. readonly noLabel = true; // Indicates this has no label, so don't create normal form row
  304. constructor(meta) {
  305. Object.assign(this, meta);
  306. }
  307. }
  308. // ---------------------------------------------------------------------------------------------------------------------
  309. // Display
  310. class DisplayField {
  311. value: string;
  312. link?: ILink;
  313. readonly noFormControls = true; // Indicates this has no FormControls associated with it
  314. constructor(meta) {
  315. Object.assign(this, meta);
  316. }
  317. }
  318. // ---------------------------------------------------------------------------------------------------------------------
  319. // ---------------------------------------------------------------------------------------------------------------------
  320. // ---------------------------------------------------------------------------------------------------------------------
  321. // Exports
  322. export {
  323. SimpleField,
  324. TextField, TextareaField, PasswordField, SelectField, RadioField, HiddenField,
  325. CheckbuttonField, DropdownModifiedInputField, MultilineField,
  326. CheckbuttonGroup,
  327. TimepickerField, ClrDatepickerField,
  328. Container, RepeatingContainer,
  329. ButtonGroup, Heading, DisplayField
  330. };