index.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import { FormBuilder, FormControl } from '@angular/forms';
  2. import { reduce } from 'lodash/fp';
  3. import * as fmdModels from './../models';
  4. import { Container } from '@angular/compiler/src/i18n/i18n_ast';
  5. /*
  6. * FORM UTILITIES
  7. *
  8. * Exports
  9. * -------
  10. * autoMeta(model) - generate basic metadata from a raw or mapped model, recursively if necessary
  11. * combineExtraMeta(metadata, extraMeta) - combine extra metadata into metatdata, lazyly and recursively
  12. * buildModedMeta(metadata) - use field metadta models to fill out metadata
  13. * buildFormGroup(metadata) - builds FormGroups from modelled metdata, recursively if necessary
  14. *
  15. * Variable names
  16. * --------------
  17. * metaF = metadata for Field
  18. * metaG = metadata for Group (possibly nested)
  19. * metaFoG = metadata for Field Or Group
  20. *
  21. */
  22. const fb = new FormBuilder();
  23. // ---------------------------------------------------------------------------------------------------------------------
  24. // Helper Funstions
  25. const addProp = (obj, key, val) => { obj[key] = val; return obj; };
  26. // Is Group
  27. // Helper function to distinguish group from field
  28. const isGroup = metaFoG => metaFoG.meta;
  29. // Is Container
  30. // Helper function to distinguish container group (a group of child fields)
  31. const isContainer = metaFoG => isGroup(metaFoG) && (metaFoG.type === 'container' || typeof metaFoG.type === 'undefined');
  32. // ---------------------------------------------------------------------------------------------------------------------
  33. // Add Missing Names
  34. // Helper function to add any missing 'name' properties to Fields and Groups using property's key, recursively
  35. // MAYBE NOT NEEDED?
  36. const addNameIfMissing = (metaFoG, key) => metaFoG.name ? metaFoG : addProp(metaFoG, 'name', key);
  37. const addNameToSelfAndChildren = ( [key, metaFoG] ) => {
  38. metaFoG = addNameIfMissing(metaFoG, key);
  39. if (isGroup(metaFoG)) {
  40. metaFoG.meta = addMissingNames(metaFoG.meta); // Recursion
  41. }
  42. return [key, metaFoG];
  43. };
  44. const addMissingNames = metaG => Object.entries(metaG)
  45. .map(addNameToSelfAndChildren)
  46. .reduce((res, [key, val]) => addProp(res, key, val), {});
  47. // ---------------------------------------------------------------------------------------------------------------------
  48. // Raw Model (or Mapped Raw Model) to Automatic Metadata
  49. const isScalar = val => typeof val === 'boolean' || typeof val === 'number' || typeof val === 'string';
  50. const keyValPairToMeta = (val, key) => ({ name: key, [isScalar(val) ? 'value' : 'meta']: val });
  51. const keyValPairToMetaRecursive = ( [key, val] ) => {
  52. const innerVal = isScalar(val) ? val : autoMeta(val);
  53. const metaVal = keyValPairToMeta(innerVal, key);
  54. return [key, metaVal];
  55. };
  56. const autoMeta = model => Object.entries(model)
  57. .map(keyValPairToMetaRecursive)
  58. .reduce((res, [key, val]) => addProp(res, key, val), {});
  59. // ---------------------------------------------------------------------------------------------------------------------
  60. // Combine automatically generated metadata with overrides
  61. const combineMetaForField = (metaF, metaFextra) => Object.assign(metaF, metaFextra);
  62. const combineExtraMeta = (metaG, metaExtra) => {
  63. const combinedMeta = {};
  64. Object.entries(metaExtra).forEach(([key, val]) => {
  65. if (typeof metaG[key] === 'object') {
  66. combinedMeta[key] = (<any>val).meta ?
  67. combineMetaForField(metaG[key], { meta: combineExtraMeta(metaG[key].meta, (<any>val).meta) }) :
  68. combineMetaForField(metaG[key], val);
  69. }
  70. });
  71. return { ...metaG, ...combinedMeta };
  72. };
  73. // ---------------------------------------------------------------------------------------------------------------------
  74. // Build Modelled Metadata - Form Metadata Factory
  75. const buildClassName = (t = 'text') => {
  76. const start = t[0].toUpperCase() + t.slice(1);
  77. if (start === 'Container' || t.slice(-5) === 'Group') {
  78. return start;
  79. }
  80. return start + 'Field';
  81. };
  82. const buildModeledField = metaFoG => {
  83. const type = isContainer(metaFoG) ? 'container' : metaFoG.type;
  84. const className = buildClassName(type);
  85. if (!fmdModels[className]) {
  86. throw new Error(`No metadata model "${className}" for type "${type}"`);
  87. }
  88. return new fmdModels[className](metaFoG);
  89. };
  90. // Build Form Group Member
  91. const buildModeledGroupMember = metaFoG => {
  92. const modeledGroupMember = buildModeledField(metaFoG);
  93. if (isContainer(metaFoG)) {
  94. modeledGroupMember.meta = _buildModeledGroup(metaFoG.meta);
  95. }
  96. return modeledGroupMember;
  97. };
  98. // Build Form Group
  99. const buildModeledGroupReducerIteree = (res, metaFoG) => Object.assign(res, { [metaFoG.name]: buildModeledGroupMember(metaFoG) });
  100. const _buildModeledGroup = metaG => reduce(buildModeledGroupReducerIteree, {}, metaG);
  101. const buildModeledMeta = metaG => _buildModeledGroup(metaG);
  102. // ---------------------------------------------------------------------------------------------------------------------
  103. // Functions which build FormControls and FormGroups
  104. // Build Form Control
  105. // TODO: Flesh out function to build validators
  106. const buildControlState = metaF => ({ value: metaF.value || metaF.default, disabled: metaF.isDisabled });
  107. const buildValidators = metaF => ({
  108. validators: null,
  109. asyncValidators: null,
  110. updateOn: 'blur'
  111. });
  112. const buildFormControl = metaF => new FormControl(buildControlState(metaF) /*, buildValidators(metaF) */);
  113. // Build Form Group Member
  114. const buildFormGroupMember = metaFoG => isGroup(metaFoG) ? _buildFormGroup(metaFoG.meta) : buildFormControl(metaFoG);
  115. // Build Form Group
  116. const buildFormGroupReducerIteree = (res, metaFoG) => Object.assign(res, { [metaFoG.name]: buildFormGroupMember(metaFoG) });
  117. const _buildFormGroup = metaG => fb.group(reduce(buildFormGroupReducerIteree, {}, metaG));
  118. const buildFormGroup = metaG => _buildFormGroup(addMissingNames(metaG));
  119. // ---------------------------------------------------------------------------------------------------------------------
  120. // Exports
  121. export { autoMeta, combineExtraMeta, buildModeledMeta, buildFormGroup };