4. Controls
Overview
Controls are the "widgets" users interact with on a page. Domkit includes a standard set of controls that can be used to build UIs:
- Button
- ButtonGroup
- Checkbox
- Combo
- FilePicker
- Label
- ListButton
- Link
- Menu
- ProgressBar
- RadioButton
- Table
- TextArea
- TextField
- ToggleButton
- Tooltip
- Tree
Button
Button is a widget that invokes an action when pressed.
Button
{
it.text = "Press me"
it.onAction { echo("Pressed!") }
}
Button
{
it.add(Elem("b") { it.text="Really Press me!" })
it.onAction { echo("Pressed!") }
}

See Button for full API details.
See also: ToggleButton, ListButton
ButtonGroup
ButtonGroup groups a set of toggle or radio buttons and handles making sure only one button in group is selected at a time.
group := ButtonGroup
{
it.add(ToggleButton { ... })
it.add(ToggleButton { ... })
it.add(ToggleButton { ... })
}
group.selIndex = 1 // set group selection
sel := group.selIndex // get current selection
See ButtonGroup for full API details.
See also: ToggleButton, RadioButton
Checkbox
Checkbox displays a checkbox that can be toggled on and off.
Checkbox {}
Checkbox { it.checked = true }
On its own, only the actual checkbox is displayed. Generally its desirable to display a text label attached to the checkbox. You can extend the click target area to this label using the wrap method:
Checkbox {}.wrap("You can click here too!")

To receive callbacks when the state changes, add an onAction event handler:
Checkbox
{
it.onAction |c| { echo("checked: $c.checked") }
}
See Checkbox for full API details.
Combo
Combo combines a TextField and ListButton into a single widget that allows a user to select from a list or manually enter a value. The internal TextField component is available with Combo. In practice you will interact with Combo the same as TextField, so Combo.field is the right place to register event callbacks such as onModify and onAction.
Combo
{
it.items = ["Alpha, "Beta", "Gamma"]
it.field.onAction |f| { echo("value: $f.val") }
}

See Combo for full API details.
FilePicker
FilePicker allows selection of files to upload from the client browser.
For simple form uploads, FilePicker is backed by an <input type="file"> so can be enabled just by giving a name when inside a <form>:
FilePicker { it->name="upload" }
To configure what file types can be selected, or to enable multiple selection:
FilePicker
{
it.accept = "image/*" // allow only images
it.multi = true // allow multiple files to upload
}
To receive callbacks when a file is selected, add an onSelect event handler:
FilePicker
{
it.onSelect |p| { ... }
}
The list of selected files can be introspected client-side via the files field:
// list of files
files := picker.files
f.name // filename of file
f.size // size of file
f.type // MIME type of file
// async load file contents as a text string client-side
f.readAsText |text| { ... }
// async load file contents and encode as a data:// URI client-side
f.readAsDataUri |uri| { ... }
The FilePicker UI can be customized by hiding the actual FilePicker DOM element and using the open method to programmatically trigger displaying the browser's native file picker:
picker := FilePicker { it.style->display="none" }
button := Button
{
it.text = "Choose Files"
it.onAction { picker.open }
}
parent.add(picker) // make sure FilePicke is actually mounted in DOM
parent.add(button)
See FilePicker and DomFile for full API details.
Label
Label simply displays text content. Labels are designed to naturally align vertically with control widgets like Button:
Label { it.text="My Label" }
See Label for full API details.
ListButton
ListButton allows user selection of a list item by showing a listbox popup when a button is pressed:
ListButton
{
it.items = ["Alpha", "Beta", "Gamma"]
it.onSelect |b| { echo("Selected $b.sel.item") }
}

By default ListButton will display items using toStr. To customize how the display element is is created, use onElem:
ListButton
{
it.items = [1,2,3,4]
it.onElem |v| { "Item #$v" }
}

See ListButton for full API details.
Link
Link creates an <a> tag for links:
Link
{
it.uri = `https://fantom.org`
it.text = "Fantom"
}
Use target to specify a link target:
Link
{
it.uri = `https://fantom.org`
it.text = "Fantom"
it.target = "_blank"
}
See Link for full API details.
Menu
Menu displays a menu of selectable MenuItems.
menu := Menu
{
MenuItem { it.text="Alpha"; it.onAction { ... } },
MenuItem { it.text="Beta"; it.onAction { ... } },
MenuItem { it.text="Gamma"; it.onAction { ... } },
MenuItem { it.text="Delta"; it.onAction { ... } },
}
menu.open(100, 100)

See Menu for full API details.
ProgressBar
ProgressBar visualizes progress of a long running operation.
ProgressBar {}
ProgressBar
{
it.val = 25 // set progress value
it.onText |p| { "${p.val}%" } // set bar text
}
ProgressBar
{
it.val = 75
it.onText |p| { "${p.val}%" }
it.onBarColor |p| { "#2ecc71" } // set bar color
}

See ProgressBar for full API details.
RadioButton
RadioButton displays a radio button:
RadioButton {}
RadioButton { it.checked = true }
On its own, only the actual radio button is displayed. Generally its desirable to display a text label attached to the radio. You can extend the click target area to this label using the wrap method:
RadioButton {}.wrap("You can click here too!")

To receive callbacks when the state changes, add an onAction event handler:
RadioButton
{
it.onAction |c| { echo("checked: $c.checked") }
}
For grouping sets of radios for exclusive selection, see ButtonGroup.
See RadioButton for full API details.
Table
Table displays a grid of rows and columns.
@Js class MyTableModel : TableModel
{
override Int numCols() { 100 }
override Int numRows() { 10 }
override Void onCell(Elem cell, Int col, Int row, TableFlags flags)
{
cell.text = "C$col:R$row""
}
}
Table
{
// Note that 'rebuild' is required to display the initial
// table, and to update the table due to any model changes
it.model = MyTableModel()
it.rebuild
}

Common table operations:
// toggle table header
table.showHeader = false
// sort a column
table.sort(2, Dir.down)
// customize zebra-striping for table rows; use empty list
// to remove background color from all rows
table.stripeClasses = [,]
table.stripeClasses = ["even", "odd"]
// enable multiple selection
table.sel.multi = true
// callback when selection changes
table.onSelect |t| { echo(t.sel.index) }
// callback when row is double-clicked; to access Selection.item
// be sure to override TableModel.item to return backing object
table.onAction |t| { echo(t.sel.item) }
// callback for cell events
table.onTableEvent("mousedown") |e| { echo(e) }
// enable custom header popup located in top-right corner of table
table.onHeaderPopup |t| { return Popup { ... }}
See API for full details: Table, TableModel, Selection, TableEvent
TextArea
TextArea allows multi-line text input.
TextArea
{
it.cols = 40
it.rows = 10
it.val = "Some text\n Here\nAnd there"
}

Use onModify to receive callbacks when text is modified in TextArea:
TextArea
{
it.onModify |f| { echo(f.val) }
}
See TextArea for full API details.
TextField
TextField allows text input.
TextField {}
TextField { it.val = "Hello, World" }
TextField { it.placeholder = "Search..." }

Use onModify to receive callbacks when text is modified in TextField:
TextField
{
it.onModify |f| { echo(f.val) }
}
Use onAction to receive callbacks when the Enter key is pressed in a TextField:
TextField
{
it.onAction |f| { echo(f.val) }
}
See TextField for full API details.
ToggleButton
ToggleButton models a boolean state toggled by pressing a button:
ToggleButton
{
it.text = "Toggle Me"
it.onAction |b| { echo("state: $b.selected") }
}
The content may be modified based on selected state by specifying elemOn and elemOff:
ToggleButton
{
it.elemOn = Elem { it.text="On" }
it.elemOff = Elem { it.text="Off" }
it.selected = false // make sure to set default state last
}
You may also pass any object to elemOn and elemOff and the Elem instance will be created using Obj.toStr:
ToggleButton
{
it.elemOn = "On"
it.elemOff = "Off"
it.selected = false
}

For grouping sets of toggle buttons for exclusive selection, see ButtonGroup.
See ToggleButton for full API details.
Tooltip
Tooltip displays a small popup when the mouse hovers over the bound node element, and is dismissed when the mouse moves out.
Tooltip
{
it.text = "More info here!"
it.bind(parent)
}
See Tooltip for full API details.
Tree
Tree visualizes TreeNodes as a series of expandable nodes.
@Js class MyTreeNode : TreeNode
{
new make(Obj item) { this.item = item }
override TreeNode[] children() { ... }
override Void onElem(Elem elem, TreeFlags flags)
{
elem.text = obj.toStr
}
private Obj obj
}
Tree
{
// Note that 'rebuild' is required to display the initial
// tree, and to update the tree due to any model changes
it.roots = [MyTreeNode(...), MyTreeNode(...), ...]
it.rebuild
}

Common tree operations:
// callback when selection changes; item is TreeNode instance
tree.onSelect |t| { echo(t.sel.item) }
// callback when row is double-clicked; item is TreeNode instance
tree.onAction |t| { echo(t.sel.item) }
// callback for node events
tree.onTreeEvent("mousedown") |e| { echo(e) }
Note that Selection.index is not valid for Tree instances.
Lazily-loading tree nodes:
@Js class LazyTreeNode : TreeNode
{
new make(Tree tree, Obj item)
{
this.tree = tree
this.item = item
}
...
override Bool hasChildren()
{
// return true if kids not loaded
kids==null ? true : kids.size > 0
}
override TreeNode[] children()
{
// return kids if already loaded
if (kids != null) return kids
// async load kids
doAsyncLoad(this) |items|
{
this.kids = items.map |i| { LazyTreeNode(tree, i) }
tree.refreshNode(this)
}
// return empty; doAsyncLoad will refresh
return TreeNode#.emptyList
}
private Tree tree
private Obj item
private LazyTreeNode[]? kids
}
See API for full details: Tree, TreeNode, Selection, TreeEvent