Showcase

This section provides a series of working examples to demonstrate the capabilities of SmarkForm without diving into code details.

It highlights key features through examples, using short and readable code that prioritizes clarity over UX/semantics. The examples use minimal or no CSS (if any you’ll find it at the CSS tab) to show layout independence.

They go step by step from the most basic form to more advanced and fully featured ones.

👉 If you are eager to to see the full power of SmarkForm in action, you can check the 🔗 Examples section first.

👉 Nonetheless, if you are impatient to get your hands dirty, the 🔗 Quick Start is there for you.

📖 Table of Contents

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: This is where you can see the code in action.
  • Notes: Additional notes and insights for better understanding. Don't miss it‼️

✨ Additionally, in the Preview tab, yow will find handy buttons:

  • ⬇️ Export to export the form data to the JSON data viewer/editor.
  • ⬆️ Import to import data into the form from the JSON data viewer/editor.
  • ❌ Clear to reset the form to its initial state.

Basics

Simple plain form

To begin with the basics, we’ll start with a simple form that includes a few input fields.

🔗
🗒️ HTML
⚙️ JS
👁️ Preview
📝 Notes
<div id="myForm">
    <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>
const myForm = new SmarkForm(document.getElementById("myForm"));

Model details

Left Right

👉 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 (I swear).

For instance, you can:

  • Type some data in the form.
  • Export it 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.

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”.

🔗
🗒️ HTML
⚙️ JS
👁️ Preview
<div id="myForm">
    <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>
    <fieldset data-smark='{"name":"safety","type":"form"}'>
        <legend>Safety Features:</legend>
        <span>
            <label><input type="checkbox" name="airbag" data-smark /> Airbag.</label>
        </span>
        &nbsp;&nbsp;
        <span>
            <label><input type="checkbox" name="abs" data-smark /> ABS.</label>
        </span>
        &nbsp;&nbsp;
        <span>
            <label><input type="checkbox" name="esp" data-smark /> ESP.</label>
        </span>
        &nbsp;&nbsp;
        <span>
            <label><input type="checkbox" name="tc" data-smark />TC.</label>
        </span>
    </fieldset>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));

Model details

Left Right

Safety Features:         

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:

🔗
🗒️ HTML
⚙️ JS
👁️ Preview
📝 Notes
<div id="myForm">
    <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>
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 got 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 which allows us to make any HTML element to work as a regular input field.

We call the singleton pattern when we use any HTML element different from <input>, <select>, <textarea>, etc., as a regular SmarkForm field.

For this to work we only need define the data-smark property on it specifying the appropriate type and 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:

🔗
🗒️ HTML
🎨 CSS
⚙️ JS
👁️ Preview
📝 Notes
<div id="myForm">
    <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>📞 Telephone
                <span data-smark='{"action":"position"}'>N</span>
                </label>
                <button data-smark='{"action":"removeItem"}' title='Remove this phone number'></button>
                <input type="tel" data-smark>
                <button data-smark='{"action":"addItem"}' title='Insert phone number'></button>
            </li>
        </ul>
</div>
#myForm ul li {
    list-style-type: none;
}
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.
  • Prepended a button to each item to cherry-pick which items to remove.
  • Appended a button to each item to allow inserting items at a given position.
  • Returned to the default behaviour of not exporting empty items.
  • Made it sortable (by dragging and dropping items).
  • Also notice that when the max_items limit is reached, every addItem trigger, like the button is automatically disabled.
  • …Samme applies to removeItem triggers when the min_items limit is reached.

This example may look a bit bloated, but it is just to show the power and flexibility of SmarkForm trigger components.

In a real application you will be able to pick those controls that best suit your needs and use them as you like.

👉 And, again, don’t miss to check the 📝 Notes tab for more powerful insights and tips.

Deeply nested forms

Despite of usability concerns, there is no limit in form nesting depth.

In fact, all examples in this chapter are entirely built with SmarkForm itself with no additonal JS code.

🚀 Including ⬇️ Export, ⬆️ Import and ❌ Clear buttons are just SmarkForm trigger components that work out of the box.

🤔 …it’s just that part is omitted in the shown HTML source to keep the examples simple and focused on the subject they are intended to illustrate.

🕵️ Below this line you can explore the previous example again with all the HTML source code:

🔗
🗒️ HTML
⚙️ JS
👁️ Preview
<div id="myForm">
    <div style="display: flex; flex-direction:column; align-items:left; gap: 1em">
        <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>
            <fieldset data-smark='{"name":"safety","type":"form"}'>
                <legend>Safety Features:</legend>
                <span>
                    <label><input type="checkbox" name="airbag" data-smark /> Airbag.</label>
                </span>
                &nbsp;&nbsp;
                <span>
                    <label><input type="checkbox" name="abs" data-smark /> ABS.</label>
                </span>
                &nbsp;&nbsp;
                <span>
                    <label><input type="checkbox" name="esp" data-smark /> ESP.</label>
                </span>
                &nbsp;&nbsp;
                <span>
                    <label><input type="checkbox" name="tc" data-smark />TC.</label>
                </span>
            </fieldset>        </div>
        <div style="display: flex; justify-content: space-evenly">
            <span><button
                data-smark='{"action":"export","context":"demo","target":"../editor"}'
                title="Export 'demo' subform to 'editor' textarea"
                >⬇️ Export</button></span>
            <span><button
                data-smark='{"action":"import","context":"demo","target":"../editor"}'
                title="Import 'editor' textarea contents to 'demo' subform"
                >⬆️ Import</button></span>
            <span><button
                data-smark='{"action":"clear", "context":"demo"}'
                title="Clear the whole form"
                >❌ Clear</button></span>        </div>
        <textarea
            cols="20"
            placeholder="JSON data viewer / editor"
            data-smark='{"name":"editor","type":"input"}'
            style="resize: vertical; align-self: stretch; min-height: 8em; flex-grow: 1;"
        ></textarea>
    </div>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));

Model details

Left Right

Safety Features:         
  • If you look closer to the HTML source, you will see that ⬆️ Import and ⬇️ Export buttons 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 ⬇️ Export, ⬆️ Import and ❌ Clear 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.

More on lists

SmarkForm’s lists are incredibly powerful and flexible. They can be used to create complex data structures, such as schedules, inventories, or any other repeating data structure.

To begin with, another interesting use case for lists is to create a schedule list like the following example:

🔗
🗒️ HTML
⚙️ JS
👁️ Preview
<div id="myForm">
<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,"exportEmpties":true}'>
        <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>
const myForm = new SmarkForm(document.getElementById("myForm"));

to (Closed) , and

…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…

🔗
🗒️ HTML
🎨 CSS
⚙️ JS
👁️ Preview
📝 Notes
<div id="myForm">
    <table data-smark='{"type":"form","name":"schedules"}' style="width: 30em">
        <tr data-smark='{"type":"list","name":"rcpt_schedule","min_items":0,"max_items":3,"exportEmpties":true}'>
            <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,"exportEmpties":true}'>
            <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,"exportEmpties":true}'>
            <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,"exportEmpties":true}'>
            <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>

#myForm .time_slot {
    white-space: nowrap;
    width: 10em;
}

const myForm = new SmarkForm(document.getElementById("myForm"));
🛎️ Reception: (Closed) to
🍸 Bar (Closed) to
🍽️ Restaurant: (Closed) to
🏊 Pool: (Closed) to

👉 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:

🔗
🗒️ HTML
🎨 CSS
⚙️ JS
👁️ Preview
📝 Notes
<div id="myForm">
<h2>🗓️ Periods:</h2>
<div data-smark='{"type":"list","name":"periods","sortable":true,"exportEmpties":true}'>
    <fieldset style='margin-top: 1em'>
        <legend>Period
            <span data-smark='{"action":"position"}'>N</span>
            of
            <span data-smark='{"action":"count"}'>M</span>
        </legend>
        <button
            data-smark='{"action":"removeItem","hotkey":"-"}'
            title='Remove this period'
            style="float: right"
        ></button>
        <p>
          <label data-smark>Start Date:</label>&nbsp;<input data-smark type='date' name='start_date'>
          <label data-smark>End Date:</label>&nbsp;<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,"exportEmpties":true}'>
                            <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,"exportEmpties":true}'>
                            <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,"exportEmpties":true}'>
                            <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,"exportEmpties":true}'>
                            <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>

#myForm .time_slot {
    white-space: nowrap;
    width: 10em;
}

const myForm = new SmarkForm(document.getElementById("myForm"));

🗓️ Periods:

Period N of M

   

🛎️ Reception: (Closed) to
🍸 Bar (Closed) to
🍽️ Restaurant: (Closed) to
🏊 Pool: (Closed) to

👉 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.

⚡ There is no theoretical limit to the depth of nesting beyond the logical usability concerns.

👉 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 drag-drop-touch library from Bernardo Castilho:

⚡ Not yet implemented but, in a near future, SmarkForm lists will also support automatic sorting features that, in this case, would allow to automatically sort the periods by start date.

Item duplication

Adding similar items to a list—like periods—can be tedious if users have to re-enter all fields each time. To make this easier, SmarkForm lets you add a new item prefilled with data from an existing one by using an addItem trigger button with the source property set to another item in the list (for inxtance, the previous item -specified by the special path .-1-).

This way, users can duplicate an entry and just edit what’s different.

Below is the same example as before, but with an additional button to duplicate the data from the previous one.

🔗
🗒️ HTML
🎨 CSS
⚙️ JS
👁️ Preview
<div id="myForm">
<h2>🗓️ Periods:</h2>
<div data-smark='{"type":"list","name":"periods","sortable":true,"exportEmpties":true}'>
    <fieldset style='margin-top: 1em'>
        <legend>Period
            <span data-smark='{"action":"position"}'>N</span>
            of
            <span data-smark='{"action":"count"}'>M</span>
        </legend>
        <button
            data-smark='{"action":"removeItem","hotkey":"-"}'
            title='Remove this period'
            style="float: right"
        ></button>
        <button
            data-smark='{"action":"addItem","source":".-1","hotkey":"d"}'
            title='Duplicate this period'
            style="float: right"
        ></button>
        <p>
          <label data-smark>Start Date:</label>&nbsp;<input data-smark type='date' name='start_date'>
          <label data-smark>End Date:</label>&nbsp;<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,"exportEmpties":true}'>
                            <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,"exportEmpties":true}'>
                            <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,"exportEmpties":true}'>
                            <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,"exportEmpties":true}'>
                            <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>

#myForm .time_slot {
    white-space: nowrap;
    width: 10em;
}

const myForm = new SmarkForm(document.getElementById("myForm"));

🗓️ Periods:

Period N of M

   

🛎️ Reception: (Closed) to
🍸 Bar (Closed) to
🍽️ Restaurant: (Closed) to
🏊 Pool: (Closed) to

Import and Export Data

Exporting and importing data in SmarkForm cannot be easier.

Let’s recall the example showing the full HTML sourece in the Deeply nested forms section:

🔗
🗒️ HTML
⚙️ JS
👁️ Preview
<div id="myForm">
    <div style="display: flex; flex-direction:column; align-items:left; gap: 1em">
        <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>
            <fieldset data-smark='{"name":"safety","type":"form"}'>
                <legend>Safety Features:</legend>
                <span>
                    <label><input type="checkbox" name="airbag" data-smark /> Airbag.</label>
                </span>
                &nbsp;&nbsp;
                <span>
                    <label><input type="checkbox" name="abs" data-smark /> ABS.</label>
                </span>
                &nbsp;&nbsp;
                <span>
                    <label><input type="checkbox" name="esp" data-smark /> ESP.</label>
                </span>
                &nbsp;&nbsp;
                <span>
                    <label><input type="checkbox" name="tc" data-smark />TC.</label>
                </span>
            </fieldset>        </div>
        <div style="display: flex; justify-content: space-evenly">
            <span><button
                data-smark='{"action":"export","context":"demo","target":"../editor"}'
                title="Export 'demo' subform to 'editor' textarea"
                >⬇️ Export</button></span>
            <span><button
                data-smark='{"action":"import","context":"demo","target":"../editor"}'
                title="Import 'editor' textarea contents to 'demo' subform"
                >⬆️ Import</button></span>
            <span><button
                data-smark='{"action":"clear", "context":"demo"}'
                title="Clear the whole form"
                >❌ Clear</button></span>        </div>
        <textarea
            cols="20"
            placeholder="JSON data viewer / editor"
            data-smark='{"name":"editor","type":"input"}'
            style="resize: vertical; align-self: stretch; min-height: 8em; flex-grow: 1;"
        ></textarea>
    </div>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));

Model details

Left Right

Safety Features:         

There we learned that the ⬇️ Export, ⬆️ Import and ❌ Clear buttons used in all examples in this documentation are just triggers that call the export and import actions on a subform called “demo” (their context):

  • ⬇️ Export Exports the “demo” subform to the “editor” textarea (its target).
  • ⬆️ Import Imports the JSON data from the “editor” textarea to the “demo” subform (its target).
  • ❌ Clear Clears the “demo” subform (its context).

This is a very handy use case for the import and export actions because it does not require any additional JavaScript code.

But this is 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 💾 Save and 📂 Load buttons.

They are export and import triggers, but placed outside of any subform so that their natural context is the whole form.

In the JS tab there is a simple JavaScript code that:

  • Intercepts the onAfterAction_export and onBeforeAction_import events.
  • Shows the JSON of the whole form in a window.alert(...) window in the case of export (💾) action.
  • Prompts with a window.prompt(...) dialog for JSON data to import into the whole form.

🔗
🗒️ HTML
⚙️ JS
👁️ Preview
📝 Notes
<div id="myForm">
    <div style="display: flex; flex-direction:column; align-items:left; gap: 1em">
        <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>
            <fieldset data-smark='{"name":"safety","type":"form"}'>
                <legend>Safety Features:</legend>
                <span>
                    <label><input type="checkbox" name="airbag" data-smark /> Airbag.</label>
                </span>
                &nbsp;&nbsp;
                <span>
                    <label><input type="checkbox" name="abs" data-smark /> ABS.</label>
                </span>
                &nbsp;&nbsp;
                <span>
                    <label><input type="checkbox" name="esp" data-smark /> ESP.</label>
                </span>
                &nbsp;&nbsp;
                <span>
                    <label><input type="checkbox" name="tc" data-smark />TC.</label>
                </span>
            </fieldset>
        </div>
        <div style="display: flex; justify-content: space-evenly">
            <span><button
                data-smark='{"action":"export","context":"demo","target":"../editor"}'
                title="Export 'demo' subform to 'editor' textarea"
                >⬇️ Export</button></span>
            <span><button
                data-smark='{"action":"import","context":"demo","target":"../editor"}'
                title="Import 'editor' textarea contents to 'demo' subform"
                >⬆️ Import</button></span>
            <span><button
                data-smark='{"action":"clear", "context":"demo"}'
                title="Clear the whole form"
                >❌ Clear</button></span>        </div>
        <textarea
            cols="20"
            placeholder="JSON data viewer / editor"
            data-smark='{"name":"editor","type":"input"}'
            style="resize: vertical; align-self: stretch; min-height: 8em; flex-grow: 1;"
        ></textarea>
        <div style="display: flex; justify-content: space-evenly">
            <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>
        </div>
    </div>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));

myForm.on("AfterAction_export", ({context, data})=>{
    /* Only for the whole form */
    /* (avoiding to interfere with `⬇️ Export` 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);
});

myForm.on("BeforeAction_import", async (ev)=>{
    /* Only for the whole form */
    /* (avoiding to interfere with `⬆️ Import ` button */
    if (ev.context.getPath() !== "/") return;

    /* BONUS: Read previous data 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();
    };
});

Model details

Left Right

Safety Features:         

👉 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(...).

👉 See the JS tab to see how the BeforeAction_import event handler prefills the prompt dialog with the JSON export of the whole form.

👉 Remember to check the 📝 Notes tab for more…

In this case, the JS code goes a little further than the essential to:

  • Inhibit themselves when context is not the root form to avoid interferring with the ⬇️ Export and ⬆️ Import buttons.

  • Pretty-print the JSON data in the export action making it more readable.

  • Prefill the window.prompt(...) dialog with the JSON of current data in the import allowing you to edit it instead of having to write it from scratch.

👌 But you get the idea: This is just a simple mock of how you can interact with your application logic or server APIs.

Notice you can even abort the import action by calling ev.preventDefault() in case of failure or, as shown for the ❌ Clear button in the 🔗 Quick Start section, in case of user cancellation.

Submitting the form

🚧 Section still under construction… 🚧

A note on context of the triggers

As we have seen in the previous examples:

  • We can use the export and import actions to export/import data from/to any context: The whole form, any of its subforms or even a single field.

  • That context is, by default, determined by the place where the trigger is placed in the DOM tree, but it can be explicitly set by the context property of the trigger component.

  • We can use the target property to set the destination/source of that data or intercept the afterAction_export and beforeAction_import events to programatically handle the data.

For the sake of simplicity, from now on, we’ll stick to the layout of the very first example (⬇️ Export, ⬆️ Import and ❌ Clear buttons targetting the “editor” textarea) that doesn’t need any additional JS code.

That part of the layout will also be ommitted in the HTML source since we’ve already know how it works.

👌 If you want a clearer example on how the context affect the triggers, take a look to the following example:

🔗
🗒️ HTML
⚙️ JS
👁️ Preview
📝 Notes
<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: vertical; 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:

👉 Notice that all Import and Export buttons (triggers) are handled by the same event handlers (for “BeforeAction_import” and “AfterAction_export”, respectively).

👉 All Import and Export buttons (triggers) 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 –or number–).

🚀 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”.

Advanced UX Improvements

Finally, we’ll showcase some advanced user experience improvements that SmarkForm offers, such as smart auto-enabling/disabling of controls and non-breaking unobtrusive keyboard navigation among others.

Auto enabling or disabling of actions

As you may have already noticed, SmarkForm automatically enables or disables actions based on the current state of the form. For example, if a list has reached its maximum number of items specified by the max_items option, the “Add Item” button will be disabled until an item is removed.

The same happen with the “Remove Item” button when the list has reached its minimum number of items specified by min_items.

Let’s recall our Singleton List Example with just slight modifications:

  1. Keep the min_items to its default value of 1, so that the list cannot be empty.
  2. Add a little CSS to make the disabled buttons more evident.

🔗
🗒️ HTML
🎨 CSS
⚙️ JS
👁️ Preview
<div id="myForm">
    <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, "max_items":5}'>
            <li class="row">
                <label data-smark>📞 Telephone
                <span data-smark='{"action":"position"}'>N</span>
                </label>
                <button data-smark='{"action":"removeItem"}' title='Remove this phone number'></button>
                <input type="tel" data-smark>
                <button data-smark='{"action":"addItem"}' title='Insert phone number'></button>
            </li>
        </ul>
</div>
#myForm ul li {
    list-style-type: none;
}
#myForm :disabled {
    opacity: 0.4; /* Make disabled buttons more evident */
}
const myForm = new SmarkForm(document.getElementById("myForm"));

👉 Notice that the 🧹 and buttons get disabled then the list has only one item (at the beginning or afger removing enouth items to reach min_items’ value) and the same happens with the button when the list reaches its max_items limit.

Context-Driven Keyboard Shortcuts (Hot Keys)

All SmarkForm triggers can be assigned a hotkey property to make them accessible via keyboard shortcuts.

To trigger an action using a keyboard shortcut the user only needs to press the Ctrl key and the key defined in the hotkey property of the trigger.

In the following example you can use the Ctrl++ and Ctrl+- combinations to add or remove phone numbers from the list, respectively.

🔗
🗒️ HTML
🎨 CSS
⚙️ JS
👁️ Preview
<div id="myForm">
    <button data-smark='{"action":"removeItem", "context":"phones", "target":"*", "hotkey":"Delete", "keep_non_empty":true}' title='Remove unused fields'>🧹</button>
    <button data-smark='{"action":"removeItem", "context":"phones", "hotkey":"-", "keep_non_empty":true}' title='Remove phone number'></button>
    <button data-smark='{"action":"addItem","context":"phones", "hotkey":"+"}' title='Add phone number'></button>
    <label data-smark>Phones:</label>
    <ul data-smark='{"name": "phones", "of": "input", "sortable":true, "max_items":5}'>
        <li class="row">
            <label data-smark>📞 Telephone
            <span data-smark='{"action":"position"}'>N</span>
            </label>
            <button data-smark='{"action":"removeItem", "hotkey":"-"}' title='Remove this phone number'></button>
            <input type="tel" data-smark>
            <button data-smark='{"action":"addItem", "hotkey":"+"}' title='Insert phone number'></button>
        </li>
    </ul>
</div>

#myForm [data-hotkey] {
  position: relative;
  overflow-x: display;
}
#myForm [data-hotkey]::before {
  content: attr(data-hotkey);
  display: inline-block;
  position: absolute;
  top: 2px;
  left: 2px;
  z-index: 10;
  pointer-events: none;
  background-color: #ffd;
  outline: 1px solid lightyellow;
  padding: 2px 8px;
  border-radius: 4px;
  font-weight: bold;
  font-family: sans-serif;
  font-size: 0.8em;
  white-space: nowrap;
  transform: scale(1.8) translate(0.1em, 0.1em);
}
#myForm ul li {
    list-style-type: none;
}
#myForm :disabled {
    opacity: 0.4; /* Make disabled buttons more evident */
}

const myForm = new SmarkForm(document.getElementById("myForm"));

Reveal of hot keys

If you tinkered a bit with the previous example, you may have noticed that as soon as you press the Ctrl key, the related hot keys are revealed beside corresponding buttons.

🚀 This means that the user does not need to know every hotkeys in advance, but can discover them on the fly by pressing the Ctrl key.

For instance I bet you already discovered that you can use the Ctrl+Delete combination to activate the 🧹 button and remove all unused phone number fields in the list.

For this to work, a little CSS setup is needed to define how the hint will look like.

Hotkey hints are dynamically revealed/unrevealied by setting/removing the data-hotkey attribute in the trigger’s DOM node.

Check the CSS tab of the exaple above to see an example of how to style the hot keys hints.

Hotkeys and context

In SmarkForm, hotkeys are context-aware, meaning that the same hotkey can trigger different actions depending on the context in which the focus is.

If you dug a bit into the HTML source of the previous example, you may have noticed that the outer and buttons have the hotkey property set as well but, unlike the 🧹 button, they are not announced when pressing the Ctrl key.

The reason behind this is that the value of their hotkey property is the same of their inner counterparts and hotkeys are discovered from the inner focused field to the outside, giving preference to the innermost ones in case of conflict.

Let’s see the same example with a few additional fields outside the list:

If you focus one of them and press the Ctrl key, you’ll see that nothing happens. But if you navigate to any phone number in the list (for instance by repeatedly pressing the Tab key) and press the Ctrl key, you’ll see that now the hotkeys we defined are available again.

🔗
🗒️ HTML
🎨 CSS
⚙️ JS
👁️ Preview
<div id="myForm">
    <p>
        <label data-smark='{"type": "label"}'>Name:</label>
        <input name='name' data-smark='{"type": "input"}' />
    </p>
    <p>
        <label data-smark='{"type": "label"}'>Surname:</label>
        <input name='surname' data-smark='{"type": "input"}' />
    </p>
    <button data-smark='{"action":"removeItem", "context":"phones", "target":"*", "hotkey":"Delete", "keep_non_empty":true}' title='Remove unused fields'>🧹</button>
    <button data-smark='{"action":"removeItem", "context":"phones", "hotkey":"-", "keep_non_empty":true}' title='Remove phone number'></button>
    <button data-smark='{"action":"addItem","context":"phones", "hotkey":"+"}' title='Add phone number'></button>
    <label data-smark>Phones:</label>
    <ul data-smark='{"name": "phones", "of": "input", "sortable":true, "max_items":5}'>
        <li class="row">
            <label data-smark>📞 Telephone
            <span data-smark='{"action":"position"}'>N</span>
            </label>
            <button data-smark='{"action":"removeItem", "hotkey":"-"}' title='Remove this phone number'></button>
            <input type="tel" data-smark>
            <button data-smark='{"action":"addItem", "hotkey":"+"}' title='Insert phone number'></button>
        </li>
    </ul>
</div>

#myForm [data-hotkey] {
  position: relative;
  overflow-x: display;
}
#myForm [data-hotkey]::before {
  content: attr(data-hotkey);
  display: inline-block;
  position: absolute;
  top: 2px;
  left: 2px;
  z-index: 10;
  pointer-events: none;
  background-color: #ffd;
  outline: 1px solid lightyellow;
  padding: 2px 8px;
  border-radius: 4px;
  font-weight: bold;
  font-family: sans-serif;
  font-size: 0.8em;
  white-space: nowrap;
  transform: scale(1.8) translate(0.1em, 0.1em);
}
#myForm ul li {
    list-style-type: none;
}
#myForm :disabled {
    opacity: 0.4; /* Make disabled buttons more evident */
}

const myForm = new SmarkForm(document.getElementById("myForm"));

Smooth navigation

Last but not least, if you played a bit with keyboard navigation in the previous example, you may have noticed that you can navigate through the outer 🧹, and buttons using the Tab key, but you cannot navigate to the inner and buttons in every list item.

👉 This is automatically handled by SmarkForm to improve User Experience:

  • Passing throug all and buttons in every list item would have made it hard to navigate through the list.

  • SmarkForm detects that they have a hotkey defined and take them out of the navigation flow since the user only needs to press the Ctrl key to discover a handy alternative to activate them from the keyboard.

  • The outer ones, by contrast, are always kept in the navigation flow since they are outside of their actual context and their functionality may be required before having chance to bring the focus inside their context.

    • Put in other words, with min_items set to 0, it would be impossible to create the first item without resorting to the mouse.

2nd level hotkeys

Let’s recall the previous example with few personal data and a list of phones and wrap it in a list to build a simple phonebook.

As we’ve learned, we can use “+” and “-“ hotkeys to add or remove entries in our phonebook without causing any conflict. When the user presses the Ctrl key the proper hotkeys are revealed depending on the context of the current focus.

🤔 But now let’s say you filled in the last phone number in the current entry and you want to add a new contact to the phonebook without turning to the mouse. You cannot reach the outer button to add a new contact because its hotkey is the same as the inner button to add a new phone number.

🚀 For this kind of situations, SmarkForm provides a 2nd level hotkey access:

👉 Just combine the Alt key with the Ctrl key and the hotkeys in their nearest level will be automatically inhibited allowing those in the next higher level to reveal.

Try it in the following example:

🔗
🗒️ HTML
🎨 CSS
⚙️ JS
👁️ Preview
<div id="myForm">
    <div data-smark='{"type": "list", "name": "phonelist", "sortable": true}'>
        <fieldset>
            <legend>
                <span data-smark='{"action":"removeItem", "hotkey":"-"}' title='Delete this phonebook entry' style='cursor:pointer'>[➖]</span>
                <strong>
                    Contact
                    <span data-smark='{"action":"position"}'>N</span>
                </strong>
            </legend>            <p>
                <label data-smark='{"type": "label"}'>Name:</label>
                <input name='name' data-smark='{"type": "input"}' />
            </p>
            <p>
                <label data-smark='{"type": "label"}'>Surname:</label>
                <input name='surname' data-smark='{"type": "input"}' />
            </p>
            <button data-smark='{"action":"removeItem", "context":"phones", "target":"*", "hotkey":"Delete", "keep_non_empty":true}' title='Remove unused fields'>🧹</button>
            <button data-smark='{"action":"removeItem", "context":"phones", "hotkey":"-", "keep_non_empty":true}' title='Remove phone number'></button>
            <button data-smark='{"action":"addItem","context":"phones", "hotkey":"+"}' title='Add phone number'></button>
            <label data-smark>Phones:</label>
            <ul data-smark='{"name": "phones", "of": "input", "sortable":true, "max_items":5}'>
                <li class="row">
                    <label data-smark>📞 Telephone
                    <span data-smark='{"action":"position"}'>N</span>
                    </label>
                    <button data-smark='{"action":"removeItem", "hotkey":"-"}' title='Remove this phone number'></button>
                    <input type="tel" data-smark>
                    <button data-smark='{"action":"addItem", "hotkey":"+"}' title='Insert phone number'></button>
                </li>
            </ul>        </fieldset>
    </div>
    <p style="text-align: right; margin-top: 1em">
        <b>Total entries:</b>
        <span data-smark='{"action":"count", "context": "phonelist"}'>M</span>
    </p>
    <button
        data-smark='{"action":"addItem","context":"phonelist","hotkey":"+"}'
        style="float: right; margin-top: 1em"
    >➕ Add Contact</button>

</div>

#myForm [data-hotkey] {
  position: relative;
  overflow-x: display;
}
#myForm [data-hotkey]::before {
  content: attr(data-hotkey);
  display: inline-block;
  position: absolute;
  top: 2px;
  left: 2px;
  z-index: 10;
  pointer-events: none;
  background-color: #ffd;
  outline: 1px solid lightyellow;
  padding: 2px 8px;
  border-radius: 4px;
  font-weight: bold;
  font-family: sans-serif;
  font-size: 0.8em;
  white-space: nowrap;
  transform: scale(1.8) translate(0.1em, 0.1em);
}
#myForm ul li {
    list-style-type: none;
}
#myForm :disabled {
    opacity: 0.4; /* Make disabled buttons more evident */
}

const myForm = new SmarkForm(document.getElementById("myForm"));
[➖] Contact N

Total entries: M

Hidden actions

As we already learned, SmarkForm hotkeys are defined over trigger components so, to define a hotkey to perform some action, we need to place a trigger component that calls that action somewhere in the form.

This aligns well with the SmarkForm philosophy of providing a consistent functionality no matter the device or input method used. For instance, if you use a touch device, you will hardly use the keyboard, let alone a hotkey. But you will always be able to tap the button to perform the action.

Nevertheless there are exceptions where hotkeys can be convenient but flooding the form with triggers for, maybe non essential, actions would make the form cluttered more than needed.

👉 This is the case of the and buttons surrounding every phone number field in the previous examples which allowed to cherry pick the position where to remove or add a new phone: For small devices would be enough with the general and buttons that removes or adds a phone number from/to the end of the list.

💡 In this scenario we can use CSS to hide the triggers while keeping them accessible through their hotkeys.

Keep in mind that if, like in our examples, you use a ::before (or ::after) pseudo-element to show the hotkey hint, you shouldn’t use a property that completely removes it from the DOM, like display: none;, since it will also prevent the ::before or ::after pseudo-element from appearing too.

Better use visibility: hidden; or opacity: 0; to hide the button and width: 0px; and/or height: 0px; as needed to prevent them from taking space in the layout.

🔗
🗒️ HTML
🎨 CSS
⚙️ JS
👁️ Preview
<div id="myForm">
    <div data-smark='{"type": "list", "name": "phonelist", "sortable": true}'>
        <fieldset>
            <legend>
                <span data-smark='{"action":"removeItem", "hotkey":"-"}' title='Delete this phonebook entry' style='cursor:pointer'>[➖]</span>
                <strong>
                    Contact
                    <span data-smark='{"action":"position"}'>N</span>
                </strong>
            </legend>            <p>
                <label data-smark='{"type": "label"}'>Name:</label>
                <input name='name' data-smark='{"type": "input"}' />
            </p>
            <p>
                <label data-smark='{"type": "label"}'>Surname:</label>
                <input name='surname' data-smark='{"type": "input"}' />
            </p>
            <button data-smark='{"action":"removeItem", "context":"phones", "target":"*", "hotkey":"Delete", "keep_non_empty":true}' title='Remove unused fields'>🧹</button>
            <button data-smark='{"action":"removeItem", "context":"phones", "hotkey":"-", "keep_non_empty":true}' title='Remove phone number'></button>
            <button data-smark='{"action":"addItem","context":"phones", "hotkey":"+"}' title='Add phone number'></button>
            <label data-smark>Phones:</label>
            <ul data-smark='{"name": "phones", "of": "input", "sortable":true, "max_items":5}'>
                <li class="row">
                    <label data-smark>📞 Telephone
                    <span data-smark='{"action":"position"}'>N</span>
                    </label>
                    <button data-smark='{"action":"removeItem", "hotkey":"-"}' title='Remove this phone number'></button>
                    <input type="tel" data-smark>
                    <button data-smark='{"action":"addItem", "hotkey":"+"}' title='Insert phone number'></button>
                </li>
            </ul>        </fieldset>
    </div>
    <p style="text-align: right; margin-top: 1em">
        <b>Total entries:</b>
        <span data-smark='{"action":"count", "context": "phonelist"}'>M</span>
    </p>
    <button
        data-smark='{"action":"addItem","context":"phonelist","hotkey":"+"}'
        style="float: right; margin-top: 1em"
    >➕ Add Contact</button>

</div>

#myForm li.row button[data-smark] {
    visibility: hidden;
    width: 0px;
    pointer-events: none;
}
#myForm li.row button[data-smark]::before {
    visibility: visible;
}

#myForm [data-hotkey] {
  position: relative;
  overflow-x: display;
}
#myForm [data-hotkey]::before {
  content: attr(data-hotkey);
  display: inline-block;
  position: absolute;
  top: 2px;
  left: 2px;
  z-index: 10;
  pointer-events: none;
  background-color: #ffd;
  outline: 1px solid lightyellow;
  padding: 2px 8px;
  border-radius: 4px;
  font-weight: bold;
  font-family: sans-serif;
  font-size: 0.8em;
  white-space: nowrap;
  transform: scale(1.8) translate(0.1em, 0.1em);
}
#myForm ul li {
    list-style-type: none;
}
#myForm :disabled {
    opacity: 0.4; /* Make disabled buttons more evident */
}


const myForm = new SmarkForm(document.getElementById("myForm"));
[➖] Contact N

Total entries: M

This is just a simple trick and not any new SmarkForm feature, but it is worth to mention it here since it helps to build smoother and cleaner forms.

If you try to fill the former example you’ll notice that, when hitting the Ctrl key, the “+” and “-“ hotkey hints are shown beside the position of the, now hidden, and buttons.

…And, at the same time, the ones still visible in the outer context will allow touch device users to add or remove phone numbers even only to/from the end of the list.

Smart value coercion

Section still under construction…

🚧 Missing Example 🚧

This section is still under construction and this example is not yet available.

Example id: .

🙏 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: .

🙏 Thank you for your patience.

Animations

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!



Hello footer