form

package module
v0.0.0-...-6d82c9b Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2021 License: MIT, Unlicense Imports: 0 Imported by: 0

README

form

Go Reference

Primitives for specifying forms.

The fundamental idea explored here is in separating form concerns. The three core abstractions are the Form, the Value and the Input. Value and Input combine together into a Field.

  • Input abstracts the interactible graphical widget that accepts input.
  • Value abstracts the transformation of text to structured data.
  • Field maps an Input to a Value.
  • Form provides a consistent api for handling a group of fields, namely batch and realtime validation.

Status

This repo is experimental, and does not have a stable interface.

Ideally form initialization would be fully declarative, where the zero value is directly usable. However it's not clear how to achieve such an api.

Some potential avenues to explore:

  • code generation: take a struct definition and generate the form code.
  • reflection: introspect a struct definition and generate form.Fields on the fly.

Use

Since the api is experimental, there is no "idiomatic usage".

Fundamentally, a form binds inputs to values and there are numerous ways to compose it.

This is an example of one way to use the package: one-time initialization that you can call in an update function once per frame.

type Person struct {
    Age    int
    Name   string
    Salary float64
}

type PersonForm struct {
    form.Form
    // Model contains the structured data.
    Model Person
    // Inputs contains the input state.
    Inputs struct {
        Age    component.TextField
        Name   component.TextField
        Salary component.TextField
    }
    init sync.Once
}

func (pf *PersonForm) Update() {
    pf.init.Do(func() {
        pf.Form.Load([]form.Field{
            {
                Value: value.Int{Value: &pf.Model.Age, Default: 18},
                Input: &pf.Inputs.Age,
            },
            {
                Value: value.Required{value.Text{Value: &pf.Model.Name, Default: ""}},
                Input: &pf.Inputs.Name,
            },
            {
                Value: value.Float{Value: &pf.Model.Salary, Default: 0},
                Input: &pf.Inputs.Salary,
            },
        })
    })
}

Documentation

Overview

Package form implements primitives that reduce form boilerplate by allowing the caller to specify their fields exactly once.

All values are processed via a chain of transformations that map text into a structured value, and visa versa. Each transformation is encapsulated in a `form.Value` implementation, for instance a `value.Int` will transform text into a Go integer and signal any errors that occur during that transformation.

Forms are initialized once with all the fields via a call to `form.Load`. Each field binds an input to a value.

By contention, value objects depend on pointer variables, this means you can simply point into a predefined "model" struct. Once the form is submitted, the model will contain the validated values ready to use. However this is only a convention, a value object can arbitrarily handle it's internal state.

The following is an example of one way to use the form:

type Person struct {
	Age    int
	Name   string
	Salary float64
}

type PersonForm struct {
	form.Form
	// Model contains the structured data.
	Model Person
	// Inputs contains the input state.
	Inputs struct {
		Age    component.TextField
		Name   component.TextField
		Salary component.TextField
	}
	init sync.Once
}

func (pf *PersonForm) Update() {
	pf.init.Do(func() {
		pf.Form.Load([]form.Field{
			{
				Value: value.Int{Value: &pf.Model.Age, Default: 18},
				Input: &pf.Inputs.Age,
			},
			{
				Value: value.Required{value.Text{Value: &pf.Model.Name, Default: ""}},
				Input: &pf.Inputs.Name,
			},
			{
				Value: value.Float{Value: &pf.Model.Salary, Default: 0},
				Input: &pf.Inputs.Salary,
			},
		})
	})
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Field

type Field struct {
	Value Value
	Input Input
}

Field binds a Value to an Input.

func (*Field) Validate

func (field *Field) Validate() bool

Validate the field by running the text through the Valuer. Precise validation logic is implemented by the Valuer. Returns a boolean indicating success.

type Form

type Form struct {
	Fields []Field
	// contains filtered or unexported fields
}

Form exercises field bindings.

There's two primary ways of using a Form: 1. Realtime Validation 2. Batch Validation

Realtime validation, expressed by `Form.Validate`, processes all non-zero and changed fields. That is, they must have a value and that value must be different since the last validation.

Batch validation, expressed by `Form.Submit`, processes _all_ fields including zero-value fields.

The semantic difference is that an unsubmitted zero-value input is not in an errored state because the user hasn't input a value yet. If you attempt to submit that zero-value input then it submission error and the field is now in an errored state.

Realtime validation is useful for providing fast feedback on input events. You can create a `form.Value` that maps to some complex data source. For example, you can run queries on the fly to figure out if an entry exists as the user is typing.

Batch validation is useful for quickly testing if the whole form is valid before using the field data.

`form.Submit` must be called to synchronize field values. If not called, the values stored in each `form.Value` could be different to what is displayed in the graphical input.

func (*Form) Clear

func (f *Form) Clear()

func (*Form) Load

func (f *Form) Load(fields []Field)

Load values into inputs.

func (*Form) Submit

func (f *Form) Submit() (ok bool)

Submit batch validates the fields and returns a boolean indication success. If true, all the fields validated and you can use the data.

func (*Form) Validate

func (f *Form) Validate()

Validate form fields. Can be used per frame for realtime validation.

type Input

type Input interface {
	Text() string
	SetText(string)
	SetError(string)
	ClearError()
}

Input objects display text, and handle user input events. Input will typically be implemented by a graphic widget, such as the `gioui.org/x/component#TextField`.

type Value

type Value interface {
	// To converts the value into textual form.
	To() (text string, err error)
	// From parses the value from textual form.
	From(text string) (err error)
	// Clear resets the value.
	Clear()
}

Value implements a bi-directional mapping between textual data and structured data. Value handles data validation, which is expresssed by the error return.

Directories

Path Synopsis
Package example shows usage of the `form` package.
Package example shows usage of the `form` package.
Package parse implements text parsing for common data types.
Package parse implements text parsing for common data types.
Package value defines some `form.Value` implementations for common data types.
Package value defines some `form.Value` implementations for common data types.

Jump to

Keyboard shortcuts

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