summaryrefslogtreecommitdiff
path: root/form/builder.go
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--form/builder.go96
1 files changed, 96 insertions, 0 deletions
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
+}