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 ¶
- Variables
- type Handler
- func (handler *Handler) ServeHTTP(response http.ResponseWriter, request *http.Request)
- func (handler *Handler) SetArgDefaultValue(arg string, value any)
- func (handler *Handler) SetArgInputType(arg string, value string)
- func (handler *Handler) SetArgOptions(arg string, options []Option)
- func (handler *Handler) SetArgRequired(arg string, required bool)
- func (handler *Handler) SetArgValidator(arg string, validator types.ValidatErr)
- func (handler *Handler) SetSubmitButtonText(text string)
- type Option
Constants ¶
This section is empty.
Variables ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
SetSubmitButtonText customizes the text displayed on the form's submit button. Default is "Submit".
Example:
handler.SetSubmitButtonText("Create Account")
handler.SetSubmitButtonText("Save Changes")