domui

package module
v0.0.0-...-213e71c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 13, 2021 License: MIT Imports: 10 Imported by: 0

README

domui: DOM UI framework for Go

Features

  • 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

Prerequisites

  • Go compiler 1.16 or newer
  • If you are not familiar with compiling and running WebAssembly program, please read the official wiki

Table of contents

Tutorial

Minimal runnable program

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 dependent system

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)
}

The reactive system

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)
}

DOM element specifications

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)
}

Parameterized element

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)
}

Miscellaneous usages

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))
      }),
    )
  }
}

Additional topics

Differences between DomUI and reactjs

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.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var CSS = Styles
View Source
var Class = Classes
View Source
var Focus = FocusSpec{}
View Source
var (
	Methods = dscope.Methods
)
View Source
var S = Text

Functions

func Attr

func Attr(name string) func(value any) AttrSpec

func NewSpecMap

func NewSpecMap() (
	get func(key any, fn func() Spec) Spec,
)

func On

func On(ev string) func(cb any) EventSpec

func Style

func Style(name string) func(format string, args ...any) StyleSpec

func Tag

func Tag(name string) func(specs ...Spec) *Node

Types

type App

type App struct {
	// contains filtered or unexported fields
}

func NewApp

func NewApp(
	renderElement js.Value,
	defs ...any,
) *App

func (*App) HTML

func (a *App) HTML() string

func (*App) Render

func (a *App) Render()

func (*App) Update

func (a *App) Update(decls ...any) Scope

type AttrSpec

type AttrSpec struct {
	Name  string
	Value any
}

func (AttrSpec) IsSpec

func (_ AttrSpec) IsSpec()

type AttrsSpec

type AttrsSpec struct {
	Attrs map[string]any
}

func Attrs

func Attrs(args ...any) AttrsSpec

func (AttrsSpec) IsSpec

func (_ AttrsSpec) IsSpec()

type ClassesSpec

type ClassesSpec struct {
	Classes map[string]bool
}

func Classes

func Classes(names ...string) ClassesSpec

func (ClassesSpec) IsSpec

func (_ ClassesSpec) IsSpec()

type Def

type Def struct{}

func (Def) OnAppInit

func (_ Def) OnAppInit() OnAppInit

func (Def) SlowRenderThreshold

func (_ Def) SlowRenderThreshold() SlowRenderThreshold

type EventSpec

type EventSpec struct {
	Event string
	Func  any
}

func (EventSpec) IsSpec

func (_ EventSpec) IsSpec()

type FocusSpec

type FocusSpec struct{}

func (FocusSpec) IsSpec

func (_ FocusSpec) IsSpec()

type IDSpec

type IDSpec struct {
	Value string
}

func ID

func ID(id string) IDSpec

func (IDSpec) IsSpec

func (_ IDSpec) IsSpec()

type Lazy

type Lazy func() Spec

func (Lazy) IsSpec

func (_ Lazy) IsSpec()

type Node

type Node struct {
	Kind       NodeKind
	Text       string
	ID         string
	Style      string
	Styles     SortedMap // string: string
	Classes    SortedMap // string: struct{}
	Attributes SortedMap // string: any
	Events     map[string][]EventSpec

	Focus bool
	// contains filtered or unexported fields
}

func Text

func Text(format string, args ...any) *Node

func (*Node) ApplySpec

func (node *Node) ApplySpec(spec Spec)

func (*Node) IsSpec

func (_ *Node) IsSpec()

func (*Node) ToElement

func (n *Node) ToElement(scope Scope) (_ js.Value, err error)

type NodeKind

type NodeKind uint8
const (
	TagNode NodeKind = iota
	TextNode
)

type OnAppInit

type OnAppInit func()

func (OnAppInit) Reduce

func (_ OnAppInit) Reduce(_ Scope, vs []reflect.Value) reflect.Value

type RootElement

type RootElement Spec

type Scope

type Scope = dscope.Scope

type SlowRenderThreshold

type SlowRenderThreshold time.Duration

type SortedMap

type SortedMap []SortedMapItem

func (SortedMap) Get

func (s SortedMap) Get(k string) (any, bool)

func (*SortedMap) Set

func (s *SortedMap) Set(k string, v any)

type SortedMapItem

type SortedMapItem struct {
	Key   string
	Value any
}

type Spec

type Spec interface {
	IsSpec()
}

func Alt

func Alt(cond bool, spec1 Spec, spec2 Spec) Spec

func If

func If(cond bool, specs ...Spec) Spec

type Specs

type Specs []Spec

func For

func For(slice any, fn any) Specs

func Range

func Range(slice any, fn any) Specs

func (Specs) IsSpec

func (_ Specs) IsSpec()

type StyleSpec

type StyleSpec struct {
	Name  string
	Value string
}

func (StyleSpec) IsSpec

func (_ StyleSpec) IsSpec()

type StyleString

type StyleString string

func (StyleString) IsSpec

func (_ StyleString) IsSpec()

type StylesSpec

type StylesSpec struct {
	Styles map[string]string
}

func Styles

func Styles(args ...any) StylesSpec

func (StylesSpec) IsSpec

func (_ StylesSpec) IsSpec()

type Update

type Update func(decls ...any) Scope

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL