StyleInline function

StyleInline converts a map of CSS properties into an inline style string.

Keys and values are concatenated as "key:value" pairs separated by semicolons.

Parameters:

  • styles map[string]string

Returns:

  • string
Show/Hide Function Body
{
	var b strings.Builder
	first := true
	for k, v := range styles {
		if !first {
			b.WriteByte(';')
		}
		first = false
		b.WriteString(k)
		b.WriteByte(':')
		b.WriteString(v)
	}
	return b.String()
}

BindStoreInputsForComponent function

BindStoreInputsForComponent is a no-op outside wasm builds.

Parameters:

  • string
  • any
Show/Hide Function Body
{}

BindStoreInputs function

BindStoreInputs is a no-op outside wasm builds.

Parameters:

  • any
Show/Hide Function Body
{}

SnapshotComponentSignals function

SnapshotComponentSignals is a stub returning nil outside wasm builds.

Parameters:

  • string

Returns:

  • map[string]any
Show/Hide Function Body
{ return nil }

Event struct

Event wraps a browser event.

Methods:

PreventDefault

PreventDefault prevents the default action for the event.


Show/Hide Method Body
{ e.Call("preventDefault") }

StopPropagation

StopPropagation stops the event from bubbling.


Show/Hide Method Body
{ e.Call("stopPropagation") }

VDOMNode struct

VDOMNode represents a node in a virtual DOM tree derived from an HTML

template.

Fields:

  • Tag (string)
  • Attributes (map[string]string)
  • Children ([]*VDOMNode)
  • Text (string)

NewVDOM function

NewVDOM parses an HTML template into a virtual DOM tree.

Parameters:

  • htmlTemplate string

Returns:

  • *VDOMNode
  • error
Show/Hide Function Body
{
	reader := strings.NewReader(htmlTemplate)
	doc, err := html.Parse(reader)
	if err != nil {
		return nil, err
	}

	root := mapHTMLNode(doc)
	return root, nil
}

mapHTMLNode function

Parameters:

  • n *html.Node

Returns:

  • *VDOMNode
Show/Hide Function Body
{
	if n.Type == html.TextNode {
		return &VDOMNode{
			Text: n.Data,
		}
	}

	if n.Type == html.DocumentNode || n.Type == html.ElementNode {
		node := &VDOMNode{
			Tag:        n.Data,
			Attributes: mapAttributes(n),
		}

		for child := n.FirstChild; child != nil; child = child.NextSibling {
			node.Children = append(node.Children, mapHTMLNode(child))
		}

		return node
	}

	return nil
}

mapAttributes function

Parameters:

  • n *html.Node

Returns:

  • map[string]string
Show/Hide Function Body
{
	attrs := make(map[string]string)
	for _, attr := range n.Attr {
		attrs[attr.Key] = attr.Val
	}
	return attrs
}

printVDOM function

Parameters:

  • node *VDOMNode
  • indent string
Show/Hide Function Body
{
	if node == nil {
		return
	}

	if node.Tag != "" {
		fmt.Printf("%s<Tag: %s, Attributes: %v>\n", indent, node.Tag, node.Attributes)
	}

	if node.Text != "" {
		fmt.Printf("%s<Text: %s>\n", indent, node.Text)
	}

	for _, child := range node.Children {
		printVDOM(child, indent+"  ")
	}
}

eventListener struct

Fields:

  • element (js.Value)
  • event (string)
  • handler (js.Func)

parseModifiers function

Parameters:

  • attr string

Returns:

  • map[string]bool
Show/Hide Function Body
{
	mods := make(map[string]bool)
	if attr == "" {
		return mods
	}
	for _, m := range strings.Split(attr, ",") {
		m = strings.TrimSpace(m)
		if m != "" {
			mods[m] = true
		}
	}
	return mods
}

nodeByPath function

Parameters:

  • root js.Value
  • path []int

Returns:

  • js.Value
Show/Hide Function Body
{
	node := root
	for _, idx := range path {
		children := node.Get("children")
		if idx >= children.Length() {
			return js.Null()
		}
		node = children.Index(idx)
	}
	return node
}

BindEventListeners function

BindEventListeners attaches registered handlers to nodes within the component

identified by componentID.

Parameters:

  • componentID string
  • root js.Value
Show/Hide Function Body
{
	if bs, ok := compiledBindings[componentID]; ok {
		for _, b := range bs {
			node := nodeByPath(root, b.Path)
			handler := GetHandler(b.Handler)
			if !handler.Truthy() {
				continue
			}
			mods := make(map[string]bool)
			for _, m := range b.Modifiers {
				mods[m] = true
			}
			wrapped := js.FuncOf(func(this js.Value, args []js.Value) any {
				if mods["stopPropagation"] && len(args) > 0 {
					args[0].Call("stopPropagation")
				}
				if mods["preventDefault"] && len(args) > 0 {
					args[0].Call("preventDefault")
				}
				anyArgs := make([]any, len(args))
				for i, a := range args {
					anyArgs[i] = a
				}
				handler.Invoke(anyArgs...)
				return nil
			})
			if mods["once"] {
				opts := js.NewDict()
				opts.Set("once", true)
				node.Call("addEventListener", b.Event, wrapped, opts.Value)
			} else {
				node.Call("addEventListener", b.Event, wrapped)
			}
			listeners[componentID] = append(listeners[componentID], eventListener{node, b.Event, wrapped})
		}
		return
	}

	nodes := root.Call("querySelectorAll", "*")
	for i := 0; i < nodes.Length(); i++ {
		node := nodes.Index(i)
		attrs := node.Call("getAttributeNames")
		for j := 0; j < attrs.Length(); j++ {
			name := attrs.Index(j).String()
			if strings.HasPrefix(name, "data-on-") && !strings.HasSuffix(name, "-modifiers") {
				event := strings.TrimPrefix(name, "data-on-")
				handlerName := node.Call("getAttribute", name).String()
				modsAttr := node.Call("getAttribute", fmt.Sprintf("data-on-%s-modifiers", event)).String()
				modifiers := parseModifiers(modsAttr)
				handler := GetHandler(handlerName)
				if handler.Truthy() {
					wrapped := js.FuncOf(func(this js.Value, args []js.Value) any {
						if modifiers["stopPropagation"] && len(args) > 0 {
							args[0].Call("stopPropagation")
						}
						if modifiers["preventDefault"] && len(args) > 0 {
							args[0].Call("preventDefault")
						}
						anyArgs := make([]any, len(args))
						for i, a := range args {
							anyArgs[i] = a
						}
						handler.Invoke(anyArgs...)
						return nil
					})
					if modifiers["once"] {
						opts := js.NewDict()
						opts.Set("once", true)
						node.Call("addEventListener", event, wrapped, opts.Value)
					} else {
						node.Call("addEventListener", event, wrapped)
					}
					listeners[componentID] = append(listeners[componentID], eventListener{node, event, wrapped})
				}
			}
		}
	}
}

RemoveEventListeners function

RemoveEventListeners detaches all event listeners associated with the

specified component.

Parameters:

  • componentID string
Show/Hide Function Body
{
	if ls, ok := listeners[componentID]; ok {
		for _, l := range ls {
			l.element.Call("removeEventListener", l.event, l.handler)
			l.handler.Release()
		}
		delete(listeners, componentID)
	}
	delete(compiledBindings, componentID)
}

CreateElement function

CreateElement returns a new element with the given tag name.

Parameters:

  • tag string

Returns:

  • Element

References:

Show/Hide Function Body
{ return Doc().CreateElement(tag) }

ByID function

ByID fetches an element by its id attribute.

Parameters:

  • id string

Returns:

  • Element

References:

Show/Hide Function Body
{ return Doc().ByID(id) }

Query function

Query returns the first element matching the CSS selector.

Parameters:

  • selector string

Returns:

  • Element

References:

Show/Hide Function Body
{ return Doc().Query(selector) }

QueryAll function

QueryAll returns all elements matching the CSS selector.

Parameters:

  • selector string

Returns:

  • Element

References:

Show/Hide Function Body
{ return Doc().QueryAll(selector) }

ByClass function

ByClass returns all elements with the given class name.

Parameters:

  • name string

Returns:

  • Element

References:

Show/Hide Function Body
{ return Doc().ByClass(name) }

ByTag function

ByTag returns all elements with the given tag name.

Parameters:

  • tag string

Returns:

  • Element

References:

Show/Hide Function Body
{ return Doc().ByTag(tag) }

SetInnerHTML function

SetInnerHTML replaces an element's children with the provided HTML string.

Parameters:

  • el Element
  • html string

References:

Show/Hide Function Body
{ el.SetHTML(html) }

Text function

Text returns an element's text content.

Parameters:

  • el Element

Returns:

  • string

References:

Show/Hide Function Body
{ return el.Text() }

SetText function

SetText sets an element's text content.

Parameters:

  • el Element
  • text string

References:

Show/Hide Function Body
{ el.SetText(text) }

Attr function

Attr retrieves the value of an attribute or an empty string if unset.

Parameters:

  • el Element
  • name string

Returns:

  • string

References:

Show/Hide Function Body
{ return el.Attr(name) }

SetAttr function

SetAttr sets the value of an attribute on the element.

Parameters:

  • el Element
  • name string
  • value string

References:

Show/Hide Function Body
{ el.SetAttr(name, value) }

AddClass function

AddClass adds a class to the element's class list.

Parameters:

  • el Element
  • class string

References:

Show/Hide Function Body
{ el.AddClass(class) }

RemoveClass function

RemoveClass removes a class from the element's class list.

Parameters:

  • el Element
  • class string

References:

Show/Hide Function Body
{ el.RemoveClass(class) }

HasClass function

HasClass reports whether the element has the specified class.

Parameters:

  • el Element
  • class string

Returns:

  • bool

References:

Show/Hide Function Body
{ return el.HasClass(class) }

ToggleClass function

ToggleClass toggles the presence of a class on the element's class list.

Parameters:

  • el Element
  • class string

References:

Show/Hide Function Body
{ el.ToggleClass(class) }

SetStyle function

SetStyle sets an inline style property on the element.

Parameters:

  • el Element
  • prop string
  • value string

References:

Show/Hide Function Body
{ el.SetStyle(prop, value) }

binding struct

binding represents a precompiled event binding.

Fields:

  • Path ([]int)
  • Event (string)
  • Handler (string)
  • Modifiers ([]string)

RegisterBindings function

RegisterBindings generates and associates bindings for a component instance.

Parameters:

  • id string
  • name string
  • template string
Show/Hide Function Body
{
	if bs, ok := precompiledByName[name]; ok {
		compiledBindings[id] = bs
		return
	}
	bs, err := parseTemplate(template)
	if err != nil {
		return
	}
	precompiledByName[name] = bs
	compiledBindings[id] = bs
}

OverrideBindings function

OverrideBindings replaces the cached bindings for a component name.

Parameters:

  • name string
  • template string
Show/Hide Function Body
{
	bs, err := parseTemplate(template)
	if err != nil {
		return
	}
	precompiledByName[name] = bs
}

parseTemplate function

Parameters:

  • tpl string

Returns:

  • []binding
  • error
Show/Hide Function Body
{
	processed := replaceEventHandlers(tpl)
	node, err := html.Parse(strings.NewReader(processed))
	if err != nil {
		return nil, err
	}
	return collectBindings(node, nil), nil
}

collectBindings function

Parameters:

  • n *html.Node
  • path []int

Returns:

  • []binding
Show/Hide Function Body
{
	var res []binding
	if n.Type == html.ElementNode {
		attrs := map[string]string{}
		for _, a := range n.Attr {
			attrs[a.Key] = a.Val
		}
		for k, v := range attrs {
			if strings.HasPrefix(k, "data-on-") && !strings.HasSuffix(k, "-modifiers") {
				event := strings.TrimPrefix(k, "data-on-")
				mods := []string{}
				if m, ok := attrs[fmt.Sprintf("data-on-%s-modifiers", event)]; ok && m != "" {
					for _, s := range strings.Split(m, ",") {
						s = strings.TrimSpace(s)
						if s != "" {
							mods = append(mods, s)
						}
					}
				}
				res = append(res, binding{Path: append([]int(nil), path...), Event: event, Handler: v, Modifiers: mods})
			}
		}
	}
	child := n.FirstChild
	idx := 0
	for child != nil {
		res = append(res, collectBindings(child, append(path, idx))...)
		child = child.NextSibling
		idx++
	}
	return res
}

replaceEventHandlers function

Parameters:

  • template string

Returns:

  • string
Show/Hide Function Body
{
	return eventRegex.ReplaceAllStringFunc(template, func(match string) string {
		parts := eventRegex.FindStringSubmatch(match)
		if len(parts) != 5 {
			return match
		}
		fullEvent := parts[2]
		handler := parts[3]
		suffix := parts[4]
		eventParts := strings.Split(fullEvent, ".")
		event := eventParts[0]
		modifiers := []string{}
		if len(eventParts) > 1 {
			modifiers = eventParts[1:]
		}
		attr := fmt.Sprintf("data-on-%s=\"%s\"", event, handler)
		if len(modifiers) > 0 {
			attr += fmt.Sprintf(" data-on-%s-modifiers=\"%s\"", event, strings.Join(modifiers, ","))
		}
		return attr + suffix
	})
}

Document struct

Document wraps the global document object.

Methods:

ByID

ByID fetches an element by id.


Parameters:
  • id string

Returns:
  • Element

References:


Show/Hide Method Body
{
	return Element{d.Call("getElementById", id)}
}

Query

Query returns the first element matching the selector.


Parameters:
  • sel string

Returns:
  • Element

References:


Show/Hide Method Body
{
	return Element{d.Call("querySelector", sel)}
}

QueryAll

QueryAll returns all elements matching the selector.


Parameters:
  • sel string

Returns:
  • Element

References:


Show/Hide Method Body
{
	return Element{d.Call("querySelectorAll", sel)}
}

ByClass

ByClass returns all elements with the given class name.


Parameters:
  • name string

Returns:
  • Element

References:


Show/Hide Method Body
{
	return Element{d.Call("getElementsByClassName", name)}
}

ByTag

ByTag returns all elements with the given tag name.


Parameters:
  • tag string

Returns:
  • Element

References:


Show/Hide Method Body
{
	return Element{d.Call("getElementsByTagName", tag)}
}

CreateElement

CreateElement creates a new element with the tag.


Parameters:
  • tag string

Returns:
  • Element

References:


Show/Hide Method Body
{
	return Element{d.Call("createElement", tag)}
}

Head

Head returns the document's element.


Returns:
  • Element

References:


Show/Hide Method Body
{ return Element{d.Get("head")} }

Body

Body returns the document's element.


Returns:
  • Element

References:


Show/Hide Method Body
{ return Element{d.Get("body")} }

Doc function

Doc returns the global Document.

Returns:

  • Document

References:

Show/Hide Function Body
{ return Document{js.Doc()} }

RegisterSignal function

RegisterSignal associates a signal with a component so inputs can bind to it.

Parameters:

  • componentID string
  • name string
  • sig any
Show/Hide Function Body
{
	componentSignalsMu.Lock()
	if componentSignals[componentID] == nil {
		componentSignals[componentID] = make(map[string]any)
	}
	componentSignals[componentID][name] = sig
	componentSignalsMu.Unlock()
}

RemoveComponentSignals function

RemoveComponentSignals cleans up signals for a component on unmount.

Parameters:

  • componentID string
Show/Hide Function Body
{
	componentSignalsMu.Lock()
	delete(componentSignals, componentID)
	componentSignalsMu.Unlock()
}

getSignal function

Parameters:

  • componentID string
  • name string

Returns:

  • any
Show/Hide Function Body
{
	componentSignalsMu.RLock()
	defer componentSignalsMu.RUnlock()
	if m, ok := componentSignals[componentID]; ok {
		return m[name]
	}
	return nil
}

SnapshotComponentSignals function

SnapshotComponentSignals returns a copy of the signals registered for a component.

Parameters:

  • componentID string

Returns:

  • map[string]any
Show/Hide Function Body
{
	componentSignalsMu.RLock()
	defer componentSignalsMu.RUnlock()
	if signals, ok := componentSignals[componentID]; ok {
		clone := make(map[string]any, len(signals))
		for k, v := range signals {
			clone[k] = v
		}
		return clone
	}
	return nil
}

UpdateDOM function

UpdateDOM patches the DOM of the specified component with the provided

HTML string, resolving the target via typed Document/Element wrappers.

Parameters:

  • componentID string
  • html string
Show/Hide Function Body
{
	doc := Doc()
	var element Element
	if componentID == "" {
		element = doc.ByID("app")
	} else {
		element = doc.Query(fmt.Sprintf("[data-component-id='%s']", componentID))
		if element.IsNull() || element.IsUndefined() {
			element = doc.ByID("app")
		}
	}
	if element.IsNull() || element.IsUndefined() {
		return
	}

	RemoveEventListeners(componentID)

	if strings.HasPrefix(html, "<root") && strings.EqualFold(element.Get("nodeName").String(), "ROOT") {
		if end := strings.LastIndex(html, "</root>"); end != -1 {
			if start := strings.Index(html, ">"); start != -1 {
				html = html[start+1 : end]
			}
		}
	}

	patchInnerHTML(element.Value, html)

	if TemplateHook != nil {
		TemplateHook(componentID, html)
	}

	BindStoreInputsForComponent(componentID, element.Value)
	BindSignalInputs(componentID, element.Value)

	BindEventListeners(componentID, element.Value)
}

BindStoreInputsForComponent function

BindStoreInputsForComponent binds input elements to store variables while

providing the component context for runtime hooks.

Parameters:

  • componentID string
  • element js.Value
Show/Hide Function Body
{
	inputs := element.Call("querySelectorAll", "input, select, textarea")
	for i := 0; i < inputs.Length(); i++ {
		input := inputs.Index(i)

		valueAttr := input.Get("value").String()
		checkedAttr := ""
		if input.Call("hasAttribute", "checked").Bool() {
			checkedAttr = input.Call("getAttribute", "checked").String()
		}

		re := regexp.MustCompile(`@store:(\w+)\.(\w+)\.(\w+):w`)
		valueMatch := re.FindStringSubmatch(valueAttr)
		checkedMatch := re.FindStringSubmatch(checkedAttr)

		var module, storeName, key string
		var usesChecked bool
		if len(valueMatch) == 4 {
			module, storeName, key = valueMatch[1], valueMatch[2], valueMatch[3]
		} else if len(checkedMatch) == 4 {
			module, storeName, key = checkedMatch[1], checkedMatch[2], checkedMatch[3]
			usesChecked = true
		} else {
			continue
		}

		store := state.GlobalStoreManager.GetStore(module, storeName)
		if store == nil {
			continue
		}

		if StoreBindingHook != nil && componentID != "" {
			StoreBindingHook(componentID, module, storeName, key)
		}

		storeValue := store.Get(key)

		if usesChecked {
			boolVal, _ := storeValue.(bool)
			input.Set("checked", boolVal)
			ch := events.Listen("change", input)
			go func(in js.Value, st *state.Store, k string) {
				for range ch {
					st.Set(k, in.Get("checked").Bool())
				}
			}(input, store, key)
			continue
		}

		if storeValue == nil {
			storeValue = ""
		}
		input.Set("value", fmt.Sprintf("%v", storeValue))
		ch := events.Listen("input", input)
		go func(in js.Value, st *state.Store, k string) {
			for range ch {
				st.Set(k, in.Get("value").String())
			}
		}(input, store, key)
	}
}

BindStoreInputs function

BindStoreInputs binds input elements to store variables.

Parameters:

  • element js.Value
Show/Hide Function Body
{
	BindStoreInputsForComponent("", element)
}

BindSignalInputs function

BindSignalInputs binds input elements to local component signals.

Parameters:

  • componentID string
  • element js.Value
Show/Hide Function Body
{
	inputs := element.Call("querySelectorAll", "input, select, textarea")
	for i := 0; i < inputs.Length(); i++ {
		input := inputs.Index(i)

		valueAttr := input.Get("value").String()
		checkedAttr := ""
		if input.Call("hasAttribute", "checked").Bool() {
			checkedAttr = input.Call("getAttribute", "checked").String()
		}

		re := regexp.MustCompile(`@signal:(\w+):w`)
		valueMatch := re.FindStringSubmatch(valueAttr)
		checkedMatch := re.FindStringSubmatch(checkedAttr)

		var name string
		var usesChecked bool
		if len(valueMatch) == 2 {
			name = valueMatch[1]
		} else if len(checkedMatch) == 2 {
			name = checkedMatch[1]
			usesChecked = true
		} else {
			continue
		}

		sig := getSignal(componentID, name)
		if sig == nil {
			continue
		}

		if usesChecked {
			if s, ok := sig.(interface {
				Read() any
				Set(bool)
			}); ok {
				if b, ok := s.Read().(bool); ok {
					input.Set("checked", b)
				}
				ch := events.Listen("change", input)
				go func(in js.Value, sg interface{ Set(bool) }) {
					for range ch {
						sg.Set(in.Get("checked").Bool())
					}
				}(input, s)
			}
			continue
		}

		if s, ok := sig.(interface {
			Read() any
			Set(string)
		}); ok {
			input.Set("value", fmt.Sprintf("%v", s.Read()))
			ch := events.Listen("input", input)
			go func(in js.Value, sg interface{ Set(string) }) {
				for range ch {
					sg.Set(in.Get("value").String())
				}
			}(input, s)
		}
	}
}

patchInnerHTML function

Parameters:

  • element js.Value
  • html string
Show/Hide Function Body
{
	template := CreateElement("template")
	template.Set("innerHTML", html)
	newContent := template.Get("content")
	patchChildren(element, newContent)
}

patchChildren function

Parameters:

  • oldParent js.Value
  • newParent js.Value
Show/Hide Function Body
{
	oldChildren := oldParent.Get("childNodes")
	newChildren := newParent.Get("childNodes")

	keyed := make(map[string]js.Value)
	for i := 0; i < oldChildren.Length(); i++ {
		child := oldChildren.Index(i)
		if key := getDataKey(child); key != "" {
			keyed[key] = child
		}
	}

	index := 0
	for i := 0; i < newChildren.Length(); i++ {
		newChild := newChildren.Index(i)
		key := getDataKey(newChild)
		if key != "" {
			if oldChild, ok := keyed[key]; ok {
				patchNode(oldChild, newChild)
				ref := oldParent.Get("childNodes").Index(index)
				if !oldChild.Equal(ref) {
					if ref.Truthy() {
						oldParent.Call("insertBefore", oldChild, ref)
					} else {
						oldParent.Call("appendChild", oldChild)
					}
				}
				delete(keyed, key)
			} else {
				clone := newChild.Call("cloneNode", true)
				ref := oldParent.Get("childNodes").Index(index)
				if ref.Truthy() {
					oldParent.Call("insertBefore", clone, ref)
				} else {
					oldParent.Call("appendChild", clone)
				}
			}
			index++
			continue
		}

		oldChild := oldParent.Get("childNodes").Index(index)
		if oldChild.Truthy() && getDataKey(oldChild) == "" {
			patchNode(oldChild, newChild)
		} else {
			clone := newChild.Call("cloneNode", true)
			ref := oldParent.Get("childNodes").Index(index)
			if ref.Truthy() {
				oldParent.Call("insertBefore", clone, ref)
			} else {
				oldParent.Call("appendChild", clone)
			}
		}
		index++
	}

	for _, child := range keyed {
		child.Call("remove")
	}

	for oldParent.Get("childNodes").Length() > index {
		oldParent.Get("childNodes").Index(index).Call("remove")
	}
}

getDataKey function

Parameters:

  • node js.Value

Returns:

  • string
Show/Hide Function Body
{
	if node.Get("nodeType").Int() != 1 {
		return ""
	}
	key := node.Call("getAttribute", "data-key")
	if key.Truthy() {
		return key.String()
	}
	return ""
}

patchNode function

Parameters:

  • oldNode js.Value
  • newNode js.Value
Show/Hide Function Body
{
	nodeType := newNode.Get("nodeType").Int()
	if nodeType == 3 { // Text node
		if oldNode.Get("nodeValue").String() != newNode.Get("nodeValue").String() {
			oldNode.Set("nodeValue", newNode.Get("nodeValue"))
		}
		return
	}

	if oldNode.Get("nodeName").String() != newNode.Get("nodeName").String() {
		oldNode.Call("replaceWith", newNode.Call("cloneNode", true))
		return
	}

	if nodeType == 1 { // Element node
		patchAttributes(oldNode, newNode)
	}
	patchChildren(oldNode, newNode)
}

patchAttributes function

Parameters:

  • oldNode js.Value
  • newNode js.Value
Show/Hide Function Body
{
	oldAttrs := oldNode.Call("getAttributeNames")
	for i := 0; i < oldAttrs.Length(); i++ {
		name := oldAttrs.Index(i).String()
		if !newNode.Call("hasAttribute", name).Bool() {
			oldNode.Call("removeAttribute", name)
		}
	}

	newAttrs := newNode.Call("getAttributeNames")
	for i := 0; i < newAttrs.Length(); i++ {
		name := newAttrs.Index(i).String()
		val := newNode.Call("getAttribute", name)
		if oldNode.Call("getAttribute", name).String() != val.String() {
			oldNode.Call("setAttribute", name, val)
		}
	}
}

TestUpdateDOMSkipsNonElementNodes function

Ensure UpdateDOM handles nodes without attributes (e.g. comments) without panicking.

Parameters:

  • t *testing.T
Show/Hide Function Body
{
	body := js.Doc().Get("body")
	root := CreateElement("div")
	root.Set("id", "root")
	body.Call("appendChild", root)
	defer root.Call("remove")

	SetInnerHTML(root, "<!--old-->")
	UpdateDOM("root", "<!--new-->")
}

TestPlaceholder function

Parameters:

  • t *testing.T
Show/Hide Function Body
{}

TestDocumentElementBasics function

Parameters:

  • t *testing.T
Show/Hide Function Body
{
	doc := Doc()
	el := doc.CreateElement("div")
	el.SetText("hello")
	if got := el.Text(); got != "hello" {
		t.Fatalf("Text() = %q", got)
	}
}

TestDocumentHead function

Parameters:

  • t *testing.T
Show/Hide Function Body
{
	doc := Doc()
	if node := doc.Head().Get("nodeName").String(); node != "HEAD" {
		t.Fatalf("Head() node = %q", node)
	}
}

ScheduleRender function

ScheduleRender updates the DOM of the specified component after a delay.

Parameters:

  • componentID string
  • html string
  • delay time.Duration
Show/Hide Function Body
{
	sched.Lock()
	defer sched.Unlock()
	if t, ok := sched.timers[componentID]; ok {
		t.Stop()
	}
	sched.timers[componentID] = time.AfterFunc(delay, func() {
		UpdateDOM(componentID, html)
		sched.Lock()
		delete(sched.timers, componentID)
		sched.Unlock()
	})
}

TestStyleInline function

Parameters:

  • t *testing.T
Show/Hide Function Body
{
	got := StyleInline(map[string]string{"color": "red", "display": "block"})
	if !strings.Contains(got, "color:red") || !strings.Contains(got, "display:block") {
		t.Fatalf("StyleInline() = %q", got)
	}
}

Element struct

Element wraps a DOM element and provides typed helpers.

Methods:

On

On attaches a listener for event to the element and returns a stop function.


Parameters:
  • event string
  • handler func(Event)

Returns:
  • func()

Show/Hide Method Body
{
	fn := js.FuncOf(func(this js.Value, args []js.Value) any {
		var evt js.Value
		if len(args) > 0 {
			evt = args[0]
		}
		handler(Event{evt})
		return nil
	})
	e.Call("addEventListener", event, fn)
	return func() {
		e.Call("removeEventListener", event, fn)
		fn.Release()
	}
}

OnClick

OnClick attaches a click handler to the element.


Parameters:
  • handler func(Event)

Returns:
  • func()

Show/Hide Method Body
{
	return e.On("click", handler)
}

Query

Query returns the first descendant matching the CSS selector.


Parameters:
  • sel string

Returns:
  • Element

References:


Show/Hide Method Body
{
	return Element{e.Call("querySelector", sel)}
}

QueryAll

QueryAll returns all descendants matching the selector.


Parameters:
  • sel string

Returns:
  • Element

References:


Show/Hide Method Body
{
	return Element{e.Call("querySelectorAll", sel)}
}

ByClass

ByClass returns all descendants with the given class name.


Parameters:
  • name string

Returns:
  • Element

References:


Show/Hide Method Body
{
	return Element{e.Call("getElementsByClassName", name)}
}

ByTag

ByTag returns all descendants with the given tag name.


Parameters:
  • tag string

Returns:
  • Element

References:


Show/Hide Method Body
{
	return Element{e.Call("getElementsByTagName", tag)}
}

Text

Text returns the element's text content.


Returns:
  • string

Show/Hide Method Body
{ return e.Get("textContent").String() }

SetText

SetText sets the element's text content.


Parameters:
  • txt string

Show/Hide Method Body
{ e.Set("textContent", txt) }

HTML

HTML returns the element's inner HTML.


Returns:
  • string

Show/Hide Method Body
{ return e.Get("innerHTML").String() }

SetHTML

SetHTML replaces the element's children with raw HTML.


Parameters:
  • html string

Show/Hide Method Body
{ e.Set("innerHTML", html) }

AppendChild

AppendChild appends a child element.


Parameters:
  • child Element

References:


Show/Hide Method Body
{ e.Call("appendChild", child.Value) }

Attr

Attr retrieves the value of an attribute or "" if unset.


Parameters:
  • name string

Returns:
  • string

Show/Hide Method Body
{
	v := e.Call("getAttribute", name)
	if v.Truthy() {
		return v.String()
	}
	return ""
}

SetAttr

SetAttr sets the value of an attribute on the element.


Parameters:
  • name string
  • value string

Show/Hide Method Body
{ e.Call("setAttribute", name, value) }

SetStyle

SetStyle sets an inline style property on the element.


Parameters:
  • prop string
  • value string

Show/Hide Method Body
{
	e.Get("style").Call("setProperty", prop, value)
}

AddClass

AddClass adds a class to the element.


Parameters:
  • name string

Show/Hide Method Body
{ e.Get("classList").Call("add", name) }

RemoveClass

RemoveClass removes a class from the element.


Parameters:
  • name string

Show/Hide Method Body
{ e.Get("classList").Call("remove", name) }

HasClass

HasClass reports whether the element has the given class.


Parameters:
  • name string

Returns:
  • bool

Show/Hide Method Body
{
	return e.Get("classList").Call("contains", name).Bool()
}

ToggleClass

ToggleClass toggles the presence of a class on the element.


Parameters:
  • name string

Show/Hide Method Body
{
	e.Get("classList").Call("toggle", name)
}

Length

Length returns the number of children when the element represents a collection.


Returns:
  • int

Show/Hide Method Body
{ return e.Get("length").Int() }

Index

Index retrieves the element at the given position when representing a collection.


Parameters:
  • i int

Returns:
  • Element

References:


Show/Hide Method Body
{ return Element{e.Value.Index(i)} }

TestElementAttrsAndStyle function

Parameters:

  • t *testing.T
Show/Hide Function Body
{
	doc := Doc()
	el := doc.CreateElement("div")
	el.SetAttr("data-x", "y")
	if got := el.Attr("data-x"); got != "y" {
		t.Fatalf("Attr() = %q", got)
	}
	el.SetHTML("<span>ok</span>")
	if got := el.HTML(); got != "<span>ok</span>" {
		t.Fatalf("HTML() = %q", got)
	}
	el.SetStyle("color", "red")
	if v := el.Get("style").Call("getPropertyValue", "color").String(); v != "red" {
		t.Fatalf("style color = %q", v)
	}
}

TestElementCollections function

Parameters:

  • t *testing.T
Show/Hide Function Body
{
	doc := Doc()
	parent := doc.CreateElement("div")
	parent.SetHTML("<span>a</span><span>b</span>")
	spans := parent.QueryAll("span")
	if spans.Length() != 2 {
		t.Fatalf("Length() = %d", spans.Length())
	}
	second := spans.Index(1)
	if second.Text() != "b" {
		t.Fatalf("Index(1).Text() = %q", second.Text())
	}
	second.ToggleClass("x")
	if !second.HasClass("x") {
		t.Fatalf("ToggleClass/HasClass failed")
	}
}

TestElementAppendChild function

Parameters:

  • t *testing.T
Show/Hide Function Body
{
	doc := Doc()
	parent := doc.CreateElement("div")
	child := doc.CreateElement("span")
	parent.AppendChild(child)
	if got := parent.Query("span"); !got.Truthy() {
		t.Fatalf("AppendChild() did not append")
	}
}

RegisterHandler function

RegisterHandler registers a Go function with custom arguments in the handler registry.

Parameters:

  • name string
  • fn func(this js.Value, args []js.Value) any
Show/Hide Function Body
{
	handlerRegistry[name] = js.FuncOf(fn).Value
}

RegisterHandlerFunc function

RegisterHandlerFunc registers a no-argument Go function in the handler registry.

Parameters:

  • name string
  • fn func()
Show/Hide Function Body
{
	RegisterHandler(name, func(this js.Value, args []js.Value) any {
		fn()
		return nil
	})
}

RegisterHandlerEvent function

RegisterHandlerEvent registers a Go function that receives the first argument as an event object.

Parameters:

  • name string
  • fn func(js.Value)
Show/Hide Function Body
{
	RegisterHandler(name, func(this js.Value, args []js.Value) any {
		var evt js.Value
		if len(args) > 0 {
			evt = args[0]
		}
		fn(evt)
		return nil
	})
}

GetHandler function

GetHandler retrieves a registered handler by name.

Parameters:

  • name string

Returns:

  • js.Value
Show/Hide Function Body
{
	if v, ok := handlerRegistry[name]; ok {
		return v
	}
	return js.Undefined()
}

strings import

Import example:

import "strings"

github.com/rfwlab/rfw/v1/js import

Import example:

import "github.com/rfwlab/rfw/v1/js"

Imported as:

js

fmt import

Import example:

import "fmt"

strings import

Import example:

import "strings"

golang.org/x/net/html import

Import example:

import "golang.org/x/net/html"

github.com/rfwlab/rfw/v1/js import

Import example:

import "github.com/rfwlab/rfw/v1/js"

Imported as:

js

fmt import

Import example:

import "fmt"

regexp import

Import example:

import "regexp"

strings import

Import example:

import "strings"

golang.org/x/net/html import

Import example:

import "golang.org/x/net/html"

github.com/rfwlab/rfw/v1/js import

Import example:

import "github.com/rfwlab/rfw/v1/js"

Imported as:

js

fmt import

Import example:

import "fmt"

regexp import

Import example:

import "regexp"

strings import

Import example:

import "strings"

sync import

Import example:

import "sync"

github.com/rfwlab/rfw/v1/events import

Import example:

import "github.com/rfwlab/rfw/v1/events"

Imported as:

events

github.com/rfwlab/rfw/v1/js import

Import example:

import "github.com/rfwlab/rfw/v1/js"

Imported as:

js

github.com/rfwlab/rfw/v1/state import

Import example:

import "github.com/rfwlab/rfw/v1/state"

testing import

Import example:

import "testing"

github.com/rfwlab/rfw/v1/js import

Import example:

import "github.com/rfwlab/rfw/v1/js"

Imported as:

js

testing import

Import example:

import "testing"

testing import

Import example:

import "testing"

sync import

Import example:

import "sync"

time import

Import example:

import "time"

strings import

Import example:

import "strings"

testing import

Import example:

import "testing"

github.com/rfwlab/rfw/v1/js import

Import example:

import "github.com/rfwlab/rfw/v1/js"

Imported as:

js

testing import

Import example:

import "testing"

github.com/rfwlab/rfw/v1/js import

Import example:

import "github.com/rfwlab/rfw/v1/js"

Imported as:

js