Skip to content

Commit

Permalink
feat(form): add support for select multiple with correct marshaller
Browse files Browse the repository at this point in the history
  • Loading branch information
rande committed Oct 25, 2023
1 parent 3f6edbc commit e1fcf78
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 54 deletions.
Binary file added .DS_Store
Binary file not shown.
13 changes: 13 additions & 0 deletions .dev/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM golang:bullseye

# Install dependencies
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*

RUN go install github.com/cortesi/modd/cmd/modd@latest

# Set the Current Working Directory inside the container
WORKDIR /app
7 changes: 7 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": []
}
49 changes: 22 additions & 27 deletions core/form/__snapshots__/form_pongo_test.snap
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@

[Test_Form_Rendering - 1]
<form action="POST" action="" encoding="">
<label for="name"class="block text-gray-700 text-sm font-bold mb-2">name</label>
<input name="name" type="text" value="John Doe" id="name" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" 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 for="email"class="block text-gray-700 text-sm font-bold mb-2">email</label>
<input name="email" type="email" value="john.doe@gmail.com" id="email" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" max="" min="">



<label for="date"class="block text-gray-700 text-sm font-bold mb-2">date</label>
<input name="date" type="date" value="2022-04-01" id="date" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" max="" min="">



</form>
---

[Test_Form_Rendering_Error - 1]
<form action="POST" action="" encoding="">
<label for="position"class="block text-gray-700 text-sm font-bold mb-2">position</label>
1
<input name="position" type="int" value="1" id="position" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" max="" min="">
<input name="position" type="int" value="1" id="position" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" max="" min="">
<span>The position</span>


Expand All @@ -13,35 +32,11 @@
[Test_Form_Rendering_Error - 2]
<form action="POST" action="" encoding="">
<label for="position"class="block text-gray-700 text-sm font-bold mb-2">position</label>
foo
<input name="position" type="int" value="foo" id="position" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" max="" min="">
<input name="position" type="int" value="foo" id="position" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" max="" min="">
<span>The position</span>
<li class="text-red-500 text-xs italic" style="" >value does not match the expected type</li>
<li class="text-red-500 text-xs italic" style="" >the value is not a valid email</li>


</form>
---

[Test_Form_Rendering - 1]
<form action="POST" action="" encoding="">
<label for="name"class="block text-gray-700 text-sm font-bold mb-2">name</label>
John Doe
<input name="name" type="text" value="John Doe" id="name" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" 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 for="email"class="block text-gray-700 text-sm font-bold mb-2">email</label>
john.doe@gmail.com
<input name="email" type="email" value="john.doe@gmail.com" id="email" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" max="" min="">



<label for="date"class="block text-gray-700 text-sm font-bold mb-2">date</label>
2022-04-01
<input name="date" type="date" value="2022-04-01" id="date" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" max="" min="">



</form>
---
38 changes: 34 additions & 4 deletions core/form/form_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,53 @@ import (
"strconv"
)

func convert(value interface{}, kind reflect.Kind) (interface{}, bool) {
func StrToValue(value interface{}, src reflect.Value) (interface{}, bool) {
if value == nil {
return nil, false
}

switch kind {
switch src.Kind() {
case reflect.Bool:
return StrToBool(value)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
return StrToNumber(value.(string), kind)
return StrToNumber(value.(string), src.Kind())
case reflect.String:
return value, true
default:
panic(fmt.Sprintf("Case not implemented - StrToValue: value `%s` to type `%s`", value, src.Kind()))
}
}

return nil, false
func ValueToStrSlice(value interface{}, src reflect.Value) ([]string, bool) {
c := make([]string, 0)
// for _, _ := range value.([]interface{}) {
// // c = append(c, ValueToStr(v, kind.Elem().Kind().Elem()))
// }
return c, true
}

func ValueToStr(value interface{}, src reflect.Value) (string, bool) {
if value == nil {
return "", false
}

fmt.Printf("ValueToStr: %s - %s, %s\n", value, src.Kind(), src.Kind())

switch src.Kind() {

case reflect.Bool:
return BoolToStr(value)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
return NumberToStr(value)
case reflect.String:
return value.(string), true
default:
panic(fmt.Sprintf("Case not implemented - ValueToStr: value `%s` to type `%s`", value, src.Kind()))
}
}

func yes(value string) bool {
Expand Down
79 changes: 66 additions & 13 deletions core/form/form_marshaller.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,26 @@ import (
"fmt"
"net/url"
"reflect"
"strings"
"time"
)

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

var replacers = strings.NewReplacer(".", "_", "[", "_", "]", "")

func generateId(name string) string {
return replacers.Replace(name)
}

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

Expand Down Expand Up @@ -61,6 +68,7 @@ type MarshallerResult struct {
}

func findMarshaller(rv reflect.Value) *MarshallerResult {

if rv.Kind() == reflect.String {
return &MarshallerResult{
Marshaller: defaultMarshal,
Expand All @@ -69,6 +77,14 @@ func findMarshaller(rv reflect.Value) *MarshallerResult {
}
}

if rv.Kind() == reflect.Slice {
return &MarshallerResult{
Marshaller: sliceMarshal,
Unmarshaller: sliceUnmarshal,
Type: "slice",
}
}

if rv.Kind() == reflect.Int ||
rv.Kind() == reflect.Int8 ||
rv.Kind() == reflect.Int16 ||
Expand Down Expand Up @@ -105,6 +121,8 @@ func findMarshaller(rv reflect.Value) *MarshallerResult {
Type: "date",
}
}

panic(fmt.Sprintf("Unable to find marshaller for %s", rv.Type()))
}

if rv.Kind() == reflect.Ptr {
Expand All @@ -123,6 +141,8 @@ func findMarshaller(rv reflect.Value) *MarshallerResult {
Type: "collection",
}
}

panic(fmt.Sprintf("Unable to find marshaller for %s", rv.Type()))
}

return &MarshallerResult{
Expand All @@ -139,7 +159,6 @@ func configure(field *FormField, form *Form) {
}

if field.reflect.Kind() == reflect.Invalid && field.InitialValue != nil {

field.reflect = reflect.ValueOf(field.InitialValue)
}

Expand All @@ -162,10 +181,21 @@ func configure(field *FormField, form *Form) {
}
}

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

return nil
}

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

return nil
}

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)
field.Input.Id = replacers.Replace(field.Input.Name)
field.Input.Id = generateId(field.Input.Name)

return nil
}
Expand Down Expand Up @@ -299,7 +329,7 @@ func formMarshal(field *FormField, form *Form) error {

for _, subField := range field.Children {
subField.Input.Name = fmt.Sprintf("%s.%s", field.Name, subField.Name)
subField.Input.Id = replacers.Replace(subField.Input.Name)
subField.Input.Id = generateId(subField.Input.Name)
subField.Prefix = field.Input.Name + "."
configure(subField, subForm)
marshal(subField, subForm)
Expand All @@ -320,15 +350,15 @@ func collectionMarshal(field *FormField, form *Form) error {
options := field.Options.(*FieldCollectionOptions)

field.Input.Name = fmt.Sprintf("%s%s", field.Prefix, field.Name)
field.Input.Id = replacers.Replace(field.Input.Name)
field.Input.Id = generateId(field.Input.Name)

for _, value := range options.Items {
subForm := options.Configure(value.Value)

subField := create(value.Key, "form", subForm)
subField.Input.Name = fmt.Sprintf("%s[%s]", field.Input.Name, value.Key)

subField.Input.Id = replacers.Replace(subField.Input.Name)
subField.Input.Id = generateId(subField.Input.Name)
subField.Prefix = field.Input.Name + "."

field.Children = append(field.Children, subField)
Expand All @@ -354,14 +384,14 @@ func collectionUnmarshal(field *FormField, form *Form, values url.Values) error

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)
field.Input.Id = generateId(field.Input.Name)

for i, option := range field.Options.(FieldOptions) {
// find a nice way to generate the name
subField := CreateFormField()
subField.Name = fmt.Sprintf("%d", i)
subField.Input.Name = fmt.Sprintf("%s[%s]", field.Input.Name, subField.Name)
subField.Input.Id = replacers.Replace(subField.Input.Name)
subField.Input.Id = generateId(subField.Input.Name)
subField.Label.Value = option.Label
subField.Input.Type = "checkbox"
subField.InitialValue = option.Checked
Expand Down Expand Up @@ -406,23 +436,45 @@ func checkboxUnmarshal(field *FormField, form *Form, values url.Values) error {

func selectMarshal(field *FormField, form *Form) error {
field.Input.Name = fmt.Sprintf("%s%s", field.Prefix, field.Name)
field.Input.Id = replacers.Replace(field.Input.Name)
field.Input.Id = generateId(field.Input.Name)

marshallers := findMarshaller(field.reflect)
marshallers.Marshaller(field, form)

if field.reflect.Kind() == reflect.Slice {
field.SetMultiple(true)
}

for i, option := range field.Options.(FieldOptions) {
marshallers := findMarshaller(reflect.ValueOf(option.Value))

// find a nice way to generate the name
subField := CreateFormField()
subField.InitialValue = option.Value
subField.Name = fmt.Sprintf("%d", i)
subField.Input.Name = fmt.Sprintf("%s[%s]", field.Input.Name, subField.Name)
subField.Input.Id = replacers.Replace(subField.Input.Name)
subField.Label.Value = option.Label
subField.Input.Type = "option"
subField.Marshaller = marshallers.Marshaller
subField.Unmarshaller = marshallers.Unmarshaller

marshal(subField, form)

subField.Input.Name = fmt.Sprintf("%s[%s]", field.Input.Name, subField.Name)
subField.Input.Id = generateId(subField.Input.Name)

if field.reflect.Kind() == reflect.Slice {
for i := 0; i < field.reflect.Len(); i++ {
v := field.reflect.Index(i)

if v.Equal(reflect.ValueOf(option.Value)) {
subField.Input.Checked = true
}
}

} else {
subField.Input.Checked = field.Input.Value == subField.Input.Value
}

field.Children = append(field.Children, subField)
}

Expand All @@ -440,10 +492,11 @@ func selectUnmarshal(field *FormField, form *Form, values url.Values) error {
field.Children[0].Unmarshaller(field, form, values)
} else {
slice := reflect.MakeSlice(reflect.SliceOf(field.reflect.Type().Elem()), 0, 0)
value := reflect.Zero(field.reflect.Type().Elem())

for _, valueStr := range values[field.Input.Id] {
if value, ok := convert(valueStr, field.reflect.Type().Elem().Kind()); ok {
slice = reflect.Append(slice, reflect.ValueOf(value))
if v, ok := StrToValue(valueStr, value); ok {
slice = reflect.Append(slice, reflect.ValueOf(v))
} else {
// fmt.Printf("Unable to convert %s to %s\n", valueStr, field.reflect.Type().Elem())
field.Errors = append(field.Errors, "Unable to convert value to the correct type")
Expand Down
4 changes: 1 addition & 3 deletions core/form/form_structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,13 @@ import (
"fmt"
"net/url"
"reflect"
"strings"
)

var (
ErrNoValue = errors.New("unable to find the value")
ErrInvalidSubmittedData = errors.New("invalid submitted data")
)

var replacers = strings.NewReplacer(".", "_", "[", "_", "]", "")

type FieldCollectionValue struct {
Value interface{}
Key string
Expand Down Expand Up @@ -230,6 +227,7 @@ func create(name string, options ...interface{}) *FormField {
if field.Input.Type == "select" {
field.Marshaller = selectMarshal
field.Unmarshaller = selectUnmarshal
field.Input.Template = "form:fields/input.select.tpl"
}

// if fieldType == "form" {
Expand Down
Loading

0 comments on commit e1fcf78

Please sign in to comment.