htmlform

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2025 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package htmlform provides utilities for automatically generating HTML forms from function signatures.

Overview

The htmlform package enables you to create web forms that automatically generate HTML input fields based on a function's parameters. When the form is submitted, the values are parsed and passed to the function, making it easy to create web interfaces for any Go function.

Basic Usage

Generate an HTML form from a function:

func CreateUser(ctx context.Context, name, email string, age int, active bool) error {
    fmt.Printf("Creating user: %s (%s), age %d, active: %v\n", name, email, age, active)
    return nil
}

func main() {
    wrapper := function.MustReflectWrapper("CreateUser", CreateUser)
    handler := htmlform.MustNewHandler(
        wrapper,
        "Create New User",           // Form title
        httpfun.RespondStaticHTML("<h1>User created successfully!</h1>"),
    )

    http.Handle("/create-user", handler)
    http.ListenAndServe(":8080", nil)
}

When users visit /create-user:

  • GET request: Displays an HTML form with fields for name, email, age, and active
  • POST request: Submits the form, calls CreateUser, and displays success message

Automatic Field Type Detection

The package automatically selects appropriate HTML input types based on Go types:

string         → <input type="text">
int, float     → <input type="number">
bool           → <input type="checkbox">
fs.FileReader  → <input type="file">
time.Time      → <input type="datetime-local"> (commented out in current version)
date.Date      → <input type="date"> (commented out in current version)

Customizing Fields

You can customize form fields using the Handler methods:

handler := htmlform.MustNewHandler(wrapper, "User Form", resultWriter)

// Set field as required (overrides type-based detection)
handler.SetArgRequired("email", true)

// Set default value
handler.SetArgDefaultValue("age", 18)
handler.SetArgDefaultValue("active", true)

// Set custom input type
handler.SetArgInputType("email", "email")
handler.SetArgInputType("bio", "textarea")
handler.SetArgInputType("password", "password")

// Add dropdown options
handler.SetArgOptions("role", []htmlform.Option{
    {Label: "User", Value: "user"},
    {Label: "Admin", Value: "admin"},
    {Label: "Guest", Value: "guest"},
})

// Customize submit button
handler.SetSubmitButtonText("Create User")

Field Validation

Add custom validators for form fields:

handler.SetArgValidator("email", func(value any) error {
    email, ok := value.(string)
    if !ok || !strings.Contains(email, "@") {
        return errors.New("invalid email address")
    }
    return nil
})

handler.SetArgValidator("age", func(value any) error {
    age, ok := value.(int)
    if !ok || age < 18 {
        return errors.New("must be 18 or older")
    }
    return nil
})

Required Fields

Fields are automatically marked as required based on their type:

  • Non-pointer types (int, string, etc.) → Required by default
  • Pointer types (*int, *string) → Optional
  • String type → Optional (empty string is valid)
  • Types implementing IsNull() → Optional

Override automatic detection:

handler.SetArgRequired("email", true)   // Force required
handler.SetArgRequired("age", false)    // Make optional

Custom Form Templates

The default form template can be customized by modifying FormTemplate or by creating a new template:

handler, _ := htmlform.NewHandler(wrapper, "My Form", resultWriter)
customTemplate := `<!DOCTYPE html>
<html>
<head><title>{{.Title}}</title></head>
<body>
    <h1>{{.Title}}</h1>
    <form method="post">
        {{range .Fields}}
            <label>{{.Label}}</label>
            <input type="{{.Type}}" name="{{.Name}}" value="{{.Value}}">
        {{end}}
        <button>{{.SubmitButtonText}}</button>
    </form>
</body>
</html>`
handler.template, _ = template.New("form").Parse(customTemplate)

File Uploads

Functions accepting fs.FileReader automatically get file upload fields:

func ProcessDocument(ctx context.Context, name string, document fs.FileReader) error {
    data, err := document.ReadAll()
    if err != nil {
        return err
    }
    fmt.Printf("Processing %s: %d bytes\n", name, len(data))
    return nil
}

handler := htmlform.MustNewHandler(
    function.MustReflectWrapper("ProcessDocument", ProcessDocument),
    "Upload Document",
    httpfun.RespondStaticHTML("<p>Document processed!</p>"),
)

The form will include a file input field for the document parameter.

Complete Example

package main

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

    "github.com/domonda/go-function"
    "github.com/domonda/go-function/htmlform"
    "github.com/domonda/go-function/httpfun"
)

func RegisterUser(ctx context.Context, username, email, password string, age int, newsletter bool) error {
    fmt.Printf("Registering: %s (%s), age %d, newsletter: %v\n",
        username, email, age, newsletter)
    return nil
}

func main() {
    wrapper := function.MustReflectWrapper("RegisterUser", RegisterUser)
    handler := htmlform.MustNewHandler(
        wrapper,
        "User Registration",
        httpfun.RespondStaticHTML("<h2>Registration successful!</h2>"),
    )

    // Customize fields
    handler.SetArgInputType("email", "email")
    handler.SetArgInputType("password", "password")
    handler.SetArgRequired("email", true)
    handler.SetArgDefaultValue("age", 18)
    handler.SetSubmitButtonText("Register")

    http.Handle("/register", handler)
    fmt.Println("Server running on http://localhost:8080/register")
    http.ListenAndServe(":8080", nil)
}

Result Handling

After form submission, use any httpfun.ResultsWriter to handle the response:

// Simple success message
httpfun.RespondStaticHTML("<h1>Success!</h1>")

// JSON response
httpfun.RespondJSON

// Redirect after success
httpfun.RespondRedirect("/thank-you")

// Custom response based on results
httpfun.ResultsWriterFunc(func(results []any, err error, w http.ResponseWriter, r *http.Request) error {
    if err != nil {
        return err
    }
    fmt.Fprintf(w, "<p>Created user with ID: %v</p>", results[0])
    return nil
})

Error Handling

The handler automatically:

  • Catches panics and displays them as HTTP errors
  • Validates form submissions
  • Handles multipart form data (including file uploads)
  • Returns appropriate HTTP status codes

Errors from the wrapped function are passed to the result writer, which can handle them appropriately.

Styling

The default template includes minimal CSS styling. Customize the template to add your own styles, or inject CSS through the form title:

title := `My Form
<style>
    form { max-width: 600px; margin: 0 auto; }
    input, select, textarea { width: 100%; padding: 8px; }
</style>`
handler := htmlform.MustNewHandler(wrapper, title, resultWriter)

Best Practices

  • Use context.Context as the first parameter for cancellation support
  • Return error as the last result for proper error handling
  • Set sensible default values for optional fields
  • Use appropriate input types (email, password, url, tel, etc.)
  • Provide clear labels through function documentation
  • Add validators for complex validation logic
  • Use dropdown options for fields with limited choices
  • Test file upload handling with size limits
  • Customize the success response to match your application style

Limitations

  • Context.Context parameters are automatically excluded from the form
  • Complex types (nested structs, maps) are not supported
  • Array/slice parameters need manual handling
  • Time.Time support is currently commented out (awaiting implementation)
  • Form validation happens server-side only

Thread Safety

Handler instances are safe for concurrent use after configuration. Do not modify handler settings (SetArgRequired, etc.) after the handler starts serving requests.

Index

Constants

This section is empty.

Variables

View Source
var FormTemplate = `` /* 1397-byte string literal not displayed */

FormTemplate is the default HTML template used to render forms. It supports multiple input types including text, number, checkbox, select, textarea, and file.

The template expects a struct with:

  • Title: Page title and heading
  • Fields: Slice of formField (Name, Label, Type, Value, Required, Options)
  • SubmitButtonText: Text for the submit button

You can customize this template by parsing your own HTML template and assigning it to handler.template after creating the handler.

The template handles these field types specially:

  • checkbox: Inline label, checked state support
  • select: Dropdown with options, selected value support
  • textarea: Multi-line text with 40 cols × 5 rows
  • all others: Standard <input> tags with type attribute

Example customization:

handler, _ := htmlform.NewHandler(wrapper, "My Form", resultWriter)
handler.template, _ = template.New("form").Parse(myCustomTemplate)

Functions

This section is empty.

Types

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler is an http.Handler that generates and processes HTML forms for a wrapped function. It automatically creates form fields based on function parameters and handles form submission.

The handler responds to:

  • GET requests: Displays the HTML form
  • POST requests: Processes form submission and calls the wrapped function

Example usage:

func CreateUser(ctx context.Context, name, email string, age int) error {
    // Implementation
    return nil
}

handler := htmlform.MustNewHandler(
    function.MustReflectWrapper("CreateUser", CreateUser),
    "Create User",
    httpfun.RespondStaticHTML("<h1>Success!</h1>"),
)
http.Handle("/create-user", handler)

func MustNewHandler

func MustNewHandler(fun function.Wrapper, title string, successHandler httpfun.ResultsWriter) (handler *Handler)

MustNewHandler is like NewHandler but panics on error. Use this in initialization code where form creation failures should be fatal.

func NewHandler

func NewHandler(wrappedFunc function.Wrapper, title string, resultWriter httpfun.ResultsWriter) (handler *Handler, err error)

NewHandler creates a new form handler for the given wrapped function.

Parameters:

  • wrappedFunc: The function to generate a form for
  • title: The HTML page title and form heading
  • resultWriter: Handler for the function's results after form submission

Returns an error if the form template fails to parse.

Example:

handler, err := htmlform.NewHandler(
    wrapper,
    "User Registration",
    httpfun.RespondJSON,
)

func (*Handler) ServeHTTP

func (handler *Handler) ServeHTTP(response http.ResponseWriter, request *http.Request)

ServeHTTP implements http.Handler. It handles both GET requests (display form) and POST requests (process form submission). Panics are recovered and converted to HTTP errors.

func (*Handler) SetArgDefaultValue

func (handler *Handler) SetArgDefaultValue(arg string, value any)

SetArgDefaultValue sets the default value displayed in the form field.

Example:

handler.SetArgDefaultValue("age", 18)
handler.SetArgDefaultValue("active", true)
handler.SetArgDefaultValue("country", "US")

func (*Handler) SetArgInputType

func (handler *Handler) SetArgInputType(arg string, value string)

SetArgInputType overrides the automatically detected HTML input type.

Supported types include:

  • "text", "email", "password", "url", "tel", "search"
  • "number", "range"
  • "date", "datetime-local", "time", "month", "week"
  • "checkbox", "radio"
  • "file"
  • "textarea"
  • "color"

Example:

handler.SetArgInputType("email", "email")
handler.SetArgInputType("password", "password")
handler.SetArgInputType("bio", "textarea")
handler.SetArgInputType("age", "range")

func (*Handler) SetArgOptions

func (handler *Handler) SetArgOptions(arg string, options []Option)

SetArgOptions configures a field to display as a dropdown select with the given options.

Example:

handler.SetArgOptions("country", []htmlform.Option{
    {Label: "United States", Value: "US"},
    {Label: "Canada", Value: "CA"},
    {Label: "Mexico", Value: "MX"},
})

func (*Handler) SetArgRequired

func (handler *Handler) SetArgRequired(arg string, required bool)

SetArgRequired overrides the automatic required field detection. By default, non-pointer types and non-string types are required.

Example:

handler.SetArgRequired("email", true)   // Make required
handler.SetArgRequired("age", false)    // Make optional

func (*Handler) SetArgValidator

func (handler *Handler) SetArgValidator(arg string, validator types.ValidatErr)

SetArgValidator registers a custom validator for a specific function argument. The validator is called server-side when the form is submitted.

Example:

handler.SetArgValidator("email", func(value any) error {
    email, _ := value.(string)
    if !strings.Contains(email, "@") {
        return errors.New("invalid email")
    }
    return nil
})

func (*Handler) SetSubmitButtonText

func (handler *Handler) SetSubmitButtonText(text string)

SetSubmitButtonText customizes the text displayed on the form's submit button. Default is "Submit".

Example:

handler.SetSubmitButtonText("Create Account")
handler.SetSubmitButtonText("Save Changes")

type Option

type Option struct {
	Label string
	Value any
}

Option represents a choice in a select dropdown field. The Label is displayed to the user, while Value is submitted with the form.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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