Skip to content
forked from donseba/go-form

Render forms in go based on struct layout

License

Notifications You must be signed in to change notification settings

LarsBrand/go-form

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

go-form

Render forms in go based on struct layout, and tags.

Please note that this package is a pre-alfa release and mainly only a visualization of my initial thought. I am also trying to keep the footprint as low as possible without using third party packages.

it can convert to following data:

data := struct {
    Form   ExampleForm
    Errors []error
}{
    Form: ExampleForm{
        Name:  "John Wick",
        Email: "john.wick@gmail.com",
        Address: &AddressBlock{
            Street1: "121 Mill Neck",
            City:    "Long Island",
            State:   "NY",
            Zip:     "11765",
        },
        CheckBox:  true,
        CheckBox2: false,
    },
    Errors: []error{
        fieldError{
            Field: "Email",
            Issue: "is already taken",
        },
        fieldError{
            Field: "Address.Street1",
            Issue: "is required",
        },
    },
}

into:

Call form_render inside the template and pass it the form struct and the errors :

<form class="space-y-6" action="#" method="POST">
    {{ form_render .Form .Errors }}
    <div class="flex items-center justify-between">
        <button type="submit" class="flex w-full justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Signup</button>
    </div>
</form>

There is currently only one template file for all the currently supported templates. I'm thinking of making a mini template for each type.

<div>
    <label {{with .Field.Id}}for="{{.}}"{{end}} class="block text-sm font-medium text-gray-700">{{.Field.Label}}{{ if eq .Field.Required true }}*{{end}}</label>
    <div class="mt-1">
        {{ if eq .Field.Type "dropdown" }}
        <select {{with .Field.Id}}id="{{.}}"{{end}} name="{{.Name}}" class="bg-white block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm">
            {{ range $k, $option := .Field.Values }}
            <option value="{{$option.Id}}">{{$option.Name}}</option>
            {{ end }}
        </select>
        {{ else if eq .Field.Type "dropdownmapped" }}
        <select {{with .Field.Id}}id="{{.}}"{{end}} name="{{.Field.Name}}" class="text-gray-700 dark:text-gray-200 dark:bg-gray-700 bg-white block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm">
            {{ $value := .Field.Value }}
            {{ range $k, $option := .Field.Values }}
            <option value="{{$option.Value}}" {{ if eq $value.String $option.Value }}selected{{ end }} {{ if eq $option.Disabled true }}disabled{{ end }}>{{$option.Name}}</option>
            {{ end }}
        </select>
        {{ else if eq .Type "checkbox" }}
        <input {{with .Field.Id}}id="{{.}}"{{end}} name="{{.Field.Name}}" type="checkbox" {{ if eq .Field.Required true }}required{{end}} {{ if eq .Field.Value true }}checked{{end}} class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
        {{ else }}
        <input {{with .Field.Id}}id="{{.}}"{{end}} name="{{.Field.Name}}" placeholder="{{.Field.Placeholder}}" {{with .Field.Value}}value="{{.}}"{{end}} {{ if eq .Field.Required true }}required{{end}} class="block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm">
        {{ end }}
        
        {{range errors}}
        <span class="text-sm text-red-600">{{.}}</span>
        {{end}}
    </div>
</div>

Groups (nested structs) have their own template

<div class="mb-4 bg-gray-50 p-2 rounded-md">
  <label class="block text-grey-darker text-sm font-bold mb-2">{{.Name }}</label>
  {{ fields }}
</div>

please note that the errors need to implement with the FieldError interface. otherwise they will be silently skipped. You can achieve that doing so :

type fieldError struct {
    Field string
    Issue string
}

func (fe fieldError) Error() string {
    return fmt.Sprintf("%s:%s", fe.Field, fe.Issue)
}

func (fe fieldError) FieldError() (field, err string) {
    return fe.Field, fe.Issue
}

Support for sorted maps is also included.

type CountryListOption struct {
	Selected     string
	SortedValues []form.SortedMap
}

type SortedMap struct {
	SKey   string
	SValue string
}

func (s SortedMap) Key() string   { return s.SKey }
func (s SortedMap) Value() string { return s.SValue }

func (t *CountryListOption) SortedMapper() []form.SortedMap {
	return t.SortedValues
}

func (t *CountryListOption) String() string {
	return t.Selected
}

Now in the struct that contains the form you can add the following:

UserForm struct {
    Country *CountryListOption
}

however the downside is that you create a direct dependancy to the form.SortedMap interface.

supported tags

  • label
  • placeholder
  • name
  • required

TODO

  • better tag handling. maybe group all possible options into one tag or keep it as is.
  • add validation to process the form once posted.

About

Render forms in go based on struct layout

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Go 100.0%