From ff28a619c210eb62ccb96514720fbc4f4b5820c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20D=C3=B6ll?= Date: Wed, 6 Nov 2024 15:04:25 +0000 Subject: [PATCH] fix: return inline errors for form --- components/forms/text.go | 1 + components/validate/errors.go | 6 +++ examples/main.go | 73 ++++++++++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/components/forms/text.go b/components/forms/text.go index e477f70..40186c4 100644 --- a/components/forms/text.go +++ b/components/forms/text.go @@ -31,6 +31,7 @@ func TextInput(p TextInputProps, children ...htmx.Node) htmx.Node { htmx.Attribute("name", p.Name), htmx.Attribute("value", p.Value), htmx.If(p.Disabled, htmx.Disabled()), + htmx.If(utilx.NotEmpty(p.Error), htmx.Attribute("aria-invalid", "true")), htmx.Attribute("placeholder", p.Placeholder), htmx.Group(children...), ) diff --git a/components/validate/errors.go b/components/validate/errors.go index 9d2374d..5a3d713 100644 --- a/components/validate/errors.go +++ b/components/validate/errors.go @@ -6,6 +6,7 @@ import ( "github.com/go-playground/validator/v10" "github.com/zeiss/pkg/slices" + "github.com/zeiss/pkg/utilx" ) // Errors is a map of field names to error messages. @@ -22,6 +23,11 @@ func (e Errors) Field(name string) string { return "" } +// HasError returns true if the field has an error. +func (e Errors) HasError(name string) bool { + return utilx.NotEmpty(e.Field(name)) +} + // TagNameFunc returns the tag name for the provided field. func TagNameFunc(fld reflect.StructField) string { name := slices.First(strings.SplitN(fld.Tag.Get("json"), ",", 2)...) diff --git a/examples/main.go b/examples/main.go index 63216f7..aab5075 100644 --- a/examples/main.go +++ b/examples/main.go @@ -113,6 +113,56 @@ func init() { rootCmd.SilenceUsage = true } +type RegisterFormProps struct { + errors validate.Errors +} + +func RegisterForm(props RegisterFormProps) htmx.Node { + return htmx.Form( + htmx.HxPost("/register"), + htmx.HxSwap("outerHTML"), + htmx.Target("cloesest div"), + forms.FormControl( + forms.FormControlProps{}, + forms.FormControlLabel( + forms.FormControlLabelProps{}, + forms.FormControlLabelText( + forms.FormControlLabelTextProps{ + ClassNames: htmx.ClassNames{ + "label-text": true, + }, + }, + htmx.Text("Email"), + ), + ), + forms.TextInput( + forms.TextInputProps{ + Error: props.errors.Field("email"), + Name: "email", + Value: "Hello, World!", + }, + htmx.Type("email"), + htmx.Required(), + ), + htmx.If( + props.errors.HasError("email"), + htmx.Div( + htmx.ClassNames{ + "text-error": true, + }, + htmx.Text("A valid email is required."), + ), + ), + ), + buttons.Button( + buttons.ButtonProps{ + Type: "submit", + }, + htmx.Text("Register"), + ), + ) +} + type exampleController struct { htmx.UnimplementedController } @@ -419,7 +469,7 @@ func (c *exampleController) Get() error { ), ), ), - + RegisterForm(RegisterFormProps{}), alerts.Info( alerts.AlertProps{}, htmx.Text("Hello, World!"), @@ -712,6 +762,27 @@ func (w *webSrv) Start(ctx context.Context, ready server.ReadyFunc, run server.R return &exampleController{} })) + app.Post("/register", htmx.NewCompFuncHandler(func(c *fiber.Ctx) (htmx.Node, error) { + type Form struct { + Email string `json:"email" validate:"required,email"` + } + + var f Form + if err := c.BodyParser(&f); err != nil { + return nil, err + } + + v := validator.New() + v.RegisterTagNameFunc(validate.TagNameFunc) + if err := v.Struct(f); err != nil { + return RegisterForm(RegisterFormProps{ + errors: validate.Errors(err.(validator.ValidationErrors)), + }), nil + } + + return RegisterForm(RegisterFormProps{}), nil + })) + app.Post("/dropdown", htmx.NewCompHandler( htmx.Fragment( dropdowns.DropdownMenuItem(