199 lines
4.3 KiB
Go
199 lines
4.3 KiB
Go
package forms
|
|
|
|
import (
|
|
"fmt"
|
|
"owl-blogs/domain/model"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
type DefaultForm struct{}
|
|
|
|
func (meta *DefaultForm) Form(binSvc model.BinaryStorageInterface) string {
|
|
form := NewForm(meta, nil)
|
|
htmlForm, _ := form.HtmlForm()
|
|
return htmlForm
|
|
}
|
|
|
|
func (meta *DefaultForm) ParseFormData(data model.HttpFormData, binSvc model.BinaryStorageInterface) (model.EntryMetaData, error) {
|
|
form := NewForm(meta, binSvc)
|
|
return form.Parse(data)
|
|
}
|
|
|
|
type Form[T interface{}] struct {
|
|
data T
|
|
binSvc model.BinaryStorageInterface
|
|
}
|
|
|
|
type FormFieldParams struct {
|
|
InputType string
|
|
Widget string
|
|
}
|
|
|
|
type FormField struct {
|
|
Name string
|
|
Value reflect.Value
|
|
Params FormFieldParams
|
|
}
|
|
|
|
func NewForm[T interface{}](data T, binaryService model.BinaryStorageInterface) *Form[T] {
|
|
return &Form[T]{
|
|
data: data,
|
|
binSvc: binaryService,
|
|
}
|
|
}
|
|
|
|
func (s *FormFieldParams) ApplyTag(tagKey string, tagValue string) error {
|
|
switch tagKey {
|
|
case "inputType":
|
|
s.InputType = tagValue
|
|
case "widget":
|
|
s.Widget = tagValue
|
|
default:
|
|
return fmt.Errorf("unknown tag key: %v", tagKey)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *FormField) ToWidget() Widget {
|
|
switch s.Params.Widget {
|
|
case "textarea":
|
|
return &TextareaWidget{*s}
|
|
case "textlist":
|
|
return &TextListWidget{*s}
|
|
case "password":
|
|
return &PasswordWidget{*s}
|
|
case "text":
|
|
return &TextWidget{*s}
|
|
default:
|
|
return &OmitWidget{*s}
|
|
}
|
|
}
|
|
|
|
func (s *FormField) Html() string {
|
|
html := ""
|
|
html += fmt.Sprintf("<label for=\"%v\">%v</label>\n", s.Name, s.Name)
|
|
if s.Params.InputType == "file" {
|
|
html += fmt.Sprintf("<input type=\"%v\" name=\"%v\" id=\"%v\" value=\"%v\" />\n", s.Params.InputType, s.Name, s.Name, s.Value)
|
|
} else {
|
|
html += s.ToWidget().Html()
|
|
html += "\n"
|
|
}
|
|
return html
|
|
}
|
|
|
|
func FieldToFormField(field reflect.StructField, value reflect.Value) (FormField, error) {
|
|
formField := FormField{
|
|
Name: field.Name,
|
|
Value: value,
|
|
Params: FormFieldParams{},
|
|
}
|
|
tag := field.Tag.Get("owl")
|
|
for _, param := range strings.Split(tag, " ") {
|
|
parts := strings.Split(param, "=")
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
err := formField.Params.ApplyTag(parts[0], parts[1])
|
|
if err != nil {
|
|
return FormField{}, err
|
|
}
|
|
}
|
|
return formField, nil
|
|
}
|
|
|
|
func StructToFormFields(data interface{}) ([]FormField, error) {
|
|
dataValue := reflect.Indirect(reflect.ValueOf(data))
|
|
dataType := reflect.TypeOf(data).Elem()
|
|
numFields := dataType.NumField()
|
|
fields := []FormField{}
|
|
for i := 0; i < numFields; i++ {
|
|
field, err := FieldToFormField(
|
|
dataType.Field(i),
|
|
dataValue.FieldByIndex([]int{i}),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fields = append(fields, field)
|
|
}
|
|
return fields, nil
|
|
}
|
|
|
|
func (s *Form[T]) HtmlForm() (string, error) {
|
|
fields, err := StructToFormFields(s.data)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
html := ""
|
|
for _, field := range fields {
|
|
html += field.Html()
|
|
}
|
|
|
|
return html, nil
|
|
}
|
|
|
|
func (s *Form[T]) Parse(ctx model.HttpFormData) (T, error) {
|
|
var empty T
|
|
|
|
if ctx == nil {
|
|
return empty, fmt.Errorf("nil context")
|
|
}
|
|
dataVal := reflect.ValueOf(s.data)
|
|
if dataVal.Kind() != reflect.Ptr {
|
|
return empty, fmt.Errorf("meta data is not a pointer")
|
|
}
|
|
fields, err := StructToFormFields(s.data)
|
|
if err != nil {
|
|
return empty, err
|
|
}
|
|
for _, field := range fields {
|
|
fieldName := field.Name
|
|
|
|
if field.Params.InputType == "file" {
|
|
file, err := ctx.FormFile(fieldName)
|
|
if err != nil {
|
|
// If field already has a value, we can ignore the error
|
|
if field.Value != reflect.Zero(field.Value.Type()) {
|
|
metaField := dataVal.Elem().FieldByName(fieldName)
|
|
if metaField.IsValid() {
|
|
metaField.SetString(field.Value.String())
|
|
}
|
|
continue
|
|
}
|
|
return empty, err
|
|
}
|
|
fileData, err := file.Open()
|
|
if err != nil {
|
|
return empty, err
|
|
}
|
|
defer fileData.Close()
|
|
fileBytes := make([]byte, file.Size)
|
|
_, err = fileData.Read(fileBytes)
|
|
if err != nil {
|
|
return empty, err
|
|
}
|
|
|
|
binaryFile, err := s.binSvc.Create(file.Filename, fileBytes)
|
|
if err != nil {
|
|
return empty, err
|
|
}
|
|
|
|
metaField := dataVal.Elem().FieldByName(fieldName)
|
|
if metaField.IsValid() {
|
|
metaField.SetString(binaryFile.Id)
|
|
}
|
|
} else {
|
|
formValue := ctx.FormValue(fieldName)
|
|
metaField := dataVal.Elem().FieldByName(fieldName)
|
|
if metaField.IsValid() {
|
|
field.ToWidget().ParseValue(formValue, metaField)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return s.data, nil
|
|
}
|