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": `
`,
- "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 "" -}}
- {{ $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) -}}
-
-
- {{ ($field.GetOption "empty_choice_label").Value }}
-
- {{- end -}}
-
- {{- range $key, $choice := $choices.GetChoices -}}
-
-
- {{- $choice.Label -}}
-
- {{- end -}}
- {{- else -}}
-
- {{- if and (not $required) (not $isMultiple) -}}
- {{ $emptyChoiceLabel }}
- {{- end -}}
- {{- range $choice := $choices.GetChoices -}}
- {{ $choice.Label }}
- {{- end -}}
-
- {{- 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": ``,
+// "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 "" -}}
+// {{ $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) -}}
+//
+//
+// {{ ($field.GetOption "empty_choice_label").Value }}
+//
+// {{- end -}}
+//
+// {{- range $key, $choice := $choices.GetChoices -}}
+//
+//
+// {{- $choice.Label -}}
+//
+// {{- end -}}
+// {{- else -}}
+//
+// {{- if and (not $required) (not $isMultiple) -}}
+// {{ $emptyChoiceLabel }}
+// {{- end -}}
+// {{- range $choice := $choices.GetChoices -}}
+// {{ $choice.Label }}
+// {{- end -}}
+//
+// {{- 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": ``,
- "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 "" -}}
- {{ $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) -}}
-
- {{ ($field.GetOption "empty_choice_label").Value }}
- {{- end -}}
+ theme["errors"] = func(args ...any) Node {
+ errors := args[0].([]validation.Error)
- {{- range $key, $choice := $choices.GetChoices -}}
-
- {{- $choice.Label -}}
- {{- end -}}
- {{- else -}}
-
- {{- if and (not $required) (not $isMultiple) -}}
- {{ $emptyChoiceLabel }}
- {{- end -}}
- {{- range $choice := $choices.GetChoices -}}
- {{ $choice.Label }}
- {{- end -}}
-
- {{- 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 -}}
-
- {{- 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
+}