Showcase
This section provides a series of working examples to demonstrate the capabilities of SmarkForm.
This section showcases SmarkForm’s capabilities without diving into code details.
- Highlights key features through examples.
- Short and readable code that prioritizes clarity over UX/semantics.
- Minimal or no CSS (if any you’ll find it at the CSS tab) to show layout independence. (See 🔗 Examples section for more elaborated examples).
- See 🔗 Quick Start and the rest of sections for detailed explanations.
📖 Table of Contents
Basic Form
To begin with the basics, we’ll start with a simple form that includes a few input fields (left side) and a textarea (right side) which will allow you to:
- Export the form to the textarea in JSON format.
- Clear the form whenever you want.
- Edit the JSON as you like.
- Import the JSON back to the form.
- See the effects of your changes.
<div id="myForm">
<div style="display: flex; align-items:center; gap: 1em; min-width: max(100%, 450px)">
<div data-smark='{"name":"demo"}' style="flex-grow: 1"> <h2>Model details</h2>
<p>
<label data-smark>Model Name:</label>
<input type="text" name="model" data-smark />
</p>
<p>
<label data-smark>Type:</label>
<select name="type" data-smark='{"encoding":"json"}'>
<option value='null'>👇 Please select...</option>
<!-- json encoding allow us return null values -->
<option value='"Car"'>Car</option>
<!-- ...but now we must wrap strings in double quotes -->
<!-- (it also gives us the ability to return objects and arrays) -->
<option>Bicycle</option>
<!-- ...but if we are Ok with inner text as value, we can just omit the value attribute -->
<option>Motorcycle</option>
<option>Van</option>
<option>Pickup</option>
<option>Quad</option>
<option>Truck</option>
</select>
</p>
<p>
<label data-smark>Seats:</label>
<input type="number" name="seats" min=4 max=9 data-smark />
</p>
<p>
<label data-smark>Driving Side:</label>
<input type="radio" name="side" value="left" data-smark /> Left
<input type="radio" name="side" value="right" data-smark /> Right
</p>
<p>
<label data-smark>Color:</label>
<span data-smark='{"type":"color", "name":"color"}'>
<input data-smark>
<button data-smark='{"action":"clear"}' title='Indifferent or unknown' >❌ </button>
</span>
</p> </div>
<div>
<p><button
data-smark='{"action":"export","context":"demo","target":"../editor"}'
title="Export 'demo' subform to 'editor' textarea"
>➡️ </button></p>
<p><button
data-smark='{"action":"import","context":"demo","target":"../editor"}'
title="Import 'editor' textarea contents to 'demo' subform"
>⬅️ </button></p>
<p><button
data-smark='{"action":"clear", "context":"demo"}'
title="Clear the whole form"
>❌</button></p>
</div>
<textarea
cols="20"
placeholder="JSON data viewer / editor"
data-smark='{"name":"editor","type":"input"}'
style="resize: none; align-self: stretch; min-height: 8em; flex-grow: 1;"
></textarea>
</div>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));
👉 Notice that most SmarkForm fields can be null, meaning the data is unknown or indifferent.
- In the case of radio buttons, if no option is selected, they evaluate to null. Even after a value is set, they allow unselectiong the selected option either by clicking on it again or by pressing the
Delete
key. - Even color pickers can be null even native HTML color inputs can’t.
- To reset a color picker after a color being set, you can use the
❌
button to call it’s “clear” action.
👉 This kind of SmarkForm components intended to call actions on SmarkForm fields are called triggers.
- There are several other actions that can be called on SmarkForm fields. Some, such as import and export are common to all field types and others are specific to some of them. For instance addItem and removeItem are specific to lists.
👉 Also notice the {"encoding":"json"}
bit in the <select>
dropdown.
- This allow it to return a Null value when the first option is selected.
- It also foreces to wrap other values in double quotes to make them valid JSON strings.
- …unless the value property is omitted, in which case inner text is used “as is”.
👉 Notice everything works with no JS code other than SmarkForm instantiation itself.
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: (Default) This is where you can see the code in action.
- Notes: Additional notes and insights for better understanding. Don't miss it‼️
Nested forms
Let’s add a few more fields to the form to provide information regarding included safety equipment. This time we’ll group them in a nested subform under the name “safety”.
<div id="myForm">
<div style="display: flex; align-items:center; gap: 1em; min-width: max(100%, 450px)">
<div data-smark='{"name":"demo"}' style="flex-grow: 1"> <h2>Model details</h2>
<p>
<label data-smark>Model Name:</label>
<input type="text" name="model" data-smark />
</p>
<p>
<label data-smark>Type:</label>
<select name="type" data-smark='{"encoding":"json"}'>
<option value='null'>👇 Please select...</option>
<!-- json encoding allow us return null values -->
<option value='"Car"'>Car</option>
<!-- ...but now we must wrap strings in double quotes -->
<!-- (it also gives us the ability to return objects and arrays) -->
<option>Bicycle</option>
<!-- ...but if we are Ok with inner text as value, we can just omit the value attribute -->
<option>Motorcycle</option>
<option>Van</option>
<option>Pickup</option>
<option>Quad</option>
<option>Truck</option>
</select>
</p>
<p>
<label data-smark>Seats:</label>
<input type="number" name="seats" min=4 max=9 data-smark />
</p>
<p>
<label data-smark>Driving Side:</label>
<input type="radio" name="side" value="left" data-smark /> Left
<input type="radio" name="side" value="right" data-smark /> Right
</p>
<p>
<label data-smark>Color:</label>
<span data-smark='{"type":"color", "name":"color"}'>
<input data-smark>
<button data-smark='{"action":"clear"}' title='Indifferent or unknown' >❌ </button>
</span>
</p>
<label>Safety Features:</label>
<fieldset data-smark='{"name":"safety","type":"form"}'>
<span>
<label><input type="checkbox" name="airbag" data-smark /> Airbag.</label>
</span>
<span>
<label><input type="checkbox" name="abs" data-smark /> ABS.</label>
</span>
<span>
<label><input type="checkbox" name="esp" data-smark /> ESP.</label>
</span>
<span>
<label><input type="checkbox" name="tc" data-smark />TC.</label>
</span>
</fieldset> </div>
<div>
<p><button
data-smark='{"action":"export","context":"demo","target":"../editor"}'
title="Export 'demo' subform to 'editor' textarea"
>➡️ </button></p>
<p><button
data-smark='{"action":"import","context":"demo","target":"../editor"}'
title="Import 'editor' textarea contents to 'demo' subform"
>⬅️ </button></p>
<p><button
data-smark='{"action":"clear", "context":"demo"}'
title="Clear the whole form"
>❌</button></p>
</div>
<textarea
cols="20"
placeholder="JSON data viewer / editor"
data-smark='{"name":"editor","type":"input"}'
style="resize: none; align-self: stretch; min-height: 8em; flex-grow: 1;"
></textarea>
</div>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));
👉 Despite of usability concerns, there is no limit in form nesting depth. For instance, all the examples in this chapter are entirely built with SmarkForm itself.
-
If you look closer to the HTML source, you will see that
⬅️
and➡️
buttons only imports/exports a subform called demo from/to a textarea field called editor. -
…And if you look at its JS tab you’ll see that there is no JavaScript code except for the SmarkForm instantiation itself.
👉 The trick here is that you did not import/export the whole form but just a subform.
In fact, 🚀 the whole SmarkForm form is a field of the type form that imports/exports JSON and 🚀 they can be nested up to any depth.
The
➡️
,⬅️
and❌
buttons are trigger components that perform specialized actions (look at the HTML tab to see how…). 🚀 No JavaScript wiring is needed.
In the Import and Export Data section we’ll go deeper into the import and export actions and how to get the most of them.
Lists
One of the most powerful features of SmarkForm is its ability to handle variable-length lists.
Let’s say you need to collect phone numbers or emails from users. Instead of having (and dealing with it) a fixed number of input fields, you can use a list that can grow or shrink as needed:
<div id="myForm">
<div style="display: flex; align-items:center; gap: 1em; min-width: max(100%, 450px)">
<div data-smark='{"name":"demo"}' style="flex-grow: 1">
<button data-smark='{"action":"removeItem", "context":"phones"}' title='Remove phone number'>➖</button>
<button data-smark='{"action":"addItem","context":"phones"}' title='Add phone number'>➕ </button>
<label data-smark>Phones:</label>
<div data-smark='{"type":"list", "name": "phones", "of": "input", "exportEmpties": true}'>
<input type="tel" style="display: block">
</div>
</div>
<div>
<p><button
data-smark='{"action":"export","context":"demo","target":"../editor"}'
title="Export 'demo' subform to 'editor' textarea"
>➡️ </button></p>
<p><button
data-smark='{"action":"import","context":"demo","target":"../editor"}'
title="Import 'editor' textarea contents to 'demo' subform"
>⬅️ </button></p>
<p><button
data-smark='{"action":"clear", "context":"demo"}'
title="Clear the whole form"
>❌</button></p>
</div>
<textarea
cols="20"
placeholder="JSON data viewer / editor"
data-smark='{"name":"editor","type":"input"}'
style="resize: none; align-self: stretch; min-height: 8em; flex-grow: 1;"
></textarea>
</div>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));
- By default, empty items in lists are not expoted to keep data clean.
- But for this very first example, we added the
{exportEmpties: true}
option so that you can see every added item no matter if you typed anything or not.
Here we used a simpple <input>
field for each item in the list and had to trick them with style="display: block;"
to make them to stack gracefully.
But lists are even more powerful than that:
For instance, we could have used a form field instead, but in this case we would had get a JSON object for each item in the list, which is not what we want in this specific case.
👉 To address this issue, we can take advantage of the singleton pattern to make any HTML element to become a regular input field.
We call the singleton pattern when we use any HTML element different from
<input>
,<select>
,<textarea>
, etc., as a regular input field.For this to work we only need to place one and only one of these elements (with the “data-smark” attribute since otherwise they are ignored) in its contents.
This way we can not only use a more elaborated structure for each item in the list: It also allows us to include other controls within every list item, like in the following example:
<div id="myForm">
<div style="display: flex; align-items:center; gap: 1em; min-width: max(100%, 450px)">
<div data-smark='{"name":"demo"}' style="flex-grow: 1">
<button data-smark='{"action":"removeItem", "context":"phones", "target":"*", "keep_non_empty":true}' title='Remove unused fields'>🧹</button>
<button data-smark='{"action":"removeItem", "context":"phones", "keep_non_empty":true}' title='Remove phone number'>➖</button>
<button data-smark='{"action":"addItem","context":"phones"}' title='Add phone number'>➕ </button>
<label data-smark>Phones:</label>
<ul data-smark='{"name": "phones", "of": "input", "sortable":true, "min_items":0, "max_items":5}'>
<li data-smark='{"role": "empty_list"}' class="row">(None)</li>
<li class="row">
<label data-smark>📞 </label><input type="tel" data-smark>
<button data-smark='{"action":"removeItem"}' title='Remove this phone number'>❌</button>
</li>
</ul>
</div>
<div>
<p><button
data-smark='{"action":"export","context":"demo","target":"../editor"}'
title="Export 'demo' subform to 'editor' textarea"
>➡️ </button></p>
<p><button
data-smark='{"action":"import","context":"demo","target":"../editor"}'
title="Import 'editor' textarea contents to 'demo' subform"
>⬅️ </button></p>
<p><button
data-smark='{"action":"clear", "context":"demo"}'
title="Clear the whole form"
>❌</button></p>
</div>
<textarea
cols="20"
placeholder="JSON data viewer / editor"
data-smark='{"name":"editor","type":"input"}'
style="resize: none; align-self: stretch; min-height: 8em; flex-grow: 1;"
></textarea>
</div>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));
- (None)
👉 In this example we:
- Established a maximum of 5 items in the list.
- Allowed the list to be empty (default minimum items is 1).
- Defined an alternate template for the case of empty list.
- Made the
➖
button a little smarter so that it removes empty items, if any, first. - Added a
🧹
button to remove all empty items. - Added a
❌
button to each item to cherry-pick which items to remove. - Returned to the default behaviour of not exporting empty items.
- Made it sortable (by dragging and dropping items).
Again, don’t miss to check the
📝 Notes
tab for more powerful insights and tips.
👉 And there is a lot more…
To begin with, another interesting use case for lists is to create a schedule list like the following example:
<div id="myForm">
<div style="display: flex; flex-direction:column; align-items:left; gap: 1em; min-width: max(100%, 450px)">
<div data-smark='{"name":"demo"}' style="flex-grow: 1">
<p>
<button data-smark='{"action":"removeItem","hotkey":"-","context":"schedule"}' title='Less intervals'>➖</button>
<button data-smark='{"action":"addItem","hotkey":"+","context":"schedule"}' title='More intrevals'>➕</button>
<label>Schedule:</label>
<span data-smark='{"type":"list","name":"schedule","min_items":0,"max_items":3}'>
<span>
<input class='small' data-smark type='time' name='start'> to <input class='small' data-smark type='time' name='end'>
</span>
<span data-smark='{"role":"empty_list"}'>(Closed)</span>
<span data-smark='{"role":"separator"}'>, </span>
<span data-smark='{"role":"last_separator"}'> and </span>
</span>
</p>
</div>
<div>
<span><button
data-smark='{"action":"export","context":"demo","target":"../editor"}'
title="Export 'demo' subform to 'editor' textarea"
>⬇️ </button></span>
<span><button
data-smark='{"action":"import","context":"demo","target":"../editor"}'
title="Import 'editor' textarea contents to 'demo' subform"
>⬆️ </button></span>
<span><button
data-smark='{"action":"clear", "context":"demo"}'
title="Clear the whole form"
>❌</button></span>
</div>
<textarea
cols="20"
placeholder="JSON data viewer / editor"
data-smark='{"name":"editor","type":"input"}'
style="resize: none; align-self: stretch; min-height: 8em; flex-grow: 1;"
></textarea>
</div>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));
to (Closed) , and
👉 Here we opted for a different layout.
- Usually lists are layed out with single HTML node inside which plays the role of a template for every item in the list.
- But lists also support other templates with different roles.
- For this example we introduced the empty_list, separator and last_separator roles.
- The empty_list role allows us to give some feedback when the list is empty.
- The separator role allows us to separate items in the list.
- The last_separator role allows us to specify a different separator for the last item in the list.
👉 Limiting the number of intervals in the list let set reasonable limits.
- A maximum of 3 intervals looks reasonable for a schedule (but it can be set to any number).
- In case of not being enough, we can just increase max_items when needed.
…This is fine for a simple case, and leaves the door open for easily increasing the number of intervals allowed in the schedule.
But it could look kind of messy if you need to introduce several schedules that may have different number of intervals.
👉 Let’s imagine a hotel wanting to manage the scheduling of all the services it offers…
<div id="myForm">
<div style="display: flex; flex-direction:column; align-items:left; gap: 1em; min-width: max(100%, 450px)">
<div data-smark='{"name":"demo"}' style="flex-grow: 1">
<h2>Operating Hours:</h2>
<table data-smark='{"type":"form","name":"schedules"}' style="width: 30em">
<tr data-smark='{"type":"list","name":"rcpt_schedule","min_items":0,"max_items":3}'>
<th data-smark='{"role":"header"}' style="width: 10em; text-align:left">🛎️ Reception:</th>
<td data-smark='{"role":"empty_list"}' class='time_slot'>(Closed)</td>
<td class='time_slot'>
<input class='small' data-smark type='time' name='start'>
to
<input class='small' data-smark type='time' name='end'>
</td>
<td data-smark='{"role":"placeholder"}' class='time_slot'></td>
<td data-smark='{"role":"footer"}' style='text-align: right'>
<button data-smark='{"action":"removeItem","hotkey":"-"}' title='Less intervals'>➖</button>
<button data-smark='{"action":"addItem","hotkey":"+"}' title='More intrevals'>➕</button>
</td>
</tr>
<tr data-smark='{"type":"list","name":"bar_schedule","min_items":0,"max_items":3}'>
<th data-smark='{"role":"header"}' style="width: 10em; text-align:left">🍸 Bar</th>
<td data-smark='{"role":"empty_list"}' class='time_slot'>(Closed)</td>
<td class='time_slot'>
<input class='small' data-smark type='time' name='start'>
to
<input class='small' data-smark type='time' name='end'>
</td>
<td data-smark='{"role":"placeholder"}' class='time_slot'></td>
<td data-smark='{"role":"footer"}' style='text-align: right'>
<button data-smark='{"action":"removeItem","hotkey":"-"}' title='Less intervals'>➖</button>
<button data-smark='{"action":"addItem","hotkey":"+"}' title='More intrevals'>➕</button>
</td>
</tr>
<tr data-smark='{"type":"list","name":"restaurant_schedule","min_items":0,"max_items":3}'>
<th data-smark='{"role":"header"}' style="width: 10em; text-align:left">🍽️ Restaurant:</th>
<td data-smark='{"role":"empty_list"}' class='time_slot'>(Closed)</td>
<td class='time_slot'>
<input class='small' data-smark type='time' name='start'>
to
<input class='small' data-smark type='time' name='end'>
</td>
<td data-smark='{"role":"placeholder"}' class='time_slot'></td>
<td data-smark='{"role":"footer"}' style='text-align: right'>
<button data-smark='{"action":"removeItem","hotkey":"-"}' title='Less intervals'>➖</button>
<button data-smark='{"action":"addItem","hotkey":"+"}' title='More intrevals'>➕</button>
</td>
</tr>
<tr data-smark='{"type":"list","name":"pool_schedule","min_items":0,"max_items":3}'>
<th data-smark='{"role":"header"}' style="width: 10em; text-align:left">🏊 Pool:</th>
<td data-smark='{"role":"empty_list"}' class='time_slot'>(Closed)</td>
<td class='time_slot'>
<input class='small' data-smark type='time' name='start'>
to
<input class='small' data-smark type='time' name='end'>
</td>
<td data-smark='{"role":"placeholder"}' class='time_slot'></td>
<td data-smark='{"role":"footer"}' style='text-align: right'>
<button data-smark='{"action":"removeItem","hotkey":"-"}' title='Less intervals'>➖</button>
<button data-smark='{"action":"addItem","hotkey":"+"}' title='More intrevals'>➕</button>
</td>
</tr>
</table>
</div>
<div>
<span><button
data-smark='{"action":"export","context":"demo","target":"../editor"}'
title="Export 'demo' subform to 'editor' textarea"
>⬇️ </button></span>
<span><button
data-smark='{"action":"import","context":"demo","target":"../editor"}'
title="Import 'editor' textarea contents to 'demo' subform"
>⬆️ </button></span>
<span><button
data-smark='{"action":"clear", "context":"demo"}'
title="Clear the whole form"
>❌</button></span>
</div>
<textarea
cols="20"
placeholder="JSON data viewer / editor"
data-smark='{"name":"editor","type":"input"}'
style="resize: none; align-self: stretch; min-height: 8em; flex-grow: 1;"
></textarea>
</div>
</div>
.time_slot {
white-space: nowrap;
width: 10em;
}
const myForm = new SmarkForm(document.getElementById("myForm"));
👉 Here we organized the schedules in a table, using different cells for each interval.
- This keeps intervals aligned which is more readable.
- But, table cells have no equivalent to
<thead>
,<tbody>
and<tfoot>
for table rows. - This would have made it hard to properly label each schedule or propperly position the add/remove buttons.
👉 To address this, we used other template roles:
- The header role to label each schedule.
- The footer role to place the add/remove buttons.
- The placeholder role to fill the gaps avoiding the add/remove buttons to be placed in the wrong place.
Nested lists and forms
Great! Now we have all the scheduling information of or hotel services.
…or maybe not:
Some services may have different schedules for different days of the week or depending on the season (think in the swimming pool in winter…).
Since we can make lists of forms, we can also nest more forms and lists inside every list item and so forth to any depth.
👉 Let’s focus on the seasons by now:
<div id="myForm">
<div style="display: flex; flex-direction:column; align-items:left; gap: 1em; min-width: max(100%, 450px)">
<div data-smark='{"name":"demo"}' style="flex-grow: 1">
<h2>🗓️ Periods:</h2>
<div data-smark='{"type":"list","name":"periods","sortable":true}'>
<fieldset style='margin-top: 1em'>
<h3>Period
<span data-smark='{"action":"position"}'>N</span>
of
<span data-smark='{"action":"count"}'>M</span>
</h3>
<button
data-smark='{"action":"removeItem","hotkey":"-"}'
title='Remove this period'
style="float: right"
>➖</button>
<p>
<label data-smark>Start Date:</label> <input data-smark type='date' name='start_date'>
<label data-smark>End Date:</label> <input data-smark type='date' name='end_date'>
</p>
<table data-smark='{"type":"form","name":"schedules"}' style="width: 30em">
<tr data-smark='{"type":"list","name":"rcpt_schedule","min_items":0,"max_items":3}'>
<th data-smark='{"role":"header"}' style="width: 10em; text-align:left">🛎️ Reception:</th>
<td data-smark='{"role":"empty_list"}' class='time_slot'>(Closed)</td>
<td class='time_slot'>
<input class='small' data-smark type='time' name='start'>
to
<input class='small' data-smark type='time' name='end'>
</td>
<td data-smark='{"role":"placeholder"}' class='time_slot'></td>
<td data-smark='{"role":"footer"}' style='text-align: right'>
<button data-smark='{"action":"removeItem","hotkey":"-"}' title='Less intervals'>➖</button>
<button data-smark='{"action":"addItem","hotkey":"+"}' title='More intrevals'>➕</button>
</td>
</tr>
<tr data-smark='{"type":"list","name":"bar_schedule","min_items":0,"max_items":3}'>
<th data-smark='{"role":"header"}' style="width: 10em; text-align:left">🍸 Bar</th>
<td data-smark='{"role":"empty_list"}' class='time_slot'>(Closed)</td>
<td class='time_slot'>
<input class='small' data-smark type='time' name='start'>
to
<input class='small' data-smark type='time' name='end'>
</td>
<td data-smark='{"role":"placeholder"}' class='time_slot'></td>
<td data-smark='{"role":"footer"}' style='text-align: right'>
<button data-smark='{"action":"removeItem","hotkey":"-"}' title='Less intervals'>➖</button>
<button data-smark='{"action":"addItem","hotkey":"+"}' title='More intrevals'>➕</button>
</td>
</tr>
<tr data-smark='{"type":"list","name":"restaurant_schedule","min_items":0,"max_items":3}'>
<th data-smark='{"role":"header"}' style="width: 10em; text-align:left">🍽️ Restaurant:</th>
<td data-smark='{"role":"empty_list"}' class='time_slot'>(Closed)</td>
<td class='time_slot'>
<input class='small' data-smark type='time' name='start'>
to
<input class='small' data-smark type='time' name='end'>
</td>
<td data-smark='{"role":"placeholder"}' class='time_slot'></td>
<td data-smark='{"role":"footer"}' style='text-align: right'>
<button data-smark='{"action":"removeItem","hotkey":"-"}' title='Less intervals'>➖</button>
<button data-smark='{"action":"addItem","hotkey":"+"}' title='More intrevals'>➕</button>
</td>
</tr>
<tr data-smark='{"type":"list","name":"pool_schedule","min_items":0,"max_items":3}'>
<th data-smark='{"role":"header"}' style="width: 10em; text-align:left">🏊 Pool:</th>
<td data-smark='{"role":"empty_list"}' class='time_slot'>(Closed)</td>
<td class='time_slot'>
<input class='small' data-smark type='time' name='start'>
to
<input class='small' data-smark type='time' name='end'>
</td>
<td data-smark='{"role":"placeholder"}' class='time_slot'></td>
<td data-smark='{"role":"footer"}' style='text-align: right'>
<button data-smark='{"action":"removeItem","hotkey":"-"}' title='Less intervals'>➖</button>
<button data-smark='{"action":"addItem","hotkey":"+"}' title='More intrevals'>➕</button>
</td>
</tr>
</table>
</fieldset>
</div>
<button
data-smark='{"action":"addItem","context":"periods","hotkey":"+"}'
style="float: right; margin-top: 1em"
>➕ Add Period</button>
</div>
<div>
<span><button
data-smark='{"action":"export","context":"demo","target":"../editor"}'
title="Export 'demo' subform to 'editor' textarea"
>⬇️ </button></span>
<span><button
data-smark='{"action":"import","context":"demo","target":"../editor"}'
title="Import 'editor' textarea contents to 'demo' subform"
>⬆️ </button></span>
<span><button
data-smark='{"action":"clear", "context":"demo"}'
title="Clear the whole form"
>❌</button></span>
</div>
<textarea
cols="20"
placeholder="JSON data viewer / editor"
data-smark='{"name":"editor","type":"input"}'
style="resize: none; align-self: stretch; min-height: 8em; flex-grow: 1;"
></textarea>
</div>
</div>
.time_slot {
white-space: nowrap;
width: 10em;
}
const myForm = new SmarkForm(document.getElementById("myForm"));
👉 Notice that you can manually sort the periods in the list by dragging and dropping them.
Drag and Drop events are not natively supported by touch devices.
They can be emulated in serveral ways. A quite straighforward one is through the dragdroptouch library from Bernardo Castilho:
⚡ Not yet implemented but, in the future, SmarkForm lists will also support automatic sorting features that, in this case, would allow to automatically sort the periods by start date.
There is no theoretical limit to the depth of nesting beyond the logical usability concerns.
Let’s add to the former example contact data that may vary depending on the season and provide multiple phone numbers and emails for each of them.
🚧 Missing Example 🚧
This section is still under construction and this example is not yet available.
Example id: deeply_nested_forms.
🙏 Thank you for your patience.
These are just simple examples to show the concept. You can see more elaborated examples in the Examples section of this documentation.
Import and Export Data
Exporting and importing data in SmarkForm cannot be easier.
Let’s recall the example in the Nested forms section.
There we learnt that the ➡️
and ⬅️
buttons used in all examples in this chapter are just triggers that call the export and import actions on a subform called “demo” (their context):
- Exports the "demo" subform to the "editor" textarea (its target).
- Imports the JSON data from the "editor" textarea to the "demo" subform (its target).
This is a very handy use case for the import and export actions because it does not require any additional JavaScript code.
But this are not the only way to use the import and export actions.
Intercepting the import and export events
Below these lines you can see the exact same form with additional 💾
and 📂
buttons and a little additional JavaScript code to mock the “save” and “load” operations through window’s alert()
and prompt()
, reespectively.
<div id="myForm">
<div style="display: flex; align-items:center; gap: 1em; min-width: max(100%, 450px)">
<div data-smark='{"name":"demo"}' style="flex-grow: 1"> <h2>Model details</h2>
<p>
<label data-smark>Model Name:</label>
<input type="text" name="model" data-smark />
</p>
<p>
<label data-smark>Type:</label>
<select name="type" data-smark='{"encoding":"json"}'>
<option value='null'>👇 Please select...</option>
<!-- json encoding allow us return null values -->
<option value='"Car"'>Car</option>
<!-- ...but now we must wrap strings in double quotes -->
<!-- (it also gives us the ability to return objects and arrays) -->
<option>Bicycle</option>
<!-- ...but if we are Ok with inner text as value, we can just omit the value attribute -->
<option>Motorcycle</option>
<option>Van</option>
<option>Pickup</option>
<option>Quad</option>
<option>Truck</option>
</select>
</p>
<p>
<label data-smark>Seats:</label>
<input type="number" name="seats" min=4 max=9 data-smark />
</p>
<p>
<label data-smark>Driving Side:</label>
<input type="radio" name="side" value="left" data-smark /> Left
<input type="radio" name="side" value="right" data-smark /> Right
</p>
<p>
<label data-smark>Color:</label>
<span data-smark='{"type":"color", "name":"color"}'>
<input data-smark>
<button data-smark='{"action":"clear"}' title='Indifferent or unknown' >❌ </button>
</span>
</p>
<label>Safety Features:</label>
<fieldset data-smark='{"name":"safety","type":"form"}'>
<span>
<label><input type="checkbox" name="airbag" data-smark /> Airbag.</label>
</span>
<span>
<label><input type="checkbox" name="abs" data-smark /> ABS.</label>
</span>
<span>
<label><input type="checkbox" name="esp" data-smark /> ESP.</label>
</span>
<span>
<label><input type="checkbox" name="tc" data-smark />TC.</label>
</span>
</fieldset> </div>
<div>
<p><button
data-smark='{"action":"export","context":"demo","target":"../editor"}'
title="Export 'demo' subform to 'editor' textarea"
>➡️ </button></p>
<p><button
data-smark='{"action":"import","context":"demo","target":"../editor"}'
title="Import 'editor' textarea contents to 'demo' subform"
>⬅️ </button></p>
<p><button
data-smark='{"action":"export"}'
title="Export the whole form as JSON (see JS tab)"
>💾</button></p>
<p><button
data-smark='{"action":"import"}'
title="Import the whole form as JSON (see JS tab)"
>📂</button></p>
<p><button
data-smark='{"action":"clear", "context":"demo"}'
title="Clear the whole form"
>❌</button></p>
</div>
<textarea
cols="20"
placeholder="JSON data viewer / editor"
data-smark='{"name":"editor","type":"input"}'
style="resize: none; align-self: stretch; min-height: 8em; flex-grow: 1;"
></textarea>
</div>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));
myForm.on("BeforeAction_import", async (ev)=>{
/* Only for the whole form */
/* (avoiding to interfere with `⬅️ ` buttons */
if (ev.context.getPath() !== "/") return;
/* BONUS: Read previous value to use it as default value */
/* so that you only need to edit it. */
let previous_value = await ev.context.export();
let isObject = typeof previous_value == "object";
if (isObject) previous_value = JSON.stringify(previous_value);
/* Read new value: */
let data = window.prompt("Edit JSON data", previous_value);
if (data === null) return void ev.preventDefault(); /* User cancelled */
/* Parse as JSON, warn if invalid and set */
try {
if (isObject) data = JSON.parse(data);
ev.data = data; /* ← Set the new value */
} catch(err) {
alert(err.message); /* ← Show error message */
ev.preventDefault();
};
});
myForm.on("AfterAction_export", ({context, data})=>{
/* Only for the whole form */
/* (avoiding to interfere with `➡️ ` button) */
if (context.getPath() !== "/") return; /* Only for root */
/* Pretty print and show */
if (typeof data == "object") data = JSON.stringify(data, null, 4);
window.alert(data);
});
👉 Since 💾
and 📂
buttons are in the higher context level, in this case we used a litle JavaScript code intercepting the related events to, resepectively, show the whole form in a window.alert(...)
dialog and import a new JSON data to the whole form throught a window.prompt(...)
.
-
The JavaScript code in this example is, in fact, a little more complex than it would be needed just to avoid interfering the ‘➡️ ‘ and ‘ ⬅️ ‘ that also rely on the export and import actions.
-
And, as a BONUS, the BeforeAction_import event handler performs a soft export to prefill the prompt dialog (so that you can edit the JSON data instead of manually copying ot writing it from scratch).
- See the JS tab to see how the BeforeAction_import event handler prefills the prompt dialog with the JSON export of the whole form.
👉 Here you can:
- Repeat all the same trials as in the beforementioned Nested forms’ example (with identical results).
- Use the
💾
button to export the whole form to awindow.alert(...)
dialog. - Use the
📂
button to import new JSON data to the whole form.
Remember to check the
📝 Notes
tab for more…
🚧 TODO: Add an example showcasing the use of the target property to duplicate list items.
Submitting the form
🚧 Section still under construction… 🚧
A note on context of the triggers
Let’s return to the previous examples…
There we had the 💾
and 📂
buttons opeating on the whole form because it is their natural context.
In the case of the ➡️
, ⬅️
and ❌
buttons, they have their context explicitly set by the option of the same name.
We could have wanted to make the
💾
and📂
buttons to operate only on the demo subform.To do so, we could have set their context property to “demo”, in which case then they would have exported/imported the same data than
➡️
and⬅️
buttons.Or, alternatively, 🚀 we could just have placed them inside of that context in the markup as it is shown in the following example:
<div id="myForm">
<div style="display: flex; align-items:center; gap: 1em; min-width: max(100%, 450px)">
<div data-smark='{"name":"demo"}' style="flex-grow: 1"> <h2>Model details</h2>
<p>
<label data-smark>Model Name:</label>
<input type="text" name="model" data-smark />
</p>
<p>
<label data-smark>Type:</label>
<select name="type" data-smark='{"encoding":"json"}'>
<option value='null'>👇 Please select...</option>
<!-- json encoding allow us return null values -->
<option value='"Car"'>Car</option>
<!-- ...but now we must wrap strings in double quotes -->
<!-- (it also gives us the ability to return objects and arrays) -->
<option>Bicycle</option>
<!-- ...but if we are Ok with inner text as value, we can just omit the value attribute -->
<option>Motorcycle</option>
<option>Van</option>
<option>Pickup</option>
<option>Quad</option>
<option>Truck</option>
</select>
</p>
<p>
<label data-smark>Seats:</label>
<input type="number" name="seats" min=4 max=9 data-smark />
</p>
<p>
<label data-smark>Driving Side:</label>
<input type="radio" name="side" value="left" data-smark /> Left
<input type="radio" name="side" value="right" data-smark /> Right
</p>
<p>
<label data-smark>Color:</label>
<span data-smark='{"type":"color", "name":"color"}'>
<input data-smark>
<button data-smark='{"action":"clear"}' title='Indifferent or unknown' >❌ </button>
</span>
</p>
<label>Safety Features:</label>
<fieldset data-smark='{"name":"safety","type":"form"}'>
<span>
<label><input type="checkbox" name="airbag" data-smark /> Airbag.</label>
</span>
<span>
<label><input type="checkbox" name="abs" data-smark /> ABS.</label>
</span>
<span>
<label><input type="checkbox" name="esp" data-smark /> ESP.</label>
</span>
<span>
<label><input type="checkbox" name="tc" data-smark />TC.</label>
</span>
</fieldset>
<p>
<!-- Import and export triggers with implicit context -->
<button
data-smark='{"action":"export"}'
title="Export the whole form as JSON (see JS tab)"
>💾 Save</button>
<button
data-smark='{"action":"import"}'
title="Import the whole form as JSON (see JS tab)"
>📂 Load</button>
</p>
</div>
<div>
<p><button
data-smark='{"action":"export","context":"demo","target":"../editor"}'
title="Export 'demo' subform to 'editor' textarea"
>➡️ </button></p>
<p><button
data-smark='{"action":"import","context":"demo","target":"../editor"}'
title="Import 'editor' textarea contents to 'demo' subform"
>⬅️ </button></p>
<p><button
data-smark='{"action":"clear", "context":"demo"}'
title="Clear the whole form"
>❌</button></p>
</div>
<textarea
cols="20"
placeholder="JSON data viewer / editor"
data-smark='{"name":"editor","type":"input"}'
style="resize: none; align-self: stretch; min-height: 8em; flex-grow: 1;"
></textarea>
</div>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));
myForm.on("BeforeAction_import", async (ev)=>{
/* Only for triggers without target */
if (!! ev.target) return;
/* BONUS: Read previous value to use it as default value */
/* so that you only need to edit it. */
let previous_value = await ev.context.export();
let isObject = typeof previous_value == "object";
if (isObject) previous_value = JSON.stringify(previous_value);
/* Read new value: */
let data = window.prompt("Edit JSON data", previous_value);
if (data === null) return void ev.preventDefault(); /* User cancelled */
/* Parse as JSON, warn if invalid and set */
try {
if (isObject) data = JSON.parse(data);
ev.data = data; /* ← Set the new value */
} catch(err) {
alert(err.message); /* ← Show error message */
ev.preventDefault();
};
});
myForm.on("AfterAction_export", ({target, data})=>{
/* Only for triggers without target */
if (!! target) return;
/* Pretty print and show */
if (typeof data == "object") data = JSON.stringify(data, null, 4);
window.alert(data);
});
If you compare the JS tab with the one in fhe former one, you’ll see that there is a little difference between them.
👉 In the first one, the “BeforeActionImport” and “AfterActionExport” event handlers inhibits themselves depending on whether the context is the root form or not while, in the later, it just focus on the fact that the target is not provided.
👉 The second is a more generic approach for this kind of event handlers. But the first one serves as an alternative example showing how we can base those event handlers’ behaviour on the specific context (path) of every trigger.
🚀 Just for the sake of showing the power of event handlers, in this case, the BeforeActionImport event handler have been evolved to fill the prompt dialog with the JSON export of the form when the target is not provided.
👉 Now the 💾 Save
and 📂 Load
buttons work on the “/demo” path (that is: they only import/export the “demo” subform) just like ➡️
and ⬅️
ones do but without explicitly specifying their context. They just receive their context by the place they are in the form.
For the sake of simplicity (except for the following example) from now on, having we already demonstrated how to work with import and export actions’ events, we’ll stick to the layout of the very first example (
➡️
,⬅️
and❌
buttons targetting the “editor” textarea) that doesn’t need any additional JS code.
👌 If you want a clearer example on how the context affect the triggers, take a look to the following example:
<div id='myForm'>
<p>
<label data-smark>Name:</label>
<input name='name' data-smark>
</p>
<p>
<label data-smark>Surname:</label>
<input name='surname' data-smark>
</p>
<table>
<tr style="text-align:center">
<th>Name field:</th>
<th>Surname field:</th>
<th>Whole Form:</th>
</tr>
<tr style="text-align:center">
<td><button data-smark='{"action":"import","context":"name"}'>⬆️ Import</button></td>
<td><button data-smark='{"action":"import","context":"surname"}'>⬆️ Import</button></td>
<td><button data-smark='{"action":"import"}'>⬆️ Import</button></td>
</tr>
<tr style="text-align:center">
<td><button data-smark='{"action":"export","context":"name"}'>⬇️ Export</button></td>
<td><button data-smark='{"action":"export","context":"surname"}'>⬇️ Export</button></td>
<td><button data-smark='{"action":"export"}'>⬇️ Export</button></td>
</tr>
<tr style="text-align:center">
<td><button data-smark='{"action":"clear","context":"name"}'>❌ Clear</button></td>
<td><button data-smark='{"action":"clear","context":"surname"}'>❌ Clear</button></td>
<td><button data-smark='{"action":"clear"}'>❌ Clear</button></td>
</tr>
</table>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));
myForm.on("BeforeAction_import", async (ev)=>{
/* Read previous value: */
let previous_value = await ev.context.export();
let isObject = typeof previous_value == "object";
if (isObject) previous_value = JSON.stringify(previous_value);
let data = window.prompt(`Edit ${ev.context.getPath()}`, previous_value);
if (data === null) return void ev.preventDefault();
try {
if (isObject) data = JSON.parse(data);
ev.data = data;
} catch(err) {
alert(err.message);
ev.preventDefault();
};
});
myForm.on("AfterAction_export", ({context, data})=>{
if (typeof data == "object") data = JSON.stringify(data, null, 4);
window.alert(`Value of ${context.getPath()}: ${data}`);
});
Name field: | Surname field: | Whole Form: |
---|---|---|
👉 Notice that all Import and Export buttons (triggers) are handled by the same event handlers (for “BeforeAction_import” and “AfterAction_export”, respectively).
👉 They belong to different SmarkForm fields determined by (1) where they are placed in the DOM and (2) the relative path from that place pointed by the context property.
ℹ️ Different field types may import/export different data types (forms import/export JSON while regular inputs import/export text).
🔧 For the sake of simplicity, the BeforeAction_import event handler reads the previous value of the field (no matter its type) and provides it stringified as JSON as default value for the window.prompt() call. Making it easy to edit the value no matter if we are importing one of the text fields or the whole form.
✋ Don’t panic!!
If you felt overwhelmed by the previous example’s JavaScript code, don’t worry. It was just to show off the power of the event handlers.
Look at this other version of the former example with zero JavaScript (despite SmarkForm instantiation itself):
<div id='myForm'>
<div data-smark='{"name":"demo"}'>
<p>
<label data-smark>Name:</label>
<input name='name' data-smark>
</p>
<p>
<label data-smark>Surname:</label>
<input name='surname' data-smark>
</p>
<table>
<tr style="text-align:center">
<th>Name field:</th>
<th>Surname field:</th>
<th>Whole Form:</th>
</tr>
<tr style="text-align:center">
<td><button data-smark='{"action":"import","context":"name","target":"/editor"}'>⬆️ Import</button></td>
<td><button data-smark='{"action":"import","context":"surname","target":"/editor"}'>⬆️ Import</button></td>
<td><button data-smark='{"action":"import","target":"/editor"}'>⬆️ Import</button></td>
</tr>
<tr style="text-align:center">
<td><button data-smark='{"action":"export","context":"name","target":"/editor"}'>⬇️ Export</button></td>
<td><button data-smark='{"action":"export","context":"surname","target":"/editor"}'>⬇️ Export</button></td>
<td><button data-smark='{"action":"export","target":"/editor"}'>⬇️ Export</button></td>
</tr>
<tr style="text-align:center">
<td><button data-smark='{"action":"clear","context":"name"}'>❌ Clear</button></td>
<td><button data-smark='{"action":"clear","context":"surname"}'>❌ Clear</button></td>
<td><button data-smark='{"action":"clear"}'>❌ Clear</button></td>
</tr>
</table>
</div>
<div style="display: flex; flex-direction:column; align-items:left; gap: 1em; width: 100%">
<textarea
cols="20"
placeholder="JSON data viewer / editor"
data-smark='{"name":"editor","type":"input"}'
style="resize: none; align-self: stretch; min-height: 8em; flex-grow: 1;"
></textarea>
</div>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));
Name field: | Surname field: | Whole Form: |
---|---|---|
🚀 As you can see, the same actions can be applied to different parts of the form just by placing the triggers in the right place or explicitly setting the right path to the desired context.
👉 You can import, export or clear either the whole form or any of its fields. Try exporting / exporting / clearing the whole form or individual fields whith the help of the “JSON data viewer / editor”.
Context-Driven Keyboard Shortcuts
Section still under construction…
SmarkForm supports context-driven keyboard shortcuts, enhancing the user experience by allowing quick navigation and actions. This example will demonstrate how to configure and use these shortcuts in your forms.
🚧 Missing Example 🚧
This section is still under construction and this example is not yet available.
Example id: keyboard_shortcuts.
🙏 Thank you for your patience.
Dynamic Dropdown Options
Section still under construction…
In this example, we’ll illustrate how to create dropdown menus with dynamic options. This is particularly useful for forms that need to load options based on user input or external data sources.
🚧 Missing Example 🚧
This section is still under construction and this example is not yet available.
Example id: dynamic_dropdown.
🙏 Thank you for your patience.
Smart value coercion
Section still under construction…
🚧 Missing Example 🚧
This section is still under construction and this example is not yet available.
Example id: smart_value_coercion.
🙏 Thank you for your patience.
Advanced UX Improvements
Section still under construction…
Finally, we’ll showcase some advanced user experience improvements that SmarkForm offers, such as smart auto-enablement/disablement of controls and non-breaking unobtrusive keyboard navigation.
🚧 Missing Example 🚧
This section is still under construction and this example is not yet available.
Example id: advanced_ux.
🙏 Thank you for your patience.
Conclusion
Section still under construction…
We hope these examples have given you a good overview of what SmarkForm can do. By leveraging the power of markup-driven forms, SmarkForm simplifies the creation of interactive and intuitive forms, allowing you to focus on your application’s business logic. Feel free to experiment with these examples and adapt them to suit your specific needs.
For more detailed information and documentation, please refer to the other sections of this manual. If you have any questions or need further assistance, don’t hesitate to reach out to the SmarkForm community.
Happy form building!