go-form/theme/html5.go

466 lines
12 KiB
Go

package theme
import (
"bytes"
"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
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
var Html5 = CreateTheme(func() map[string]RenderFunc {
theme := make(map[string]RenderFunc)
theme["attributes"] = func(parent map[string]RenderFunc, args ...any) Node {
var result []Node
for i, v := range args[0].(form.Attrs) {
result = append(result, Attr(i, v))
}
return Group(result)
}
theme["form_attributes"] = func(parent map[string]RenderFunc, args ...any) Node {
form := args[0].(*form.Form)
if !form.HasOption("attr") {
return Raw("")
}
return parent["attributes"](parent, form.GetOption("attr").AsAttrs())
}
theme["errors"] = func(parent map[string]RenderFunc, args ...any) Node {
errors := args[0].([]validation.Error)
var result []Node
for _, v := range errors {
result = append(result, Li(Text(string(v))))
}
return Ul(
Class("gf-errors"),
Group(result),
)
}
theme["form_errors"] = func(parent map[string]RenderFunc, args ...any) Node {
form := args[0].(*form.Form)
return If(
len(form.Errors) > 0,
parent["errors"](parent, form.Errors),
)
}
theme["form_widget_errors"] = func(parent map[string]RenderFunc, args ...any) Node {
field := args[0].(*form.Field)
return If(
len(field.Errors) > 0,
parent["errors"](parent, field.Errors),
)
}
theme["help"] = func(parent map[string]RenderFunc, args ...any) Node {
help := args[0].(string)
var extra Node
if len(help) == 0 {
return Raw("")
}
if len(args) == 2 {
extra = args[1].(Node)
}
return Div(
Class("gf-help"),
Text(help),
extra,
)
}
theme["form_help"] = func(parent map[string]RenderFunc, args ...any) Node {
form := args[0].(*form.Form)
if !form.HasOption("help") {
return Raw("")
}
return parent["help"](
parent,
form.GetOption("help").AsString(),
parent["attributes"](parent, form.GetOption("help_attr").AsAttrs()),
)
}
theme["form_widget_help"] = func(parent map[string]RenderFunc, args ...any) Node {
field := args[0].(*form.Field)
if !field.HasOption("help") {
return Raw("")
}
return parent["help"](
parent,
field.GetOption("help").AsString(),
parent["attributes"](parent, field.GetOption("help_attr").AsAttrs()),
)
}
theme["label_attributes"] = func(parent map[string]RenderFunc, args ...any) Node {
field := args[0].(*form.Field)
return parent["attributes"](parent, field.GetOption("label_attr").AsAttrs())
}
theme["form_label"] = func(parent map[string]RenderFunc, 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(
For(field.GetId()),
parent["label_attributes"](parent, field),
Text(label),
))
}
theme["field_attributes"] = func(parent map[string]RenderFunc, args ...any) Node {
field := args[0].(*form.Field)
return parent["attributes"](parent, field.GetOption("attr").AsAttrs())
}
theme["textarea_attributes"] = func(parent map[string]RenderFunc, args ...any) Node {
return parent["field_attributes"](parent, args...)
}
theme["input_attributes"] = func(parent map[string]RenderFunc, args ...any) Node {
return parent["field_attributes"](parent, args...)
}
theme["sub_form_attributes"] = func(parent map[string]RenderFunc, args ...any) Node {
return parent["field_attributes"](parent, args...)
}
theme["input"] = func(parent map[string]RenderFunc, 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 != nil && field.Data != false, Checked()),
If(field.HasOption("required") && field.GetOption("required").AsBool(), Required()),
parent["input_attributes"](parent, field),
)
}
theme["choice_options"] = func(parent map[string]RenderFunc, 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_item"] = func(parent map[string]RenderFunc, args ...any) Node {
return args[0].(Node)
}
theme["choice_attributes"] = func(parent map[string]RenderFunc, args ...any) Node {
return parent["field_attributes"](parent, args...)
}
theme["choice_expanded"] = func(parent map[string]RenderFunc, 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, parent["choice_expanded_item"](parent, Group([]Node{
Input(
Name(field.GetName()),
ID(id),
Value(""),
Type("radio"),
parent["choice_attributes"](parent, field),
If(cast.ToString(field.Data) == "", Checked()),
),
Label(
For(id),
Text(noneLabel),
parent["label_attributes"](parent, field),
),
})))
}
for key, choice := range choices.GetChoices() {
id := fmt.Sprintf("%s-%d", field.GetId(), key)
items = append(items, parent["choice_expanded_item"](parent, Group([]Node{
Input(
Name(field.GetName()),
ID(id),
Value(choice.Value),
If(isMultiple, Type("checkbox")),
If(!isMultiple, Type("radio")),
parent["choice_attributes"](parent, field),
If(choices.Match(field, choice.Value), Checked()),
),
Label(
For(id),
Text(choice.Label),
parent["label_attributes"](parent, field),
),
})))
}
return Group(items)
}
theme["choice"] = func(parent map[string]RenderFunc, 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 parent["choice_expanded"](parent, field)
} else {
return Select(
ID(field.GetId()),
If(isRequired, Required()),
If(isMultiple, Multiple()),
Name(field.GetName()),
parent["choice_attributes"](parent, field),
parent["choice_options"](parent, field),
)
}
}
theme["textarea"] = func(parent map[string]RenderFunc, 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()),
parent["textarea_attributes"](parent, field),
Text(cast.ToString(field.Data)),
)
}
theme["sub_form_label"] = func(parent map[string]RenderFunc, 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(
parent["label_attributes"](parent, field),
Text(label),
))
}
theme["sub_form_content"] = func(parent map[string]RenderFunc, args ...any) Node {
field := args[0].(*form.Field)
return parent["form_fields"](parent, field.Children)
}
theme["sub_form"] = func(parent map[string]RenderFunc, args ...any) Node {
field := args[0].(*form.Field)
return FieldSet(
ID(field.GetId()),
parent["sub_form_label"](parent, field),
parent["sub_form_attributes"](parent, field),
parent["sub_form_content"](parent, field),
)
}
theme["collection"] = func(parent map[string]RenderFunc, args ...any) Node {
field := args[0].(*form.Field)
var prototype string
if opt := field.GetOption("form"); opt != nil {
if val, ok := opt.Value.(*form.Form); ok {
var buffer bytes.Buffer
dest := form.NewFieldSubForm(field.Name)
for _, c := range val.Fields {
child := c.Copy()
child.NamePrefix = "[__name__]"
dest.Add(child)
}
fieldPrototype := parent["form_row"](parent, dest)
fieldPrototype.Render(&buffer)
prototype = buffer.String()
}
}
field.WithOptions(form.NewOption("prototype", prototype))
field.Widget = "collection_build"
return parent["form_widget"](parent, field)
}
theme["collection_build"] = func(parent map[string]RenderFunc, args ...any) Node {
field := args[0].(*form.Field)
prototype := field.GetOption("prototype").AsString()
var items []Node
for _, child := range field.Children {
items = append(items, parent["form_row"](parent, child))
}
return Div(
Attr("data-prototype", prototype),
Group(items),
)
}
theme["form_widget"] = func(parent map[string]RenderFunc, args ...any) Node {
field := args[0].(*form.Field)
tpl, ok := parent[field.Widget]
if !ok {
return Raw("Invalid field widget: " + field.Widget)
}
return tpl(parent, field)
}
theme["form_row"] = func(parent map[string]RenderFunc, 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 := parent["form_label"](parent, field)
attrs := Raw("")
if field.HasOption("row_attr") {
attrs = parent["attributes"](parent, field.GetOption("row_attr").AsAttrs())
}
return Div(
attrs,
If(!labelAfter, label),
parent["form_widget_errors"](parent, field),
parent["form_widget"](parent, field),
If(labelAfter, label),
parent["form_widget_help"](parent, field),
)
}
theme["form_fields"] = func(parent map[string]RenderFunc, args ...any) Node {
var items []Node
for _, item := range args[0].([]*form.Field) {
items = append(items, parent["form_row"](parent, item))
}
return Group(items)
}
theme["form_content"] = func(parent map[string]RenderFunc, args ...any) Node {
form := args[0].(*form.Form)
return Group([]Node{
parent["form_errors"](parent, form),
parent["form_help"](parent, form),
parent["form_fields"](parent, form.Fields),
})
}
theme["form"] = func(parent map[string]RenderFunc, args ...any) Node {
form := args[0].(*form.Form)
return Form(
Class("gf-form"),
Action(form.Action),
Method(form.Method),
parent["form_attributes"](parent, form),
parent["form_content"](parent, form),
)
}
return theme
})