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.
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!
A listboxItem
has two special states:
active
.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) }
}
}
}
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 }
}
}
}
}
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" }
}
//...
}
Listbox is an OpenClose
component. There are different Flow
s 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.
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) {
//...
}
}
}
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) {
//...
}
}
}
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 } }
}
}
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
.
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.
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 |
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>
}
// }
}
}
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. |
Available in the scope of: listbox
Parameters: classes
, scope
, tag
, initialize
Default-Tag: button
Available in the scope of: listbox
Parameters: classes
, scope
, tag
, initialize
Default-Tag: label
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 ComponentValidationMessage s |
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. |
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. |