Skip to content

Commit f854d1c

Browse files
authored
Merge pull request #106 from larrymjordan/sets_array_to_form_value
Support for nested slices and arrays
2 parents 0e9ca01 + 9702ef3 commit f854d1c

File tree

2 files changed

+141
-5
lines changed

2 files changed

+141
-5
lines changed

form/form_for.go

+24-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"database/sql/driver"
55
"fmt"
66
"reflect"
7+
"regexp"
8+
"strconv"
79
"strings"
810
"sync"
911

@@ -13,6 +15,8 @@ import (
1315
"github.com/markbates/inflect"
1416
)
1517

18+
var arrayFieldRegExp = regexp.MustCompile("^([A-Za-z0-9]+)\\[(\\d+)\\]$")
19+
1620
//FormFor is a form made for a struct
1721
type FormFor struct {
1822
*Form
@@ -186,16 +190,33 @@ func (f FormFor) value(field string) interface{} {
186190

187191
if fn.IsValid() == false {
188192
dots := strings.Split(field, ".")
189-
if len(dots) == 1 {
193+
194+
if len(dots) == 1 && !arrayFieldRegExp.Match([]byte(dots[0])) {
190195
if !strings.HasSuffix(field, "ID") {
191196
return f.value(field + "ID")
192197
}
193198
return ""
194199
}
200+
201+
matches := arrayFieldRegExp.FindStringSubmatch(dots[0])
202+
if len(matches) != 0 {
203+
dots[0] = matches[1]
204+
}
205+
195206
fn = f.reflection.FieldByName(dots[0])
207+
196208
if fn.IsValid() {
197-
ff := NewFormFor(fn.Interface(), f.Options)
198-
return ff.value(strings.Join(dots[1:], "."))
209+
fn = reflect.Indirect(fn)
210+
211+
if fn.Kind() == reflect.Slice || fn.Kind() == reflect.Array {
212+
index, _ := strconv.Atoi(matches[2])
213+
fn = reflect.Indirect(fn.Index(index))
214+
}
215+
216+
if fn.Kind() == reflect.Struct {
217+
ff := NewFormFor(fn.Interface(), f.Options)
218+
return ff.value(strings.Join(dots[1:], "."))
219+
}
199220
}
200221
}
201222

form/form_for_test.go

+117-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package form_test
22

33
import (
44
"database/sql"
5+
"strconv"
56
"testing"
67
"time"
78

@@ -159,8 +160,10 @@ func Test_FormFor_NullableField(t *testing.T) {
159160
}
160161

161162
type Person struct {
162-
Name string
163-
Address Address
163+
Name string
164+
Address Address
165+
References []Address
166+
Contacts []string
164167
}
165168

166169
type Address struct {
@@ -185,6 +188,118 @@ func Test_FormFor_Nested_Struct(t *testing.T) {
185188
r.Equal(exp, tag.String())
186189
}
187190

191+
func Test_FormFor_Nested_Slice_Struct(t *testing.T) {
192+
r := require.New(t)
193+
p := Person{
194+
Name: "Mark",
195+
Address: Address{
196+
City: "Boston",
197+
State: "MA",
198+
},
199+
}
200+
p.References = []Address{p.Address}
201+
202+
f := form.NewFormFor(p, tags.Options{})
203+
tag := f.InputTag("References[0].City", tags.Options{})
204+
205+
exp := `<input id="person-References[0].City" name="References[0].City" type="text" value="Boston" />`
206+
r.Equal(exp, tag.String())
207+
}
208+
209+
func Test_FormFor_Nested_Slice_String(t *testing.T) {
210+
r := require.New(t)
211+
p := Person{
212+
Contacts: []string{
213+
"Contact 1",
214+
"Contact 2",
215+
"Contact 3",
216+
},
217+
}
218+
219+
f := form.NewFormFor(p, tags.Options{})
220+
221+
for i := 0; i < len(p.Contacts); i++ {
222+
expectedValue := p.Contacts[i]
223+
index := strconv.Itoa(i)
224+
tag := f.InputTag("Contacts["+index+"]", tags.Options{})
225+
exp := `<input id="person-Contacts[` + index + `]" name="Contacts[` + index + `]" type="text" value="` + expectedValue + `" />`
226+
r.Equal(exp, tag.String())
227+
}
228+
}
229+
230+
func Test_FormFor_Nested_Slice_String_Pointer(t *testing.T) {
231+
r := require.New(t)
232+
p := struct {
233+
Contacts *[]string
234+
}{
235+
&[]string{"Contact 1", "Contact 2"},
236+
}
237+
238+
f := form.NewFormFor(p, tags.Options{})
239+
tag := f.InputTag("Contacts[0]", tags.Options{})
240+
241+
exp := `<input id="-Contacts[0]" name="Contacts[0]" type="text" value="Contact 1" />`
242+
r.Equal(exp, tag.String())
243+
}
244+
245+
func Test_FormFor_Nested_Slice_Pointer(t *testing.T) {
246+
r := require.New(t)
247+
p := struct {
248+
Persons *[]Person
249+
}{
250+
&[]Person{{Name: "Mark"}, {Name: "Clayton"}, {Name: "Iain"}},
251+
}
252+
253+
f := form.NewFormFor(p, tags.Options{})
254+
255+
for i := 0; i < len(*p.Persons); i++ {
256+
expectedValue := (*p.Persons)[i].Name
257+
index := strconv.Itoa(i)
258+
tag := f.InputTag("Persons["+index+"].Name", tags.Options{})
259+
exp := `<input id="-Persons[` + index + `].Name" name="Persons[` + index + `].Name" type="text" value="` + expectedValue + `" />`
260+
r.Equal(exp, tag.String())
261+
}
262+
}
263+
264+
func Test_FormFor_Nested_Slice_Pointer_Elements(t *testing.T) {
265+
r := require.New(t)
266+
p := struct {
267+
Persons []*Person
268+
}{
269+
[]*Person{
270+
&Person{Name: "Mark"},
271+
},
272+
}
273+
274+
f := form.NewFormFor(p, tags.Options{})
275+
tag := f.InputTag("Persons[0].Name", tags.Options{})
276+
277+
exp := `<input id="-Persons[0].Name" name="Persons[0].Name" type="text" value="Mark" />`
278+
r.Equal(exp, tag.String())
279+
}
280+
281+
func Test_FormFor_Nested_Slice_With_Sub_Slices(t *testing.T) {
282+
r := require.New(t)
283+
p := struct {
284+
Persons *[]Person
285+
}{
286+
&[]Person{
287+
{
288+
Name: "Mark",
289+
References: []Address{
290+
{City: "Boston"},
291+
},
292+
},
293+
},
294+
}
295+
296+
f := form.NewFormFor(p, tags.Options{})
297+
tag := f.InputTag("Persons[0].References[0].City", tags.Options{})
298+
299+
exp := `<input id="-Persons[0].References[0].City" name="Persons[0].References[0].City" type="text" value="Boston" />`
300+
r.Equal(exp, tag.String())
301+
}
302+
188303
func Test_FormFor_DateTimeTag(t *testing.T) {
189304
r := require.New(t)
190305

0 commit comments

Comments
 (0)