Initial commit of form submission struct.
This commit is contained in:
parent
b61077dac9
commit
57159c4fba
5 changed files with 126 additions and 21 deletions
91
controller/form.go
Normal file
91
controller/form.go
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FormSubmission struct {
|
||||||
|
IsSubmitted bool
|
||||||
|
|
||||||
|
Errors map[string][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FormSubmission) Process(ctx echo.Context, form interface{}) error {
|
||||||
|
f.Errors = make(map[string][]string)
|
||||||
|
f.IsSubmitted = true
|
||||||
|
|
||||||
|
// Validate the form
|
||||||
|
if err := ctx.Validate(form); err != nil {
|
||||||
|
f.setErrorMessages(form, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FormSubmission) HasErrors() bool {
|
||||||
|
if f.Errors == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return len(f.Errors) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FormSubmission) FieldHasError(fieldName string) bool {
|
||||||
|
return len(f.GetFieldErrors(fieldName)) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FormSubmission) GetFieldErrors(fieldName string) []string {
|
||||||
|
if f.Errors == nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
errors, has := f.Errors[fieldName]
|
||||||
|
if !has {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FormSubmission) setErrorMessages(form interface{}, err error) {
|
||||||
|
// Only this is supported right now
|
||||||
|
ves, ok := err.(validator.ValidationErrors)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
formType := reflect.TypeOf(form)
|
||||||
|
|
||||||
|
for _, ve := range ves {
|
||||||
|
var message string
|
||||||
|
|
||||||
|
// Default the field form name to the name of the struct field
|
||||||
|
fieldName := ve.StructField()
|
||||||
|
|
||||||
|
// Attempt to get the form field name from the field's struct tag
|
||||||
|
if field, ok := formType.FieldByName(ve.Field()); ok {
|
||||||
|
if fieldNameTag := field.Tag.Get("form"); fieldNameTag != "" {
|
||||||
|
fieldName = fieldNameTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide better error messages depending on the failed validation tag
|
||||||
|
// This should be expanded as you use additional tags in your validation
|
||||||
|
switch ve.Tag() {
|
||||||
|
case "required":
|
||||||
|
message = "This field is required."
|
||||||
|
case "email":
|
||||||
|
message = "Enter a valid email address."
|
||||||
|
case "eqfield":
|
||||||
|
message = "Does not match."
|
||||||
|
default:
|
||||||
|
message = "Invalid value."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the error
|
||||||
|
f.Errors[fieldName] = append(f.Errors[fieldName], message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,8 +16,9 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
ContactForm struct {
|
ContactForm struct {
|
||||||
Email string `form:"email" validate:"required,email" label:"Email address"`
|
Email string `form:"email" validate:"required,email"`
|
||||||
Message string `form:"message" validate:"required" label:"Message"`
|
Message string `form:"message" validate:"required"`
|
||||||
|
Submission controller.FormSubmission
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -36,28 +37,31 @@ func (c *Contact) Get(ctx echo.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Contact) Post(ctx echo.Context) error {
|
func (c *Contact) Post(ctx echo.Context) error {
|
||||||
fail := func(message string, err error) error {
|
//fail := func(message string, err error) error {
|
||||||
ctx.Logger().Errorf("%s: %v", message, err)
|
// ctx.Logger().Errorf("%s: %v", message, err)
|
||||||
msg.Danger(ctx, "An error occurred. Please try again.")
|
// msg.Danger(ctx, "An error occurred. Please try again.")
|
||||||
return c.Get(ctx)
|
// return c.Get(ctx)
|
||||||
}
|
//}
|
||||||
|
|
||||||
// Parse the form values
|
// Parse the form values
|
||||||
var form ContactForm
|
var form ContactForm
|
||||||
if err := ctx.Bind(&form); err != nil {
|
if err := ctx.Bind(&form); err != nil {
|
||||||
return fail("unable to parse contact form", err)
|
ctx.Logger().Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := form.Submission.Process(ctx, form); err != nil {
|
||||||
|
// TOOD
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Set(context.FormKey, form)
|
ctx.Set(context.FormKey, form)
|
||||||
|
|
||||||
// Validate the form
|
if form.Submission.HasErrors() {
|
||||||
if err := ctx.Validate(form); err != nil {
|
|
||||||
c.SetValidationErrorMessages(ctx, err, form)
|
|
||||||
return c.Get(ctx)
|
return c.Get(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := controller.NewHTMX(ctx)
|
htmx := controller.NewHTMXRequest(ctx)
|
||||||
|
|
||||||
if p.Request.Enabled {
|
if htmx.Enabled {
|
||||||
return ctx.String(http.StatusOK, "<b>HELLO!</b>")
|
return ctx.String(http.StatusOK, "<b>HELLO!</b>")
|
||||||
} else {
|
} else {
|
||||||
msg.Success(ctx, "Thank you for contacting us!")
|
msg.Success(ctx, "Thank you for contacting us!")
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
{{define "csrf"}}
|
{{define "csrf"}}
|
||||||
<input type="hidden" name="csrf" value="{{.CSRF}}"/>
|
<input type="hidden" name="csrf" value="{{.CSRF}}"/>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{define "form-field-errors"}}
|
||||||
|
{{range .}}
|
||||||
|
<p class="help is-danger">{{.}}</p>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" style="height:100%;">
|
||||||
<head>
|
<head>
|
||||||
{{template "metatags" .}}
|
{{template "metatags" .}}
|
||||||
{{template "css" .}}
|
{{template "css" .}}
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="has-background-light" style="min-height:100%;">
|
||||||
<nav class="navbar is-dark">
|
<nav class="navbar is-dark">
|
||||||
<div class="container" hx-boost="true">
|
<div class="container" hx-boost="true">
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div class="box">
|
||||||
{{- if .Title}}
|
{{- if .Title}}
|
||||||
<h1 class="title">{{.Title}}</h1>
|
<h1 class="title">{{.Title}}</h1>
|
||||||
{{- end}}
|
{{- end}}
|
||||||
|
|
@ -35,6 +36,7 @@
|
||||||
{{template "messages" .}}
|
{{template "messages" .}}
|
||||||
{{template "content" .}}
|
{{template "content" .}}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<form id="contact" method="post" hx-post>
|
<form id="contact" method="post" >
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="email" class="label">Email address</label>
|
<label for="email" class="label">Email address</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input id="email" name="email" type="email" class="input" value="{{.Data.Email}}">
|
<input id="email" name="email" type="email" class="input" value="{{.Data.Email}}">
|
||||||
</div>
|
</div>
|
||||||
|
{{template "form-field-errors" (.Data.Submission.GetFieldErrors "email")}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
|
@ -12,6 +13,7 @@
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<textarea id="message" name="message" class="textarea">{{.Data.Message}}</textarea>
|
<textarea id="message" name="message" class="textarea">{{.Data.Message}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
{{template "form-field-errors" (.Data.Submission.GetFieldErrors "message")}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field is-grouped">
|
<div class="field is-grouped">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue