Dialog
Dialog asks the app user something without leaving the page: a confirmation, a short form. Its body is only computed while it is open, so the query behind "delete these 3 rows?" does not run on every rerun.
Usage
func Dialog(c *tgframe.Container, title string, conf ...*DialogConf) *DialogContainer
c: Parent container.title: The heading of the dialog.conf: Optional configuration, at most one.
// DialogConf is the configuration for the Dialog component.
type DialogConf struct {
tgframe.Base // ID
// Width is DialogWidthSmall (default), DialogWidthMedium or
// DialogWidthLarge. Anything else panics.
Width string
// Dismissible lets the app user close the dialog with X, ESC or a click
// outside, default on. Set it with SetDismissible.
Dismissible *bool
}
Dismissible is a pointer, against the positive naming the other confs use
for their bools, because the default is on: a plain bool's zero value would
mean "not dismissible", which is the rarer of the two. Set it through the
helper rather than taking the address of a variable:
(&tgcomp.DialogConf{}).SetDismissible(false)
The dialog hands back a handle:
// Open opens the dialog, from anywhere in the run.
func (d *DialogContainer) Open()
// Close closes the dialog.
func (d *DialogContainer) Close()
// IsOpen reports whether the dialog is open, as of this point in the run.
func (d *DialogContainer) IsOpen() bool
// With writes the body, and calls f only while the dialog is open.
func (d *DialogContainer) With(f func(c *tgframe.Container))
Whether the dialog is open is kept by the page rather than the client, so a
dialog claims an id derived from its title. Two dialogs with the same title
collide; give one of them a Conf.ID.
Example
d := tgcomp.Dialog(p.Main, "Delete confirm")
if tgcomp.Button(p.Main, "Delete") {
d.Open()
}
d.With(func(c *tgframe.Container) {
tgcomp.Text(c, "Delete "+name+"?")
if tgcomp.Button(c, "Yes, delete") {
remove(name)
d.Close()
}
})
Handle the trigger first, then With
With is what draws the body, so whatever opens the dialog has to be handled
above it, as in the example. Open() still works below With — the dialog is
open on the client the moment it is called, wherever in the run that is,
because being open is a property of the dialog rather than "was the body
sent". But the body of that run is empty: With already went by while the
dialog was closed. It appears on the next run.
Close() is the mirror image. The body of the run it is called in has already
been sent, so the client is told to close separately and keeps what it holds
until the run ends. The next run does not write the body, and the client drops
it then. Nothing has to cut the run short.
Widget state while closed
Widgets inside the body keep their state while the dialog is closed, the same
as any component hidden behind an if: a closed dialog does not write them,
and a page only gives an id's state back when something takes it off the
screen. So a half-filled form is still half-filled when the dialog is reopened.
To throw that state away instead, write the body into an
Empty slot and clear it when the dialog closes: clearing a slot
gives back the ids it held, and the state under them goes with it.
Rendering
The dialog is a portal: it covers the window from wherever it is written, so
one declared in p.Sidebar still darkens the whole page rather than the side
column. A closed dialog renders as an empty portal and takes up no room.
// A dialog is a portal wherever it is written, so one declared in the
// sidebar covers the whole window rather than the side column.
sideDialog := tgcomp.Dialog(p.Sidebar, "From the sidebar",
(&tgcomp.DialogConf{Width: tgcomp.DialogWidthMedium}).
SetDismissible(false))
if tgcomp.Button(p.Sidebar, "Open the sidebar dialog") {
sideDialog.Open()
}
sideDialog.With(func(c *tgframe.Container) {
tgcomp.Text(c, "Declared in the sidebar, shown over the page.")
if tgcomp.Button(c, "Close the sidebar dialog") {
sideDialog.Close()
}
})
Two dialogs may be open at once, and the one opened later is drawn over the
one opened earlier, whichever order the page writes them in. ESC and a click
outside reach that topmost dialog only, so dismissing it leaves the one under
it open; a dialog with Dismissible off on top swallows them rather than
letting the dialog beneath take them.
A popover opened inside a dialog counts as being on top of it, so ESC closes the popover first and the dialog on the press after.
d := tgcomp.Dialog(p.Main, "Delete confirm")
why := tgcomp.Dialog(p.Main, "What deleting does")
d.With(func(c *tgframe.Container) {
if tgcomp.Button(c, "What does this do?") {
why.Open()
}
})
why.With(func(c *tgframe.Container) {
tgcomp.Text(c, "The rows are removed for good.")
})
why.Open() is called from d's body, which is above why.With, so the
second dialog's body is drawn in the same run it opens in.