Skip to content

Commit

Permalink
feat(form): add support for validator, etc ...
Browse files Browse the repository at this point in the history
  • Loading branch information
rande committed Sep 25, 2023
1 parent 4f30eeb commit 13148d8
Show file tree
Hide file tree
Showing 15 changed files with 504 additions and 93 deletions.
23 changes: 18 additions & 5 deletions core/form/__snapshots__/form_pongo_test.snap
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@

[Test_Form_Rendering - 1]
[Test_Form_Rendering_Error - 1]
<form action="POST" action="" encoding="">
<label class="" style="" for="name">name</label>
<input name="name" type="text" value="John Doe" id="name" placeholder="Enter the name" required readonly autofocus size="10" maxlength="100" minlength="10" max="100" min="10" step="10" pattern="^[a-z]+$" autocomplete="on">
<label class="" style="" for="email">email</label>
<input name="email" type="email" value="john.doe@gmail.com" id="email">
<label for="position">position</label>
<input name="position" type="number" value="1" id="position">
<span>The position</span>

</form>
---

[Test_Form_Rendering_Error - 2]
<form action="POST" action="" encoding="">
<label for="position">position</label>
<input name="position" type="number" value="foo" id="position">
<span>The position</span>
<ul>
<li>value does not match the expected type</li>
<li>the value is not a valid email</li>
</ul>

</form>
---
2 changes: 2 additions & 0 deletions core/form/form_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ func Configure(l *goapp.Lifecycle, conf *config.Config) {
pongo.Globals["form_field"] = createPongoField(pongo)
pongo.Globals["form_label"] = createPongoLabel(pongo)
pongo.Globals["form_input"] = createPongoInput(pongo)
pongo.Globals["form_help"] = createPongoHelp(pongo)
pongo.Globals["form_errors"] = createPongoErrors(pongo)

return nil
})
Expand Down
95 changes: 84 additions & 11 deletions core/form/form_marshaler.go → core/form/form_marshaller.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,52 @@
package form

import (
"errors"
"fmt"
"net/url"
"reflect"
"strconv"
"time"
)

var (
ErrInvalidType = errors.New("value does not match the expected type")
)

func iterateFields(form *Form, fields []*FormField) {
for _, field := range fields {
field.Input.Name = field.Name
marshal(field, form)
field.Input.Id = replacers.Replace(field.Input.Name)
}
}

func marshal(field *FormField, form *Form) {
// we do not try to get the value, if the value is already set
// and if form.Data is not set.
if form.Data != nil || field.InitialValue == nil {
field.reflect = form.reflect.FieldByName(field.Name)

if field.reflect.Kind() != reflect.Invalid {
field.InitialValue = field.reflect.Interface()
}
}

field.Marshal(field, form)
}

func unmarshal(field *FormField, form *Form, values url.Values) {
field.Errors = []string{}

if values.Has(field.Input.Name) {
field.Input.Value = values.Get(field.Input.Name)
}

field.Unmarshal(field, form, values)

field.HasErrors = len(field.Errors) > 0
}

func defaultMarshal(field *FormField, form *Form) error {
field.Input.Value = fmt.Sprintf("%s", field.InitialValue)
field.Input.Name = fmt.Sprintf("%s%s", field.Prefix, field.Name)
Expand All @@ -37,7 +78,6 @@ func defaultUnmarshal(field *FormField, form *Form, values url.Values) error {
}

func booleanMarshal(field *FormField, form *Form) error {

defaultMarshal(field, form)

if v, ok := field.InitialValue.(bool); ok {
Expand All @@ -52,7 +92,6 @@ func booleanMarshal(field *FormField, form *Form) error {
}

func booleanUnmarshal(field *FormField, form *Form, values url.Values) error {

defaultUnmarshal(field, form, values)

if field.HasErrors {
Expand All @@ -71,7 +110,11 @@ func booleanUnmarshal(field *FormField, form *Form, values url.Values) error {
}

func numberMarshal(field *FormField, form *Form) error {
return defaultMarshal(field, form)
defaultMarshal(field, form)

field.Input.Value = fmt.Sprintf("%d", field.InitialValue)

return nil
}

func intUnmarshal(field *FormField, form *Form, values url.Values) error {
Expand All @@ -83,7 +126,7 @@ func intUnmarshal(field *FormField, form *Form, values url.Values) error {

if v, ok := field.SubmitedValue.(string); ok {
if i, err := strconv.ParseInt(v, 10, 64); err != nil {
field.Errors = append(field.Errors, err.Error())
field.Errors = append(field.Errors, ErrInvalidType.Error())
} else {
field.SubmitedValue = i
}
Expand All @@ -101,7 +144,7 @@ func unintUnmarshal(field *FormField, form *Form, values url.Values) error {

if v, ok := field.SubmitedValue.(string); ok {
if i, err := strconv.ParseUint(v, 10, 64); err != nil {
field.Errors = append(field.Errors, err.Error())
field.Errors = append(field.Errors, ErrInvalidType.Error())
} else {
field.SubmitedValue = i
}
Expand All @@ -119,7 +162,7 @@ func floatUnmarshal(field *FormField, form *Form, values url.Values) error {

if v, ok := field.SubmitedValue.(string); ok {
if i, err := strconv.ParseFloat(v, 64); err != nil {
field.Errors = append(field.Errors, err.Error())
field.Errors = append(field.Errors, ErrInvalidType.Error())
} else {
field.SubmitedValue = i
}
Expand All @@ -128,6 +171,36 @@ func floatUnmarshal(field *FormField, form *Form, values url.Values) error {
return nil
}

// The displayed date format will differ from the actual value —
// the displayed date is formatted based on the locale of the user's browser,
// but the parsed value is always formatted yyyy-mm-dd.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date
func dateMarshal(field *FormField, form *Form) error {
defaultMarshal(field, form)

if v, ok := field.InitialValue.(time.Time); ok {
field.Input.Value = v.Format("2006-01-02")
} else {
fmt.Printf("Invalid date: %s", field.InitialValue)
}

return nil
}

func dateUnmarshal(field *FormField, form *Form, values url.Values) error {
defaultUnmarshal(field, form, values)

if v, ok := field.SubmitedValue.(string); ok {
if t, err := time.ParseInLocation("2006-01-02", v, time.UTC); err != nil {
field.Errors = append(field.Errors, err.Error())
} else {
field.SubmitedValue = t
}
}

return nil
}

func formMarshal(field *FormField, form *Form) error {
subForm := field.InitialValue.(*Form)

Expand All @@ -148,7 +221,7 @@ func formMarshal(field *FormField, form *Form) error {

func formUnmarshal(field *FormField, form *Form, values url.Values) error {
for _, child := range field.Children {
child.Unmarshal(child, form, values)
unmarshal(child, form, values)
}

return nil
Expand Down Expand Up @@ -192,7 +265,7 @@ func checkboxMarshal(field *FormField, form *Form) error {
field.Input.Name = fmt.Sprintf("%s%s", field.Prefix, field.Name)
field.Input.Id = replacers.Replace(field.Input.Name)

for name, option := range field.InitialValue.(FieldOptions) {
for name, option := range field.InitialValue.(CheckboxOptions) {
// find a nice way to generate the name
subField := CreateFormField()
subField.Name = name
Expand All @@ -210,8 +283,8 @@ func checkboxMarshal(field *FormField, form *Form) error {

func checkboxUnmarshal(field *FormField, form *Form, values url.Values) error {
// we need to check for extra values!
submitedValue := FieldOptions{}
for name, option := range field.InitialValue.(FieldOptions) {
submitedValue := CheckboxOptions{}
for name, option := range field.InitialValue.(CheckboxOptions) {
value, err := getValue(field.Get(name), values)

if err != nil {
Expand All @@ -221,7 +294,7 @@ func checkboxUnmarshal(field *FormField, form *Form, values url.Values) error {
return err
}

submitedValue[name] = &FieldOption{
submitedValue[name] = &CheckboxOption{
Label: option.Label,
Checked: yes(value),
}
Expand Down
45 changes: 45 additions & 0 deletions core/form/form_marshaller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright © 2014-2023 Thomas Rabaix <thomas.rabaix@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package form

import (
"net/url"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func Test_Date_Marshalling(t *testing.T) {
field := CreateFormField()
form := CreateForm(nil)

field.InitialValue = time.Date(2022, time.April, 1, 1, 1, 1, 1, time.UTC)

dateMarshal(field, form)

assert.Equal(t, "2022-04-01", field.Input.Value)
}

func Test_Date_Unmarshalling(t *testing.T) {
field := CreateFormField()
field.Input.Id = "Date"
field.Input.Name = "Date"

form := CreateForm(nil)

field.InitialValue = time.Date(2022, time.April, 1, 1, 1, 1, 1, time.UTC)

v := url.Values{
"Date": []string{"2022-04-01"},
}

dateUnmarshal(field, form, v)

expectedDate := time.Date(2022, time.April, 1, 0, 0, 0, 0, time.UTC)

assert.Equal(t, expectedDate, field.SubmitedValue)
}
62 changes: 61 additions & 1 deletion core/form/form_pongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@
package form

import (
"errors"
"fmt"

"github.com/flosch/pongo2"
"github.com/rande/gonode/core/helper"
)

var (
ErrNoTemplate = errors.New("unable to find the template to render")
)

type AttributOption struct {
Name string
Value interface{}
Expand Down Expand Up @@ -60,15 +65,70 @@ func createPongoLabel(pongo *pongo2.TemplateSet) func(field *FormField, form *Fo

func createPongoInput(pongo *pongo2.TemplateSet) func(field *FormField, form *Form) *pongo2.Value {

return func(field *FormField, form *Form) *pongo2.Value {
templates := []string{
fmt.Sprintf("%s:fields/input.%s.tpl", field.Module, field.Input.Type),
"form:fields/input.base.tpl",
}

for _, path := range templates {
tpl, err := pongo.FromFile(path)

if err == nil {
data, err := tpl.Execute(pongo2.Context{
"form": form,
"field": field,
"input": field.Input,
"label": field.Label,
})

helper.PanicOnError(err)

return pongo2.AsSafeValue(data)
}
}

helper.PanicOnError(ErrNoTemplate)

return nil
}
}

func createPongoErrors(pongo *pongo2.TemplateSet) func(field *FormField, form *Form) *pongo2.Value {

return func(field *FormField, form *Form) *pongo2.Value {

tpl, err := pongo.FromFile("form:errors.tpl")

helper.PanicOnError(err)

data, err := tpl.Execute(pongo2.Context{
"form": form,
"field": field,
"input": field.Input,
"label": field.Label,
"error": field.Errors,
"hasErrors": len(field.Errors) > 0,
})

helper.PanicOnError(err)

return pongo2.AsSafeValue(data)
}
}

func createPongoHelp(pongo *pongo2.TemplateSet) func(field *FormField, form *Form) *pongo2.Value {

return func(field *FormField, form *Form) *pongo2.Value {

tpl, err := pongo.FromFile(fmt.Sprintf("%s:fields/input.%s.tpl", field.Module, field.Input.Type))
tpl, err := pongo.FromFile("form:help.tpl")

helper.PanicOnError(err)

data, err := tpl.Execute(pongo2.Context{
"form": form,
"field": field,
"help": field.Help,
"input": field.Input,
"label": field.Label,
})
Expand Down
Loading

0 comments on commit 13148d8

Please sign in to comment.