From ca9d6a543335e998963ac4f680cf5c47e597602b Mon Sep 17 00:00:00 2001 From: René 'Necoro' Neumann Date: Wed, 16 Oct 2024 22:18:06 +0200 Subject: Inline form package --- form/builder.go | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 form/builder.go (limited to 'form/builder.go') diff --git a/form/builder.go b/form/builder.go new file mode 100644 index 0000000..97e972f --- /dev/null +++ b/form/builder.go @@ -0,0 +1,96 @@ +package form + +import ( + "errors" + "fmt" + "html/template" + "strings" +) + +type builder struct { + tpl *template.Template +} + +// Inputs will parse the provided struct into fields and then execute the +// template with each field. The returned HTML is simply all of these results +// appended one after another. +// +// Inputs' second argument - errs - will be used to render errors for +// individual fields. +func (b *builder) Inputs(v interface{}, errs ...error) (template.HTML, error) { + tpl, err := b.tpl.Clone() + if err != nil { + return "", err + } + fields := fields(v) + errors := fieldErrors(errs) + var html template.HTML + for _, field := range fields { + var sb strings.Builder + tpl.Funcs(template.FuncMap{ + "errors": func() []string { + if errs, ok := errors[field.Name]; ok { + return errs + } + return nil + }, + }) + err := tpl.Execute(&sb, field) + if err != nil { + return "", err + } + html = html + template.HTML(sb.String()) + } + return html, nil +} + +// FuncMap returns a template.FuncMap that defines both the inputs_for and +// inputs_and_errors_for functions for usage in the template package. The +// latter is provided via a closure because variadic parameters and the +// template package don't play very nicely and this just simplifies things +// a lot for end users of the form package. +func FuncMap(formTpl *template.Template) template.FuncMap { + b := builder{tpl: formTpl} + return template.FuncMap{ + "inputs_for": b.Inputs, + "inputs_and_errors_for": func(v interface{}, errs []error) (template.HTML, error) { + return b.Inputs(v, errs...) + }, + } +} + +// ParsingFuncMap is present to make it a little easier to build the input template. +// In order to parse a template that uses the `errors` function, you need to have +// that template defined when the template is parsed. We clearly don't know whether +// a field has an error or not until it is parsed. +func ParsingFuncMap() template.FuncMap { + return template.FuncMap{ + "errors": func() []string { + return nil + }, + } +} + +type FieldError struct { + Field string + Issue string +} + +func (fe FieldError) Error() string { + return fmt.Sprintf("%s: %v", fe.Field, fe.Issue) +} + +// errors will build a map where each key is the field name, and each +// value is a slice of strings representing errors with that field. +func fieldErrors(errs []error) map[string][]string { + ret := make(map[string][]string) + for _, err := range errs { + var fe FieldError + if !errors.As(err, &fe) { + fmt.Println(err, "isnt field error") + continue + } + ret[fe.Field] = append(ret[fe.Field], fe.Issue) + } + return ret +} -- cgit v1.2.3-70-g09d2