# NgDynaform ![GearWithWrench](/Users/rk/play/ng-dynaform-clarity/src/assets/docs/GearWithWrench.png) **A Dynamic Form Builder for Angular, with built in model mapper.** - Define forms with metadata, or build directly from models - Combine metadata with models to populate form values - Group related fields in containers, to any level of nesting - Use repeating containers to model complex arrays - Take advantage of terse metadata, and sensible defaults ## Basic Usage Import the Dynaform module. ```typescript import { NgModule } from '@angular/core'; ... import { DynaformModule } from './dynaform/dynaform.module'; ... @NgModule({ imports: [ ... DynaformModule ... ] ... }) export class AppModule { } ``` In any component where you want to generate a form, import and then inject `DynaformService`. ```typescript import { Component } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { DynaformService } from './dynaform'; // Paths may vary @Component({ selector: 'some-formy-thing', templateUrl: './some-formy-thing.component.html', styleUrls: ['./some-formy-thing.component.scss'] }) export class SomeFormyThingComponent implements OnInit { form: FormGroup; meta: StringMap; constructor(private dynaform: DynaformService) { } ``` Build the form from your model, your metadata, or a combination of the two. ```typescript ngOnInit() { const model = { firstName: 'Olly', lastName: 'October', country: 'France' }; const meta = { country: { type: 'select', options: ['France', 'Germany', 'Norway', 'Sweden'] } } // Build FormGroup and Modeled Metadata const { form, modeledMeta } = this.dynaform.build(model, meta); } ``` ### Build strategies Dynaform supports two build strategies: **MODELFISRT** and **METAFIRST** (the default) MODELFIRST generates a form from the supplied model, and by default will ignore any metadata points that don't occur in the model. If you supply no metadata all fields will render as text inputs. METAFIRST generates a form from the supplied metadata, and by default will ignore any data in the model that doesn't have a corresponding metadata entry. You can set the build strategy by calling `setBuildStrategy` ```typescript this.dynaform.setBuildStrategy('MODELFIRST'); ``` The behaviour of the MODELFIRST build strategy can be further modified by supplying `true` as the third parameter to `dynaform.build`, where true means "create extra fields from metadata, even when they don't occur ion the model". It's useful when the model might be incomplete. ```typescript // Build FormGroup and Modeled Metadata, creating fields for any points mentioned in // the metadata that don't occur in the model const { form, modeledMeta } = this.dynaform.build(model, meta, true); ``` ### Callbacks - Callbacks allow events inside a form to trigger functions in the host component. - Callbacks can be attached to buttons, and to the `change` handler of any field. - Callbacks are identified by a string, which maps them to a function in the host component. ```typescript // Register callbacks (probably in ngOnInit) this.dynaform.register({ 'SAYHELLO': this.sayHello, 'SAYCHEESE': this.sayCheese }, this); ``` *Then later in the component*... ```typescript handleCallback(fnId) { this.dynaform.call(fnId); } sayHello() { alert('HELLO'); } sayCheese() { alert('CHEESE'); } ``` ## Field metadata ### Common properties **All fields support the following metadata**. The missing properties will be 'filled in' by dynaform's field-specific models, allowing forms to specifiued tersely. | Property | Description | Default / *Notes* | | -------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | `name` | The FormControl name | **Do not set.**
Derived automatically from the key of the metadata object. | | `type` | The control's type | Text | | `label` | The controls's label | *An unCamelCased and spaced version of name* | | `value` | The control's initial value | *Empty string. May be set from model, overriding the default* | | `checkedValue` | Value when checked* | *Checkboxes / Checkbuttons only* | | `default` | The control's default value | | | `placeholder` | Optional placeholder text | | | `class` | CSS class(es) to apply | *Single class (string) or array of classes (string[])* | | `id` | CSS id to apply | | | `disabled` | Whether field is disbled | false | | `change` | Name of function in host component to call when value changes | | | `source` | Location of data in model | *Defaults to the same name and path. Used by model-mapper.* | | `before` | Ordering instruction - move before *name of another control in group* | | | `after` | Ordering instruction - move after *name of another control in group* | | | `validators` | Array of validator functions - following Angular FormControl API | [] | | `asyncValidators` | Array of async validator functions - following Angular FormControl API | [] | | `valFailureMessages` | Validation failure messages - used to display appropriate message if validation fails | | #### Available types - Text - Textarea - Select - Radio - Checkbox - Checkbutton - Password - Hidden - Checkbox - CheckboxGroup - Datepicker - Timepicker - DropdownModifiedInput - Multiline ### 'Option' field metadata *(for selects, radios, checkbutton groups, etc)* **Options can be specified as an array of values, an array of [value, label] pairs, or an array of objects with the structure { value: 'VAL', label: 'My label' }.** If you supply a a simple array of values, each value is used as both the value and label. #### Examples ```typescript { firstName: {}, lastName: {}, telephoneNumber: {}, country: { type: 'select', options: ['France', 'Germany', 'Italy', 'Norway'] } } ``` ```typescript { cheeseLover: { type: 'checkbox', label: 'Do you like cheese?' }, favouriteCheese: { type: 'radio', label: 'What`s your favourite?', options: [ ['CHED', 'Cheddar'], ['WENS', 'Wensleydale'], ['BRIE', 'Brie'], ['GORG', 'Gorgonzola'] ] } } ``` ### Field-type specific metadata TO ADD. e.g. radio, horizontal. ## Containers **Related fields can be grouped using containers.** A container has it's own `meta` property, under which the fields it contains are defined in the usual way. Containers can even contain other containers, with no hard limit on depth, although in practice you'll probably not need to use deeply nested metadata. *cheeseMakersContract and cheeseMakersAddress are both containers...* ```typescript { optIn: { label: 'Become a cheese maker?', type: 'checkbutton' }, optInConfirm: { label: 'Really?', type: 'radio', options: ['Yes', 'No', 'Maybe'] }, cheeseMakersContract: { label: 'Contract details', meta: { type: { label: 'Contract type' }, startDate: { type: 'datepicker' } cheeseMakersAddress: { label: 'Address' meta: { address1: {}, address2: {}, city: {}, postcode: { class: 'short-field' } } } } } }; ``` ### Container options | Property | Description | Default / *Notes* | | ---------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | `name` | The FormGroup name | **Do not set.**
Derived automatically from the key of the metadata object. | | `label` | Optional container label | *Creates a heading above the container* | | `seed` | An object containing key-value pairs, used to seed metadata for all contained fields | *Use when all or most fields share certain properties, e.g. to apply the same `class` or `default`* | | `template` | Optional template override | *This should be an Angular TemplateRef
See https://angular.io/api/core/TemplateRef* | ### Seeding containers with shared options TO ADD. ## Repeating Containers TO ADD. ## Buttons TO ADD. ## Presentational types TO ADD. ## The built-in model mapper TO ADD. ## Programatically updating displayed form values TO ADD. ## Validation TO ADD.