ToolGUI

Go Reference

This Go package provides a framework for rapidly building interactive data dashboards and web applications. It aims to offer a similar development experience to Streamlit for Python users.

⚠️ Under Development:

The API for this package is still under development, and may be subject to changes in the future.

Demo page: https://toolgui-demo.fly.dev/

screen-shot

Server-Client

Step by step Hello World

  1. Create main.go:
package main

import (
	"github.com/mudream4869/toolgui/toolgui/tgcomp"
	"github.com/mudream4869/toolgui/toolgui/tgexec"
	"github.com/mudream4869/toolgui/toolgui/tgframe"
)

func main() {
	app := tgframe.NewApp()
	app.AddPage("index", "Index", func(p *tgframe.Params) error {
		tgcomp.Text(p.Main, "Hello world")
		return nil
	})

	e := tgexec.NewWebExecutor(app).StartService(":3001")
}

  1. Create go.mod and download toolgui:
go mod init toolgui-helloworld
go mod tidy
  1. Run helloworld
go run main.go

Explain

  • Create a ToolGUI App: The App intance include the info that app needs.
app := tgframe.NewApp()
  • Register a page in App: Tell App instance, we will have a page in the App.
    • index is the name.
    • Index is the title.
app.AddPage("index", "Index", ...)
  • The Page Func: Draw a text component in the Main container.
func(p *tgframe.Params) error {
	tgcomp.Text(p.Main, "Hello world")
	return nil
}
  • WebExecuter: The App only includes the logic of app, but not includes GUI. The we executer provide web server GUI interface for App.
tgexec.NewWebExecutor(app).StartService(":3001")

How it works?

Basic

ui-state-pagefunc

The key concept is that in the Page Function, the UI component interact immediately with the running logic.

For example:

if tgcomp.Button(p.State, p.Main, "Click me") {
    tgcomp.Text(p.Main, "Hi")
}

In the first call (For example: When the web page is entering.), The Click me button will render, but the Hi will not render. Since the button is not clicked in the first round.

When the user clicks the button, the Page Function will be called again. In this round, tgcomp.Button will return true and Hi will render.

How?

Since we delcare a button which its id is Click me, and when the button clicked, we store true with key Click me into p.State.

When entering tccinput.Button, it will check if there is any data store in p.State with key Click me.

Server-Client Architecture

server arch

Server-Client need to handle a more complex part: multiple state for multiple users. Hence we need a state_id for each state.

The server part is responsible for the state pool.

App

The App includes the info of

  1. Navbar
  2. Pages: The App hold a ordered map from page name to page's data.

layout

Navbar

Here is the navbar of toolgui-demo.

navbar

The left part will be buttons which nav to the pages in App. The right part will be two button:

  1. Rerun: Rerun the Page Func without changing any state.
  2. Dark/Light Mode Switch

The button of current page will be highlighted with a specific style.

Page

Parameters

We can config the page's:

  1. Name: Or ID. The name of the page should be unique. In the web GUI provider, the name of a page will be used as the path of the page.

  2. Title: In the web GUI provider, The title of a page will be used as the title of the page and text of button on the navbar.

  3. Emoji: Optional. The emoji will be used as a icon on navbar and browser favicon.

The config type in package is:

type PageConfig struct {
	Name  string `json:"name"`
	Title string `json:"title"`
	Emoji string `json:"emoji"`
}

Page Function

The function signature of Page Function is defined as:

type RunFunc func(p *Params) error

Where Params contains these parameters to operate the page:

type Params struct {
	State   *State
	Main    *Container
	Sidebar *Container
}

Main and Sidebar is the root container component of the main and sidebar part shown in the layout image.

We can show a text in the Main container by:

tgcomp.Text(p.Main, "Hello")

The State in Params provided for

  1. The component that need to pass state. For example: the checked state of checkbox.
  2. The state that user need to store. For example: The todo items in the Todo App.

Example for adding a page

  • No emoji icon
app.AddPage("index", "Index", Main)
  • With a emoji icon
app.AddPageByConfig(&tgframe.PageConfig{
	Name:  "page2",
	Title: "Page2",
	Emoji: "🔄",
}, Page2)

State Storage

  • Faster access: Frequently used data can be retrieved from the cache much faster than recalculating it or fetching it from an external source every time. This improves the application's overall performance.

  • Reduced resource usage: By avoiding redundant calculations and external data fetching, the app can conserve resources like CPU and network bandwidth.

App Cache

An app-level cache stores data for the entire duration of the application's execution, from launch to termination.

This cache is not provided by ToolGUI and needs to be implemented by the developer.

For example:

type App1 struct {
	sync.Map data
}

func (app *App) QueryData(key string) {
	if v, ok := data.Load(key); ok{
		return v
	}

	// ...
	v, _ := data.LoadOrStore(key, calVal)
	return v
}

func (app *App) Page1(...) {
	tgcomp.text(app.QueryData(...))
}

Additional Considerations:

  • Cache Invalidation: As the application runs, the underlying data sources might change. It's crucial to have a strategy to invalidate cached data when necessary to ensure consistency. This could involve periodically refreshing the cache or implementing mechanisms to detect changes in the source data.

  • Memory Usage: App-level caches can consume memory. It's essential to choose appropriate data structures and cache eviction policies to balance performance gains with memory constraints.

State Cache

State-level cache stores data specific to the current view or "page" displayed to the user. This data lost when the user navigates away from the page or refreshes it.

Here we provide a state-level TODO App example. It stores list of todos in the state:

func Main(p *tgframe.Params) error {
	tgcomp.Title(p.Main, "Example for Todo App")

	var todos []string
	err := p.State.GetObject("todos", &todos)
	if err != nil {
		return err
	}

	inp := tgcomp.Textbox(p.State, p.Main, "Add todo")
	if tgcomp.Button(p.State, p.Main, "Add") {
		todos = append(todos, inp)
		p.State.Set("todos", todos)
	}

	for i, todo := range todos {
		tgcomp.TextWithID(p.Main,
			fmt.Sprintf("%d: %s", i, todo),
			fmt.Sprintf("todo_%d", i))
	}

	return nil
}

Components

Component Tree / Forest

When every component created, we need to assign where it should generate. The root will be Main Container or Sidebar Container. Hence the relation between components is trees.

For example, if a page function implements as:

tgcomp.Text(p.Main, "Text")
tgcomp.Button(p.State, p.Main, "Button")
box := tgcomp.Box("box")
tgcomp.Text(box, "Text1")
tgcomp.Text(box, "Text2")

Then the Component Tree will be:

component tree example

Content Components

The content components show basic content. It doesn't return any value to user.

The demo page can be found here: https://toolgui-demo.fly.dev/content

Title

Title component display a title.

API

func Title(c *tgframe.Container, text string)
func TitleWithID(c *tgframe.Container, text string, id string)
  • c is Parent container.
  • text is the title text.
  • id is a user specific element id.

Example

tgcomp.Title(p.Main, "Title")

title component

Subtitle

Subtitle component display a subtitle.

API

func Subtitle(c *tgframe.Container, text string)
func SubtitleWithID(c *tgframe.Container, text string, id string)
  • c is Parent container.
  • text is the subtitle text.
  • id is a user specific element id.

Example

tgcomp.Subtitle(p.Main, "Subtitle")

subtitle component

Text

Text component display a text.

API

func Text(c *tgframe.Container, text string)
func TextWithID(c *tgframe.Container, text string, id string)
  • c is Parent container.
  • text is the text.
  • id is a user specific element id.

Example

tgcomp.Text(p.Main, "Text")

text component

Image

Image component display an image.

API

func Image(c *tgframe.Container, img image.Image)
func ImageByURI(c *tgframe.Container, uri string)
  • c is Parent container.
  • img is the image.
  • uri is the URI form of the image. Example:
    • URL: https://http.cat/100
    • base64 uri: data:image/png;base64,...

Example

tgcomp.ImageByURI(p.Main, "https://http.cat/100")

image component

Divider

Divider component display a horizontal line.

API

func Divider(c *tgframe.Container)
func DividerWithID(c *tgframe.Container, id string)
  • c is Parent container.
  • id is a user specific element id.

Example

tgcomp.Divider(p.Main)

divider component

Link

Link component display a link.

API

func Link(c *tgframe.Container, text, url string)
func LinkWithID(c *tgframe.Container, text, url, id string)
  • c is Parent container.
  • text is the link text.
  • url is the link url.
  • id is a user specific element id.

Example

tgcomp.Link(p.Main, "Link", "https://www.example.com/")

link component

Download Button

DownloadButton create a download button component.

API

func DownloadButton(c *tgframe.Container, text string, body []byte, filename string)
func DownloadButtonWithID(c *tgframe.Container, text string, body []byte, filename, id string)
  • c is Parent container.
  • text is the link text.
  • body is the bytes of file.
  • id is a user specific element id.

Example

tgcomp.DownloadButton(p.Main,
    "Download", []byte("123"), "123.txt")

download button component

Data Components

The data components display data in some special form.

import "github.com/mudream4869/toolgui/toolgui/tgcomp"

The demo page can be found here: https://toolgui-demo.fly.dev/data

JSON

JSON component display the JSON representation of an object.

API

func JSON(c *tgframe.Container, v any)
  • c is Parent container.
  • v is the object.

Example

type DemoJSONHeader struct {
	Type int
}

type DemoJSON struct {
	Header   DemoJSONHeader
	IntValue int
	URL      string
	IsOk     bool
}

tgcomp.JSON(p.Main, &DemoJSON{})

JSON component

Table

Table component display a table.

API

func Table(c *tgframe.Container, head []string, table [][]string)
  • c is Parent container.
  • head is the head of table.
  • body is the body of table.

Example

tgcomp.Table(p.Main, []string{"a", "b"}, [][]string{{"1", "2"}, {"3", "4"}})

table component

Input Components

The input components provide UI for app-user to input their data.

import "github.com/mudream4869/toolgui/toolgui/tgcomp"

The demo page can be found here: https://toolgui-demo.fly.dev/input

Textarea

Textarea create a textarea and return its value.

API

func Textarea(s *tgframe.State, c *tgframe.Container, label string, height int) string
  • s is State.
  • c is Parent container.
  • label is the label for textbox.
  • height is heigh of the textarea.

Example

textareaValue := tgcomp.Textarea(p.State, p.Main, "Textarea", 5)
tgcomp.TextWithID(p.Main, "Value: "+textareaValue, "textarea_result")

textarea component

Textbox

Textbox create a textbox and return its value.

API

func Textbox(s *tgframe.State, c *tgframe.Container, label string) string
  • s is State.
  • c is Parent container.
  • label is the label for textbox.

Example

textboxValue := tgcomp.Textbox(p.State, p.Main, "Textbox")
tgcomp.TextWithID(p.Main, "Value: "+textboxValue, "textbox_result")

textbox component

Fileupload

Fileupload create a fileupload and return its selected file.

API

type FileObject struct {
	Name string `json:"name"`
	Type string `json:"type"`
	Size int    `json:"size"`

	Bytes []byte `json:"_"`
}

func Fileupload(s *tgframe.State, c *tgframe.Container, label string) FileObject
  • s is State.
  • c is Parent container.
  • label is the label for options group.

Example

fileObj := tgcomp.Fileupload(p.State, p.Main, "Fileupload")
tgcomp.Text(p.Main, "Fileupload filename: "+fileObj.Name)
tgcomp.Text(p.Main, fmt.Sprintf("Fileupload bytes length: %d", len(fileObj.Bytes)))

fileupload component

Checkbox

Checkbox create a checkbox and return true if it's checked.

API

func Checkbox(s *tgframe.State, c *tgframe.Container, label string) bool
  • s is State.
  • c is Parent container.
  • label is the text on checkbox.

Example

checkboxValue := tgcomp.Checkbox(p.State, p.Main, "Checkbox")
if checkboxValue {
	tgcomp.TextWithID(p.Main, "Value: true", "checkbox_result")
} else {
	tgcomp.TextWithID(p.Main, "Value: false", "checkbox_result")
}

checkbox component

Button

Button create a button and return true if it's clicked.

API

func Button(s *tgframe.State, c *tgframe.Container, label string) bool
  • s is State.
  • c is Parent container.
  • label is the text on button.

Example

btnClicked := tgcomp.Button(p.State, p.Main, "button")
if btnClicked {
	tgcomp.TextWithID(p.Main, "Value: true", "button_result")
} else {
	tgcomp.TextWithID(p.Main, "Value: false", "button_result")
}

button component

Select

Select create a select dropdown list and return its selected value.

API

func Select(s *tgframe.State, c *tgframe.Container, label string, items []string) string
  • s is State.
  • c is Parent container.
  • label is the label for select.
  • items is the list of options.

Example

selValue := tgcomp.Select(p.State, p.Main, "Select", []string{"Value1", "Value2"})
tgcomp.TextWithID(p.Main, "Value: "+selValue, "select_result")

select component

Options

Radio create a group of radio items and return its selected value.

API

func Radio(s *tgframe.State, c *tgframe.Container, label string, items []string) string
  • s is State.
  • c is Parent container.
  • label is the label for options group.
  • items is the list of options.

Example

radioValue := tgcomp.Radio(p.State, p.Main, "Radio", []string{"Value3", "Value4"})
tgcomp.TextWithID(p.Main, "Value: "+radioValue, "radio_result")

options component

Datepicker

Datepicker create a datepicker and return its selected date.

API

func Datepicker(s *tgframe.State, c *tgframe.Container, label string) string
  • s is State.
  • c is Parent container.
  • label is the label for datepicker.

Example

dateValue := tgcomp.Datepicker(p.State, p.Main, "Datepicker")
tgcomp.TextWithID(p.Main, "Value: "+dateValue, "datepicker_result")

datepicker component

Timepicker

Timepicker create a timepicker and return its selected time.

API

func Timepicker(s *tgframe.State, c *tgframe.Container, label string) string
  • s is State.
  • c is Parent container.
  • label is the label for timepicker.

Example

dateValue := tgcomp.Datetimepicker(p.State, p.Main, "Datetimepicker")
tgcomp.TextWithID(p.Main, "Value: "+dateValue, "datetimepicker_result")

timepicker component

Datetimepicker

Datetimepicker create a datetimepicker and return its selected datetime.

API

func Datetimepicker(s *tgframe.State, c *tgframe.Container, label string) string
  • s is State.
  • c is Parent container.
  • label is the label for datetimepicker.

Example

dateValue := tgcomp.Datetimepicker(p.State, p.Main, "Datetimepicker")
tgcomp.TextWithID(p.Main, "Value: "+dateValue, "datetimepicker_result")

datetimepicker component

Layout Components

The layout components display control component layout and position.

import "github.com/mudream4869/toolgui/toolgui/tgcomp"

The demo page can be found here: https://toolgui-demo.fly.dev/layout

Container

Container is the most basic layout component. Their definition are in tgframe. The Main "container" and Sidebar "container" are Containers.

Usage

To create a container under a container. Just call the function:

func (c *Container) AddContainer(id string) *Container
  • ID should be unique.

Box

Box provide a simple container that show box style.

Usage

Box create a box container.

func Box(c *tgframe.Container, id string) *tgframe.Container
  • c: Parent container.
  • id: Unique component ID.

Example

box := tgcomp.Box(boxCompCol, "box")
tgcomp.Text(box, "A box!")

Column

Column provides columns layout.

Usage

  • Column create N columns.
  • Column2 create 2 columns.
  • Column3 create 3 columns.
func Column(c *tgframe.Container, id string, n uint) []*tgframe.Container
func Column2(c *tgframe.Container, id string) (*tgframe.Container, *tgframe.Container)
func Column3(c *tgframe.Container, id string) (*tgframe.Container, *tgframe.Container, *tgframe.Container)
  • c: Parent container.
  • id: Unique component ID.
  • n: Number of column.

Example

cols := tgcomp.Column(colCompCol, "cols", 3)
for i, col := range cols {
	tgcomp.Text(col, fmt.Sprintf("col-%d", i))
}