From b2447bc967df37b31282a97e32c581954bb8bcc9 Mon Sep 17 00:00:00 2001 From: René 'Necoro' Neumann Date: Thu, 17 Oct 2024 16:37:23 +0200 Subject: Move from html/template to templ --- form/builder.go | 96 --------------------- form/errors.go | 30 +++++++ form/field.templ | 42 +++++++++ form/field_templ.go | 239 ++++++++++++++++++++++++++++++++++++++++++++++++++++ form/form.go | 21 +++++ form/reflect.go | 12 +-- 6 files changed, 333 insertions(+), 107 deletions(-) delete mode 100644 form/builder.go create mode 100644 form/errors.go create mode 100644 form/field.templ create mode 100644 form/field_templ.go create mode 100644 form/form.go (limited to 'form') 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) { +
+ + + for _, e := range errors { +

{e}

+ } +
+} \ 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("
") + 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(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, e := range errors { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") + 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("

") + 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 + } + 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 } -- cgit v1.2.3-70-g09d2