Skip to content

Widgets Data

mike-ward edited this page Jun 14, 2026 · 2 revisions

Data Widgets

Data widgets display and manipulate structured datasets. Go-gui's data layer is built for production scale: DataGrid virtualises rendering so a million-row table uses the same resources as a ten-row one.


Table

gui.Table(gui.TableCfg{...}) renders a static data table. It is the right choice for smaller datasets where every row is always visible.

gui.Table(gui.TableCfg{
    ID: "results",
    Columns: []gui.TableColumnCfg{
        {Header: "Name",  Field: "name",  Width: 200},
        {Header: "Score", Field: "score", Width: 80, Align: gui.HAlignRight},
        {Header: "Date",  Field: "date",  Width: 120},
    },
    Rows: app.TableRows,  // []map[string]string
    Sizing: gui.FillFit,
})

Columns are sortable by default; click a header to toggle ascending/descending order.


DataGrid

w.DataGrid(gui.DataGridCfg{...}) is the high-capability data grid. It virtualises row rendering — only visible rows are laid out and drawn — so it handles large datasets without manual tuning.

Feature summary:

  • Virtual rows (only visible rows rendered)
  • Multi-column sort
  • Per-column filters
  • Row selection (single or multi)
  • Inline cell editing
  • Pagination
  • Export to CSV, TSV, XLSX, PDF
  • Async data sources
w.DataGrid(gui.DataGridCfg{
    ID:    "orders",
    Sizing: gui.FillFill,
    Columns: []gui.DataGridColumnCfg{
        {
            ID:     "order_id",
            Header: "Order #",
            Width:  100,
            Sortable: true,
        },
        {
            ID:       "customer",
            Header:   "Customer",
            Width:    200,
            Sortable: true,
            Filter:   gui.DataGridFilterText,
        },
        {
            ID:       "total",
            Header:   "Total",
            Width:    100,
            Align:    gui.HAlignRight,
            Sortable: true,
        },
    },
    DataSource: app.OrdersDataSource,
    OnSelect: func(selectedIDs []string, w *gui.Window) {
        gui.State[App](w).SelectedOrders = selectedIDs
    },
})

Note the factory is w.DataGrid(...) (method on *gui.Window) rather than a package-level function, because the grid needs access to window services for async operations.


Tree

gui.Tree(gui.TreeCfg{...}) renders a collapsible hierarchical tree. Nodes can carry icons. The tree supports virtualization for large datasets and simulated lazy loading (load children on first expand).

gui.Tree(gui.TreeCfg{
    ID:      "file-tree",
    IDFocus: 1,
    Sizing:  gui.FillFit,
    Nodes: []gui.TreeNodeCfg{
        {
            Text: "src",
            Icon: gui.IconFolder,
            Nodes: []gui.TreeNodeCfg{
                {Text: "main.go",  Icon: gui.IconFile},
                {Text: "state.go", Icon: gui.IconFile},
            },
        },
        {
            Text: "docs",
            Icon: gui.IconFolder,
            Nodes: []gui.TreeNodeCfg{
                {Text: "README.md", Icon: gui.IconFile},
            },
        },
    },
    OnSelect: func(id string, _ *gui.Event, w *gui.Window) {
        gui.State[App](w).SelectedFile = id
    },
})

For a virtualized tree that handles thousands of nodes, add IDScroll:

gui.Tree(gui.TreeCfg{
    ID:       "large-tree",
    IDFocus:  2,
    IDScroll: 200,
    MaxHeight: 400,
    Sizing:   gui.FillFit,
    Nodes:    buildLargeTree(),
    OnSelect: onSelect,
})

DatePicker and DatePickerRoller

See Input Widgets — DatePicker for configuration details. DatePicker and DatePickerRoller are catalogued in the Data category because they produce structured time.Time values rather than raw strings.


Form

gui.Form(gui.FormCfg{...}) is a container with built-in validation, submit, and reset semantics. Fields register sync/async validators via w.FormField(...). The form tracks touch, dirty, and error state per field and exposes it through three slot callbacks.

gui.Form(gui.FormCfg{
    ID: "login-form",
    Content: []gui.View{
        gui.Input(gui.InputCfg{ID: "email", Placeholder: "Email"}),
        gui.Input(gui.InputCfg{ID: "password", Placeholder: "Password", HideText: true}),
        gui.Button(gui.ButtonCfg{Text: "Submit", FormSubmit: "login-form"}),
    },
    OnSubmit: func(e gui.FormSubmitEvent, w *gui.Window) {
        // e.Values is a map[string]string of all field values
        login(e.Values["email"], e.Values["password"])
    },
    ErrorSlot: func(fieldID string, issues []gui.FormIssue) gui.View {
        return gui.Text(gui.TextCfg{
            Text:      issues[0].Msg,
            TextStyle: gui.CurrentTheme().ColorError.Style(12),
        })
    },
})

Registration of a field validator — typically called once in WindowCfg.OnInit:

w.FormField("login-form", "email", gui.FormFieldCfg{
    SyncValidators: []gui.FormSyncValidator{func(ctx gui.FormCtx) []gui.FormIssue {
        if !strings.Contains(ctx.Value, "@") {
            return []gui.FormIssue{{Code: "invalid_email", Msg: "Enter a valid email"}}
        }
        return nil
    }},
})

FormCfg key fields:

Field Purpose
ID Required — identifies the form for FormField and FormSubmit
Content Form fields and action buttons
OnSubmit Called when form is submitted (button with FormSubmit = ID, or Enter)
OnReset Called on reset (button with FormReset = ID)
ErrorSlot Renders errors for a single field — called once per field with issues
SummarySlot Renders a form-level summary (total error count, pending state)
PendingSlot Renders while async validators are running
ValidateOn FormValidateOnChange, FormValidateOnBlur, FormValidateOnSubmit (default: blur+submit)
AllowInvalidSubmit True to permit submission even with validation errors
AllowPendingSubmit True to permit submission during async validation
NoSubmitOnEnter True to disable Enter-to-submit

Clone this wiki locally