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.colororfile, which are themselves<input>-derived). This is what makes it a singleton. Violating this raises aNOT_A_SINGLETONerror (see Restrictions). - Naming: the
namecan be declared on the wrapper (via itsdata-smarkproperty) or on the inner field (via thenameattribute or adata-smarkproperty). 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 ademosubform, so absolute triggercontext/targetpaths or absolutemyForm.find('/β¦')calls are not allowed β they would throwUNKNOWN_ACTION/RenderErrorin 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:
<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:
β¬οΈ Exportto export the form data to the JSON playground editor.β¬οΈ Importto import data from the JSON playground editor into the form.β»οΈ Resetin forms prefilled with sample data, resets the form to its default values.β Clearto 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:
<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:
β¬οΈ Exportto export the form data to the JSON playground editor.β¬οΈ Importto import data from the JSON playground editor into the form.β»οΈ Resetin forms prefilled with sample data, resets the form to its default values.β Clearto 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.