domui: DOM UI framework for Go
- Pure go code compiled to wasm
- Utilizing Go's excellent support for static typing and dynamic reflection
- Runs on any WebAssembly and DOM supported browsers, Electron, etc
- Dependent reactive system for both view and state management
- Conceptually unify view component and state management
- No hooks, redux, recoil, mobx ...
- Auto and efficient DOM tree updating
- Based on virtual DOM diff and patch
- Go compiler 1.16 or newer
- If you are not familiar with compiling and running WebAssembly program, please read the official wiki
package main
import (
"github.com/reusee/domui"
"syscall/js"
"time"
)
// ergonomic aliases
var (
Div = domui.Tag("div")
T = domui.Text
)
// A type to hold all definitions
type Def struct{}
// define the root UI element
func (_ Def) RootElement() domui.RootElement {
// same to <div>Hello, world!</div>
return Div(T("Hello, world!"))
}
func main() {
domui.NewApp(
// render on <div id="app">
js.Global().Get("document").Call("getElementById", "app"),
// pass Def's methods as definitions
domui.Methods(new(Def))...,
)
// prevent from exiting
time.Sleep(time.Hour * 24 * 365 * 100)
}
The definition of RootElement can be refactored to multiple dependent components.
package main
import (
"github.com/reusee/domui"
"syscall/js"
"time"
)
var (
Div = domui.Tag("div")
T = domui.Text
)
type (
Def struct{}
Spec = domui.Spec
)
// A string-typed state
type Greetings string
// define Greetings
func (_ Def) Greetings() Greetings {
return "Hello, world!"
}
// A UI element
type GreetingsElement Spec
// define GreetingsElement
func (_ Def) GreetingsElement(
// use Greetings
greetings Greetings,
) GreetingsElement {
return Div(T("%s", greetings))
}
// The root UI element
func (_ Def) RootElement(
// use GreetingsElement
greetingsElem GreetingsElement,
) domui.RootElement {
return Div(
greetingsElem,
)
}
func main() {
domui.NewApp(
js.Global().Get("document").Call("getElementById", "app"),
domui.Methods(new(Def))...,
)
time.Sleep(time.Hour * 24 * 365 * 100)
}
Definitions can be updated. All affected definitions will be re-calculated recursively till the RootElement.
package main
import (
"github.com/reusee/domui"
"syscall/js"
"time"
)
var (
Div = domui.Tag("div")
T = domui.Text
OnClick = domui.On("click")
)
type (
Def struct{}
Spec = domui.Spec
Update = domui.Update
)
type Greetings string
func (_ Def) Greetings() Greetings {
return "Hello, world!"
}
type GreetingsElement Spec
func (_ Def) GreetingsElement(
greetings Greetings,
) GreetingsElement {
return Div(T("%s", greetings))
}
func (_ Def) RootElement(
greetingsElem GreetingsElement,
// use the Update function
update Update,
) domui.RootElement {
return Div(
greetingsElem,
// when clicked, do update
OnClick(func() {
// provide a new definition for Greetings
update(func() Greetings {
return "Hello, DomUI!"
})
}),
)
}
func main() {
domui.NewApp(
js.Global().Get("document").Call("getElementById", "app"),
domui.Methods(new(Def))...,
)
time.Sleep(time.Hour * 24 * 365 * 100)
}
The above programs demonstrated tag and event usages. Attributes, styles, classes can also be specified.
package main
import (
"github.com/reusee/domui"
"syscall/js"
"time"
)
var (
Div = domui.Tag("div")
Link = domui.Tag("a")
Ahref = domui.Attr("href")
Sfont_size = domui.Style("font-size")
T = domui.Text
ID = domui.ID
Class = domui.Class
)
type Def struct{}
func (_ Def) RootElement() domui.RootElement {
return Div(
Link(
T("Hello, world!"),
// id
ID("link"),
// class
Class("link1", "link2"),
// href attribute
Ahref("http://github.com"),
// font-size style
Sfont_size("1.6rem"),
),
)
}
func main() {
domui.NewApp(
js.Global().Get("document").Call("getElementById", "app"),
domui.Methods(new(Def))...,
)
time.Sleep(time.Hour * 24 * 365 * 100)
}
To make a reusable element, define it as a function.
package main
import (
"fmt"
"github.com/reusee/domui"
"reflect"
"strings"
"syscall/js"
"time"
)
var (
Div = domui.Tag("div")
T = domui.Text
OnClick = domui.On("click")
)
type (
Def struct{}
any = interface{}
Spec = domui.Spec
Update = domui.Update
Specs = domui.Specs
)
// Greetings with name parameter
type Greetings func(name any) Spec
func (_ Def) Greetings(
update Update,
) Greetings {
return func(name any) Spec {
return Specs{
T("Hello, %s!", name),
OnClick(func() {
// when clicked, update the name argument to upper case
// use reflect to support all string-typed arguments
nameValue := reflect.New(reflect.TypeOf(name))
nameValue.Elem().SetString(
strings.ToUpper(fmt.Sprintf("%s", name)))
update(nameValue.Interface())
}),
}
}
}
// string-typed states
type (
TheWorld string
TheDomUI string
)
// define two types in one function
func (_ Def) Names() (TheWorld, TheDomUI) {
return "world", "DomUI"
}
func (_ Def) RootElement(
greetings Greetings,
world TheWorld,
domUI TheDomUI,
) domui.RootElement {
return Div(
// use Greetings
Div(
greetings(world),
),
Div(
greetings(domUI),
),
)
}
func main() {
domui.NewApp(
js.Global().Get("document").Call("getElementById", "app"),
domui.Methods(new(Def))...,
)
time.Sleep(time.Hour * 24 * 365 * 100)
}
When updating a definition that has no dependency, instead of passing a function, a pointer can be used.
newGreetings := Greetings("Hello!")
update(&newGreetings)
// is the same to
update(func() Greetings {
return "Hello!"
})
To do something on App inits, define one or more OnAppInit
func (_ Def) InitFoo() domui.OnAppInit {
// do something
}
func (_ Def) InitBar() domui.OnAppInit {
// do something
}
To access the DOM element in an event handler, use a js.Value parameter
type Foo Spec
func (_ Def) Foo() Foo {
retrun Div(
OnClick(func(elem js.Value) {
_ = elem.Call("getAttribute", "href").String()
}),
)
}
Specs can be cached for reusing
type Article func(title string, content string) Spec
func (_ Def) Article() Article {
m := domui.NewSpecMap()
return m(
// key
[2]any{title, content},
// value
func() Spec {
return Div( /* ... */ )
},
)
}
Some conditional Spec constructors are provided
var (
A = domui.Tag("a")
Ahref = domui.Attr("href")
Sfont_weight = domui.Style("font-weight")
)
type Link func(href string, bold bool) Spec
func (_ Def) Link() Link {
return func(href string, bold bool) Spec {
return A(
// If
domui.If(href != "", Ahref(href)),
// Alt
domui.Alt(bold,
Sfont_weight("bold"),
Sfont_weight("normal"),
),
)
}
}
And loop constructors
type List func(elems []string) Spec
func (_ Def) List() List {
return func(elems []string) Spec {
return Div(
// For
domui.For(elems, func(s string) Spec {
return Div(T("%s", s))
}),
// Range
domui.Range(elems, func(i int, s string) Spec {
return Div(T("%d: %s", i, s))
}),
)
}
}
In react, UI components are declared as classes or functions, and there may be multiple instances of the same class. All component instances form the component tree. In addition to the component tree, a state tree or other state management scheme may be used to hold non-UI data.
In DomUI, all UI and non-UI components are defined as type/value pairs. UI components are just Spec types or function types that returning Spec. This unification greatly reduces the complexity of nearly everything compared to other MVVM frameworks.