From 960d9175b12d8278565ff855cd45fa2ab253c485 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Sat, 26 Jul 2025 12:48:04 +0200 Subject: [PATCH] feat: replace templates with components --- form/field_input_date.go | 2 +- form/option.go | 12 + go.mod | 6 + go.sum | 6 + theme/bootstrap5.go | 278 +++++++++++------------ theme/html5.go | 464 ++++++++++++++++++++++++++++++--------- theme/renderer.go | 160 ++------------ theme/theme.go | 16 ++ 8 files changed, 558 insertions(+), 386 deletions(-) create mode 100644 theme/theme.go diff --git a/form/field_input_date.go b/form/field_input_date.go index d18bfd4..893c59d 100644 --- a/form/field_input_date.go +++ b/form/field_input_date.go @@ -37,7 +37,7 @@ func DateBeforeMount(data any, format string) (any, error) { } } - return data, nil + return nil, nil } // Generates an input[type=date] with default transformers diff --git a/form/option.go b/form/option.go index bbd3276..ba483e5 100644 --- a/form/option.go +++ b/form/option.go @@ -26,3 +26,15 @@ func NewOption(name string, value any) *Option { Value: value, } } + +func (o *Option) AsBool() bool { + return o.Value.(bool) +} + +func (o *Option) AsString() string { + return o.Value.(string) +} + +func (o *Option) AsMapString() map[string]string { + return o.Value.(map[string]string) +} diff --git a/go.mod b/go.mod index 2f88d00..0b43ae4 100644 --- a/go.mod +++ b/go.mod @@ -7,3 +7,9 @@ require ( github.com/spf13/cast v1.9.2 github.com/yassinebenaid/godump v0.11.1 ) + +require ( + github.com/samber/lo v1.51.0 // indirect + golang.org/x/text v0.22.0 // indirect + maragu.dev/gomponents v1.1.0 // indirect +) diff --git a/go.sum b/go.sum index d02abfe..fd3f0f9 100644 --- a/go.sum +++ b/go.sum @@ -10,7 +10,13 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI= +github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/yassinebenaid/godump v0.11.1 h1:SPujx/XaYqGDfmNh7JI3dOyCUVrG0bG2duhO3Eh2EhI= github.com/yassinebenaid/godump v0.11.1/go.mod h1:dc/0w8wmg6kVIvNGAzbKH1Oa54dXQx8SNKh4dPRyW44= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +maragu.dev/gomponents v1.1.0 h1:iCybZZChHr1eSlvkWp/JP3CrZGzctLudQ/JI3sBcO4U= +maragu.dev/gomponents v1.1.0/go.mod h1:oEDahza2gZoXDoDHhw8jBNgH+3UR5ni7Ur648HORydM= diff --git a/theme/bootstrap5.go b/theme/bootstrap5.go index 6571d9b..6966955 100644 --- a/theme/bootstrap5.go +++ b/theme/bootstrap5.go @@ -15,141 +15,147 @@ package theme // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -var Bootstrap5 = map[string]string{ - "form": `
- {{- form_error .Form nil -}} +var Bootstrap5 = ExtendTheme(Html5, func() map[string]RenderFunc { + theme := make(map[string]RenderFunc) - {{- form_help .Form -}} + return theme +}) - {{- range $field := .Form.Fields -}} - {{- form_row $field -}} - {{- end -}} -
`, - "attributes": `{{ range $key, $value := .Attributes }}{{ $key }}="{{ $value }}"{{ end }}`, - "help": ` - {{- if gt (len .Help) 0 -}} -
{{ .Help }}
- {{- end -}} - `, - "label": ` - {{ if .Field.HasOption "label" }} - {{ $label := (.Field.GetOption "label").Value }} - - {{- if ne $label "" -}} - - {{- end -}} - {{- end -}} - `, - "input": ` - {{- $type := .Field.GetOption "type" -}} - {{- $checked := and (eq (.Field.GetOption "type").Value "checkbox") (.Field.Data) -}} - {{- $required := and (.Field.HasOption "required") (.Field.GetOption "required").Value -}} - {{- $value := .Field.Data -}} - {{- $class := "form-control" }} - - {{- if eq $type.Value "checkbox" -}} - {{- $value = 1 -}} - {{- end -}} - - {{- if or (eq $type.Value "checkbox") (eq $type.Value "radio") -}} - {{- $class = "form-check-input" -}} - {{- end -}} - - {{- if eq $type.Value "range" -}} - {{- $class = "form-range" -}} - {{- end -}} - - {{- if or (eq $type.Value "submit") (eq $type.Value "reset") (eq $type.Value "button") -}} - {{- $class = "" -}} - - {{ if .Field.HasOption "attr" }} - {{ $class = (.Field.GetOption "attr").Value.attr.class }} - {{ end }} - {{- end -}} - - - `, - "textarea": ` - - `, - "choice": ` - {{- $required := and (.Field.HasOption "required") (.Field.GetOption "required").Value -}} - {{- $isExpanded := (.Field.GetOption "expanded").Value -}} - {{- $isMultiple := (.Field.GetOption "multiple").Value -}} - {{- $emptyChoiceLabel := (.Field.GetOption "empty_choice_label").Value -}} - {{- $choices := (.Field.GetOption "choices").Value -}} - {{- $field := .Field -}} - {{- $keyAdd := 0 -}} - - {{- if and (not $required) (not $isMultiple) -}} - {{- $keyAdd = 1 -}} - {{- end -}} - - {{- if $isExpanded -}} - {{- if and (not $required) (not $isMultiple) -}} -
- - -
- {{- end -}} - - {{- range $key, $choice := $choices.GetChoices -}} -
- - -
- {{- end -}} - {{- else -}} - - {{- end -}} - `, - "sub_form": ` -
- {{ if .Field.HasOption "label" }} - {{ $label := (.Field.GetOption "label").Value }} - - {{- if ne $label "" -}} - {{ $label }} - {{- end -}} - {{- end -}} - - {{ form_widget_help .Field }} - - {{- range $field := .Field.Children -}} - {{- form_row $field -}} - {{- end -}} -
- `, - "error": ` - {{- if gt (len .Errors) 0 -}} -
- {{- range $error := .Errors -}} -
{{- $error -}}
- {{- end -}} -
- {{- end -}} - `, - "row": `
- {{ $labelAfterWidget := and (.Field.HasOption "type") (eq (.Field.GetOption "type").Value "checkbox") }} - - {{ if and (eq (len .Field.Children) 0) (not $labelAfterWidget) }} - {{- form_label .Field -}} - {{ end }} - - {{- form_widget .Field -}} - {{- form_error nil .Field -}} - - {{ if and (eq (len .Field.Children) 0) ($labelAfterWidget) }} - {{- form_label .Field -}} - {{ end }} - - {{- form_widget_help .Field -}} -
`, -} +// var Bootstrap5 = map[string]string{ +// "form": `
+// {{- form_error .Form nil -}} +// +// {{- form_help .Form -}} +// +// {{- range $field := .Form.Fields -}} +// {{- form_row $field -}} +// {{- end -}} +//
`, +// "attributes": `{{ range $key, $value := .Attributes }}{{ $key }}="{{ $value }}"{{ end }}`, +// "help": ` +// {{- if gt (len .Help) 0 -}} +//
{{ .Help }}
+// {{- end -}} +// `, +// "label": ` +// {{ if .Field.HasOption "label" }} +// {{ $label := (.Field.GetOption "label").Value }} +// +// {{- if ne $label "" -}} +// +// {{- end -}} +// {{- end -}} +// `, +// "input": ` +// {{- $type := .Field.GetOption "type" -}} +// {{- $checked := and (eq (.Field.GetOption "type").Value "checkbox") (.Field.Data) -}} +// {{- $required := and (.Field.HasOption "required") (.Field.GetOption "required").Value -}} +// {{- $value := .Field.Data -}} +// {{- $class := "form-control" }} +// +// {{- if eq $type.Value "checkbox" -}} +// {{- $value = 1 -}} +// {{- end -}} +// +// {{- if or (eq $type.Value "checkbox") (eq $type.Value "radio") -}} +// {{- $class = "form-check-input" -}} +// {{- end -}} +// +// {{- if eq $type.Value "range" -}} +// {{- $class = "form-range" -}} +// {{- end -}} +// +// {{- if or (eq $type.Value "submit") (eq $type.Value "reset") (eq $type.Value "button") -}} +// {{- $class = "" -}} +// +// {{ if .Field.HasOption "attr" }} +// {{ $class = (.Field.GetOption "attr").Value.attr.class }} +// {{ end }} +// {{- end -}} +// +// +// `, +// "textarea": ` +// +// `, +// "choice": ` +// {{- $required := and (.Field.HasOption "required") (.Field.GetOption "required").Value -}} +// {{- $isExpanded := (.Field.GetOption "expanded").Value -}} +// {{- $isMultiple := (.Field.GetOption "multiple").Value -}} +// {{- $emptyChoiceLabel := (.Field.GetOption "empty_choice_label").Value -}} +// {{- $choices := (.Field.GetOption "choices").Value -}} +// {{- $field := .Field -}} +// {{- $keyAdd := 0 -}} +// +// {{- if and (not $required) (not $isMultiple) -}} +// {{- $keyAdd = 1 -}} +// {{- end -}} +// +// {{- if $isExpanded -}} +// {{- if and (not $required) (not $isMultiple) -}} +//
+// +// +//
+// {{- end -}} +// +// {{- range $key, $choice := $choices.GetChoices -}} +//
+// +// +//
+// {{- end -}} +// {{- else -}} +// +// {{- end -}} +// `, +// "sub_form": ` +//
+// {{ if .Field.HasOption "label" }} +// {{ $label := (.Field.GetOption "label").Value }} +// +// {{- if ne $label "" -}} +// {{ $label }} +// {{- end -}} +// {{- end -}} +// +// {{ form_widget_help .Field }} +// +// {{- range $field := .Field.Children -}} +// {{- form_row $field -}} +// {{- end -}} +//
+// `, +// "error": ` +// {{- if gt (len .Errors) 0 -}} +//
+// {{- range $error := .Errors -}} +//
{{- $error -}}
+// {{- end -}} +//
+// {{- end -}} +// `, +// "row": `
+// {{ $labelAfterWidget := and (.Field.HasOption "type") (eq (.Field.GetOption "type").Value "checkbox") }} +// +// {{ if and (eq (len .Field.Children) 0) (not $labelAfterWidget) }} +// {{- form_label .Field -}} +// {{ end }} +// +// {{- form_widget .Field -}} +// {{- form_error nil .Field -}} +// +// {{ if and (eq (len .Field.Children) 0) ($labelAfterWidget) }} +// {{- form_label .Field -}} +// {{ end }} +// +// {{- form_widget_help .Field -}} +//
`, +// } diff --git a/theme/html5.go b/theme/html5.go index 718d45b..a3dc337 100644 --- a/theme/html5.go +++ b/theme/html5.go @@ -1,5 +1,15 @@ package theme +import ( + "fmt" + + "github.com/spf13/cast" + "gitnet.fr/deblan/go-form/form" + "gitnet.fr/deblan/go-form/validation" + . "maragu.dev/gomponents" + . "maragu.dev/gomponents/html" +) + // @license GNU AGPL version 3 or any later version // // This program is free software: you can redistribute it and/or modify @@ -15,120 +25,370 @@ package theme // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -var Html5 = map[string]string{ - "form": `
- {{- form_error .Form nil -}} +var Html5 = CreateTheme(func() map[string]RenderFunc { + theme := make(map[string]RenderFunc) - {{- form_help .Form -}} + theme["attributes"] = func(args ...any) Node { + var result []Node - {{- range $field := .Form.Fields -}} - {{- form_row $field -}} - {{- end -}} -
`, - "attributes": `{{ range $key, $value := .Attributes }}{{ $key }}="{{ $value }}"{{ end }}`, - "help": ` - {{- if gt (len .Help) 0 -}} -
{{ .Help }}
- {{- end -}} - `, - "label": ` - {{ if .Field.HasOption "label" }} - {{ $label := (.Field.GetOption "label").Value }} + for i, v := range args[0].(map[string]string) { + result = append(result, Attr(i, v)) + } - {{- if ne $label "" -}} - - {{- end -}} - {{- end -}} - `, - "input": ` - {{- $type := .Field.GetOption "type" -}} - {{- $checked := and (eq (.Field.GetOption "type").Value "checkbox") (.Field.Data) -}} - {{- $required := and (.Field.HasOption "required") (.Field.GetOption "required").Value -}} - {{- $value := .Field.Data -}} + return Group(result) + } - {{- if eq $type.Value "checkbox" -}} - {{- $value = 1 -}} - {{- end -}} + theme["form_attributes"] = func(args ...any) Node { + form := args[0].(*form.Form) - - `, - "textarea": ` - - `, - "choice": ` - {{- $required := and (.Field.HasOption "required") (.Field.GetOption "required").Value -}} - {{- $isExpanded := (.Field.GetOption "expanded").Value -}} - {{- $isMultiple := (.Field.GetOption "multiple").Value -}} - {{- $emptyChoiceLabel := (.Field.GetOption "empty_choice_label").Value -}} - {{- $choices := (.Field.GetOption "choices").Value -}} - {{- $field := .Field -}} - {{- $keyAdd := 0 -}} + if !form.HasOption("attr") { + return Raw("") + } - {{- if and (not $required) (not $isMultiple) -}} - {{- $keyAdd = 1 -}} - {{- end -}} + return theme["attributes"](form.GetOption("attr").AsMapString()) + } - {{- if $isExpanded -}} - {{- if and (not $required) (not $isMultiple) -}} - - - {{- end -}} + theme["errors"] = func(args ...any) Node { + errors := args[0].([]validation.Error) - {{- range $key, $choice := $choices.GetChoices -}} - - - {{- end -}} - {{- else -}} - - {{- end -}} - `, - "sub_form": ` -
- {{ if .Field.HasOption "label" }} - {{ $label := (.Field.GetOption "label").Value }} + var result []Node - {{- if ne $label "" -}} - {{ $label }} - {{- end -}} - {{- end -}} + for _, v := range errors { + result = append(result, Li(Text(string(v)))) + } - {{ form_widget_help .Field }} + return Ul( + Class("form-errors"), + Group(result), + ) + } - {{- range $field := .Field.Children -}} - {{- form_row $field -}} - {{- end -}} -
- `, - "error": ` - {{- if gt (len .Errors) 0 -}} -
    - {{- range $error := .Errors -}} -
  • {{- $error -}}
  • - {{- end -}} -
- {{- end -}} - `, - "row": `
- {{ $labelAfterWidget := and (.Field.HasOption "type") (eq (.Field.GetOption "type").Value "checkbox") }} + theme["form_errors"] = func(args ...any) Node { + form := args[0].(*form.Form) - {{ if and (eq (len .Field.Children) 0) (not $labelAfterWidget) }} - {{- form_label .Field -}} - {{ end }} + return If( + len(form.Errors) > 0, + theme["errors"](form.Errors), + ) + } - {{- form_error nil .Field -}} - {{- form_widget .Field -}} + theme["form_widget_errors"] = func(args ...any) Node { + field := args[0].(*form.Field) - {{ if and (eq (len .Field.Children) 0) ($labelAfterWidget) }} - {{- form_label .Field -}} - {{ end }} + return If( + len(field.Errors) > 0, + theme["errors"](field.Errors), + ) + } - {{- form_widget_help .Field -}} -
`, -} + theme["help"] = func(args ...any) Node { + help := args[0].(string) + + if len(help) == 0 { + return Raw("") + } + + return Div( + Class("form-help"), + Text("ok"), + ) + } + + theme["form_help"] = func(args ...any) Node { + form := args[0].(*form.Form) + + if !form.HasOption("help") { + return Raw("") + } + + return theme["help"](form.GetOption("help").AsString()) + } + + theme["form_widget_help"] = func(args ...any) Node { + field := args[0].(*form.Field) + + if !field.HasOption("help") { + return Raw("") + } + + return theme["help"](field.GetOption("help").AsString()) + } + + theme["label_attributes"] = func(args ...any) Node { + field := args[0].(*form.Field) + + if !field.HasOption("label_attr") { + return Raw("") + } + + return theme["attributes"](field.GetOption("label_attr").AsMapString()) + } + + theme["form_label"] = func(args ...any) Node { + field := args[0].(*form.Field) + + if !field.HasOption("label") { + return Raw("") + } + + label := field.GetOption("label").AsString() + + return If(len(label) > 0, Label( + Class("form-label"), + For(field.GetId()), + theme["label_attributes"](field), + Text(label), + )) + } + + theme["field_attributes"] = func(args ...any) Node { + field := args[0].(*form.Field) + + if !field.HasOption("attr") { + return Raw("") + } + + return theme["attributes"](field.GetOption("attr").AsMapString()) + } + + theme["textarea_attributes"] = func(args ...any) Node { + return theme["field_attributes"](args...) + } + + theme["input_attributes"] = func(args ...any) Node { + return theme["field_attributes"](args...) + } + + theme["sub_form_attributes"] = func(args ...any) Node { + return theme["field_attributes"](args...) + } + + theme["input"] = func(args ...any) Node { + field := args[0].(*form.Field) + fieldType := "text" + + if field.HasOption("type") { + fieldType = field.GetOption("type").AsString() + } + + value := cast.ToString(field.Data) + + if fieldType == "checkbox" { + value = "1" + } + + return Input( + Name(field.GetName()), + ID(field.GetId()), + Type(fieldType), + Value(value), + If(fieldType == "checkbox" && field.Data != false, Checked()), + If(field.HasOption("required") && field.GetOption("required").AsBool(), Required()), + theme["input_attributes"](field), + ) + } + + theme["choice_options"] = func(args ...any) Node { + field := args[0].(*form.Field) + choices := field.GetOption("choices").Value.(*form.Choices) + + isRequired := field.HasOption("required") && field.GetOption("required").AsBool() + isMultiple := field.GetOption("multiple").AsBool() + + var options []Node + + if !isMultiple && !isRequired { + options = append(options, Option( + Text(field.GetOption("empty_choice_label").AsString()), + )) + } + + for _, choice := range choices.GetChoices() { + options = append(options, Option( + Value(choice.Value), + Text(choice.Label), + If(choices.Match(field, choice.Value), Selected()), + )) + } + + return Group(options) + } + + theme["choice_expanded"] = func(args ...any) Node { + field := args[0].(*form.Field) + choices := field.GetOption("choices").Value.(*form.Choices) + + isRequired := field.HasOption("required") && field.GetOption("required").AsBool() + isMultiple := field.GetOption("multiple").AsBool() + noneLabel := field.GetOption("empty_choice_label").AsString() + + var items []Node + + if !isMultiple && !isRequired { + id := fmt.Sprintf("%s-%s", field.GetId(), "none") + + items = append(items, Group([]Node{ + Input( + Name(field.GetName()), + ID(id), + Value(""), + Type("radio"), + theme["input_attributes"](field), + If(cast.ToString(field.Data) == "", Checked()), + ), + Label(For(id), Text(noneLabel)), + })) + } + + for key, choice := range choices.GetChoices() { + id := fmt.Sprintf("%s-%d", field.GetId(), key) + + items = append(items, Group([]Node{ + Input( + Name(field.GetName()), + ID(id), + Value(choice.Value), + If(isMultiple, Type("checkbox")), + If(!isMultiple, Type("radio")), + theme["input_attributes"](field), + If(choices.Match(field, choice.Value), Checked()), + ), + Label(For(id), Text(choice.Label)), + })) + } + + return Group(items) + } + + theme["choice"] = func(args ...any) Node { + field := args[0].(*form.Field) + + isRequired := field.HasOption("required") && field.GetOption("required").AsBool() + isExpanded := field.GetOption("expanded").AsBool() + isMultiple := field.GetOption("multiple").AsBool() + noneLabel := field.GetOption("empty_choice_label").AsString() + + _ = noneLabel + + if isExpanded { + return theme["choice_expanded"](field) + } else { + return Select( + ID(field.GetId()), + If(isRequired, Required()), + If(isMultiple, Multiple()), + Name(field.GetName()), + theme["choice_options"](field), + ) + } + } + + theme["textarea"] = func(args ...any) Node { + field := args[0].(*form.Field) + + return Textarea( + Name(field.GetName()), + ID(field.GetId()), + If(field.HasOption("required") && field.GetOption("required").AsBool(), Required()), + theme["textarea_attributes"](field), + Text(cast.ToString(field.Data)), + ) + } + + theme["sub_form_label"] = func(args ...any) Node { + field := args[0].(*form.Field) + + if !field.HasOption("label") { + return Raw("") + } + + label := field.GetOption("label").AsString() + + return If(len(label) > 0, Legend( + Class("form-label"), + theme["label_attributes"](field), + Text(label), + )) + + } + + theme["sub_form_content"] = func(args ...any) Node { + field := args[0].(*form.Field) + + return theme["form_fields"](field.Children) + } + + theme["sub_form"] = func(args ...any) Node { + field := args[0].(*form.Field) + + return FieldSet( + ID(field.GetId()), + theme["sub_form_label"](field), + theme["sub_form_attributes"](field), + theme["sub_form_content"](field), + ) + } + + theme["form_widget"] = func(args ...any) Node { + field := args[0].(*form.Field) + + tpl, ok := theme[field.Widget] + + if !ok { + return Raw("Invalid field widget: " + field.Widget) + } + + return tpl(field) + } + + theme["form_row"] = func(args ...any) Node { + field := args[0].(*form.Field) + + isCheckbox := field.HasOption("type") && field.GetOption("type").AsString() == "checkbox" + hasChildren := len(field.Children) > 0 + labelAfter := isCheckbox && !hasChildren + label := theme["form_label"](field) + + return Div( + Class("form-row"), + If(!labelAfter, label), + theme["form_widget_errors"](field), + theme["form_widget"](field), + If(labelAfter, label), + theme["form_widget_help"](field), + ) + } + + theme["form_fields"] = func(args ...any) Node { + var items []Node + + for _, item := range args[0].([]*form.Field) { + items = append(items, theme["form_row"](item)) + } + + return Group(items) + } + + theme["form_content"] = func(args ...any) Node { + form := args[0].(*form.Form) + + return Div( + theme["form_errors"](form), + theme["form_help"](form), + theme["form_fields"](form.Fields), + ) + } + + theme["form"] = func(args ...any) Node { + form := args[0].(*form.Form) + + return Form( + Action(form.Action), + Method(form.Method), + theme["form_attributes"](form), + theme["form_content"](form), + ) + } + + return theme +}) diff --git a/theme/renderer.go b/theme/renderer.go index b572f45..98cbd51 100644 --- a/theme/renderer.go +++ b/theme/renderer.go @@ -19,172 +19,38 @@ import ( "bytes" "html/template" - "github.com/spf13/cast" "gitnet.fr/deblan/go-form/form" - "gitnet.fr/deblan/go-form/validation" + "maragu.dev/gomponents" ) +type RenderFunc func(args ...any) gomponents.Node + type Renderer struct { - Theme map[string]string + Theme map[string]RenderFunc } -func NewRenderer(theme map[string]string) *Renderer { +func NewRenderer(theme map[string]RenderFunc) *Renderer { r := new(Renderer) r.Theme = theme return r } -func (r *Renderer) RenderForm(form *form.Form) template.HTML { - return r.Render("form", r.Theme["form"], map[string]any{ - "Form": form, - }) -} - -func (r *Renderer) RenderRow(field *form.Field) template.HTML { - return r.Render("row", r.Theme["row"], map[string]any{ - "Field": field, - }) -} - -func (r *Renderer) RenderLabel(field *form.Field) template.HTML { - return r.Render("label", r.Theme["label"], map[string]any{ - "Field": field, - }) -} - -func (r *Renderer) RenderWidget(field *form.Field) template.HTML { - return r.Render("widget", r.Theme[field.Widget], map[string]any{ - "Field": field, - }) -} - -func (r *Renderer) RenderError(form *form.Form, field *form.Field) template.HTML { - var errors []validation.Error - - if field != nil { - errors = field.Errors - } - - if form != nil { - errors = form.Errors - } - - return r.Render("error", r.Theme["error"], map[string]any{ - "Errors": errors, - }) -} - -func (r *Renderer) RenderLabelAttr(field *form.Field) template.HTMLAttr { - var attributes map[string]string - - if field.HasOption("label_attr") { - attributes = field.GetOption("label_attr").Value.(map[string]string) - } - - return r.RenderAttr("label_attr", r.Theme["attributes"], map[string]any{ - "Attributes": attributes, - }) -} - -func (r *Renderer) RenderWidgetAttr(field *form.Field) template.HTMLAttr { - var attributes map[string]string - - if field.HasOption("attr") { - attributes = field.GetOption("attr").Value.(map[string]string) - } - - return r.RenderAttr("widget_attr", r.Theme["attributes"], map[string]any{ - "Attributes": attributes, - }) -} - -func (r *Renderer) RenderFormAttr(form *form.Form) template.HTMLAttr { - var attributes map[string]string - - if form.HasOption("attr") { - attributes = form.GetOption("attr").Value.(map[string]string) - } - - return r.RenderAttr("form_attr", r.Theme["attributes"], map[string]any{ - "Attributes": attributes, - }) -} - -func (r *Renderer) RenderRowAttr(field *form.Field) template.HTMLAttr { - var attributes map[string]string - - if field.HasOption("row_attr") { - attributes = field.GetOption("row_attr").Value.(map[string]string) - } - - return r.RenderAttr("raw_attr", r.Theme["attributes"], map[string]any{ - "Attributes": attributes, - }) -} - -func (r *Renderer) RenderFormHelp(form *form.Form) template.HTML { - var help string - - if form.HasOption("help") { - help = form.GetOption("help").Value.(string) - } - - return r.Render("help", r.Theme["help"], map[string]any{ - "Help": help, - }) -} - -func (r *Renderer) RenderWidgetHelp(field *form.Field) template.HTML { - var help string - - if field.HasOption("help") { - help = field.GetOption("help").Value.(string) - } - - return r.Render("help", r.Theme["help"], map[string]any{ - "Help": help, - }) -} - -func (r *Renderer) RenderAttr(name, tpl string, args any) template.HTMLAttr { - t, err := template.New(name).Parse(tpl) - - if err != nil { - return template.HTMLAttr("") - } - +func toTemplateHtml(n gomponents.Node) template.HTML { var buf bytes.Buffer - err = t.Execute(&buf, args) - if err != nil { - return template.HTMLAttr("") - } + n.Render(&buf) - return template.HTMLAttr(buf.String()) + return template.HTML(buf.String()) +} + +func (r *Renderer) RenderForm(form *form.Form) template.HTML { + return toTemplateHtml(r.Theme["form"](form)) } func (r *Renderer) FuncMap() template.FuncMap { return template.FuncMap{ - "form": r.RenderForm, - "form_row": r.RenderRow, - "form_label": r.RenderLabel, - "form_widget": r.RenderWidget, - "form_error": r.RenderError, - "form_attr": r.RenderFormAttr, - "form_widget_attr": r.RenderWidgetAttr, - "form_label_attr": r.RenderLabelAttr, - "form_row_attr": r.RenderRowAttr, - "form_help": r.RenderFormHelp, - "form_widget_help": r.RenderWidgetHelp, - "sum": func(values ...any) float64 { - res := float64(0) - for _, value := range values { - res += cast.ToFloat64(value) - } - - return res - }, + "form": r.RenderForm, } } diff --git a/theme/theme.go b/theme/theme.go new file mode 100644 index 0000000..e63864e --- /dev/null +++ b/theme/theme.go @@ -0,0 +1,16 @@ +package theme + +func CreateTheme(generator func() map[string]RenderFunc) map[string]RenderFunc { + return generator() +} + +func ExtendTheme(base map[string]RenderFunc, generator func() map[string]RenderFunc) map[string]RenderFunc { + extended := CreateTheme(generator) + + for i, v := range base { + extended[i] = v + extended["base_"+i] = v + } + + return extended +}