Skip to content

Commit fe7e7ce

Browse files
committed
feat(form): add support for select multiple with correct marshaller
1 parent 3f6edbc commit fe7e7ce

File tree

8 files changed

+148
-54
lines changed

8 files changed

+148
-54
lines changed
Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,28 @@
11

2+
[Test_Form_Rendering - 1]
3+
<form action="POST" action="" encoding="">
4+
<label for="name"class="block text-gray-700 text-sm font-bold mb-2">name</label>
5+
<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">
6+
7+
8+
9+
<label for="email"class="block text-gray-700 text-sm font-bold mb-2">email</label>
10+
<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="">
11+
12+
13+
14+
<label for="date"class="block text-gray-700 text-sm font-bold mb-2">date</label>
15+
<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="">
16+
17+
18+
19+
</form>
20+
---
21+
222
[Test_Form_Rendering_Error - 1]
323
<form action="POST" action="" encoding="">
424
<label for="position"class="block text-gray-700 text-sm font-bold mb-2">position</label>
5-
1
6-
<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="">
25+
<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="">
726
<span>The position</span>
827

928

@@ -13,35 +32,11 @@
1332
[Test_Form_Rendering_Error - 2]
1433
<form action="POST" action="" encoding="">
1534
<label for="position"class="block text-gray-700 text-sm font-bold mb-2">position</label>
16-
foo
17-
<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="">
35+
<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="">
1836
<span>The position</span>
1937
<li class="text-red-500 text-xs italic" style="" >value does not match the expected type</li>
2038
<li class="text-red-500 text-xs italic" style="" >the value is not a valid email</li>
2139

2240

23-
</form>
24-
---
25-
26-
[Test_Form_Rendering - 1]
27-
<form action="POST" action="" encoding="">
28-
<label for="name"class="block text-gray-700 text-sm font-bold mb-2">name</label>
29-
John Doe
30-
<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">
31-
32-
33-
34-
<label for="email"class="block text-gray-700 text-sm font-bold mb-2">email</label>
35-
john.doe@gmail.com
36-
<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="">
37-
38-
39-
40-
<label for="date"class="block text-gray-700 text-sm font-bold mb-2">date</label>
41-
2022-04-01
42-
<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="">
43-
44-
45-
4641
</form>
4742
---

core/form/form_conversion.go

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,53 @@ import (
1111
"strconv"
1212
)
1313

14-
func convert(value interface{}, kind reflect.Kind) (interface{}, bool) {
14+
func StrToValue(value interface{}, src reflect.Value) (interface{}, bool) {
1515
if value == nil {
1616
return nil, false
1717
}
1818

19-
switch kind {
19+
switch src.Kind() {
2020
case reflect.Bool:
2121
return StrToBool(value)
2222
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
2323
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
2424
reflect.Float32, reflect.Float64:
25-
return StrToNumber(value.(string), kind)
25+
return StrToNumber(value.(string), src.Kind())
2626
case reflect.String:
2727
return value, true
28+
default:
29+
panic(fmt.Sprintf("Case not implemented - StrToValue: value `%s` to type `%s`", value, src.Kind()))
2830
}
31+
}
2932

30-
return nil, false
33+
func ValueToStrSlice(value interface{}, src reflect.Value) ([]string, bool) {
34+
c := make([]string, 0)
35+
// for _, _ := range value.([]interface{}) {
36+
// // c = append(c, ValueToStr(v, kind.Elem().Kind().Elem()))
37+
// }
38+
return c, true
39+
}
40+
41+
func ValueToStr(value interface{}, src reflect.Value) (string, bool) {
42+
if value == nil {
43+
return "", false
44+
}
45+
46+
fmt.Printf("ValueToStr: %s - %s, %s\n", value, src.Kind(), src.Kind())
47+
48+
switch src.Kind() {
49+
50+
case reflect.Bool:
51+
return BoolToStr(value)
52+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
53+
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
54+
reflect.Float32, reflect.Float64:
55+
return NumberToStr(value)
56+
case reflect.String:
57+
return value.(string), true
58+
default:
59+
panic(fmt.Sprintf("Case not implemented - ValueToStr: value `%s` to type `%s`", value, src.Kind()))
60+
}
3161
}
3262

3363
func yes(value string) bool {

core/form/form_marshaller.go

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,26 @@ import (
1010
"fmt"
1111
"net/url"
1212
"reflect"
13+
"strings"
1314
"time"
1415
)
1516

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

21+
var replacers = strings.NewReplacer(".", "_", "[", "_", "]", "")
22+
23+
func generateId(name string) string {
24+
return replacers.Replace(name)
25+
}
26+
2027
func iterateFields(form *Form, fields []*FormField) {
2128
for _, field := range fields {
2229
field.Input.Name = field.Name
2330
configure(field, form)
2431
marshal(field, form)
25-
field.Input.Id = replacers.Replace(field.Input.Name)
32+
field.Input.Id = generateId(field.Input.Name)
2633
}
2734
}
2835

@@ -61,6 +68,7 @@ type MarshallerResult struct {
6168
}
6269

6370
func findMarshaller(rv reflect.Value) *MarshallerResult {
71+
6472
if rv.Kind() == reflect.String {
6573
return &MarshallerResult{
6674
Marshaller: defaultMarshal,
@@ -69,6 +77,14 @@ func findMarshaller(rv reflect.Value) *MarshallerResult {
6977
}
7078
}
7179

80+
if rv.Kind() == reflect.Slice {
81+
return &MarshallerResult{
82+
Marshaller: sliceMarshal,
83+
Unmarshaller: sliceUnmarshal,
84+
Type: "slice",
85+
}
86+
}
87+
7288
if rv.Kind() == reflect.Int ||
7389
rv.Kind() == reflect.Int8 ||
7490
rv.Kind() == reflect.Int16 ||
@@ -105,6 +121,8 @@ func findMarshaller(rv reflect.Value) *MarshallerResult {
105121
Type: "date",
106122
}
107123
}
124+
125+
panic(fmt.Sprintf("Unable to find marshaller for %s", rv.Type()))
108126
}
109127

110128
if rv.Kind() == reflect.Ptr {
@@ -123,6 +141,8 @@ func findMarshaller(rv reflect.Value) *MarshallerResult {
123141
Type: "collection",
124142
}
125143
}
144+
145+
panic(fmt.Sprintf("Unable to find marshaller for %s", rv.Type()))
126146
}
127147

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

141161
if field.reflect.Kind() == reflect.Invalid && field.InitialValue != nil {
142-
143162
field.reflect = reflect.ValueOf(field.InitialValue)
144163
}
145164

@@ -162,10 +181,21 @@ func configure(field *FormField, form *Form) {
162181
}
163182
}
164183

184+
func sliceMarshal(field *FormField, form *Form) error {
185+
defaultMarshal(field, form)
186+
187+
return nil
188+
}
189+
190+
func sliceUnmarshal(field *FormField, form *Form, values url.Values) error {
191+
192+
return nil
193+
}
194+
165195
func defaultMarshal(field *FormField, form *Form) error {
166196
field.Input.Value = fmt.Sprintf("%s", field.InitialValue)
167197
field.Input.Name = fmt.Sprintf("%s%s", field.Prefix, field.Name)
168-
field.Input.Id = replacers.Replace(field.Input.Name)
198+
field.Input.Id = generateId(field.Input.Name)
169199

170200
return nil
171201
}
@@ -299,7 +329,7 @@ func formMarshal(field *FormField, form *Form) error {
299329

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

322352
field.Input.Name = fmt.Sprintf("%s%s", field.Prefix, field.Name)
323-
field.Input.Id = replacers.Replace(field.Input.Name)
353+
field.Input.Id = generateId(field.Input.Name)
324354

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

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

331-
subField.Input.Id = replacers.Replace(subField.Input.Name)
361+
subField.Input.Id = generateId(subField.Input.Name)
332362
subField.Prefix = field.Input.Name + "."
333363

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

355385
func checkboxMarshal(field *FormField, form *Form) error {
356386
field.Input.Name = fmt.Sprintf("%s%s", field.Prefix, field.Name)
357-
field.Input.Id = replacers.Replace(field.Input.Name)
387+
field.Input.Id = generateId(field.Input.Name)
358388

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

407437
func selectMarshal(field *FormField, form *Form) error {
408438
field.Input.Name = fmt.Sprintf("%s%s", field.Prefix, field.Name)
409-
field.Input.Id = replacers.Replace(field.Input.Name)
439+
field.Input.Id = generateId(field.Input.Name)
440+
441+
marshallers := findMarshaller(field.reflect)
442+
marshallers.Marshaller(field, form)
443+
444+
if field.reflect.Kind() == reflect.Slice {
445+
field.SetMultiple(true)
446+
}
410447

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

414451
// find a nice way to generate the name
415452
subField := CreateFormField()
453+
subField.InitialValue = option.Value
416454
subField.Name = fmt.Sprintf("%d", i)
417-
subField.Input.Name = fmt.Sprintf("%s[%s]", field.Input.Name, subField.Name)
418-
subField.Input.Id = replacers.Replace(subField.Input.Name)
419455
subField.Label.Value = option.Label
420456
subField.Input.Type = "option"
421457
subField.Marshaller = marshallers.Marshaller
422458
subField.Unmarshaller = marshallers.Unmarshaller
423459

424460
marshal(subField, form)
425461

462+
subField.Input.Name = fmt.Sprintf("%s[%s]", field.Input.Name, subField.Name)
463+
subField.Input.Id = generateId(subField.Input.Name)
464+
465+
if field.reflect.Kind() == reflect.Slice {
466+
for i := 0; i < field.reflect.Len(); i++ {
467+
v := field.reflect.Index(i)
468+
469+
if v.Equal(reflect.ValueOf(option.Value)) {
470+
subField.Input.Checked = true
471+
}
472+
}
473+
474+
} else {
475+
subField.Input.Checked = field.Input.Value == subField.Input.Value
476+
}
477+
426478
field.Children = append(field.Children, subField)
427479
}
428480

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

444497
for _, valueStr := range values[field.Input.Id] {
445-
if value, ok := convert(valueStr, field.reflect.Type().Elem().Kind()); ok {
446-
slice = reflect.Append(slice, reflect.ValueOf(value))
498+
if v, ok := StrToValue(valueStr, value); ok {
499+
slice = reflect.Append(slice, reflect.ValueOf(v))
447500
} else {
448501
// fmt.Printf("Unable to convert %s to %s\n", valueStr, field.reflect.Type().Elem())
449502
field.Errors = append(field.Errors, "Unable to convert value to the correct type")

core/form/form_structs.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,13 @@ import (
1010
"fmt"
1111
"net/url"
1212
"reflect"
13-
"strings"
1413
)
1514

1615
var (
1716
ErrNoValue = errors.New("unable to find the value")
1817
ErrInvalidSubmittedData = errors.New("invalid submitted data")
1918
)
2019

21-
var replacers = strings.NewReplacer(".", "_", "[", "_", "]", "")
22-
2320
type FieldCollectionValue struct {
2421
Value interface{}
2522
Key string
@@ -230,6 +227,7 @@ func create(name string, options ...interface{}) *FormField {
230227
if field.Input.Type == "select" {
231228
field.Marshaller = selectMarshal
232229
field.Unmarshaller = selectUnmarshal
230+
field.Input.Template = "form:fields/input.select.tpl"
233231
}
234232

235233
// if fieldType == "form" {

core/form/form_structs_test.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -444,10 +444,18 @@ func Test_Bind_Form_Select(t *testing.T) {
444444
{Label: "Car", Value: int32(2)},
445445
{Label: "Travel", Value: int32(3)},
446446
{Label: "Games", Value: int32(4)},
447-
}).SetMultiple(true)
447+
})
448448

449449
PrepareForm(form)
450450

451+
assert.True(t, form.Get("Items").Input.Multiple)
452+
assert.True(t, form.Get("Items").Get("0").Input.Checked)
453+
assert.True(t, form.Get("Items").Get("1").Input.Checked)
454+
assert.False(t, form.Get("Items").Get("2").Input.Checked)
455+
assert.False(t, form.Get("Items").Get("3").Input.Checked)
456+
457+
assert.Equal(t, form.Get("Items").Get("3").Input.Name, "Items[3]")
458+
451459
v := url.Values{
452460
"Enabled": []string{"0"},
453461
"Position": []string{"3"},
@@ -462,8 +470,8 @@ func Test_Bind_Form_Select(t *testing.T) {
462470
err = AttachValues(form)
463471
assert.Nil(t, err)
464472

465-
// assert.Equal(t, false, user.Enabled)
466-
// assert.Equal(t, int32(3), user.Position)
473+
assert.Equal(t, false, user.Enabled)
474+
assert.Equal(t, int32(3), user.Position)
467475
assert.Equal(t, []int32{1, 3}, user.Items)
468476
}
469477

0 commit comments

Comments
 (0)