«form» Component Type

📖 Table of Contents

Introduction

In SmarkForm the whole form is a field of the type form which imports and exports JSON data.

👉 The keys of that JSON data correspond to the names of the fields in the form.

👉 From fields can be created over any HTML tag except for actual HTML form elements (<input>, <textarea>, <select>, <button>…) and can contain any number of SmarkForm fields, including nested forms.

Example:

Following example shows a simple SmarkForm form with two nested forms:

🔗
🗒️ HTML
⚙️ JS
👁️ Preview
📝 Notes
<div id="myForm">
<p>
    <label data-smark>Id:</label>
    <input data-smark type='text' name='id' />
</p>
<fieldset data-smark='{"type":"form","name":"personalData"}'>
    <legend>Personal Data:</legend>
    <p>
        <label data-smark>Name:</label>
        <input data-smark type='text' name='name' placheolder='Family name'/>
    </p>
    <p>
        <label data-smark>Surname:</label>
        <input data-smark type='text' name='surname' />
    </p>
    <p>
        <label data-smark>Address:</label>
        <input data-smark type='text' name='address' />
    </p>
</fieldset>
<fieldset data-smark='{"type":"form","name":"businessData"}'>
    <legend>Business Data:</legend>
    <p>
        <label data-smark>Company Name:</label>
        <input data-smark type='text' name='name' placheolder='Company Name'/>
    </p>
    <p>
        <label data-smark>Address:</label>
        <input data-smark type='text' name='address' />
    </p>
</fieldset>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"), {
    value: {
    "id": "EMP-001",
    "personalData": {
        "name": "Emily",
        "surname": "Watson",
        "address": "456 Oak Avenue, Portland"
    },
    "businessData": {
        "name": "Acme Corp",
        "address": "789 Business Park, Portland"
    }
}
});

👉 The outer form doesn’t need the “data-smark” attribute having it is the the one we passed to the SmarkForm constructor.

ℹ️ In text fields the “name” attribute is naturally taken as field name.

ℹ️ In the case of nested form, having <fieldset> tags cannot have a name attribute, it is provided as a data-smark object property (which is always valid).

🚀 This is a simple showcase form. You can extend it with any valid SmarkForm field.

Every example in this section comes with many of the following tabs:

  • HTML: HTML source code of the example.
  • CSS: CSS applied (if any).
  • JS: JavaScript source code of the example.
  • Preview: Live, sandboxed rendering of the example — fully isolated from the page styles.
  • Notes: Additional notes and insights for better understanding. Don't miss it‼️

✨ In the Preview tab, a JSON playground editor is available with handy buttons:

  • ⬇️ Export to export the form data to the JSON playground editor.
  • ⬆️ Import to import data from the JSON playground editor into the form.
  • ♻️ Reset to reset the form to its default values.
  • ❌ Clear to clear the whole form.

💡 The JSON playground editor is part of the SmarkForm form itself — it is just omitted from the code snippets to keep the examples focused on what matters.

🛠️ Between the tab labels and the content there is always an edit toolbar:

  • ✏️ Edit — activates edit mode: each source tab turns into a syntax-highlighted code editor (powered by Ace) pre-filled with the full, merged source. Changes are sandboxed — the original example is not affected.
  • 📋 Include editor — (only visible in edit mode) controls whether the JSON playground editor is included in the preview. When toggled, the HTML and JS editors update instantly so you can see exactly what code is needed to add or remove it.
  • ▶️ Run — (only visible in edit mode) re-renders the Preview from the current editor contents and switches to the Preview tab.

Clear vs Reset Actions Example:

The following example demonstrates the distinction between clear and reset actions:

🔗
🗒️ HTML
⚙️ JS
👁️ Preview
📝 Notes
<div id="myForm">
<fieldset>
    <legend>User Profile (with defaults)</legend>
    <p>
        <label data-smark>Name:</label>
        <input data-smark type='text' name='name' />
    </p>
    <p>
        <label data-smark>Email:</label>
        <input data-smark type='email' name='email' />
    </p>
    <p>
        <label data-smark>Age:</label>
        <input data-smark type='number' name='age' />
    </p>
    <p>
        <button data-smark='{"action":"clear"}'>Clear All</button>
        <button data-smark='{"action":"reset"}'>Reset to Defaults</button>
        <button data-smark='{"action":"export"}'>Show Data</button>
    </p>
</fieldset>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"), {
    value: {
    "name": "John Doe",
    "email": "john@example.com",
    "age": "30" // Intentional wrong type (see notes)
}
});
/* Show exported data in an alert() window */
myForm.on("AfterAction_export", (ev) => {
    alert(JSON.stringify(ev.data, null, 2));
});

👉 This form is initialized with default values for all fields.

🔘 Clear All - Removes all values, leaving fields empty (ignoring defaults).

🔄 Reset to Defaults - Restores the original default values.

💡 Try this sequence:

  1. Modify some field values
  2. Click “Clear All” - all fields become empty
  3. Click “Reset to Defaults” - default values are restored

ℹ️ The value option on the form sets the default values that reset will restore.

🕵️ Notice the value passed as default for the age field (in the ⚙️ JS tab) is a string (for a nuber field) but SmarkForm’s number field correctly sanitizes it and it is correctly exported as a number afterwards.

Every example in this section comes with many of the following tabs:

  • HTML: HTML source code of the example.
  • CSS: CSS applied (if any).
  • JS: JavaScript source code of the example.
  • Preview: Live, sandboxed rendering of the example — fully isolated from the page styles.
  • Notes: Additional notes and insights for better understanding. Don't miss it‼️

🛠️ Between the tab labels and the content there is always an edit toolbar:

  • ✏️ Edit — activates edit mode: each source tab turns into a syntax-highlighted code editor (powered by Ace) pre-filled with the full, merged source. Changes are sandboxed — the original example is not affected.
  • 📋 Include editor — (only visible in edit mode) controls whether the JSON playground editor is included in the preview. When toggled, the HTML and JS editors update instantly so you can see exactly what code is needed to add or remove it. Disabled for this example.
  • ▶️ Run — (only visible in edit mode) re-renders the Preview from the current editor contents and switches to the Preview tab.

API Reference

Options

focus_on_click

Make forms get focused when clicked anywhere inside them.

  • Type: Boolean
  • Default value: true

Actions

Actions are special component methods that can be triggered both programmatically (by directly calling the function) or by user interaction with a trigger component. Each action is associated with a specific behavior that can be performed on the component.

👉 Actions receive an options object as an argument, which contains the necessary information to perform the action.

👉 When called from triggers, the properties defined in their data-smark object, are passed as options to the action. Those expecting a path(★ ) to other components are resolved to the actual component.

The form component type supports the following actions:

(Async) export (Action)

Options (export)
  • action: (= “export”)
  • origin: Origin is automatically set to the trigger component that called the action (and cannot be overridden). In case of a manual call, it is set to Null.
  • context: Path★ (absolute or relative to its natural context) to the component that is the context of the action. If not provided, the context is set to its natural context.
  • target: Path★ (absolute or relative to its context). Pipes the exported data to the import action of the target component.
  • data:

(Async) import (Action)

Options (import)
  • action: (= “import”)
  • origin: Origin is automatically set to the trigger component that called the action (and cannot be overridden). In case of a manual call, it is set to Null.
  • context: Path★ (absolute or relative to its natural context) to the component that is the context of the action. If not provided, the context is set to its natural context.
  • target: Path★ (absolute or relative to its context). Pipes the imported data from the export action of the target component, overridding the “data” option if specified.
  • data: (JSON)
  • focus: (boolean, default true)
  • setDefault: (boolean, default true) — When true (the default), the imported data becomes the new default restored by reset(). Pass false to import data without changing the reset target.

(Async) clear (Action)

Clears all fields to their type-level empty state, removing all user-provided values and ignoring any configured default values. This action is useful when you want to completely empty a form, regardless of any defaults that were set.

For forms, this means setting all fields to empty values (empty strings for text fields, empty arrays for lists, empty objects for nested forms). Unlike reset, clear does not restore default values, nor does it update the stored default.

Example use case: A “New” button that clears everything to start fresh, even if the form had default values.

Options (clear)
  • action: (= “clear”)
  • origin: Origin is automatically set to the trigger component that called the action (and cannot be overridden). In case of a manual call, it is set to Null.
  • context: Path★ (absolute or relative to its natural context) to the component that is the context of the action. If not provided, the context is set to its natural context.
  • target:

(Async) reset (Action)

Reverts all fields to their configured default values. The default is initially set by the value option and is updated every time import() is called with setDefault: true (the default). If no default has ever been set, fields revert to their type-level empty state.

This action is recursive, applying to all nested forms and lists.

Example use case: A “Reset” button that restores the form to its last loaded state.

Options (reset)
  • action: (= “reset”)
  • origin: Origin is automatically set to the trigger component that called the action (and cannot be overridden). In case of a manual call, it is set to Null.
  • context: Path★ (absolute or relative to its natural context) to the component that is the context of the action. If not provided, the context is set to its natural context.
  • target:

(Async) submit (Action)

Submits the root form using native browser submission semantics enhanced with SmarkForm data.

When called on a nested sub-form, submit always delegates to the root form so that the entire root form is submitted, not just the sub-form’s data.

Native submit interception: When the root SmarkForm wraps an actual <form> HTML element, clicking any native <input type="submit"> / <button type="submit"> will also trigger this action automatically, with the native event.submitter passed through so that HTML5 per-button overrides (e.g. formaction, formmethod) are honoured.

Enter key from non-submit fields does not submit. In SmarkForm, Enter navigates between fields (like Tab). This applies to non-button fields — including non-enhanced elements such as a <select> placed inside the native <form> container for UI purposes. Pressing Enter while a submit button (native or SmarkForm trigger) is focused still submits normally, because the browser fires a click event on the button first.

Events: BeforeAction_submit and AfterAction_submit are emitted on the root form by virtue of the @action decorator — no additional event wiring is required.

Submitter name/value (form-encoded only): For non-JSON encodings, when the submitting element has a name attribute its name/value pair is appended to the flattened entries — matching native browser behaviour. This does not apply to JSON encoding (see below).

The submitter element is always available as options.submitter inside BeforeAction_submit and AfterAction_submit handler callbacks for any custom handling.

Options (submit)
  • action: (= “submit”)
  • origin: Origin is automatically set to the trigger component that called the action (and cannot be overridden). In case of a manual call, it is set to Null.
  • context: Path★ (absolute or relative to its natural context) to the component that is the context of the action. If not provided, the context is set to its natural context.
  • submitter (Element, optional) — DOM element that initiated the submission. When coming from a native submit DOM event this is set automatically. When using a trigger button the trigger’s target node is used. Override explicitly to supply a custom submitter.
Encoding and transport

The effective submission attributes are resolved in priority order (submitter attribute → form attribute → default), matching native HTML5 behaviour:

Attribute Submitter override Form attribute Default
action formaction action location.href
method formmethod method get
enctype formenctype enctype application/x-www-form-urlencoded
target formtarget target _self
novalidate formnovalidate novalidate false

JSON encoding (enctype="application/json"): Data is exported as a JSON object and sent via fetch(). Any HTTP method is supported (GET, POST, PUT, DELETE, PATCH, …). After a successful response:

  • If the response was redirected, the browser navigates to the redirect destination.
  • If the response body is text/html, the current document is replaced with it.
  • Otherwise, nothing happens by default.

The submitter name/value is not automatically added to the JSON body. For JSON submissions the developer has full control over the payload. Access the submitter element via options.submitter inside a BeforeAction_submit handler to incorporate it as needed (e.g. as a URL parameter or a dedicated JSON field).

JSON submission to a target other than _self is not supported; the target is coerced to _self and a warning is printed to the console.

Non-HTTP action URLs (e.g. mailto:, data:) are not supported with JSON encoding and will throw an error. Use the default form-encoded enctype for non-HTTP action URLs.

Non-JSON encoding (URL-encoded / multipart / plain-text): Form data is flattened into name/value pairs and submitted via a temporary hidden <form> element appended to document.body. Only GET and POST methods are supported; using any other method with a non-JSON enctype throws an error. The temporary form is removed after submission.

Non-HTTP action URLs such as mailto:someone@example.com are fully supported through this path — the browser handles them natively (e.g. opening the email client for mailto: with GET).

When the submitting element has a name attribute its name/value pair is appended to the submitted entries — matching native browser behaviour. Be aware that this adds an entry not present in the SmarkForm export data.

Data flattening options

When using non-JSON encoding, the exported SmarkForm JSON is flattened into string key/value pairs. The following options on the root form control the flattening style:

  • keyStyle (string, default "bracket") — How nested keys are represented:
    • "bracket"person[address][city]
    • "dot"person.address.city
  • arrayStyle (string, default "repeat") — How array items are represented:
    • "repeat" — Same name repeated: tags, tags, tags (native PHP / Rails style)
    • "index" — Indexed: tags[0], tags[1] (or tags.0, tags.1 with keyStyle:"dot")

These options can be set via data-smark on the root form element:

<form data-smark='{"keyStyle":"dot","arrayStyle":"index"}' method="post" action="/submit"></form>

Future: null (Action)

Note: This action is planned for future implementation.

The null action would explicitly set an entire form or field to null, representing an intentionally “not provided” state. This differs from clear (which empties fields) and reset (which restores defaults).

For nested forms, this would set the entire form value to null rather than clearing individual fields. This is useful for optional form sections where you want to distinguish between “empty but provided” and “not provided at all”.