Help us shape the future of Go! Take the 2026 Go Developer Survey

forms

package
v1.1.1 Latest Latest
Warning

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

Go to latest
Published: Sep 18, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

README

Portable entity forms

ui/forms renders the existing field controls and captures their typed component interfaces without a database, router, session or application. It belongs to the public foundation and consumes entity definitions and components. Use the Go version in the root go.mod.

Pass field definitions, explicit display labels and string values to Example:

form, err := forms.Example("notes/new", forms.Model{
    Fields: []forms.Field{{
        Definition: entity.Field{Name: "title", Type: entity.TypeString, Required: true},
        Label: "Title",
    }},
    Values: map[string]string{"title": "Field notes"},
    Create: true,
}, forms.Options{
    Namespace: "notes-editor", Title: "New note",
    Action: "/notes", CancelURL: "/notes",
})
if err != nil {
    return err
}
return form.Node.Render(writer)

Import github.com/septagon-oss/platformkit/ui/forms and github.com/septagon-oss/platformkit/kit/entity. writer is your io.Writer. Rendering emits HTML; it does not open a connection or execute the save action. Use ui.Compose for the shared stylesheet and export.Export to capture this same example for design tools. The surrounding shell supplies assets and landmarks.

The source ID (notes/new) identifies an authoring occurrence. The separate DOM namespace (notes-editor) identifies an instance and produces notes-editor-form. Use distinct, stable namespaces when displaying two forms. Namespaces accept an ASCII letter followed by letters, digits, underscores or hyphens. Field IDs encode JSON names and retain identity when fields move; their labels and feedback refer to those IDs. Invalid namespaces return ErrNamespace without a partial example.

Field.Definition is the existing entity metadata, not another schema. Labels and select-option labels are presentation inputs; field names and submitted values retain their domain meaning. Missing create values use declared defaults; explicit empty values remain empty. Readonly fields never render. Immutable fields are absent on create and disabled or readonly on edit. Handlers still enforce these rules and decide validation errors, authorization and transaction outcomes.

The captured tree retains Core's Form, Input, Textarea, Select, Checkbox, Alert and Button identities, including field/<name> children and the actions slot. Edits are presentation candidates governed by existing export revision checks. Rendering alone does not establish browser, native or A2UI adapter compatibility. HTMX focus after a refused write requires the existing browser assets and the handler's refusal response; direct rendering does not execute that controller.

screens.FormExample derives the form's DOM scope from the address it posts to (forms.Namespace), so the create and edit screens of one entity own different identities and two forms may share a page. Control is available for custom trusted composition with an explicit control ID; without one it falls back to the component's name-derived identity, which is why a page holding two forms built that way collides — pass an ID when you compose more than one.

MustExample is Example for a composition already gated at mount. The generated screens use it because a renderer returns a page, not an error, and kit/rest already refuses a schema whose field names collide (rest_test.go names the case). Reaching its panic means a resource reached a renderer unmounted.

Run go test ./ui/forms ./ui/screens ./ui/components for local rendering, capture, namespace and compatibility checks. Product HTTP/browser behavior remains the responsibility of its assembled application checks.

Example refuses empty or duplicate field names with ErrFields before rendering, because submitted values, labels and source identities need one owner.

Documentation

Overview

Package forms composes portable entity forms from detached presentation inputs. Handlers own authorization, parsing, persistence and the meaning of errors.

Index

Constants

This section is empty.

Variables

View Source
var ErrFields = errors.New("forms: field names must be nonempty and unique")

ErrFields refuses ambiguous input names and the DOM/source identities they create.

View Source
var ErrNamespace = errors.New("forms: namespace must start with a letter and contain only letters, digits, underscores or hyphens")

ErrNamespace reports an unusable instance scope before any example is returned.

Functions

func Control

func Control(p ControlProps) g.Node

Control renders the existing component chosen by the entity widget/type. Its capture retains "field/" plus the schema name and Core's Props contract.

func Example

func Example(id string, model Model, options Options) (examples.Example, error)

Example captures the same typed Form and field constructors used at runtime. It returns a presentation candidate; rendering or editing it performs no write. DOM identities and HTMX targets belong to Namespace, never to the source ID.

func MustExample

func MustExample(id string, model Model, options Options) examples.Example

MustExample is Example for a composition whose field names were already kept distinct by the thing that built the schema. It exists for the generated screens, which render a page and have no channel left to report on: kit/crud derives one name per struct field, falling back to the Go name when the JSON tag is empty, and go vet refuses a struct that repeats a tag, so a schema that reached a mounted route cannot hold two fields of one name.

Reaching the panic therefore means a hand-built entity.Schema — a design invocation, an adapter — named two fields the same. That is a composition bug, and it is said in the same shape regexp.MustCompile and this repository's own component constructors already use it.

func Namespace

func Namespace(action string) string

Namespace derives a form's DOM scope from the address it posts to. It exists because the two things a generated form's identity has to satisfy pull apart: it must differ from every other form on the page, and it must not change when the same screen re-renders a refused submission — the swap that puts the field errors back targets the id the form was drawn with. An address gives both: one screen posts to one address, and a refused POST is answered by the screen at that same address. A fixed id gave neither, which is why one screen could hold only one generated form.

The answer is always something Example accepts: a letter, a digit, an underscore or a hyphen is kept, everything else is a separator, consecutive separators collapse and a leading separator is dropped, so the identity stays readable in DevTools rather than a run of dashes. Two addresses are given the same identity only where they differ in punctuation alone, and a screen serves one address, so that never puts two forms on one page. A leading digit is prefixed rather than dropped, and an input with nothing usable in it gets a fixed scope. namespace_test.go feeds it the inputs that would break that and checks every byte of every answer.

Types

type ControlProps

type ControlProps struct {
	Field            Field
	ID, Value, Error string
	Immutable        bool
}

ControlProps describes one field's presentation. ID is the trusted DOM identity; leaving it empty uses the component's historical name-derived default. Enclosing forms should use Example, which supplies a distinct identity to every control.

type Field

type Field struct {
	Definition entity.Field
	Label      string
	Options    []components.SelectOption
}

Field presents an existing entity definition. Label and Options supply display words explicitly; they do not change the definition's names or enum values. Empty labels use the field name, and absent options use the declared enum text.

type Model

type Model struct {
	Fields    []Field
	Values    map[string]string
	Errors    map[string]string
	Immutable []string
	Detail    string
	Create    bool
}

Model contains a form's already-read values and already-decided field errors. A missing value uses the declared default only on create; an explicit empty value remains empty. ReadOnly fields are omitted, as are immutable create fields.

type Options

type Options struct {
	Namespace                              string
	Action, CancelURL, Title               string
	CancelLabel, SubmitLabel, FailureTitle string
}

Options supplies presentation and command addresses. Namespace is a DOM scope, separate from an example's source identity. Use a unique stable namespace per rendered instance. It must start with an ASCII letter and contain only letters, digits, underscores or hyphens. The resulting form ID is Namespace + "-form".

Jump to

Keyboard shortcuts

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