The essentials, each as ONE live picker you configure with control switches — selection & input mode, content & display, and groups — plus declarative markup, the scroll-to API, and filter-vs-navigate search.
🌐 client — runs in the browser (element JS API) 🖥️ server — LiveView round-trip
One picker, many behaviors. Flip between multi- and single-select, add the inline
clear (✕) and the count badge, change the search_input_mode (a
readonly or hidden search gives keyboard-only navigation),
toggle close_on_select, and switch text direction (LTR / RTL).
phx-update="ignore" so morphdom stays out of the
component's shadow-managed DOM — which means a control change here
remounts the element with the new attributes instead of patching
them in place, so the selection resets to the seeded value={~w(js ts)}
on each toggle. (For runtime option/value changes without a remount, use
push_update/3; see BU05b.)
Icons and subtitles on the option rows, plus how the current selection is shown in the
control: the badges_display_mode, where badges sit
(badges_position), and the order the selected items appear in
(selected_order).
selected_order="custom" needs a comparator (selectedOrderCompareCallback),
a JS-only callback — see the Custom Rendering page. Same for the group custom-label and
ratio-counter callbacks in BU03.
Group options under headers with a group member. Each group header shows a
count of that group's selected items (in any mode — open the dropdown
to see it). Turn on per-group select-all
(group_select_mode="cascade") to also put a tristate checkbox on each
header that toggles the whole group — the group name is never itself a value, so the
selection carries member values only and a partially-selected group reads indeterminate.
Turning on custom labels renders the header via
renderGroupLabelContentCallback, whose 2nd arg carries the per-group
selection — here it writes GROUP — N remaining
(selectableCount − selectedCount).
renderGroupLabelContentCallback / getCountLabelCallback are
JS function props (no attribute equivalent), installed here by a small
Bu03Callbacks hook on (re)mount — see the Custom Rendering page for more
callback examples.
Pass inline <option>/<optgroup> children with data-icon · data-subtitle · selected
Use standard HTML <option>
elements - no JavaScript needed for simple cases! The component reads value, selected, and data-* attributes from each option.
data-icon attribute
<optgroup> for grouping
data-subtitle for descriptions
renderGroupLabelContentCallback
to customize group headers
JS-only element methods (no wrapper attribute): el.scrollToGroup(name)
· el.scrollToValue(value)
· el.scrollToIndex(i)
· el.clearSearch() — wired in the inline <script>
Imperative commands that bring an option or group into view. Pair with open()
for an "open + jump" gesture (deep-linking to a member, revealing the active row on open).
They work in floating, mobile fullscreen, virtual-scroll and tree modes and return
false
when the target isn't in the current filtered list. A companion clearSearch()
reveals an option a search had filtered out so you can then scroll to it.
scrollToGroup centers the group header.
el.open();
el.scrollToGroup('backend'); // header centered (or the group's first row in virtual mode)
el.scrollToValue('redis'); // → false if filtered out by the current search
el.scrollToIndex(0);
// Reveal a filtered-out option, then scroll to it:
el.clearSearch();
el.scrollToValue('jp');
The SAME commands as BU05, but driven from the LiveView with
Keenmate.WebMultiselect.push_command/3 — no inline
<script>. The element only needs hook=true.
Every button below is a plain phx-click: the server receives it and
replies with a push_command that opens/closes/scrolls the widget. The
"auto-open on entry" widget uses ready_event= — when the picker signals
it has finished building, the server opens it and jumps to a group, with zero user
interaction. This is the reliable "land on the page with it already open + scrolled"
pattern.
push_command/3.
ready event fired — no click. (Reset by reloading the page.)
Pinned to mobile_presentation="floating" so the automatic
open-on-entry stays a dismissible dropdown on phones instead of taking over
the screen with the fullscreen sheet.
(pick an option anywhere on this page)