import { FormBuilder, FormControl } from '@angular/forms'; import { reduce } from 'lodash/fp'; import * as fmdModels from './../models'; import { Container } from '@angular/compiler/src/i18n/i18n_ast'; /* * FORM UTILITIES * * Exports * ------- * autoMeta(model) - generate basic metadata from a raw or mapped model, recursively if necessary * combineExtraMeta(metadata, extraMeta) - combine extra metadata into metatdata, lazyly and recursively * buildModedMeta(metadata) - use field metadta models to fill out metadata * buildFormGroup(metadata) - builds FormGroups from modelled metdata, recursively if necessary * * Variable names * -------------- * metaF = metadata for Field * metaG = metadata for Group (possibly nested) * metaFoG = metadata for Field Or Group * */ const fb = new FormBuilder(); // --------------------------------------------------------------------------------------------------------------------- // Helper Funstions const addProp = (obj, key, val) => { obj[key] = val; return obj; }; // Is Group // Helper function to distinguish group from field const isGroup = metaFoG => metaFoG.meta; // Is Container // Helper function to distinguish container group (a group of child fields) const isContainer = metaFoG => isGroup(metaFoG) && (metaFoG.type === 'container' || typeof metaFoG.type === 'undefined'); // --------------------------------------------------------------------------------------------------------------------- // Add Missing Names // Helper function to add any missing 'name' properties to Fields and Groups using property's key, recursively // MAYBE NOT NEEDED? const addNameIfMissing = (metaFoG, key) => metaFoG.name ? metaFoG : addProp(metaFoG, 'name', key); const addNameToSelfAndChildren = ( [key, metaFoG] ) => { metaFoG = addNameIfMissing(metaFoG, key); if (isGroup(metaFoG)) { metaFoG.meta = addMissingNames(metaFoG.meta); // Recursion } return [key, metaFoG]; }; const addMissingNames = metaG => Object.entries(metaG) .map(addNameToSelfAndChildren) .reduce((res, [key, val]) => addProp(res, key, val), {}); // --------------------------------------------------------------------------------------------------------------------- // Raw Model (or Mapped Raw Model) to Automatic Metadata const isScalar = val => typeof val === 'boolean' || typeof val === 'number' || typeof val === 'string'; const keyValPairToMeta = (val, key) => ({ name: key, [isScalar(val) ? 'value' : 'meta']: val }); const keyValPairToMetaRecursive = ( [key, val] ) => { const innerVal = isScalar(val) ? val : autoMeta(val); const metaVal = keyValPairToMeta(innerVal, key); return [key, metaVal]; }; const autoMeta = model => Object.entries(model) .map(keyValPairToMetaRecursive) .reduce((res, [key, val]) => addProp(res, key, val), {}); // --------------------------------------------------------------------------------------------------------------------- // Combine automatically generated metadata with overrides const combineMetaForField = (metaF, metaFextra) => Object.assign(metaF, metaFextra); const combineExtraMeta = (metaG, metaExtra) => { const combinedMeta = {}; Object.entries(metaExtra).forEach(([key, val]) => { if (typeof metaG[key] === 'object') { combinedMeta[key] = (val).meta ? combineMetaForField(metaG[key], { meta: combineExtraMeta(metaG[key].meta, (val).meta) }) : combineMetaForField(metaG[key], val); } }); return { ...metaG, ...combinedMeta }; }; // --------------------------------------------------------------------------------------------------------------------- // Build Modelled Metadata - Form Metadata Factory const buildClassName = (t = 'text') => { const start = t[0].toUpperCase() + t.slice(1); if (start === 'Container' || t.slice(-5) === 'Group') { return start; } return start + 'Field'; }; const buildModeledField = metaFoG => { const type = isContainer(metaFoG) ? 'container' : metaFoG.type; const className = buildClassName(type); if (!fmdModels[className]) { throw new Error(`No metadata model "${className}" for type "${type}"`); } return new fmdModels[className](metaFoG); }; // Build Form Group Member const buildModeledGroupMember = metaFoG => { const modeledGroupMember = buildModeledField(metaFoG); if (isContainer(metaFoG)) { modeledGroupMember.meta = _buildModeledGroup(metaFoG.meta); } return modeledGroupMember; }; // Build Form Group const buildModeledGroupReducerIteree = (res, metaFoG) => Object.assign(res, { [metaFoG.name]: buildModeledGroupMember(metaFoG) }); const _buildModeledGroup = metaG => reduce(buildModeledGroupReducerIteree, {}, metaG); const buildModeledMeta = metaG => _buildModeledGroup(metaG); // --------------------------------------------------------------------------------------------------------------------- // Functions which build FormControls and FormGroups // Build Form Control // TODO: Flesh out function to build validators const buildControlState = metaF => ({ value: metaF.value || metaF.default, disabled: metaF.isDisabled }); const buildValidators = metaF => ({ validators: null, asyncValidators: null, updateOn: 'blur' }); const buildFormControl = metaF => new FormControl(buildControlState(metaF) /*, buildValidators(metaF) */); // Build Form Group Member const buildFormGroupMember = metaFoG => isGroup(metaFoG) ? _buildFormGroup(metaFoG.meta) : buildFormControl(metaFoG); // Build Form Group const buildFormGroupReducerIteree = (res, metaFoG) => Object.assign(res, { [metaFoG.name]: buildFormGroupMember(metaFoG) }); const _buildFormGroup = metaG => fb.group(reduce(buildFormGroupReducerIteree, {}, metaG)); const buildFormGroup = metaG => _buildFormGroup(addMissingNames(metaG)); // --------------------------------------------------------------------------------------------------------------------- // Exports export { autoMeta, combineExtraMeta, buildModeledMeta, buildFormGroup };