Listbox

A list box offers the user the opportunity to select an option from a given set. The selection options are normally only visible when the list box is active.

Basic Example

A list box is created by the factory function fun <T> listbox(). T is the data type of the choices to be offered, such as a domain data type like movie characters.

By clicking on the building block created by listboxButton or Space with focused listboxButton, the selection list is displayed. The listboxButton often shows the currently selected item.

It is mandatory to specify a data stream or a store of type T as data binding via the value property. The component supports two-way data binding, i.e. it reflects a selected element from the outside by a Flow<T> but also emits the updated selection to the outside by some Handler.

You can navigate within the selection list using the keyboard. By Enter, Space or a mouse click an item is selected. If the list box loses focus or the user clicks outside the selection list, ths box is hidden.

The selection list is generated by the listboxItems block factory. Within this scope the individual options are defined by listboxItem, which receives as the first parameter the value that represents this option. A typical pattern is therefore the use of a loop in which this factory function is called accordingly.

Beware: Entries cannot be removed from the list box. Once an entry is added to the list by listboxItem, it will remain inside forever. So never use some reactive pattern for populating the list box as some Flow<List<T>> combined with some call to renderEach or alike!

If the entries change, you must re-render the whole component.

// some domain type for this example, a collection to choose from, and an external store
val characters = listOf("Luke", "Leia", "Han",)
val bestCharacter = storeOf("Luke", job = Job())

listbox<String> {
// set up (two-way) data binding
value(bestCharacter)

listboxButton {
span { value.data.renderText() }
svg { content(HeroIcons.selector) }
}

listboxItems {
// using a loop is a typical pattern to create the options
characters.forEach { entry ->
listboxItem(entry) { // needs to be created for each selectable option
span { +entry }
}
}
}
}

Do not forget to include a portalRoot()-call at the end of your initial RenderContext as explained here!

Styling the active or selected Item

A listboxItem has two special states:

  • if the item was navigated to by keyboard or mouse movement, it is active.
  • A click, Enter or Space select the active. It is then selected.

In the scope of a listboxItem, active and selected are available as Flow<Boolean> to apply styling or to render or hide certain elements accordingly to the state:

listboxItem(entry) {
//change fore- und background-color is item is active
className(active.map {
if (it) "text-amber-900 bg-amber-100" else "text-gray-300"
})

span { +entry }

//render checked-icon only if item is selected
selected.render {
if (it) {
svg { content(HeroIcons.check) }
}
}
}

Disable Items

The individual items in the selection list can be disabled statically or dynamically. In the scope of a listboxItem the Handler<Boolean> disable is available for this. It can be called up directly or used to dynamically set the disabled status of an item depending on an external data flow. The disabled state of the item is also available as Flow<Boolean> to style the item depending on it, for example.

val characters = listOf(
"Luke" to true,
"Leia" to false,
"Chewbakka" to false,
"Han" to false
)

val bestCharacter = storeOf("Luke", job = Job())

listbox<String> {
value(bestCharacter)

//...

listboxItems {
characters.forEach { (entry, disabledState) ->
listboxItem(entry) {
//set disabled state
disable(disabledState)

//change text-color if item is disabled
className(disabled.map {
if (it) "text-gray-300" else "text-gray-800"
})

span { +entry }
}
}
}
}

Add a Label

The list box can be supplemented with a label using listboxLabel, e.g. for use in forms or for a screen reader:

listbox<String> {
//...
listboxLabel {
span { +"select best character of franchise" }
}
//...
}

Open State

Listbox is an OpenClose component. There are different Flows and Handler like opened in its scope available to control the open state of a list box based on state changes.

The opening state of the list box can be bound to an external store via data binding, e.g. to always display the list box.

Transitions

Showing and hiding the selection list can be easily animated with the help of transition:

listboxItems {
transition(opened,
enter = "transition duration-100 ease-out",
enterStart = "opacity-0 scale-95",
enterEnd = "opacity-100 scale-100",
leave = "transition duration-100 ease-in",
leaveStart = "opacity-100 scale-100",
leaveEnde = "opacity-0 scale-95"
)

characters.forEach { (entry, disabledState) ->
listboxItem(entry) {
//...
}
}
}

Positioning

listboxItems is a PopUpPanel and therefore provides in its scope some configuration options, e.g. to control the position or distance of the list box from the listboxButton as a reference element:

listboxItems {
placement = PlacementValues.top
addMiddleware(offset(20))

characters.forEach { (entry, disabledState) ->
listboxItem(entry) {
//...
}
}
}

Validation

Data binding allows the Listbox component to grab the validation messages and provide its own building block listboxValidationMessages that is only rendered when there are some messages. These messages are exposed within its scope as a data stream msgs.

listbox<String> {
value(bestCharacter)
listboxButton { /* ... */ }

listboxItems {
characters.forEach { entry ->
listboxItem(entry) {
span { +entry }
}
}
}

listboxValidationMessages(tag = RenderContext::ul) {
msgs.renderEach { li { +it.message } }
}
}

Focus Management

If the selection list of the list box is open, the focus is on the listboxItems element. If it loses focus again, the select list is closed and the focus returns to the listboxButton.

Mouse Interaction

A click on the listboxButton toggles the state of the selection list. A click outside the opened selection list closes this. If the mouse is moved over an item in the open list, it becomes active marked. Clicking on an item when the list is open selects it and closes the list.

Keyboard Interaction

Command Description
Enter Space when listBoxButton is focused Opens listbox and activates current selected item
when listbox is open Activates previous / next item
Home End when listbox is open Activates first / last item
A-Z a-z when listbox is open Activates first item beginning with according letter
Esc when listbox is open Closes listbox
Enter Space when listbox is open Selects active item

API

Summary / Sketch

listbox<T>() {
val value: DatabindingProperty<T>
// inherited by `OpenClose`
val openState: DatabindingProperty<Boolean>
val opened: Flow<Boolean>
val close: SimpleHandler<Unit>
val open: SimpleHandler<Unit>
val toggle: SimpleHandler<Unit>

listboxButton() { }
listboxLabel() { }
listboxValidationMessages() {
val msgs: Flow<List<ComponentValidationMessage>>
}
listboxItems() {
// inherited by `PopUpPanel`
var placement: Placement
var strategy: Strategy
var flip: Boolean
var skidding: Int
var distance: int

// for each T {
listboxItem(entry: T) {
val index: Int
val selected: Flow<Boolean>
val active: Flow<Boolean>
val disabled: Flow<Boolean>
val disable: SimpleHandler<Boolean>
}
// }
}
}

listbox

Parameters: classes, id, scope, tag, initialize

Default-Tag: div

Scope property Typ Description
value DatabindingProperty<T> Mandatory (tow-way) data binding for a selected item.
openState DatabindingProperty<Boolean> Optional (two-way) data binding for opening and closing.
opened Flow<Boolean> Data stream that provides Boolean values related to the "open" state. Quite useless within a list box, as it is always true
close SimpleHandler<Unit> Handler to close the list box from inside. Should not be used, as the component handles this internally.
open SimpleHandler<Unit> handler to open; does not make sense to use within a list box!
toggle SimpleHandler<Unit> handler for switching between open and closed; does not make sense to use within a list box.

listboxButton

Available in the scope of: listbox

Parameters: classes, scope, tag, initialize

Default-Tag: button

listboxLabel

Available in the scope of: listbox

Parameters: classes, scope, tag, initialize

Default-Tag: label

listboxValidationMessages

Available in the scope of: listbox

Parameters: classes, scope, tag, initialize

Default-Tag: div

Scope property Typ Description
msgs Flow<List<ComponentValidationMessage>> provides a data stream with a list of ComponentValidationMessages

listboxItems

Available in the scope of: listbox

Parameters: classes, scope, tag, initialize

Default-Tag: div

Scope property Typ Description
placement Placement Defines the position of the building block, e.g. Placement.top, Placement.bottomRight, etc. Default is Placement.auto. The presumably best position is determined automatically based on the available visible space.
strategy Strategy Determines whether the block should be positioned absolute (default) or fixed.
flip Boolean If the block comes too close to the edge of the visible area, the position automatically changes to the other side if more space is available there.
skidding Int Defines the shifting of the selection list along the reference element in pixels. The default value is 0.
distance Int Defines the distance of the selection list from the reference element in pixels. The default value is 10.

listboxItem

Available in the scope of: listboxItems

Parameters: classes, scope, tag, initialize

Default-Tag: button

Scope property Typ Description
index Int The index within the items.
selected Flow<Boolean> This data stream provides the selection state of the managed option: true the option is selected, false if not.
active Flow<Boolean> This data stream indicates whether an item has focus: true the option has focus, false if not. Only one option can have focus at a time.
disabled Flow<Boolean> This data stream indicates whether an entry is active (false) or inactive (true).
disable SimpleHandler<Boolean> This handler enables or disables an entry.
Edit this page on Github