neith

package module
v0.4.11 Latest Latest
Warning

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

Go to latest
Published: Jun 6, 2026 License: MIT Imports: 15 Imported by: 0

README

Neith logo

Neith

Neith is a small Go package for building server-rendered, interactive HTML components. It wraps Go components with a WebSocket-backed dispatch layer so the server can render HTML, respond to DOM events, update elements, redirect the browser, call client-side JavaScript, and keep per-client state.

The package is designed around a minimal component interface:

type Component interface {
	Render(ctx context.Context, w io.Writer) error
}

That means plain neith.HTML, templ components, or your own renderable types can be used as Neith components.

Features

  • Render Go components into the DOM over WebSockets.
  • Attach server-side handlers to browser events.
  • Swap, append, prepend, or remove elements by tag or ID.
  • Add and remove CSS classes from the server.
  • Redirect the browser from a handler.
  • Run custom JavaScript functions from Go.
  • Read typed event payloads with EventData[T].
  • Inspect uploaded files and form submitter metadata from handlers.
  • Store per-connection state with generic caches.
  • Configure logging and cache expiry.

Installation

go get github.com/seanbman/neith

Neith requires Go 1.21 or newer.

Quick Start

Serve an HTML shell with a <main> element and the bundled browser client, then wrap your route with neith.MiddleWareFn.

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"

	"github.com/seanbman/neith"
)

func page(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	fmt.Fprint(w, `<!doctype html>
<html>
	<head>
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<script defer src="/assets/index.min.js"></script>
	</head>
	<body>
		<main></main>
	</body>
</html>`)
}

func app(ctx context.Context) neith.FnComponent {
	return neith.NewFn(ctx, neith.HTML(`
		<button>Click me</button>
	`)).WithEvents(clicked, neith.OnClick)
}

func clicked(ctx context.Context) neith.FnComponent {
	return neith.NewFn(ctx, neith.HTML(`
		<section>
			<h1>Hello from the server</h1>
			<p>The button click was handled in Go.</p>
		</section>
	`)).SwapTagInner("main")
}

func main() {
	http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("static/assets"))))
	http.HandleFunc("/", neith.MiddleWareFn(page, app))

	log.Fatal(http.ListenAndServe(":8080", nil))
}

Open http://localhost:8080. The first request serves the HTML shell. The browser client then opens a WebSocket back to the same route with ?neith_id=..., and MiddleWareFn sends the initial FnComponent to the client.

Rendering Components

Create an FnComponent with NewFn(ctx, component). By default, it swaps the inner HTML of the first <main> tag.

func view(ctx context.Context) neith.FnComponent {
	return neith.NewFn(ctx, neith.HTML(`<h1>Dashboard</h1>`))
}

You can change where the rendered HTML is applied:

neith.NewFn(ctx, component).SwapTagInner("main")
neith.NewFn(ctx, component).SwapTagOuter("main")
neith.NewFn(ctx, component).AppendTag("ul")
neith.NewFn(ctx, component).PrependTag("ul")

neith.NewFn(ctx, component).SwapElementInner("content")
neith.NewFn(ctx, component).SwapElementOuter("content")
neith.NewFn(ctx, component).AppendElement("items")
neith.NewFn(ctx, component).PrependElement("items")

To render components to a string outside the live dispatch flow:

html := neith.RenderComponent(neith.HTML(`<p>Rendered</p>`))

Events

Attach one or more DOM events with WithEvents.

func form(ctx context.Context) neith.FnComponent {
	return neith.NewFn(ctx, neith.HTML(`
		<form>
			<input name="name" placeholder="Name">
			<button>Save</button>
		</form>
	`)).WithEvents(save, neith.OnSubmit)
}

func save(ctx context.Context) neith.FnComponent {
	values, err := neith.EventData[map[string]string](ctx)
	if err != nil {
		return neith.FnErr(ctx, err)
	}

	return neith.NewFn(ctx, neith.HTML(
		"<p>Saved " + values["name"] + "</p>",
	)).SwapTagInner("main")
}

For pointer, mouse, keyboard, drag, touch, input, change, submit, and other DOM events, the browser client sends event data back to Go. Use EventData[T] with the matching type, such as neith.PointerEvent, neith.DragEvent, or your own form-data struct/map.

Event targets include the element ID, name, classes, tag name, HTML, value, checked, disabled, hidden, inline style, attributes, dataset, and selected option values. For mouse, pointer, drag, touch, and keyboard payloads, source is the element that caused the event and component is the Neith wrapper listening for it. For submit events, EventSubmitter returns the button or input that submitted the form.

func save(ctx context.Context) neith.FnComponent {
	values, err := neith.EventData[map[string]string](ctx)
	if err != nil {
		return neith.FnErr(ctx, err)
	}

	submitter, err := neith.EventSubmitter(ctx)
	if err != nil {
		return neith.FnErr(ctx, err)
	}
	if submitter != nil && submitter.Value == "delete" {
		return neith.NewFn(ctx, neith.HTML("<p>Delete requested</p>"))
	}

	return neith.NewFn(ctx, neith.HTML("<p>Saved " + values["name"] + "</p>"))
}
File Uploads

Forms with file inputs upload file bytes over HTTP before the websocket event is sent. Normal form values stay available through EventData, and uploaded file metadata is available with EventUploads.

func upload(ctx context.Context) neith.FnComponent {
	values, err := neith.EventData[map[string]string](ctx)
	if err != nil {
		return neith.FnErr(ctx, err)
	}

	uploads, err := neith.EventUploads(ctx)
	if err != nil {
		return neith.FnErr(ctx, err)
	}

	return neith.NewFn(ctx, neith.HTML(
		"<p>Saved " + values["title"] + " with " + uploads[0].FileName + "</p>",
	))
}

Uploaded files are written to Config.UploadDir, or to the system temp directory under neith-uploads when no directory is configured.

Server-Initiated Updates

Handlers can dispatch extra effects while handling an event:

func clicked(ctx context.Context) neith.FnComponent {
	neith.AddClasses(ctx, "status", "active")
	neith.RemoveClasses(ctx, "status", "pending")
	neith.JS(ctx, "Testing", "called from Go")

	return neith.NewFn(ctx, neith.HTML(`<p id="status">Done</p>`))
}

Other helpers:

neith.SetAttribute(ctx, "save", "aria-busy", "true")
neith.RemoveAttribute(ctx, "save", "aria-busy")
neith.SetStyle(ctx, "status", "color", "green")
neith.RemoveStyle(ctx, "status", "color")
neith.SetText(ctx, "status", "Saved")
neith.SetValue(ctx, "search", "")
neith.Focus(ctx, "search")
neith.Blur(ctx, "search")
neith.ScrollIntoView(ctx, "results")
neith.Disable(ctx, "save")
neith.Enable(ctx, "save")
neith.RemoveElement(ctx, "modal")
neith.RemoveTag(ctx, "dialog")
return neith.RedirectURL(ctx, "/next")

The browser client also exposes lifecycle hooks:

window.neith.on("afterRender", ({ dispatch, element }) => {
	console.log("rendered", dispatch.function, element)
})

window.neith.on("beforeEventDispatch", ({ dispatch, event }) => {
	console.log("sending event", dispatch.event.on)
})

The socket emits connect, disconnect, and reconnect hooks. Unexpected disconnects are retried with capped exponential backoff instead of reloading the page.

Cache

Neith includes a generic, per-connection cache. Create a cache once, then reuse it from later handlers for the same client connection.

func app(ctx context.Context) neith.FnComponent {
	_, _ = neith.NewCache(ctx, "count", 0)
	return counter(ctx)
}

func counter(ctx context.Context) neith.FnComponent {
	count, err := neith.UseCache[int](ctx, "count")
	if err != nil {
		return neith.FnErr(ctx, err)
	}

	_ = count.Set(count.Value() + 1)

	return neith.NewFn(ctx, neith.HTML(fmt.Sprintf(
		`<button>Clicked %d times</button>`,
		count.Value(),
	))).WithEvents(func(ctx context.Context) neith.FnComponent {
		return counter(ctx)
	}, neith.OnClick)
}

Cache helpers include:

  • Set(value, timeout...)
  • Value()
  • Delete()
  • CreatedAt()
  • UpdatedAt()
  • Expiry()
  • Record(true)
  • History()
  • OnCacheChange(cache, fn)
  • OnCacheTimeOut(cache, fn)

Use Record(true) before calling Set when you want to keep a history of cache updates:

cache, err := neith.UseCache[int](ctx, "count")
if err != nil {
	return neith.FnErr(ctx, err)
}

cache.Record(true)
_ = cache.Set(cache.Value() + 1)

history, ok := cache.History()
if ok {
	// history contains recorded values keyed by update time.
}

Configuration

The default config uses a 30 minute cache timeout and logs errors.

neith.SetConfig(&neith.Config{
	CacheTimeOut: 10 * time.Minute,
	LogLevel:     neith.Info,
})

Set Silent: true or LogLevel: neith.None to disable package logs.

Example App

The repository includes an external consumer-style app in examples/readme-setup. It has its own go.mod, imports github.com/seanbman/neith, and uses a local replace directive so it runs against the package source in this repo.

Run it from the repo root:

make example

Run it in debug mode from VS Code with the Debug README Example launch configuration, or start a headless Delve server from the repo root:

make example-debug

Then attach with the Attach README Example launch configuration. The default debug port is 40000; override it with DEBUG_PORT=40001 make example-debug. The debug target requires Delve:

go install github.com/go-delve/delve/cmd/dlv@latest

If dlv still is not found after install, add Go's bin directory to your shell:

echo 'export PATH="$PATH:$(go env GOPATH)/bin"' >> ~/.bashrc
source ~/.bashrc

Or run it from the example folder:

cd examples/readme-setup
go run github.com/a-h/templ/cmd/templ@v0.2.513 generate
go run .

Then open:

http://localhost:8080

The example UI is written as templ components in examples/readme-setup/dashboard.templ; dashboard_templ.go is the generated Go file checked in beside it. Regenerate that file from the repo root with make example-templ.

The example serves the package's bundled browser client from static/assets, so it can test local Neith changes without copying generated assets into the example folder.

Detailed Notes

Detailed function notes and usage examples live in notes:

  • notes/cache/README.md
  • notes/component/README.md

Development

Run Go tests:

go test ./... -v

Run browser-client tests:

cd static/assets
npm install
npm test

Type-check browser-client TypeScript:

tsc -p static/assets/

Build bundled assets:

make assets

License

MIT

Documentation

Overview

Package neith brings enhanced functionality to the Component interface.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddClasses

func AddClasses(ctx context.Context, id string, classes ...string)

AddClasses immediately adds CSS classes to one element by ID.

The mutation is sent as a class dispatch and runs through classList.add on the browser client.

func Blur

func Blur(ctx context.Context, id string)

Blur immediately calls blur() on an element by ID.

func Disable

func Disable(ctx context.Context, id string)

Disable immediately sets disabled=true on a form control by ID.

func Enable

func Enable(ctx context.Context, id string)

Enable immediately sets disabled=false on a form control by ID.

func EventData

func EventData[T any](ctx context.Context) (T, error)

EventData unmarshals the current event's client payload into T.

Handlers normally call EventData from inside a HandleFn. The payload shape depends on the browser event: form events usually decode into a map or struct, while pointer, keyboard, drag, mouse, and touch events can decode into the matching neith event structs.

func Focus

func Focus(ctx context.Context, id string)

Focus immediately calls focus() on an element by ID.

func JS

func JS(ctx context.Context, fn string, arg any)

JS dispatches a custom JavaScript call immediately.

Unlike FnComponent.JS, this helper sends the dispatch right away and does not return a component for a handler to return.

func MiddleWareFn

func MiddleWareFn(h http.HandlerFunc, hf HandleFn) http.HandlerFunc

func OnCacheChange

func OnCacheChange[T any](c Cache[T], f func())

OnCacheChange registers a callback to run after Set writes a cache value.

Updating metadata with helpers like Record uses saveCache and does not trigger this callback. Only value-changing Set calls trigger OnCacheChange.

func OnCacheTimeOut

func OnCacheTimeOut[T any](c Cache[T], f func())

OnCacheTimeOut registers a callback to run when this cache entry expires.

The callback is keyed to this cache's client/key pair. It runs after an expiry watcher confirms the cache value is still the same update that started the watcher.

func RemoveAttribute

func RemoveAttribute(ctx context.Context, id string, name string)

RemoveAttribute immediately removes one attribute from an element by ID.

func RemoveClasses

func RemoveClasses(ctx context.Context, id string, classes ...string)

RemoveClasses immediately removes CSS classes from one element by ID.

The mutation is sent as a class dispatch and runs through classList.remove on the browser client.

func RemoveElement

func RemoveElement(ctx context.Context, id string)

RemoveElement immediately removes one DOM element by ID.

This sends a render dispatch with Remove set and TargetID populated.

func RemoveStyle

func RemoveStyle(ctx context.Context, id string, name string)

RemoveStyle immediately removes one inline style property from an element by ID.

func RemoveTag

func RemoveTag(ctx context.Context, tag string)

RemoveTag immediately removes the first matching DOM tag.

This sends a render dispatch with Remove set and Tag populated.

func RenderComponent

func RenderComponent(c ...Component) (html string)

RenderComponent renders one or more components into a single HTML string.

This helper is useful when code needs a component's HTML outside the live websocket dispatch path. It renders with context.Background and appends each component into the same buffer in the order provided.

func ScrollIntoView

func ScrollIntoView(ctx context.Context, id string)

ScrollIntoView immediately scrolls an element into view by ID.

func SetAttribute

func SetAttribute(ctx context.Context, id string, name string, value string)

SetAttribute immediately sets one attribute on an element by ID.

func SetConfig

func SetConfig(c *Config)

func SetStyle

func SetStyle(ctx context.Context, id string, name string, value string)

SetStyle immediately sets one inline style property on an element by ID.

func SetText

func SetText(ctx context.Context, id string, value string)

SetText immediately replaces an element's textContent by ID.

func SetValue

func SetValue(ctx context.Context, id string, value string)

SetValue immediately sets the value property on a form control by ID.

Types

type Cache

type Cache[T any] struct {
	// contains filtered or unexported fields
}

func NewCache

func NewCache[T any](ctx context.Context, key string, initial T) (Cache[T], error)

NewCache creates a typed cache entry for the current client session.

The client ID is read from the dispatch details stored in ctx by the neith middleware. Each client session gets its own cache store, so the same key can be reused safely across different browser clients. NewCache returns ErrCacheExists if the key already exists for this client session, including when the existing cache was created with a different type.

func UseCache

func UseCache[T any](ctx context.Context, key string) (Cache[T], error)

UseCache retrieves a typed cache entry for the current client session.

The requested type T must match the type used when the cache was created. If the key does not exist, UseCache returns ErrCacheNotFound; if the key exists with a different type, it returns ErrCacheWrongType.

https://pkg.go.dev/github.com/seanbman/neith#UseCache

func (*Cache[T]) CreatedAt

func (c *Cache[T]) CreatedAt() time.Time

CreatedAt returns the time recorded when this cache entry was first created.

The timestamp is stored on the cache value itself and is preserved when Set updates the cache.

func (*Cache[T]) Delete

func (c *Cache[T]) Delete()

Delete removes this cache entry from its client-session store.

Delete is idempotent from the caller's perspective: deleting a missing cache does not return an error, though a missing store may be logged at debug level.

func (*Cache[T]) Expiry

func (c *Cache[T]) Expiry() time.Time

Expiry returns the wall-clock time when the current cache value expires.

Expiry is calculated from UpdatedAt plus TimeOut. It is useful for inspection and tests; expiry deletion is handled internally by watchExpiry.

func (*Cache[T]) History

func (c *Cache[T]) History() (map[string]T, bool)

History returns the recorded cache values for this cache entry.

The returned map is keyed by the time each value was recorded. The boolean is false when no history exists or when a stored history value cannot be asserted back to T.

func (*Cache[T]) Record

func (c *Cache[T]) Record(record bool)

Record controls whether future cache updates are stored in history.

The flag is persisted into the backing cache entry, so calling Record(true) before Set is enough to have later Set calls recorded even though Set reloads the current cache state from the store.

func (*Cache[T]) Set

func (c *Cache[T]) Set(data T, timeout ...time.Duration) error

Set writes a new value into the cache and refreshes its expiry timer.

Passing a positive timeout shorter than config.CacheTimeOut uses that timeout for this cache update. Passing 0 keeps the previous timeout when one exists. Omitting timeout, passing a negative timeout, or passing a timeout greater than config.CacheTimeOut falls back to config.CacheTimeOut. Set also triggers OnCacheChange callbacks and starts an expiry watcher for this exact update.

func (*Cache[T]) TimeOut

func (c *Cache[T]) TimeOut() time.Duration

TimeOut returns the duration after UpdatedAt when this cache entry expires.

Expiry is checked by a background watcher created by Set. The watcher verifies the cache has not been updated since it started before deleting anything.

func (*Cache[T]) UpdatedAt

func (c *Cache[T]) UpdatedAt() time.Time

UpdatedAt returns the time recorded when this cache value was last written.

The value changes each time Set successfully persists a new cache value.

func (*Cache[T]) Value

func (c *Cache[T]) Value() T

Value returns the latest stored cache value.

If the cache no longer exists or cannot be read as T, Value returns the zero value for T. Use UseCache when the caller needs to distinguish those errors.

type CacheError

type CacheError string
const (
	ErrCacheNotFound  CacheError = "cache not found"
	ErrStoreNotFound  CacheError = "cache store not found; create cache first"
	ErrCacheWrongType CacheError = "cache wrong type"
	ErrCacheExists    CacheError = "cache already exists; delete existing cache first"
)

func (CacheError) Error

func (e CacheError) Error() string

type CacheOnFn

type CacheOnFn string

type Component

type Component interface {
	Render(ctx context.Context, w io.Writer) error
}

Component is the minimal renderable unit neith can send to the browser.

It intentionally matches the shape used by templ and many simple Go HTML renderers: Render receives a context and writes HTML to the supplied writer.

type Config

type Config struct {
	Silent          bool          // If true, no logs will be printed
	CacheTimeOut    time.Duration // Default cache timeout
	LogLevel        LogLevel
	Logger          *log.Logger
	UploadDir       string // Directory for multipart event uploads; defaults to os.TempDir()/neith-uploads
	UploadMaxBytes  int64  // Maximum request size for one upload request
	UploadMaxMemory int64  // Maximum multipart memory before files spill to disk
}

func (*Config) Set

func (c *Config) Set()

type ContextKey

type ContextKey string

ContextKey is used to store values in context esp. for event listeners

const (
	// EventKey is used to store EventListeners in context
	EventKey ContextKey = "event"
	// RequestKey is used to store http.Request in context
	RequestKey ContextKey = "request"
	// ResponseKey is used to store http.ResponseWriter in context
	ErrorKey ContextKey = "error"
)

type Dispatch

type Dispatch struct {
	ID         string        `json:"id"`
	Key        string        `json:"key"`
	ConnID     string        `json:"conn_id"`
	HandlerID  string        `json:"handler_id"`
	Action     string        `json:"action"`
	Label      string        `json:"label"`
	Function   functionName  `json:"function"`
	FnEvent    EventListener `json:"event"`
	FnPing     FnPing        `json:"ping"`
	FnRender   FnRender      `json:"render"`
	FnClass    FnClass       `json:"class"`
	FnDOM      FnDOM         `json:"dom"`
	FnRedirect FnRedirect    `json:"redirect"`
	FnCustom   FnCustom      `json:"custom"`
	FnError    FnError       `json:"error"`
	// contains filtered or unexported fields
}

Dispatch is the websocket message exchanged by Go and the browser client.

The flat JSON shape mirrors static/assets/neith_types.ts. Function selects which nested payload is active for a given message.

type DispatchError

type DispatchError string
const (
	ErrCtxMissingDispatch DispatchError = "context missing dispatch"
	ErrNoClientConnection DispatchError = "no connection to client"
	ErrConnectionNotFound DispatchError = "connection not found"
	ErrConnectionFailed   DispatchError = "connection failed"
	ErrCtxMissingEvent    DispatchError = "context missing event"
)

func (DispatchError) Error

func (e DispatchError) Error() string

type DragEvent

type DragEvent struct {
	IsTrusted        bool        `json:"isTrusted"`
	AltKey           bool        `json:"altKey"`
	Bubbles          bool        `json:"bubbles"`
	Button           int         `json:"button"`
	Buttons          int         `json:"buttons"`
	Cancelable       bool        `json:"cancelable"`
	ClientX          int         `json:"clientX"`
	ClientY          int         `json:"clientY"`
	Composed         bool        `json:"composed"`
	CtrlKey          bool        `json:"ctrlKey"`
	Component        EventTarget `json:"component"`
	DefaultPrevented bool        `json:"defaultPrevented"`
	Detail           int         `json:"detail"`
	EventPhase       int         `json:"eventPhase"`
	MetaKey          bool        `json:"metaKey"`
	MovementX        int         `json:"movementX"`
	MovementY        int         `json:"movementY"`
	OffsetX          int         `json:"offsetX"`
	OffsetY          int         `json:"offsetY"`
	PageX            int         `json:"pageX"`
	PageY            int         `json:"pageY"`
	RelatedTarget    EventTarget `json:"relatedTarget"`
	Source           EventTarget `json:"source"`
}

DragEvent contains the drag payload sent by the browser client.

type EventListener

type EventListener struct {
	context.Context `json:"-"`
	ID              string       `json:"id"`
	TargetID        string       `json:"target_id"`
	Handler         HandleFn     `json:"-"`
	On              OnEvent      `json:"on"`
	Data            any          `json:"data"`
	Uploads         []Upload     `json:"uploads,omitempty"`
	Submitter       *EventTarget `json:"submitter,omitempty"`
}

EventListener is the server-side handler metadata serialized into rendered components and echoed back by the browser when the matching DOM event fires.

type EventTarget

type EventTarget struct {
	ID              string   `json:"id"`
	Name            string   `json:"name"`
	ClassList       []string `json:"classList"`
	TagName         string   `json:"tagName"`
	InnerHTML       string   `json:"innerHTML"`
	OuterHTML       string   `json:"outerHTML"`
	Value           string   `json:"value"`
	Checked         bool     `json:"checked"`
	Disabled        bool     `json:"disabled"`
	Hidden          bool     `json:"hidden"`
	Style           string   `json:"style"`
	Attributes      []string `json:"attributes"`
	Dataset         []string `json:"dataset"`
	SelectedOptions []string `json:"selectedOptions"`
}

EventTarget is a JSON-safe snapshot of a DOM element involved in an event.

func EventSubmitter

func EventSubmitter(ctx context.Context) (*EventTarget, error)

EventSubmitter returns the button or input that submitted a form event.

type FnClass

type FnClass struct {
	TargetID string   `json:"target_id"`
	Remove   bool     `json:"remove"`
	Names    []string `json:"names"`
}

FnClass adds or removes CSS classes from a browser element.

type FnComponent

type FnComponent struct {
	context.Context
	// contains filtered or unexported fields
}

FnComponent is the server-side wrapper around rendered HTML and dispatch data.

It implements io.Writer so components can render directly into it. The dispatch field tracks how the browser should apply the rendered HTML, which client session it belongs to, and any event listeners attached to it.

func FnErr

func FnErr(ctx context.Context, err error) FnComponent

FnErr creates an error dispatch from a context and error.

This is a convenience for event handlers that return FnComponent and want to surface an error through neith's normal error path.

func NewFn

func NewFn(ctx context.Context, c Component) FnComponent

NewFn creates a new FnComponent from a Component.

The component is given a unique DOM wrapper ID and, when ctx was created by neith middleware, is attached to the active websocket dispatch context. NewFn defaults to swapping the inner HTML of <main>, which gives simple apps a useful render target without extra setup.

func RedirectURL

func RedirectURL(ctx context.Context, url string) FnComponent

RedirectURL creates a redirect dispatch for handler return values.

Returning this from a HandleFn tells the browser client to navigate to url.

func (FnComponent) AppendElement

func (f FnComponent) AppendElement(id string) FnComponent

AppendElement appends the rendered component to an element by ID.

The browser client finds document.getElementById(id) and appends the rendered HTML to that element's innerHTML.

func (FnComponent) AppendTag

func (f FnComponent) AppendTag(tag string) FnComponent

AppendTag appends the rendered component to the first matching tag in the DOM.

The browser client finds document.getElementsByTagName(tag)[0] and appends the rendered HTML to that element's innerHTML.

func (FnComponent) Dispatch

func (f FnComponent) Dispatch()

Dispatch immediately queues this component for the active browser connection.

Dispatch requires a client session and handler ID from middleware context. If the component was created outside an neith request/event context, Dispatch logs the missing connection and returns without sending anything.

func (FnComponent) JS

func (f FnComponent) JS(fn string, arg any) FnComponent

JS changes this dispatch into a custom browser-side JavaScript call.

fn is the name of a function on window and arg is serialized as the payload. The browser client calls window[fn](arg) and sends the result back in the dispatch's custom result field.

func (FnComponent) PrependElement

func (f FnComponent) PrependElement(id string) FnComponent

PrependElement prepends the rendered component to an element by ID.

The browser client finds document.getElementById(id) and inserts the rendered HTML before the element's existing innerHTML.

func (FnComponent) PrependTag

func (f FnComponent) PrependTag(tag string) FnComponent

PrependTag prepends the rendered component to the first matching tag.

The browser client inserts the rendered HTML before the existing innerHTML of document.getElementsByTagName(tag)[0].

func (FnComponent) Render

func (f FnComponent) Render(ctx context.Context, w io.Writer) error

Render writes the component wrapper and buffered HTML.

The wrapper div carries the component ID, optional label, and serialized event listener metadata. The browser client reads those attributes after inserting the HTML so it can attach DOM listeners and route events back to Go.

func (FnComponent) SwapElementInner

func (f FnComponent) SwapElementInner(id string) FnComponent

SwapElementInner replaces one element's inner HTML by ID.

Use this when the target element should remain in place but its contents should be replaced by the rendered component.

func (FnComponent) SwapElementOuter

func (f FnComponent) SwapElementOuter(id string) FnComponent

SwapElementOuter replaces one element's outer HTML by ID.

Use this when the rendered component should replace the selected element itself, including its tag.

func (FnComponent) SwapTagInner

func (f FnComponent) SwapTagInner(tag string) FnComponent

SwapTagInner replaces the first matching tag's inner HTML.

This is NewFn's default render mode for <main>, making it the common path for full-page or main-region updates.

func (FnComponent) SwapTagOuter

func (f FnComponent) SwapTagOuter(tag string) FnComponent

SwapTagOuter replaces the first matching tag's outer HTML.

Use this when the rendered component should replace the target element itself, not just the target element's contents.

func (FnComponent) WithContext

func (f FnComponent) WithContext(ctx context.Context) FnComponent

WithContext replaces the component context and refreshes dispatch details.

Use this when a component value is created outside the request/event context and later needs to be associated with the active client session before dispatch.

func (FnComponent) WithError

func (f FnComponent) WithError(err error) FnComponent

WithError changes this dispatch into an error message.

The server-side handler pipeline logs these errors through the package logger unless logging is disabled. A nil error is normalized to a useful message so callers do not silently dispatch an empty error.

func (FnComponent) WithEvents

func (f FnComponent) WithEvents(h HandleFn, e ...OnEvent) FnComponent

WithEvents attaches one server handler to one or more DOM event types.

Each event listener is registered in the client-session event registry and serialized into the component's wrapper metadata. When the browser receives this component, it attaches listeners and sends matching DOM events back to h.

func (FnComponent) WithLabel

func (f FnComponent) WithLabel(label string) FnComponent

WithLabel sets a human-readable label on the component wrapper.

The label may be used to identify a component on the server and client, especially during debugging.

func (FnComponent) WithRedirect

func (f FnComponent) WithRedirect(url string) FnComponent

WithRedirect changes this dispatch into a browser redirect.

Redirect components do not need rendered HTML. When returned from a handler or dispatched directly, the browser client sets window.location to url.

func (FnComponent) Write

func (f FnComponent) Write(p []byte) (n int, err error)

Write appends rendered bytes to this component's dispatch buffer.

Component renderers call this indirectly when NewFn renders a Component into the FnComponent. The buffered bytes are later wrapped by Render and sent to the browser.

type FnCustom

type FnCustom struct {
	Function string `json:"function"`
	Data     any    `json:"data"`
	Result   any    `json:"result"`
}

FnCustom calls a named browser function and receives its result.

type FnDOM

type FnDOM struct {
	TargetID  string `json:"target_id"`
	Operation string `json:"operation"`
	Name      string `json:"name,omitempty"`
	Value     string `json:"value,omitempty"`
}

FnDOM applies a focused DOM mutation such as attribute, style, text, value, focus, blur, scroll, enable, disable, or removal.

type FnError

type FnError struct {
	Message string `json:"message"`
}

FnError carries an error message between Go and the browser client.

type FnPing

type FnPing struct {
	Server bool `json:"server"`
	Client bool `json:"client"`
}

FnPing confirms that the websocket is still alive on both sides.

type FnRedirect

type FnRedirect struct {
	URL string `json:"url"`
}

FnRedirect sends the browser to a new URL.

type FnRender

type FnRender struct {
	TargetID       string          `json:"target_id"`
	Tag            string          `json:"tag"`
	Inner          bool            `json:"inner"`
	Outer          bool            `json:"outer"`
	Append         bool            `json:"append"`
	Prepend        bool            `json:"prepend"`
	Remove         bool            `json:"remove"`
	HTML           string          `json:"html"`
	EventListeners []EventListener `json:"event_listeners"`
}

FnRender describes how rendered HTML should be applied in the browser.

type FormDataEvent

type FormDataEvent struct {
	IsTrusted        bool           `json:"isTrusted"`
	Bubbles          bool           `json:"bubbles"`
	Cancelable       bool           `json:"cancelable"`
	Composed         bool           `json:"composed"`
	Component        EventTarget    `json:"component"`
	DefaultPrevented bool           `json:"defaultPrevented"`
	EventPhase       int            `json:"eventPhase"`
	FormData         map[string]any `json:"formData"`
}

FormDataEvent contains the form payload sent by the browser client.

type HTML

type HTML string

HTML adapts a raw HTML string to the Component interface.

It is useful for small examples, tests, and simple components that do not need a dedicated struct or templ-generated renderer.

func (HTML) Render

func (h HTML) Render(ctx context.Context, w io.Writer) error

Render writes the HTML string to the provided writer.

The context parameter is accepted to satisfy Component; HTML itself does not inspect it.

func (*HTML) Write

func (h *HTML) Write(p []byte) (n int, err error)

Write appends bytes to the HTML value.

This lets HTML act as a small string buffer when code wants an io.Writer that accumulates rendered HTML.

type HandleFn

type HandleFn func(context.Context) FnComponent

type KeyboardEvent

type KeyboardEvent struct {
	IsTrusted        bool        `json:"isTrusted"`
	AltKey           bool        `json:"altKey"`
	Bubbles          bool        `json:"bubbles"`
	Cancelable       bool        `json:"cancelable"`
	Code             string      `json:"code"`
	Composed         bool        `json:"composed"`
	CtrlKey          bool        `json:"ctrlKey"`
	Component        EventTarget `json:"component"`
	DefaultPrevented bool        `json:"defaultPrevented"`
	Detail           int         `json:"detail"`
	EventPhase       int         `json:"eventPhase"`
	IsComposing      bool        `json:"isComposing"`
	Key              string      `json:"key"`
	Location         int         `json:"location"`
	MetaKey          bool        `json:"metaKey"`
	Repeat           bool        `json:"repeat"`
	ShiftKey         bool        `json:"shiftKey"`
	Source           EventTarget `json:"source"`
}

KeyboardEvent contains the keyboard payload sent by the browser client.

type LogLevel

type LogLevel log.Level
const (
	Debug LogLevel = -4
	Info  LogLevel = 0
	Warn  LogLevel = 4
	Error LogLevel = 8
	Fatal LogLevel = 12
	None  LogLevel = math.MaxInt32
)

type MouseEvent

type MouseEvent struct {
	IsTrusted        bool        `json:"isTrusted"`
	AltKey           bool        `json:"altKey"`
	Bubbles          bool        `json:"bubbles"`
	Button           int         `json:"button"`
	Buttons          int         `json:"buttons"`
	Cancelable       bool        `json:"cancelable"`
	ClientX          int         `json:"clientX"`
	ClientY          int         `json:"clientY"`
	Composed         bool        `json:"composed"`
	CtrlKey          bool        `json:"ctrlKey"`
	Component        EventTarget `json:"component"`
	DefaultPrevented bool        `json:"defaultPrevented"`
	Detail           int         `json:"detail"`
	EventPhase       int         `json:"eventPhase"`
	MetaKey          bool        `json:"metaKey"`
	MovementX        int         `json:"movementX"`
	MovementY        int         `json:"movementY"`
	OffsetX          int         `json:"offsetX"`
	OffsetY          int         `json:"offsetY"`
	PageX            int         `json:"pageX"`
	PageY            int         `json:"pageY"`
	RelatedTarget    EventTarget `json:"relatedTarget"`
	Source           EventTarget `json:"source"`
}

MouseEvent contains the mouse payload sent by the browser client.

type OnEvent

type OnEvent string
const (
	OnAbort              OnEvent = "abort"
	OnAnimationEnd       OnEvent = "animationend"
	OnAnimationIteration OnEvent = "animationiteration"
	OnAnimationStart     OnEvent = "animationstart"
	OnBlur               OnEvent = "blur"
	OnCanPlay            OnEvent = "canplay"
	OnCanPlayThrough     OnEvent = "canplaythrough"
	OnChange             OnEvent = "change"
	OnChangeCapture      OnEvent = "changecapture"
	OnClick              OnEvent = "click"
	OnCompositionEnd     OnEvent = "compositionend"
	OnCompositionStart   OnEvent = "compositionstart"
	OnCompositionUpdate  OnEvent = "compositionupdate"
	OnContextMenuCapture OnEvent = "contextmenucapture"
	OnCopy               OnEvent = "copy"
	OnCut                OnEvent = "cut"
	OnDoubleClickCapture OnEvent = "doubleclickcapture"
	OnDrag               OnEvent = "drag"
	OnDragEnd            OnEvent = "dragend"
	OnDragEnter          OnEvent = "dragenter"
	OnDragExitCapture    OnEvent = "dragexitcapture"
	OnDragLeave          OnEvent = "dragleave"
	OnDragOver           OnEvent = "dragover"
	OnDragStart          OnEvent = "dragstart"
	OnDrop               OnEvent = "drop"
	OnDurationChange     OnEvent = "durationchange"
	OnEmptied            OnEvent = "emptied"
	OnEncrypted          OnEvent = "encrypted"
	OnEnded              OnEvent = "ended"
	OnError              OnEvent = "error"
	OnFocus              OnEvent = "focus"
	OnGotPointerCapture  OnEvent = "gotpointercapture"
	OnInput              OnEvent = "input"
	OnInvalid            OnEvent = "invalid"
	OnKeyDown            OnEvent = "keydown"
	OnKeyPress           OnEvent = "keypress"
	OnKeyUp              OnEvent = "keyup"
	OnLoad               OnEvent = "load"
	OnLoadEnd            OnEvent = "loadend"
	OnLoadStart          OnEvent = "loadstart"
	OnLoadedData         OnEvent = "loadeddata"
	OnLoadedMetadata     OnEvent = "loadedmetadata"
	OnLostPointerCapture OnEvent = "lostpointercapture"
	OnMouseDown          OnEvent = "mousedown"
	OnMouseEnter         OnEvent = "mouseenter"
	OnMouseLeave         OnEvent = "mouseleave"
	OnMouseMove          OnEvent = "mousemove"
	OnMouseOut           OnEvent = "mouseout"
	OnMouseOver          OnEvent = "mouseover"
	OnMouseUp            OnEvent = "mouseup"
	OnPause              OnEvent = "pause"
	OnPlay               OnEvent = "play"
	OnPlaying            OnEvent = "playing"
	OnPointerCancel      OnEvent = "pointercancel"
	OnPointerDown        OnEvent = "pointerdown"
	OnPointerEnter       OnEvent = "pointerenter"
	OnPointerLeave       OnEvent = "pointerleave"
	OnPointerMove        OnEvent = "pointermove"
	OnPointerOut         OnEvent = "pointerout"
	OnPointerOver        OnEvent = "pointerover"
	OnPointerUp          OnEvent = "pointerup"
	OnProgress           OnEvent = "progress"
	OnRateChange         OnEvent = "ratechange"
	OnResetCapture       OnEvent = "resetcapture"
	OnScroll             OnEvent = "scroll"
	OnSeeked             OnEvent = "seeked"
	OnSeeking            OnEvent = "seeking"
	OnSelectCapture      OnEvent = "selectcapture"
	OnStalled            OnEvent = "stalled"
	OnSubmit             OnEvent = "submit"
	OnSuspend            OnEvent = "suspend"
	OnTimeUpdate         OnEvent = "timeupdate"
	OnToggle             OnEvent = "toggle"
	OnTouchCancel        OnEvent = "touchcancel"
	OnTouchEnd           OnEvent = "touchend"
	OnTouchMove          OnEvent = "touchmove"
	OnTouchStart         OnEvent = "touchstart"
	OnTransitionEnd      OnEvent = "transitionend"
	OnVolumeChange       OnEvent = "volumechange"
	OnWaiting            OnEvent = "waiting"
	OnWheel              OnEvent = "wheel"
)

DOM event types.

type PointerEvent

type PointerEvent struct {
	IsTrusted        bool        `json:"isTrusted"`
	AltKey           bool        `json:"altKey"`
	Bubbles          bool        `json:"bubbles"`
	Button           int         `json:"button"`
	Buttons          int         `json:"buttons"`
	Cancelable       bool        `json:"cancelable"`
	ClientX          int         `json:"clientX"`
	ClientY          int         `json:"clientY"`
	Composed         bool        `json:"composed"`
	CtrlKey          bool        `json:"ctrlKey"`
	Component        EventTarget `json:"component"`
	DefaultPrevented bool        `json:"defaultPrevented"`
	Detail           int         `json:"detail"`
	EventPhase       int         `json:"eventPhase"`
	Height           int         `json:"height"`
	IsPrimary        bool        `json:"isPrimary"`
	MetaKey          bool        `json:"metaKey"`
	MovementX        int         `json:"movementX"`
	MovementY        int         `json:"movementY"`
	OffsetX          int         `json:"offsetX"`
	OffsetY          int         `json:"offsetY"`
	PageX            int         `json:"pageX"`
	PageY            int         `json:"pageY"`
	PointerId        int         `json:"pointerId"`
	PointerType      string      `json:"pointerType"`
	Pressure         int         `json:"pressure"`
	RelatedTarget    EventTarget `json:"relatedTarget"`
	Source           EventTarget `json:"source"`
}

PointerEvent contains the pointer payload sent by the browser client.

type Touch

type Touch struct {
	ClientX       int         `json:"clientX"`
	ClientY       int         `json:"clientY"`
	Identifier    int         `json:"identifier"`
	PageX         int         `json:"pageX"`
	PageY         int         `json:"pageY"`
	RadiusX       float64     `json:"radiusX"`
	RadiusY       float64     `json:"radiusY"`
	RotationAngle int         `json:"rotationAngle"`
	ScreenX       int         `json:"screenX"`
	ScreenY       int         `json:"screenY"`
	Source        EventTarget `json:"source"`
}

Touch contains one browser touch point from a TouchEvent.

type TouchEvent

type TouchEvent struct {
	ChangedTouches []Touch     `json:"changedTouches"`
	Component      EventTarget `json:"component"`
	Source         EventTarget `json:"source"`
	TargetTouches  []Touch     `json:"targetTouches"`
	Touches        []Touch     `json:"touches"`
	LayerX         int         `json:"layerX"`
	LayerY         int         `json:"layerY"`
	PageX          int         `json:"pageX"`
	PageY          int         `json:"pageY"`
}

TouchEvent contains the touch payload sent by the browser client.

type Upload

type Upload struct {
	ID          string `json:"id"`
	FieldName   string `json:"field_name"`
	FileName    string `json:"file_name"`
	ContentType string `json:"content_type"`
	Size        int64  `json:"size"`
	Path        string `json:"path"`
}

Upload describes one file uploaded for an event.

func EventUploads

func EventUploads(ctx context.Context) ([]Upload, error)

EventUploads returns file metadata uploaded before the current event dispatch.

File bytes are posted to neith's upload endpoint over HTTP. EventData still contains normal form values, and EventUploads exposes the uploaded files.

type Writer

type Writer struct {
	http.ResponseWriter
	// contains filtered or unexported fields
}

func (*Writer) Write

func (w *Writer) Write(p []byte) (n int, err error)

Jump to

Keyboard shortcuts

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