diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/.DS_Store differ diff --git a/.dev/backend/Dockerfile b/.dev/backend/Dockerfile new file mode 100644 index 0000000..75b4764 --- /dev/null +++ b/.dev/backend/Dockerfile @@ -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 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5c7247b --- /dev/null +++ b/.vscode/launch.json @@ -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": [] +} \ No newline at end of file diff --git a/core/form/__snapshots__/form_pongo_test.snap b/core/form/__snapshots__/form_pongo_test.snap index 5c18e0a..0aec146 100755 --- a/core/form/__snapshots__/form_pongo_test.snap +++ b/core/form/__snapshots__/form_pongo_test.snap @@ -1,9 +1,28 @@ +[Test_Form_Rendering - 1] +
+ + + + + + + + + + + + + + + +
+--- + [Test_Form_Rendering_Error - 1]
- 1 - + The position @@ -13,35 +32,11 @@ [Test_Form_Rendering_Error - 2] - foo - + The position
  • value does not match the expected type
  • the value is not a valid email
  • -
    ---- - -[Test_Form_Rendering - 1] -
    - - John Doe - - - - - - john.doe@gmail.com - - - - - - 2022-04-01 - - - -
    --- diff --git a/core/form/form_conversion.go b/core/form/form_conversion.go index 7bf56a6..17a34bd 100644 --- a/core/form/form_conversion.go +++ b/core/form/form_conversion.go @@ -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 { diff --git a/core/form/form_marshaller.go b/core/form/form_marshaller.go index 8d94727..6809a63 100644 --- a/core/form/form_marshaller.go +++ b/core/form/form_marshaller.go @@ -10,6 +10,7 @@ import ( "fmt" "net/url" "reflect" + "strings" "time" ) @@ -17,12 +18,18 @@ 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) } } @@ -61,6 +68,7 @@ type MarshallerResult struct { } func findMarshaller(rv reflect.Value) *MarshallerResult { + if rv.Kind() == reflect.String { return &MarshallerResult{ Marshaller: defaultMarshal, @@ -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 || @@ -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 { @@ -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{ @@ -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) } @@ -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 } @@ -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) @@ -320,7 +350,7 @@ 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) @@ -328,7 +358,7 @@ func collectionMarshal(field *FormField, form *Form) error { 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) @@ -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 @@ -406,16 +436,22 @@ 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 @@ -423,6 +459,22 @@ func selectMarshal(field *FormField, form *Form) error { 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) } @@ -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") diff --git a/core/form/form_structs.go b/core/form/form_structs.go index 79a1e77..d09305f 100644 --- a/core/form/form_structs.go +++ b/core/form/form_structs.go @@ -10,7 +10,6 @@ import ( "fmt" "net/url" "reflect" - "strings" ) var ( @@ -18,8 +17,6 @@ var ( ErrInvalidSubmittedData = errors.New("invalid submitted data") ) -var replacers = strings.NewReplacer(".", "_", "[", "_", "]", "") - type FieldCollectionValue struct { Value interface{} Key string @@ -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" { diff --git a/core/form/form_structs_test.go b/core/form/form_structs_test.go index 4042845..9c7a23f 100644 --- a/core/form/form_structs_test.go +++ b/core/form/form_structs_test.go @@ -444,10 +444,18 @@ func Test_Bind_Form_Select(t *testing.T) { {Label: "Car", Value: int32(2)}, {Label: "Travel", Value: int32(3)}, {Label: "Games", Value: int32(4)}, - }).SetMultiple(true) + }) PrepareForm(form) + assert.True(t, form.Get("Items").Input.Multiple) + assert.True(t, form.Get("Items").Get("0").Input.Checked) + assert.True(t, form.Get("Items").Get("1").Input.Checked) + assert.False(t, form.Get("Items").Get("2").Input.Checked) + assert.False(t, form.Get("Items").Get("3").Input.Checked) + + assert.Equal(t, form.Get("Items").Get("3").Input.Name, "Items[3]") + v := url.Values{ "Enabled": []string{"0"}, "Position": []string{"3"}, @@ -462,8 +470,8 @@ func Test_Bind_Form_Select(t *testing.T) { err = AttachValues(form) assert.Nil(t, err) - // assert.Equal(t, false, user.Enabled) - // assert.Equal(t, int32(3), user.Position) + assert.Equal(t, false, user.Enabled) + assert.Equal(t, int32(3), user.Position) assert.Equal(t, []int32{1, 3}, user.Items) } diff --git a/core/form/form_utils.go b/core/form/form_utils.go index 94fa2ad..8a60d72 100644 --- a/core/form/form_utils.go +++ b/core/form/form_utils.go @@ -7,7 +7,6 @@ package form import ( "errors" - "fmt" "net/http" "net/url" "reflect" @@ -74,8 +73,6 @@ func Process(form *Form, request *http.Request) error { // a serialized value used in the HTML form. This also setup the different attribures required // by the HTML form. func PrepareForm(form *Form) error { - fmt.Printf("PrepareForm > State: %d, expected: %d\n", form.State, Initialized) - if form.State != Initialized { return ErrInvalidState } diff --git a/core/form/templates/fields/input.base.tpl b/core/form/templates/fields/input.base.tpl index 853cacd..5efd9e4 100644 --- a/core/form/templates/fields/input.base.tpl +++ b/core/form/templates/fields/input.base.tpl @@ -1,4 +1,3 @@ -{{input.Value}} {%if true %} + + {% for option in field.Children %} + + {% endfor %} +{%if true %}{% endif %} \ No newline at end of file