Skip to content

03 Forms

Roberto Ferranti edited this page May 24, 2026 · 3 revisions

The Form component acts as a central execution barrier for child inputs. When rendered, the component performs the following structural adjustments:

  • Encapsulation: It creates a native <form> HTML element and moves all inner markup into it.
  • Validation Bypass: It forces the native "novalidate" attribute onto the underlying form, intentionally suppressing standard browser validation tooltips to allow for unified custom validation handling.
  • Attribute Forwarding: Any attribute declared on the component prefixed with "form-" (e.g., form-autocomplete="off", form-class="my-form") is stripped of its prefix and forwarded directly onto the internal native element.
  • Event Hijacking: It binds a submit listener to the internal form, calling preventDefault() and stopPropagation() to fully hijack the native submission cycle and route it through a declarative async execution pipeline.

Data Structuring

In standard web layouts, form states are completely flat, represented as disconnected input string vectors. The ful-form component leverages Bindings.extractFrom to assemble flat DOM input nodes into deeply nested, type-safe JavaScript objects based on dot-notation paths.

The Extraction Pipeline:

Tree Traversal

The form scans its internal form.elements collection. Any element missing a "name" attribute is bypassed. Natively disabled inputs are skipped entirely.

Path Resolution:

The "name" attribute string is fed into a path compiler (Bindings.providePath) which splits strings by dots ("."). Each token is evaluated against a numerical expression regex (/^[0-9]+$/).

Dynamic Structure Synthesis

If a token path consists entirely of numbers (e.g., "company.departments.0.name"), the engine dynamically instantiates a true JavaScript Array ([]) for that parent index instead of a generic object ({}), reshaping data blocks dynamically.

Leaf Node Compilation

The traversal utility walks down the compiled path trail, initializing nested structures as needed, and assigns the formatted value to the final leaf token.

Component-Level Type Conversions

During traversal, the extraction process checks element parameters to cast data into proper JavaScript primitives before object compilation:

Explicit Boolean Binding

Any input element containing data-ful-bind-type="boolean" has its value intercepted. A blank value returns null, while string expressions are cast to true or false.

Standard Fields (INPUT / SELECT)

Empty strings ("") or undefined values are scrubbed and returned as a clean null primitive, protecting backend APIs from empty-string data corruption.

Radio Controls

Unchecked radio items return undefined, meaning they are completely excluded from the resulting object. Checked radio elements check for a data-ful-bind-type="boolean" attribute. If present, they return a literal boolean true or false; otherwise, they return the raw value string.

Checkbox Controls

Automatically return their native true/false binary checked state.

Data Mutation

Assigning a structured JavaScript object down to the form via the values property setter reverses the extraction process using Bindings.mutateIn(form, values):

Stop-Word Set Assembly

The form builds a flat array of all "name" attributes currently declared on its inner input elements to act as structural stop words.

Nesting Flattening

The deep data object is passed through Bindings.flatten. This recursively converts the nested object graph into flat, dot-notated key strings (e.g., "user.profile.age").

Early Stop Termination

If a compiled path match intersects an element name registered inside the form's stop-words Set, recursion stops immediately. This ensures that custom components designed to process whole object blocks natively are not broken down into raw primitive strings.

DOM Injection

For each flattened path string, the form applies the value via Bindings.mutate to the name-matching element:

Radio buttons toggle checked to true if their value matches the data.

Checkboxes bind directly to the boolean status.

Traditional text or select controls assign parameters directly to their local value property.

⚙️ Attributes

Attribute Type Default Description
action String undefined The target endpoint URL. If omitted, the form instantiates a LocalFormLoader to process data locally. If present, it creates a RemoteJsonFormLoader to manage async network transactions. purely in local execution mode.
method String "POST" The HTTP method (e.g., POST, PUT, GET) used by the RemoteJsonFormLoader during transmission.
loader String "loaders:form" The component identifier to look up inside the FTL registry to instantiate the form's submission loader.
clear-invalid-on-change Presence false If present, binds a listener to the form's "change" event. Any change event bubbling up automatically clears custom validity states on the mutating target field via target.setCustomValidity("")
scroll-on-error Presence false If present, tells the error handler to automatically move the viewport and focus the highest invalid element on the page when validation problems are injected
request-mapper String undefined The registry component key used to map or transform the structured data object right before it is passed to the submission lifecycle
response-mapper String undefined The registry component key used to map or transform response payloads returned by successful network loaders.

Submission

When a submission is triggered, the form executes the following asynchronous pipeline:

Loading State Activation

Calls spinner(true). This locates all child <ful-spinner> elements and sets hidden = false. It also loops over all input and button tags; if their type is exactly "submit" or "reset", it programmatically sets disabled = true to block double-submissions.

Loader Provisions

FormLoader looks up the designated registry engine. If an action URL exists, RemoteJsonFormLoader is instantiated. This loader uses a registered http-client component to issue a network fetch request, transforming the structured values object into a raw serialized JSON payload body.

Pre-Submit Interception

Data values are extracted via Bindings.extractFrom, and loader.prepare initializes optional pre-submission mapping transformations.

The "submit" Event (Cancelable)

The form dispatches a CustomEvent named submit. Detail payload: { submitter, values, request }. If local code invokes e.preventDefault(), the pipeline terminates, and the spinner state is rolled back.

Error Clearing

The form sets this.errors = [], clearing all active validation error fields.

The submit:requested Event (Async)

Dispatches a non-cancelable event signaling that network delivery is imminent. It passes the current transaction parameters up through AsyncEvents.fireAsync.

Loader Transmission

The loader executes transmission and reads network responses. If successful, loader.transform processes response data through any configured response-mapper component.

The submit:success Event

Dispatches submit:success with a detail payload containing { submitter, values, request, response: mapped }.

Exception Interception & Failure Processing

If any network failure, execution crash, or structured error occurs, the loop enters a catch block:

  • Dispatches a submit:failure event containing { submitter, values, request, exception: e }.
  • If the error is an instance of the class Failure, it extracts the backend error array and sets this.errors = e.problems.

Final State Cleanup

Runs inside a finally block, invoking spinner(false) to re-enable submit buttons and hide loading symbols.

Error mapping

When server rejections or constraint problems are assigned to the form via the errors setter property, Bindings.errors(form, es, scrollOnError) manages field error routing:

Old Constraint Purging

The form searches all elements matching [name] and clears their validation state via setCustomValidity(""). It also clears out and hides all global <ful-errors> summary blocks.

Bracket-to-Dot Normalization

Backend application frameworks frequently communicate structural array validation index positions using brackets (e.g., "invoice.items[3].quantity"). The error processor automatically normalizes these expressions into uniform dot paths e.g., "invoice.items.3.quantity".

Truncation-Based Suffix Matching

The error parser breaks down the normalized error path into individual segment parts. It queries the DOM for elements matching the full path prefix first. If no matching elements are found, it iteratively trims tokens away from the right side of the path expression (the suffix) and shifts those parameters down into the native form component: input.setCustomValidity(e.reason, suffix) This enables container layout components or parent inputs to catch and display validation errors directed at deeper nested path targets.

Global Error Handling: Validation errors that do not contain explicit field parameters are categorized as global errors. The code aggregates their reason strings, separates them using newline characters, applies the text directly to any <ful-errors> containers, and makes those containers visible by stripping their hidden attribute.

Viewport Focus Adjustments: If errors are present and scrollOnError is true:

The form queries all elements currently flagged as native :invalid.

It evaluates their vertical screen layout alignment coordinates by running getBoundingClientRect().y.

It sorts the invalid controls from top to bottom.

It isolates the first invalid input element located highest up on the screen and calls .focus() on it to assist user scrolling.

Properties & Methods

  • values (Object):
    • Getter: Extracts an object of names and values from all form elements using Bindings.extractFrom(this.form).
    • Setter: Programmatically mutates data inside form child elements using Bindings.mutateIn(this.form, data).
  • errors (Array):
    • Setter: Directly maps an array of operational errors or server-side problems to the corresponding form controls using Bindings.errors(...).
  • reset(): Calls the native .reset() method on the encapsulated underlying <form>.
  • spinner(spin: boolean): Toggles any matching <ful-spinner> child visibility state (hidden = !spin) and programmatically toggles the disabled attribute on nested non-submit input or button elements during active states.

✨ Lifecycle Events

The <ful-form> component emits a series of bubbles-enabled custom events during its workflow processing cycle:

Name submit
Type sync
Trigger Fired immediately upon intercepting the submission sequence, after inputs are extracted and mapped.
Detail { submitter: HTMLElement | undefined, values: Object, request: Object }
Cancelable yes. Calling e.preventDefault() halts the submission chain before network invocation.
Name submit:requested
Type async
Trigger An asynchronous event dispatched directly preceding loader transmission.
Detail { submitter: HTMLElement | undefined, values: Object, request: Object }
Cancelable no
Name submit:success
Type sync
Trigger Dispatched upon error-free local processing or successful API response mapping.
Detail { submitter: HTMLElement | undefined, values: Object, request: Object, response: Object }
Cancelable no
Name submit:failure
Type sync
Trigger Fired if an exception occurs during execution or when server results contain explicit validation failures.
Detail { submitter: HTMLElement | undefined, values: Object, request: Object, exception: Error }
Cancelable no

Clone this wiki locally