summaryrefslogtreecommitdiff
path: root/form
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--form/builder.go96
-rw-r--r--form/errors.go30
-rw-r--r--form/field.templ42
-rw-r--r--form/field_templ.go239
-rw-r--r--form/form.go21
-rw-r--r--form/reflect.go12
6 files changed, 333 insertions, 107 deletions
diff --git a/form/builder.go b/form/builder.go
deleted file mode 100644
index 97e972f..0000000
--- a/form/builder.go
+++ /dev/null
@@ -1,96 +0,0 @@
-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
-}
diff --git a/form/errors.go b/form/errors.go
new file mode 100644
index 0000000..52206c4
--- /dev/null
+++ b/form/errors.go
@@ -0,0 +1,30 @@
+package form
+
+import (
+ "errors"
+ "fmt"
+)
+
+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
+}
diff --git a/form/field.templ b/form/field.templ
new file mode 100644
index 0000000..e9ce33e
--- /dev/null
+++ b/form/field.templ
@@ -0,0 +1,42 @@
+package form
+
+import "fmt"
+
+func (f *field) isCheckbox() bool {
+ return f.Type == "checkbox"
+}
+
+func (f *field) optionAttributes() templ.Attributes {
+ attrs := make(map[string]any)
+ for _, o := range f.Options {
+ attrs[o] = true
+ }
+ return templ.Attributes(attrs)
+}
+
+templ (f *field) item(errors []string) {
+ <div class={"mb-3",
+ templ.KV("form-floating", !f.isCheckbox()),
+ templ.KV("form-check form-switch", f.isCheckbox())}>
+ <input
+ if f.ID != "" {id={f.ID}}
+ type={f.Type}
+ name={f.Name}
+ placeholder={f.Placeholder}
+ if f.Value != nil {value={fmt.Sprint(f.Value)}}
+ if f.isCheckbox() {
+ class="form-check-input"
+ } else {
+ class="form-control"
+ }
+ {f.optionAttributes()...}
+ >
+ <label if f.ID != "" {for={f.ID}}
+ if f.isCheckbox() {class="form-check-label"}>
+ {f.Label}
+ </label>
+ for _, e := range errors {
+ <p style="color:red">{e}</p>
+ }
+ </div>
+} \ No newline at end of file
diff --git a/form/field_templ.go b/form/field_templ.go
new file mode 100644
index 0000000..34266e8
--- /dev/null
+++ b/form/field_templ.go
@@ -0,0 +1,239 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.778
+package form
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import "fmt"
+
+func (f *field) isCheckbox() bool {
+ return f.Type == "checkbox"
+}
+
+func (f *field) optionAttributes() templ.Attributes {
+ attrs := make(map[string]any)
+ for _, o := range f.Options {
+ attrs[o] = true
+ }
+ return templ.Attributes(attrs)
+}
+
+func (f *field) item(errors []string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ var templ_7745c5c3_Var2 = []any{"mb-3",
+ templ.KV("form-floating", !f.isCheckbox()),
+ templ.KV("form-check form-switch", f.isCheckbox())}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var3 string
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String())
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `form/field.templ`, Line: 1, Col: 0}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><input")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if f.ID != "" {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" id=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 string
+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(f.ID)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `form/field.templ`, Line: 22, Col: 35}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" type=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var5 string
+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(f.Type)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `form/field.templ`, Line: 23, Col: 24}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" name=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var6 string
+ templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(f.Name)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `form/field.templ`, Line: 24, Col: 24}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" placeholder=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var7 string
+ templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(f.Placeholder)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `form/field.templ`, Line: 25, Col: 38}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if f.Value != nil {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" value=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var8 string
+ templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(f.Value))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `form/field.templ`, Line: 26, Col: 57}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ if f.isCheckbox() {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" class=\"form-check-input\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" class=\"form-control\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, f.optionAttributes())
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("> <label")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if f.ID != "" {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" for=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var9 string
+ templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(f.ID)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `form/field.templ`, Line: 34, Col: 39}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ if f.isCheckbox() {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" class=\"form-check-label\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var10 string
+ templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(f.Label)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `form/field.templ`, Line: 36, Col: 23}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</label> ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, e := range errors {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p style=\"color:red\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var11 string
+ templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(e)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `form/field.templ`, Line: 39, Col: 35}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/form/form.go b/form/form.go
new file mode 100644
index 0000000..84758f5
--- /dev/null
+++ b/form/form.go
@@ -0,0 +1,21 @@
+package form
+
+import (
+ "context"
+ "io"
+
+ "github.com/a-h/templ"
+)
+
+func Form(v any, errs []error) templ.Component {
+ fields := fields(v)
+ errors := fieldErrors(errs)
+ return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
+ for _, field := range fields {
+ if err := field.item(errors[field.Name]).Render(ctx, w); err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+}
diff --git a/form/reflect.go b/form/reflect.go
index 4dd4018..604c9e1 100644
--- a/form/reflect.go
+++ b/form/reflect.go
@@ -1,7 +1,6 @@
package form
import (
- "html/template"
"reflect"
"strings"
)
@@ -103,13 +102,6 @@ func (f *field) applyTags(tags map[string]string) {
if v, ok := tags["id"]; ok {
f.ID = v
}
- if v, ok := tags["footer"]; ok {
- // Probably shouldn't be HTML but whatever.
- f.Footer = template.HTML(v)
- }
- if v, ok := tags["class"]; ok {
- f.Class = v
- }
if v, ok := tags["options"]; ok {
f.Options = strings.Split(v, ",")
}
@@ -143,7 +135,5 @@ type field struct {
Type string
ID string
Options []string
- Value interface{}
- Footer template.HTML
- Class string
+ Value any
}