summaryrefslogtreecommitdiff
path: root/form/builder.go
blob: 97e972f93a903214516879f2eaa6d7ab5b2147f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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
}