form

package module
v0.0.0-...-b0e3554 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2023 License: MIT Imports: 7 Imported by: 0

README

form

Pipeline Status Coverage Report Godoc Reference Go Report Card LICENSE

A package for generating HTML forms based on Go types.

Uses golang.org/x/net/html to render html forms.

Designed to work with the github.com/gorilla/schema package for converting form data back into Go types.

Table Of Contents

Usage

Basic usage:

import (
  "fmt"
  "os"

  "gitlab.com/sj1k/form"
  "golang.org/x/net/html"
  "golang.org/x/net/html/atom"
)

func ExampleNewEncoder_basic_example() {

  type Signup struct {
  	Name string `label:"Some label here!"`
  	Pass string `label:"Enter a password!" attr:"type=password"`
  	Age  int
  }

  // AttrbuteWrapper extracts and applies attributes from struct tags.
  // This will load attributes from the `attr` tag.
  attrWrapper := form.NewAttributeWrapper()

  // LabelWrapper wraps the input with a <label> element if the `label:""` tag exists.
  labelWrapper := form.NewLabelWrapper()

  encoder := form.NewEncoder()
  encoder.SetWrappers(&labelWrapper, &attrWrapper)

  parent := CreateFormNode()

  err := encoder.EncodeValue(&parent, &Signup{})
  if err != nil {
  	fmt.Println(err)
  	return
  }

  PrettyRender(os.Stdout, &parent)

  // Output:
  // <form>
  //     <label>Some label here!
  //         <input name="Name" type="text"/>
  //     </label>
  //     <label>Enter a password!
  //         <input type="password" name="Pass"/>
  //     </label>
  //     <input name="Age" type="number" value="0" min="-9223372036854775808" max="9223372036854775807"/>
  // </form>
}

func CreateFormNode() html.Node {
  return html.Node{
  	Type:     html.ElementNode,
  	Data:     atom.Form.String(),
  	DataAtom: atom.Form,
  }
} 

Documentation

Overview

Package form provides utilities for iterating over go types and generating HTML forms.

By default this package is designed to work with other packages;

Decoding Form Values

The gorilla/schema package is used to decode a submitted form back into Go types.

Custom naming schemes can be passed so other decoders are supported.

Rendering HTML

The x/net/html package is used for rendering the HTML form data and keeping track of related information such as html.Attribute and the overall element tree.

Index

Examples

Constants

View Source
const (
	// Plain `int` and `uint` types are different sizes on 32 and 64 bit systems
	// The following calculates the min / max ranges.
	// https://stackoverflow.com/questions/6878590/the-maximum-value-for-an-int-type-in-go
	MAX_UINT = ^uint(0)
	MAX_INT  = int(MAX_UINT >> 1)
	MIN_INT  = -MAX_INT - 1

	MIN_INT64 = "-9223372036854775808"
	MAX_INT64 = "9223372036854775807"

	MIN_INT32 = "-2147483648"
	MAX_INT32 = "2147483647"

	MIN_INT16 = "-32768"
	MAX_INT16 = "32767"

	MIN_INT8 = "-128"
	MAX_INT8 = "127"

	MAX_UINT64 = "18446744073709551615"
	MAX_UINT32 = "4294967295"
	MAX_UINT16 = "65535"
	MAX_UINT8  = "255"
)
View Source
const DATETIME_FORMAT = "2006-01-02T15:04:05"

Variables

View Source
var (
	RangeLookup = map[reflect.Kind]Range{
		reflect.Int: {
			Min: fmt.Sprintf("%d", MIN_INT),
			Max: fmt.Sprintf("%d", MAX_INT),
		},
		reflect.Int64: {
			Min: MIN_INT64,
			Max: MAX_INT64,
		},
		reflect.Int32: {
			Min: MIN_INT32,
			Max: MAX_INT32,
		},
		reflect.Int16: {
			Min: MIN_INT16,
			Max: MAX_INT16,
		},
		reflect.Uint: {
			Min: "0",
			Max: fmt.Sprintf("%d", MAX_UINT),
		},
		reflect.Int8: {
			Min: MIN_INT8,
			Max: MAX_INT8,
		},
		reflect.Uint64: {
			Min: "0",
			Max: MAX_UINT64,
		},
		reflect.Uint32: {
			Min: "0",
			Max: MAX_UINT32,
		},
		reflect.Uint16: {
			Min: "0",
			Max: MAX_UINT16,
		},
		reflect.Uint8: {
			Min: "0",
			Max: MAX_UINT8,
		},
	}
)

Functions

func DefaultArrayNamer

func DefaultArrayNamer(total string, index int) string

func DefaultStructNamer

func DefaultStructNamer(total, name string) string

func EncodeBool

func EncodeBool(context *EncoderContext, typeOf reflect.Type, value reflect.Value) error

func EncodeDateTime

func EncodeDateTime(context *EncoderContext, typeOf reflect.Type, value reflect.Value) error

EncodeDateTime encodes a time.Time value. This reads the "type" attribute from any StructTag information and switches time format based on requested type.

Example
type MyForm struct {
	Date     time.Time `form:"type=date"`
	Time     time.Time `form:"type=time"`
	DateTime time.Time
}
encoder := form.NewEncoder()
parent := html.Node{
	Type:     html.ElementNode,
	Data:     atom.Form.String(),
	DataAtom: atom.Form,
}
err := encoder.EncodeValue(&parent, MyForm{
	Date:     time.Date(2023, 9, 4, 0, 0, 0, 0, time.UTC),
	Time:     time.Date(2023, 9, 4, 0, 0, 0, 0, time.UTC),
	DateTime: time.Date(2023, 9, 4, 0, 0, 0, 0, time.UTC),
})
if err != nil {
	fmt.Println(err)
	return
}

PrettyRender(os.Stdout, &parent)
Output:

<form>
    <input name="Date" type="date" value="2023-09-04"/>
    <input name="Time" type="time" value="00:00:00"/>
    <input name="DateTime" type="datetime-local" value="2023-09-04T00:00:00"/>
</form>

func EncodeFloat64

func EncodeFloat64(context *EncoderContext, typeOf reflect.Type, value reflect.Value) error

func EncodeInt

func EncodeInt(context *EncoderContext, typeOf reflect.Type, value reflect.Value) error

func EncodeString

func EncodeString(context *EncoderContext, typeOf reflect.Type, value reflect.Value) error

func EncodeUint

func EncodeUint(context *EncoderContext, typeOf reflect.Type, value reflect.Value) error

func NodeDeleteAttr

func NodeDeleteAttr(node *html.Node, key string)

func NodeGetAttr

func NodeGetAttr(node *html.Node, key string) (html.Attribute, bool)

func NodeHasAttr

func NodeHasAttr(node *html.Node, key string) (int, bool)

func NodeSetAttr

func NodeSetAttr(node *html.Node, key string, value string, setmode SetMode)

func ParseTag

func ParseTag(tag string) map[string]string

func SetValidRange

func SetValidRange(node *html.Node, value reflect.Value)

SetValidRange will attempt to lookup and set min / max values if they do not already exist as an attrbute on the node.

Types

type ArrayEncoder

type ArrayEncoder struct {
	NamingScheme ArrayNamingScheme
}
Example
data := []string{
	"Name 1",
	"Name 2",
	"Name 3",
}

encoder := form.NewEncoder()
parent := html.Node{
	Type:     html.ElementNode,
	Data:     atom.Form.String(),
	DataAtom: atom.Form,
}

encoder.EncodeValue(&parent, data)

PrettyRender(os.Stdout, &parent)
Output:

<form>
    <input name="0" type="text" value="Name 1"/>
    <input name="1" type="text" value="Name 2"/>
    <input name="2" type="text" value="Name 3"/>
</form>

func NewArrayEncoder

func NewArrayEncoder(namer ArrayNamingScheme) ArrayEncoder

func (*ArrayEncoder) Encode

func (arrayEncoder *ArrayEncoder) Encode(context *EncoderContext, typeOf reflect.Type, valueOf reflect.Value) error

type ArrayNamingScheme

type ArrayNamingScheme func(total string, index int) string

type AttributeWrapper

type AttributeWrapper struct {
	FieldTag string
}

AttributeWrapper is a wrapper which extracts extra html attributes from a given struct tag. This is useful for overwriting certain html attributes via struct tags.

func NewAttributeWrapper

func NewAttributeWrapper() AttributeWrapper

func (*AttributeWrapper) Encode

func (wrapper *AttributeWrapper) Encode(context *EncoderContext, typeOf reflect.Type, valueOf reflect.Value) error

type CustomEncoder

type CustomEncoder interface {
	Encode(*EncoderContext, reflect.Type, reflect.Value) error
}

CustomEncoder is an interface for anything that can encode a reflected type and value.

type CustomEncoderFunc

type CustomEncoderFunc func(*EncoderContext, reflect.Type, reflect.Value) error

func (CustomEncoderFunc) Encode

func (encoderFunc CustomEncoderFunc) Encode(context *EncoderContext, typeOf reflect.Type, valueOf reflect.Value) error

type Encoder

type Encoder struct {
	SchemaTag string
	FormTag   string
	// contains filtered or unexported fields
}

func NewEmptyEncoder

func NewEmptyEncoder() Encoder

NewEmptyEncoder creates an empty encoder with no registered encoders. Useful for building a completely custom set of encoders.

For more information on custom encoders see: Encoder.RegisterTypeEncoder Encoder.RegisterKindEncoder

func NewEncoder

func NewEncoder() Encoder

NewEncoder creates an encoder with the default registered encoders.

Example
type MyForm struct {
	A  string
	A1 *string

	B  int
	B1 *int

	C  float64
	C1 *float64

	D  time.Time
	D1 *time.Time

	E  uint
	E1 *uint

	F  bool
	F1 *bool
}

encoder := form.NewEncoder()

parent := html.Node{
	Type:     html.ElementNode,
	Data:     atom.Form.String(),
	DataAtom: atom.Form,
}

a1 := "some string"
b1 := 2121
c1 := 3333.0
d1 := time.Date(2024, 8, 28, 0, 0, 0, 0, time.UTC)
e1 := uint(302910)
f1 := true

err := encoder.EncodeValue(&parent, MyForm{
	A:  "A string",
	A1: &a1,
	B:  2,
	B1: &b1,
	C:  3.0,
	C1: &c1,
	D:  time.Date(2023, 8, 28, 0, 0, 0, 0, time.UTC),
	D1: &d1,
	E:  uint(109812309),
	E1: &e1,
	F:  false,
	F1: &f1,
})
if err != nil {
	fmt.Println(err)
	return
}

PrettyRender(os.Stdout, &parent)
Output:

<form>
    <input name="A" type="text" value="A string"/>
    <input name="A1" type="text" value="some string"/>
    <input name="B" type="number" value="2" min="-9223372036854775808" max="9223372036854775807"/>
    <input name="B1" type="number" value="2121" min="-9223372036854775808" max="9223372036854775807"/>
    <input name="C" type="number" step="any" value="3.000000"/>
    <input name="C1" type="number" step="any" value="3333.000000"/>
    <input name="D" type="datetime-local" value="2023-08-28T00:00:00"/>
    <input name="D1" type="datetime-local" value="2024-08-28T00:00:00"/>
    <input name="E" type="number" value="109812309" min="0" max="18446744073709551615"/>
    <input name="E1" type="number" value="302910" min="0" max="18446744073709551615"/>
    <input name="F" type="checkbox"/>
    <input name="F1" type="checkbox" checked="true"/>
</form>
Example (Basic_example)
package main

import (
	"fmt"
	"os"

	"gitlab.com/sj1k/form"
	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
)

func main() {

	type Signup struct {
		Name string `label:"Some label here!"`
		Pass string `label:"Enter a password!" attr:"type=password"`
		Age  int
	}

	// AttrbuteWrapper extracts and applies attributes from struct tags.
	// This will load attributes from the `attr` tag.
	attrWrapper := form.NewAttributeWrapper()

	// LabelWrapper wraps the input with a <label> element if the `label:""` tag exists.
	labelWrapper := form.NewLabelWrapper()

	encoder := form.NewEncoder()
	encoder.SetWrappers(&labelWrapper, &attrWrapper)

	parent := CreateFormNode()

	err := encoder.EncodeValue(&parent, &Signup{})
	if err != nil {
		fmt.Println(err)
		return
	}

	PrettyRender(os.Stdout, &parent)

}

func CreateFormNode() html.Node {
	return html.Node{
		Type:     html.ElementNode,
		Data:     atom.Form.String(),
		DataAtom: atom.Form,
	}
}
Output:

<form>
    <label>Some label here!
        <input name="Name" type="text"/>
    </label>
    <label>Enter a password!
        <input type="password" name="Pass"/>
    </label>
    <input name="Age" type="number" value="0" min="-9223372036854775808" max="9223372036854775807"/>
</form>

func (*Encoder) AppendWrappers

func (encoder *Encoder) AppendWrappers(wrappers ...CustomEncoder)

AppendWrappers appends more wrappers to be run before each encoder.

func (*Encoder) Encode

func (encoder *Encoder) Encode(context *EncoderContext, typeOf reflect.Type, valueOf reflect.Value) error

func (*Encoder) EncodeValue

func (encoder *Encoder) EncodeValue(parent *html.Node, v interface{}) error

EncodeValue starts encoding the given value and appends results to the given parent. This is the main entry point for form generation.

func (*Encoder) RegisterKindEncoder

func (encoder *Encoder) RegisterKindEncoder(kind reflect.Kind, custom_encoder CustomEncoder)

RegisterKindEncoder uses the reflect.Kind of the given value to register a CustomEncoder To use a function for encoding see Encoder.RegisterKindEncoderFunc

func (*Encoder) RegisterKindEncoderFunc

func (encoder *Encoder) RegisterKindEncoderFunc(kind reflect.Kind, encoder_func CustomEncoderFunc)

func (*Encoder) RegisterTypeEncoder

func (encoder *Encoder) RegisterTypeEncoder(v interface{}, custom_encoder CustomEncoder)

RegisterTypeEncoder uses the reflect.Type of the given value to register a CustomEncoder To use a function for encoding see [ENcoder.RegisterTypeEncoderFunc]

Example
encoder := form.NewEmptyEncoder()
encoder.RegisterTypeEncoder("string", &ParagraphEncoder{})

parent := html.Node{
	Type:     html.ElementNode,
	Data:     atom.P.String(),
	DataAtom: atom.P,
}
err := encoder.EncodeValue(&parent, "Some Value")
if err != nil {
	fmt.Println(err)
	return
}
PrettyRender(os.Stdout, &parent)
Output:

<p>Some Value</p>

func (*Encoder) RegisterTypeEncoderFunc

func (encoder *Encoder) RegisterTypeEncoderFunc(v interface{}, encoder_func CustomEncoderFunc)

func (*Encoder) SetWrappers

func (encoder *Encoder) SetWrappers(wrappers ...CustomEncoder)

SetWrappers sets / overwrites which wrappers will be run before each encoder.

type EncoderChain

type EncoderChain struct {
	Encoders []CustomEncoder
}

EncoderChain calls a chain of other CustomEncoder's. The encoders are called in regular iteration order.

func ChainEncoders

func ChainEncoders(encoders ...CustomEncoder) EncoderChain

func (EncoderChain) Encode

func (chain EncoderChain) Encode(context *EncoderContext, typeOf reflect.Type, valueOf reflect.Value) error

type EncoderChainError

type EncoderChainError struct {
	Err   error
	Index int
}

func (EncoderChainError) Error

func (chainError EncoderChainError) Error() string

type EncoderContext

type EncoderContext struct {
	Encoder    *Encoder
	ParentNode *html.Node
	StructTag  *reflect.StructTag
	Name       string
	Attributes []html.Attribute
}

func (*EncoderContext) LookupTag

func (context *EncoderContext) LookupTag(name string) (string, bool)

type EncoderWrapper

type EncoderWrapper struct {
	Wrapper CustomEncoder
	Encoder CustomEncoder
}

func WrapEncoder

func WrapEncoder(wrapper CustomEncoder, encoder CustomEncoder) EncoderWrapper

WrapEncoder creates a new EncoderWrapper with a given wrapper and encoder. If you need multiple wrappers it may be worth using an EncoderChain

func (EncoderWrapper) Encode

func (wrapper EncoderWrapper) Encode(context *EncoderContext, typeOf reflect.Type, valueOf reflect.Value) error

type FieldsetWrapper

type FieldsetWrapper struct {
	FieldTag string
}

FieldsetWrapper is a wrapper which creates <fieldset> wrappers around nodes when a given struct tag is found. Optionally creates a <legend> if the struct tag has content.

Example
type MyForm struct {
	With    string `fieldset:"With a label"`
	Without string `fieldset:""`
}

fieldset := form.NewFieldsetWrapper()
// attributes := form.NewAttributeWrapper()

encoder := form.NewEncoder()
encoder.SetWrappers(
	// &attributes,
	&fieldset,
)

parent := html.Node{
	Type:     html.ElementNode,
	Data:     atom.Form.String(),
	DataAtom: atom.Form,
}

err := encoder.EncodeValue(&parent, MyForm{
	With:    "with label",
	Without: "without label",
})

if err != nil {
	fmt.Println(err)
	return
}

PrettyRender(os.Stdout, &parent)
Output:

<form>
    <fieldset>
        <legend>With a label</legend>
        <input name="With" type="text" value="with label"/>
    </fieldset>
    <fieldset>
        <input name="Without" type="text" value="without label"/>
    </fieldset>
</form>

func NewFieldsetWrapper

func NewFieldsetWrapper() FieldsetWrapper

func (*FieldsetWrapper) Encode

func (wrapper *FieldsetWrapper) Encode(context *EncoderContext, typeOf reflect.Type, valueOf reflect.Value) error

type LabelWrapper

type LabelWrapper struct {
	FieldTag string
}

func NewLabelWrapper

func NewLabelWrapper() LabelWrapper

func (*LabelWrapper) Encode

func (wrapper *LabelWrapper) Encode(context *EncoderContext, typeOf reflect.Type, valueOf reflect.Value) error

type Range

type Range struct {
	Min string
	Max string
}

type SetMode

type SetMode int

SetMode is used to know if NodeSetAttr should overwrite attributes or not. See NodeSetAttr

const (
	Overwrite SetMode = iota
	NoOverwrite
)

type StructEncoder

type StructEncoder struct {
	NamingScheme StructNamingScheme
}

func NewStructEncoder

func NewStructEncoder(namer StructNamingScheme) StructEncoder

func (*StructEncoder) Encode

func (structEncoder *StructEncoder) Encode(context *EncoderContext, typeOf reflect.Type, valueOf reflect.Value) error

type StructNamingScheme

type StructNamingScheme func(total, name string) string

type UnknownError

type UnknownError struct {
	Kind reflect.Kind
	Type reflect.Type
}

func (UnknownError) Error

func (err UnknownError) Error() string

Directories

Path Synopsis
label_parser module
tag_parsers module
schema Module

Jump to

Keyboard shortcuts

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