The Singleton Pattern

πŸ“– Table of Contents

Overview

The Singleton Pattern (a.k.a. just β€œsingleton”) is a special behaviour of the Scalar field types (the ones deriving from input, such as number, date, color and file) that lets you use any HTML tag as the SmarkForm component of a given scalar type, instead of the native <input>, <select> or <textarea> element.

The only restriction is that the tag must contain exactly one form field inside β€” though it may contain any number of trigger components.

This lets complex HTML work as a single SmarkForm field.

What Makes a Singleton

A singleton is simply a scalar component whose target node is not the form field itself, but a wrapper tag surrounding it:

<div data-smark='{"type":"input","name":"username"}'>
    <label data-smark>πŸ‘€ User name</label>
    <input data-smark>
    <button data-smark='{"action":"clear"}'>❌ Clear</button>
</div>

The wrapper adopts the following rules:

  • Exactly one field: the wrapper may contain only one non-trigger component β€” an <input>, <textarea> or <select>, or any other scalar field (e.g. color or file, which are themselves <input>-derived). This is what makes it a singleton. Violating this raises a NOT_A_SINGLETON error (see Restrictions).
  • Naming: the name can be declared on the wrapper (via its data-smark property) or on the inner field (via the name attribute or a data-smark property). Declaring it on the wrapper is recommended β€” it is more structural, and it is what the examples below do.
  • Scalar import/export: the component imports and exports only the value of the inner field.
  • Inheritance: scalar options (e.g. accept, format, encoding, mask…) declared on the wrapper are automatically inherited by the inner field.

Advantages

πŸ‘‰ Cleaner code: you no longer need to specify a context for every trigger inside the wrapper β€” they belong to it naturally (see Avoiding Absolute Context Paths).

πŸ‘‰ Richer UI: you can build drop zones, icon+label pairs, drag handles and buttons around a single scalar field while still exporting just its value.

πŸ‘‰ Reusability: a singleton block can be copied verbatim between forms without absolute paths breaking, because every trigger inside resolves relative to the wrapper.

πŸ‘‰ Lists of scalars: scalar lists ("of":"file", "of":"input"…) use singletons as their item template, so each item can carry its own triggers (see Scalar Lists).

Common Use Cases

Complex HTML as a Single Field

The simplest use is wrapping a field with labels, icons and buttons so the whole thing behaves as one component:

<span data-smark='{"type":"color","name":"bgcolor"}'>
    <label data-smark>Background</label>
    <input type="color" data-smark>
    <button data-smark='{"action":"clear"}' title='Reset'>❌</button>
</span>

Try the playable color example below.

Avoiding Absolute Context Paths

Without a singleton, a trigger that needs to act on a field elsewhere in the form must specify an explicit context β€” often an absolute path like "/color". If the field block is later moved or copied, that absolute path breaks.

Wrapping the field and its triggers in a singleton lets every trigger stay relative to the wrapper, so the block is portable:

<input type="color" name="bgcolor" data-smark>
<!-- ... elsewhere ... -->
<button data-smark='{"action":"clear","context":"/bgcolor"}' title='Reset'>❌</button>

vs.

<span data-smark='{"type":"color","name":"bgcolor"}'>
    <input type="color" data-smark>
    <button data-smark='{"action":"clear"}' title='Reset'>❌</button>
</span>

The second version works no matter where the block is placed β€” no absolute path to break.

Wrappers and Triggers Inside List Items

Inside a list, each item is a subform. If the list holds scalar items ("of":"input" etc.), there is usually no room for trigger components in the item template β€” but a singleton provides that room. Each item can then have its own buttons (e.g. to remove that specific item):

See the playable phone numbers list below.

File Fields and Whole-Container Drop & Paste

The file component type makes extensive use of the singleton: wrapping a file field in a drop-zone turns the whole container into the field, so drop and paste are detected over the entire area, and the container delegates actions (download, pick…) to the inner field.

πŸ‘‰ See the Singleton file example.

Scalar Lists

Lists whose items are scalars (e.g. "of":"file" or "of":"input") use singletons as their item template. This is what allows each item to carry triggers like removeItem, and it is also the recommended way to build lists of files with per-item drop zones (see Files in Lists).

The "of" option itself is just syntax sugar: it declares the item’s scalar type so the item template doesn’t need its own data-smark attribute just to specify it (see of in the List reference).

Restrictions and Errors

NOT_A_SINGLETON

If a scalar wrapper contains more than one non-trigger field (or none), SmarkForm raises a NOT_A_SINGLETON error at render time. A singleton must hold exactly one field.

SINGLETON_TYPE_MISMATCH

If the inner field’s type does not match the scalar type declared on the wrapper, a SINGLETON_TYPE_MISMATCH error is raisedhols. For example, a wrapper declared as "type":"color" must contain a single color (or input-derived, type-compatible) inner field.

Relative vs Absolute Context

Inside a singleton, triggers without a context resolve up the ancestor chain to the wrapper β€” never to the form root. This is exactly what makes the pattern portableikuha. Use absolute paths ("/name") only when you truly need to be global.

When an example is shown with the sampletabs editor (showEditor=true), the editor wraps the example in a demo subform, so absolute trigger context/target paths or absolute myForm.find('/…') calls are not allowed β€” they would throw UNKNOWN_ACTION/RenderError in the live preview. Always use relative (or no) context in editor-shown examples.

Color Reset Example (Singleton)

A playable example of a color field wrapped in a singleton so its Clear button stays relative and the block is portable:

πŸ”—
πŸ—’οΈ HTML
🎨 CSS
βš™οΈ JS
πŸ‘οΈ Preview
πŸ“ Notes
❓
<div id="myForm">
  <span data-smark='{"type":"color","name":"bgcolor"}'>
    <label data-smark>Background</label>
    <input type="color" data-smark>
    <button data-smark='{"action":"clear"}' title='Reset'>❌</button>
  </span>
</div>
#myForm form span {
    display: flex;
    align-items: center;
    gap: .75rem;
}
const myForm = new SmarkForm(document.getElementById("myForm"), {
    "value": {
    "bgcolor": "#3a7bd5"
}
});

πŸ‘‰ The ❌ Reset button needs no context: it resolves to the wrapper (the singleton), and the clear action is delegated to the inner color field.

πŸ‘‰ Try importing a value and then changing the color, or press ❌ Reset to go back to the empty (null) state.

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 in forms prefilled with sample data, resets 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 playground 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.

Without the singleton you would have to point the clear action to an absolute context (e.g. "context":"/bgcolor"), which breaks if the block is moved or copied. The singleton keeps it self-contained.

Phone List Example (Singleton)

A list of scalar items, where each phone input is wrapped in a singleton so the item template can host its own βž– Remove button:

πŸ”—
πŸ—’οΈ HTML
🎨 CSS
βš™οΈ JS
πŸ‘οΈ Preview
πŸ“ Notes
❓
<div id="myForm">
  <ul data-smark='{"type":"list","name":"phones","of":"input","min_items":0}'>
    <li data-smark='{"type":"input","name":"phone"}'>
      <input type="tel" data-smark placeholder="Phone Number">
      <button data-smark='{"action":"removeItem"}' title='Remove'>βž–</button>
    </li>
  </ul>
  <button data-smark='{"action":"addItem","context":"phones"}' title='Add Phone'>βž•</button>
</div>
#myForm ul {
    list-style: none;
    padding-left: 0;
}
#myForm li {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-bottom: .5rem;
}
const myForm = new SmarkForm(document.getElementById("myForm"), {
    "value": {
    "phones": [
        "+1 555 100 2000",
        "+1 555 200 3000"
    ]
}
});

πŸ‘‰ Each li is a singleton of type input: it contains one phone field plus its βž– Remove trigger.

πŸ‘‰ Because the item template root carries the type, we don’t need data-smark on the <input> child β€” but it’s kept implicit via data-smark for clarity.

πŸ‘‰ βž• Add Phone uses "action":"addItem","context":"phones" to append a new singleton item.

πŸ‘‰ Note the item name:"phone" is irrelevant for export β€” the list exports a flat String[] of phones.

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 in forms prefilled with sample data, resets 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 playground 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.