Inputs
Controls that enhance plain inputs from markup: searchable selects, tri-state checkboxes, range selection for a list of checkboxes, and keyboard shortcuts. Each one starts from a class or a data attribute, with no script of your own, and form fields use the same controls.
Custom select
A select with the custom-select class becomes a searchable dropdown, built on Select2. It brings its own copy of jQuery, so the page needs none. The select keeps its name and value, and still dispatches change, so forms and your own listeners read it as a plain select.
select-no-search hides the search box, for a short list. Option groups (<optgroup>) carry over as headings in the dropdown.
<div class="ui-field-grid">
<div class="form-field">
<label class="form-label" for="uiSelectStatus">
Status
</label>
<select class="form-control custom-select" id="uiSelectStatus" name="status">
<option value="draft">
Draft
</option>
<option value="review" selected>
In review
</option>
<option value="published">
Published
</option>
</select>
</div>
<div class="form-field">
<label class="form-label" for="uiSelectPriority">
Priority, no search
</label>
<select class="form-control custom-select select-no-search" id="uiSelectPriority" name="priority">
<option value="low">
Low
</option>
<option value="normal" selected>
Normal
</option>
<option value="high">
High
</option>
</select>
</div>
<div class="form-field">
<label class="form-label" for="uiSelectTeam">
Team, grouped
</label>
<select class="form-control custom-select" id="uiSelectTeam" name="team">
<optgroup label="Product">
<option value="design">
Design
</option>
<option value="engineering">
Engineering
</option>
</optgroup>
<optgroup label="Business">
<option value="sales">
Sales
</option>
<option value="support">
Support
</option>
</optgroup>
</select>
</div>
</div>| Class | Description |
|---|---|
| custom-select | enables the control. |
| select-no-search | hides the search box. |
| select-hide-selected | hides the options already selected, in a multiple select. |
| select-dropdown-auto-width | sizes the dropdown to its options, in place of the select's width. |
| form-control-sm / form-control-lg | a smaller or larger select, with its dropdown to match. |
An <option> can carry a data-status color, shown as a dot, a data-icon with icon classes, or a data-image address, shown before its text. Inside a popup, the dropdown opens inside the dialog, so it shows above the backdrop.
Custom select with a handler
data-handler loads the options from the server as the visitor types, once data-minimum-input-length characters are in the search box. The control sends the search text as term, and the handler returns the matching options in a results array of id and text pairs. data-placeholder shows a hint while nothing is picked and adds a button to clear the choice.
<div class="form-field ui-narrow">
<label class="form-label" for="uiSelectCountry">
Country
</label>
<select
class="form-control custom-select"
id="uiSelectCountry"
name="country"
data-handler="onGetCountryOptions"
data-minimum-input-length="1"
data-placeholder="Search countries..."></select>
</div><?php
namespace App\Controllers\Reference\UiController;
/**
* InputHandlers holds the AJAX handlers behind the Inputs page.
*/
trait InputHandlers
{
/**
* onGetCountryOptions returns the countries matching the search term in the custom select's result format.
*
* @return array{results: array<int, array{id: string, text: string}>}
*/
public function onGetCountryOptions(): array
{
$term = mb_strtolower(trim((string) input('term')));
$results = collect($this->getCountryNames())
->filter(fn (string $name) => $term === '' || str_contains(mb_strtolower($name), $term))
->map(fn (string $name, string $code) => ['id' => $code, 'text' => $name])
->values()
->all();
return ['results' => $results];
}
/**
* getCountryNames returns the countries the custom select demo searches, keyed by code.
*
* @return array<string, string>
*/
protected function getCountryNames(): array
{
return [
'ar' => 'Argentina',
'au' => 'Australia',
'at' => 'Austria',
'be' => 'Belgium',
'br' => 'Brazil',
'ca' => 'Canada',
'dk' => 'Denmark',
'fr' => 'France',
'de' => 'Germany',
'jp' => 'Japan',
'nl' => 'Netherlands',
'nz' => 'New Zealand',
'pt' => 'Portugal',
'kr' => 'South Korea',
'es' => 'Spain',
'za' => 'South Africa',
];
}
}Each result can also set disabled or selected, or status, icon and image as the option data attributes do. A result with text and a children array of results is an option group, and a pagination key with more: true makes the dropdown ask for the next page as the visitor scrolls, sending its number as page. A plain array of values keyed by id works too, though JavaScript does not keep the order of its keys, so prefer the list. The results can also go under a result key.
| Attribute | Description |
|---|---|
| data-handler | the AJAX handler that returns the options. |
| data-minimum-input-length | how many characters to type before the handler runs. |
| data-ajax--delay | how long to wait after the last key press before asking, in milliseconds. |
| data-request-data | extra values sent with each request, as key: value pairs. |
| data-placeholder | a hint shown while nothing is picked, with a button to clear the choice. |
| data-token-separators | characters, separated by |, that turn the typed text into a new tag, for a select that accepts its own values. |
| data-language | the language of Select2's own messages. |
Select2 reads its other options from data-* attributes as well, as described in its documentation.
Tri-state checkbox
A form-field with the is-indeterminate class holds a checkbox with three states, which a click cycles through: unchecked, mixed and checked. data-checked on the input sets the starting state: 0 unchecked, 1 mixed and 2 checked. Without it, the input's checked attribute decides.
<div class="form-field is-indeterminate">
<input type="checkbox" class="check" id="uiTriState" name="notifications" value="1" data-checked="1">
<label for="uiTriState">
Email notifications: click to cycle through off, some and all
</label>
</div>The control updates data-checked on each click before the input's change event fires, so a change listener reads the new state from input.dataset.checked. A mixed checkbox is never checked, so it is left out of the posted form, like an unchecked one.
Checkbox range selection
Hold Shift and click a checkbox to set every checkbox between it and the one clicked before it, to the state that first checkbox was left in. Call jax.checkboxRangeRegisterClick() from your own click listener with the event, a selector for the element around each checkbox and a selector for the checkbox inside it. The elements around the checkboxes must be siblings, such as the li items of one list or the rows of a table. Each checkbox it sets dispatches change.
<ul class="list-unstyled mb-0" id="uiWeekdays">
@foreach (['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'] as $day)
<li class="form-field mb-2">
<input type="checkbox" class="check" id="uiDay{{ $day }}" name="days[]" value="{{ $day }}">
<label for="uiDay{{ $day }}">
{{ $day }}
</label>
</li>
@endforeach
</ul>
<script>
document.getElementById('uiWeekdays').addEventListener('click', (ev) => {
if (ev.target.matches('input[type=checkbox]')) {
jax.checkboxRangeRegisterClick(ev, 'li', 'input[type=checkbox]');
}
});
</script>The list widget uses the same helper for its row checkboxes.
| Argument | Description |
|---|---|
| ev | the click event. |
| containerSelector | a selector for the element around each checkbox, such as li or tr. |
| checkboxSelector | a selector for the checkbox inside it. |
Input hotkey
data-hotkey clicks any element from the keyboard, so the shortcut does what a click does, whether the element runs a handler or follows a link. Separate several shortcuts with commas, such as ctrl+s, cmd+s. The hotkey argument of the Ui buttons renders this attribute.
<button
type="button"
class="btn-solid theme-secondary"
data-hotkey="alt+shift+k"
data-request="onRefreshData"
data-request-flash>
Refresh (Alt + Shift + K)
</button>
<a href="{{ route('reference.ui.buttons') }}" class="btn-link" data-hotkey="alt+shift+b">
Go to buttons (Alt + Shift + B)
</a>A shortcut is one key, with any of the modifiers before it, joined by +. The modifiers must match exactly, so alt+shift+k does not fire for Ctrl + Alt + Shift + K.
- Modifiers:
shift,ctrl,alt(oroption) andcmd(orcommandormeta). - Keys: a letter or digit,
esc,tab,space,enter(orreturn),backspace,delete,insert,home,end,pageup,pagedown,left,up,right,down, andf1tof12.
The shortcut only fires while the element is visible, so a hotkey on a hidden tab or a closed menu does nothing. While a popup is open, only the hotkeys inside the top popup fire.
| Attribute | Description |
|---|---|
| data-hotkey | the shortcuts that click the element, separated by commas. |
| data-hotkey-visible | false fires the shortcut even while the element is hidden. Default: true. |
| data-hotkey-target | a selector for the element that listens for the keys. Default: html. |