Repository files navigation

Form

Easily create HTML forms with Go structs.

go report cardBuild StatusMIT licenseGoDoc

Overview

The form package makes it easy to take a Go struct and turn it into an HTML form using whatever HTML format you want. Below is an example, along with the output, but first let's just look at an example of what I mean.

Let's say you have a Go struct that looks like this:

typecustomerstruct {
NamestringEmailstringAddress*address
}
typeaddressstruct {
Street1stringStreet2stringCitystringStatestringZipstring`form:"label=Postal Code"`
}

Now you want to generate an HTML form for it, but that is somewhat annoying if you want to persist user-entered values if there is an error, or if you want to support loading URL query params and auto-filling the form for the user. With this package you can very easily do both of those things simply by defining what the HTML for an input field should be:

<divclass="mb-4"><labelclass="block text-grey-darker text-sm font-bold mb-2" {{with.ID}}for="{{.}}"{{end}}>
{{.Label}}
</label><inputclass="shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker leading-tight {{if errors}}border-red{{end}}" {{with.ID}}id="{{.}}"{{end}}type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with.Value}}value="{{.}}"{{end}}>
{{range errors}}
<pclass="text-red pt-2 text-xs italic">{{.}}</p>
{{end}}
</div>

This particular example is using Tailwind CSS to style the values, along with the errors template function which is provided via this form package when it creates the inputs for each field.

Now we can render this entire struct as a form by simply using the inputs_for template function which is provided by the form.Builder's FuncMap method:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_for .Customer}}
<!-- ... add buttons here --></form>

And with it we will generate an HTML form like the one below:

Example output from the forms package

Data set in the .Customer variable in our template will also be used when rendering the form, which is why you see Michael Scott and michael@dunder.com in the screenshot - these were set in the .Customer and were thus used to set the input's value.

Error rendering is also possible, but requires the usage of the inputs_and_errors_for template function, and you need to pass in errors that implement the fieldError interface (shown below, but NOT exported):

typefieldErrorinterface {
FieldError() (field, errstring)
}

For instance, in examples/errors/errors.go we pass data similar the following into our template when executing it:

data:=struct {
FormcustomerErrors []error
}{
Form: customer{
Name: "Michael Scott",
Email: "michael@dunder.com",
Address: nil,
},
Errors: []error{
fieldError{
Field: "Email",
Issue: "is already taken",
},
fieldError{
Field: "Address.Street1",
Issue: "is required",
},
...
},
}
tpl.Execute(w, data)

And then in the template we call the inputs_and_errors_for function:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_and_errors_for .Form .Errors}}
<!-- ... buttons here --></form>

And we get an output like this:

Example output from the forms package with errors

Installation

To install this package, simply go get it:

go get github.com/joncalhoun/form

Complete Examples

This entire example can be found in the examples/readme directory. Additional examples can also be found in the examples/ directory and are a great way to see how this package could be used.

Source Code

package main
import (
"html/template""net/http""github.com/joncalhoun/form"
)
varinputTpl=`<label {{with .ID}}for="{{.}}"{{end}}>	{{.Label}}</label><input {{with .ID}}id="{{.}}"{{end}} type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with .Value}}value="{{.}}"{{end}}>{{with .Footer}} <p>{{.}}</p>{{end}}`typeAddressstruct {
Street1string`form:"label=Street;placeholder=123 Sample St"`Street2string`form:"label=Street (cont);placeholder=Apt 123"`CitystringStatestring`form:"footer=Or your Province"`Zipstring`form:"label=Postal Code"`Countrystring
}
funcmain() {
tpl:=template.Must(template.New("").Parse(inputTpl))
fb:= form.Builder{
InputTemplate: tpl,
}
pageTpl:=template.Must(template.New("").Funcs(fb.FuncMap()).Parse(` <html> <body> <form> {{inputs_for .}} </form> </body> </html>`))
http.HandleFunc("/", func(w http.ResponseWriter, r*http.Request) {
w.Header().Set("Content-Type", "text/html")
pageTpl.Execute(w, Address{
Street1: "123 Known St",
Country: "United States",
})
})
http.ListenAndServe(":3000", nil)
}

Relevant HTML trimmed for brevity

<form><label>
Street
</label><inputtype="text" name="Street1" placeholder="123 Sample St" value="123 Known St"><label>
Street (cont)
</label><inputtype="text" name="Street2" placeholder="Apt 123" ><label>
City
</label><inputtype="text" name="City" placeholder="City" ><label>
State
</label><inputtype="text" name="State" placeholder="State" ><p>Or your Province</p><label>
Postal Code
</label><inputtype="text" name="Zip" placeholder="Postal Code" ><label>
Country
</label><inputtype="text" name="Country" placeholder="Country" value="United States"></form>

How it works

The form.Builder type provides a single method - Inputs - which will parse the provided struct to determine which fields it contains, any values set for each field, and any struct tags provided for the form package. Once that information is parsed it will execute the provided InputTemplate field in the builder for each field in the struct, including nested fields.

Most of the time you will probably want to just make this helper available to your html templates via the template.Funcs() functions and the template.FuncMap type, as I did in the example above.

I don't recommend tagging domain types

It is also worth mentioning that I don't really recommend adding form struct tags to your domain types, and I typically create types specifically used to generate forms. Eg:

// This is my domain typetypeUserstruct {
IDintNamestringEmailstringPasswordHashstring
}
// Somewhere else I'll create my html-specific type:typesignupFormstruct {
Namestring`form:"..."`Emailstring`form:"type=email"`Passwordstring`form:"type=password"`Confirmationstring`form:"type=password;label=Password Confirmation"`
}

Parsing submitted forms

If you also need to parse forms created by this package, I recommend using the gorilla/schema package. This package should generate input names compliant with the gorilla/schema package by default, so as long as you don't change the names it should be pretty trivial to decode.

There is an example of this in the examples/tailwind directory.

Rendering errors

If you want to render errors, see the examples/errors/errors.go example and most notably check out the inputs_and_errors_for function provided to templates via the Builder.FuncMap() function.

TODO: Add some better examples here, but the provided code sample is a complete example.

This may have bugs

This is a very early iteration of the package, and while it appears to be working for my needs chances are it doesn't cover every use case. If you do find one that isn't covered, try to provide a PR with a breaking test.

Notes

This section is mostly for myself to jot down notes, but feel free to read away.

Potential features

Parsing forms

Long term this could also support parsing forms, but gorilla/schema does a great job of that already so I don't see any reason to at this time. It would likely be easier to just make the default input names line up with what gorilla/schema expects and provide examples for how to use the two together.

Checkboxes and other data types

Maybe allow for various templates for different types, but for now this is possible to do in the HTML templates so it isn't completely missing.

Headers on nested structs

Let's say we have this type:

typeNestedstruct {
NamestringEmailstringAddressAddress
}
typeAddressstruct {
Street1stringStreet2string// ...
}

It might make sense to make an optional way to add headers in the form when the nested Address portion is rendered, so the form looks like:

Name: [ ]
Email: [ ]
<Address Header Here>
Street1: [ ]
Street2: [ ]
...

This should be pretty easy to do with struct tags on the Address Address line.

About

Easily create HTML forms with Go structs.

Resources

Stars

401 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Form

Easily create HTML forms with Go structs.

go report cardBuild StatusMIT licenseGoDoc

Overview

The form package makes it easy to take a Go struct and turn it into an HTML form using whatever HTML format you want. Below is an example, along with the output, but first let's just look at an example of what I mean.

Let's say you have a Go struct that looks like this:

typecustomerstruct {
NamestringEmailstringAddress*address
}
typeaddressstruct {
Street1stringStreet2stringCitystringStatestringZipstring`form:"label=Postal Code"`
}

Now you want to generate an HTML form for it, but that is somewhat annoying if you want to persist user-entered values if there is an error, or if you want to support loading URL query params and auto-filling the form for the user. With this package you can very easily do both of those things simply by defining what the HTML for an input field should be:

<divclass="mb-4"><labelclass="block text-grey-darker text-sm font-bold mb-2" {{with.ID}}for="{{.}}"{{end}}>
{{.Label}}
</label><inputclass="shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker leading-tight {{if errors}}border-red{{end}}" {{with.ID}}id="{{.}}"{{end}}type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with.Value}}value="{{.}}"{{end}}>
{{range errors}}
<pclass="text-red pt-2 text-xs italic">{{.}}</p>
{{end}}
</div>

This particular example is using Tailwind CSS to style the values, along with the errors template function which is provided via this form package when it creates the inputs for each field.

Now we can render this entire struct as a form by simply using the inputs_for template function which is provided by the form.Builder's FuncMap method:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_for .Customer}}
<!-- ... add buttons here --></form>

And with it we will generate an HTML form like the one below:

Example output from the forms package

Data set in the .Customer variable in our template will also be used when rendering the form, which is why you see Michael Scott and michael@dunder.com in the screenshot - these were set in the .Customer and were thus used to set the input's value.

Error rendering is also possible, but requires the usage of the inputs_and_errors_for template function, and you need to pass in errors that implement the fieldError interface (shown below, but NOT exported):

typefieldErrorinterface {
FieldError() (field, errstring)
}

For instance, in examples/errors/errors.go we pass data similar the following into our template when executing it:

data:=struct {
FormcustomerErrors []error
}{
Form: customer{
Name: "Michael Scott",
Email: "michael@dunder.com",
Address: nil,
},
Errors: []error{
fieldError{
Field: "Email",
Issue: "is already taken",
},
fieldError{
Field: "Address.Street1",
Issue: "is required",
},
...
},
}
tpl.Execute(w, data)

And then in the template we call the inputs_and_errors_for function:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_and_errors_for .Form .Errors}}
<!-- ... buttons here --></form>

And we get an output like this:

Example output from the forms package with errors

Installation

To install this package, simply go get it:

go get github.com/joncalhoun/form

Complete Examples

This entire example can be found in the examples/readme directory. Additional examples can also be found in the examples/ directory and are a great way to see how this package could be used.

Source Code

package main
import (
"html/template""net/http""github.com/joncalhoun/form"
)
varinputTpl=`<label {{with .ID}}for="{{.}}"{{end}}>	{{.Label}}</label><input {{with .ID}}id="{{.}}"{{end}} type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with .Value}}value="{{.}}"{{end}}>{{with .Footer}} <p>{{.}}</p>{{end}}`typeAddressstruct {
Street1string`form:"label=Street;placeholder=123 Sample St"`Street2string`form:"label=Street (cont);placeholder=Apt 123"`CitystringStatestring`form:"footer=Or your Province"`Zipstring`form:"label=Postal Code"`Countrystring
}
funcmain() {
tpl:=template.Must(template.New("").Parse(inputTpl))
fb:= form.Builder{
InputTemplate: tpl,
}
pageTpl:=template.Must(template.New("").Funcs(fb.FuncMap()).Parse(` <html> <body> <form> {{inputs_for .}} </form> </body> </html>`))
http.HandleFunc("/", func(w http.ResponseWriter, r*http.Request) {
w.Header().Set("Content-Type", "text/html")
pageTpl.Execute(w, Address{
Street1: "123 Known St",
Country: "United States",
})
})
http.ListenAndServe(":3000", nil)
}

Relevant HTML trimmed for brevity

<form><label>
Street
</label><inputtype="text" name="Street1" placeholder="123 Sample St" value="123 Known St"><label>
Street (cont)
</label><inputtype="text" name="Street2" placeholder="Apt 123" ><label>
City
</label><inputtype="text" name="City" placeholder="City" ><label>
State
</label><inputtype="text" name="State" placeholder="State" ><p>Or your Province</p><label>
Postal Code
</label><inputtype="text" name="Zip" placeholder="Postal Code" ><label>
Country
</label><inputtype="text" name="Country" placeholder="Country" value="United States"></form>

How it works

The form.Builder type provides a single method - Inputs - which will parse the provided struct to determine which fields it contains, any values set for each field, and any struct tags provided for the form package. Once that information is parsed it will execute the provided InputTemplate field in the builder for each field in the struct, including nested fields.

Most of the time you will probably want to just make this helper available to your html templates via the template.Funcs() functions and the template.FuncMap type, as I did in the example above.

I don't recommend tagging domain types

It is also worth mentioning that I don't really recommend adding form struct tags to your domain types, and I typically create types specifically used to generate forms. Eg:

// This is my domain typetypeUserstruct {
IDintNamestringEmailstringPasswordHashstring
}
// Somewhere else I'll create my html-specific type:typesignupFormstruct {
Namestring`form:"..."`Emailstring`form:"type=email"`Passwordstring`form:"type=password"`Confirmationstring`form:"type=password;label=Password Confirmation"`
}

Parsing submitted forms

If you also need to parse forms created by this package, I recommend using the gorilla/schema package. This package should generate input names compliant with the gorilla/schema package by default, so as long as you don't change the names it should be pretty trivial to decode.

There is an example of this in the examples/tailwind directory.

Rendering errors

If you want to render errors, see the examples/errors/errors.go example and most notably check out the inputs_and_errors_for function provided to templates via the Builder.FuncMap() function.

TODO: Add some better examples here, but the provided code sample is a complete example.

This may have bugs

This is a very early iteration of the package, and while it appears to be working for my needs chances are it doesn't cover every use case. If you do find one that isn't covered, try to provide a PR with a breaking test.

Notes

This section is mostly for myself to jot down notes, but feel free to read away.

Potential features

Parsing forms

Long term this could also support parsing forms, but gorilla/schema does a great job of that already so I don't see any reason to at this time. It would likely be easier to just make the default input names line up with what gorilla/schema expects and provide examples for how to use the two together.

Checkboxes and other data types

Maybe allow for various templates for different types, but for now this is possible to do in the HTML templates so it isn't completely missing.

Headers on nested structs

Let's say we have this type:

typeNestedstruct {
NamestringEmailstringAddressAddress
}
typeAddressstruct {
Street1stringStreet2string// ...
}

It might make sense to make an optional way to add headers in the form when the nested Address portion is rendered, so the form looks like:

Name: [ ]
Email: [ ]
<Address Header Here>
Street1: [ ]
Street2: [ ]
...

This should be pretty easy to do with struct tags on the Address Address line.

About

Easily create HTML forms with Go structs.

Resources

Stars

401 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Form

Easily create HTML forms with Go structs.

go report cardBuild StatusMIT licenseGoDoc

Overview

The form package makes it easy to take a Go struct and turn it into an HTML form using whatever HTML format you want. Below is an example, along with the output, but first let's just look at an example of what I mean.

Let's say you have a Go struct that looks like this:

typecustomerstruct {
NamestringEmailstringAddress*address
}
typeaddressstruct {
Street1stringStreet2stringCitystringStatestringZipstring`form:"label=Postal Code"`
}

Now you want to generate an HTML form for it, but that is somewhat annoying if you want to persist user-entered values if there is an error, or if you want to support loading URL query params and auto-filling the form for the user. With this package you can very easily do both of those things simply by defining what the HTML for an input field should be:

<divclass="mb-4"><labelclass="block text-grey-darker text-sm font-bold mb-2" {{with.ID}}for="{{.}}"{{end}}>
{{.Label}}
</label><inputclass="shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker leading-tight {{if errors}}border-red{{end}}" {{with.ID}}id="{{.}}"{{end}}type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with.Value}}value="{{.}}"{{end}}>
{{range errors}}
<pclass="text-red pt-2 text-xs italic">{{.}}</p>
{{end}}
</div>

This particular example is using Tailwind CSS to style the values, along with the errors template function which is provided via this form package when it creates the inputs for each field.

Now we can render this entire struct as a form by simply using the inputs_for template function which is provided by the form.Builder's FuncMap method:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_for .Customer}}
<!-- ... add buttons here --></form>

And with it we will generate an HTML form like the one below:

Example output from the forms package

Data set in the .Customer variable in our template will also be used when rendering the form, which is why you see Michael Scott and michael@dunder.com in the screenshot - these were set in the .Customer and were thus used to set the input's value.

Error rendering is also possible, but requires the usage of the inputs_and_errors_for template function, and you need to pass in errors that implement the fieldError interface (shown below, but NOT exported):

typefieldErrorinterface {
FieldError() (field, errstring)
}

For instance, in examples/errors/errors.go we pass data similar the following into our template when executing it:

data:=struct {
FormcustomerErrors []error
}{
Form: customer{
Name: "Michael Scott",
Email: "michael@dunder.com",
Address: nil,
},
Errors: []error{
fieldError{
Field: "Email",
Issue: "is already taken",
},
fieldError{
Field: "Address.Street1",
Issue: "is required",
},
...
},
}
tpl.Execute(w, data)

And then in the template we call the inputs_and_errors_for function:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_and_errors_for .Form .Errors}}
<!-- ... buttons here --></form>

And we get an output like this:

Example output from the forms package with errors

Installation

To install this package, simply go get it:

go get github.com/joncalhoun/form

Complete Examples

This entire example can be found in the examples/readme directory. Additional examples can also be found in the examples/ directory and are a great way to see how this package could be used.

Source Code

package main
import (
"html/template""net/http""github.com/joncalhoun/form"
)
varinputTpl=`<label {{with .ID}}for="{{.}}"{{end}}>	{{.Label}}</label><input {{with .ID}}id="{{.}}"{{end}} type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with .Value}}value="{{.}}"{{end}}>{{with .Footer}} <p>{{.}}</p>{{end}}`typeAddressstruct {
Street1string`form:"label=Street;placeholder=123 Sample St"`Street2string`form:"label=Street (cont);placeholder=Apt 123"`CitystringStatestring`form:"footer=Or your Province"`Zipstring`form:"label=Postal Code"`Countrystring
}
funcmain() {
tpl:=template.Must(template.New("").Parse(inputTpl))
fb:= form.Builder{
InputTemplate: tpl,
}
pageTpl:=template.Must(template.New("").Funcs(fb.FuncMap()).Parse(` <html> <body> <form> {{inputs_for .}} </form> </body> </html>`))
http.HandleFunc("/", func(w http.ResponseWriter, r*http.Request) {
w.Header().Set("Content-Type", "text/html")
pageTpl.Execute(w, Address{
Street1: "123 Known St",
Country: "United States",
})
})
http.ListenAndServe(":3000", nil)
}

Relevant HTML trimmed for brevity

<form><label>
Street
</label><inputtype="text" name="Street1" placeholder="123 Sample St" value="123 Known St"><label>
Street (cont)
</label><inputtype="text" name="Street2" placeholder="Apt 123" ><label>
City
</label><inputtype="text" name="City" placeholder="City" ><label>
State
</label><inputtype="text" name="State" placeholder="State" ><p>Or your Province</p><label>
Postal Code
</label><inputtype="text" name="Zip" placeholder="Postal Code" ><label>
Country
</label><inputtype="text" name="Country" placeholder="Country" value="United States"></form>

How it works

The form.Builder type provides a single method - Inputs - which will parse the provided struct to determine which fields it contains, any values set for each field, and any struct tags provided for the form package. Once that information is parsed it will execute the provided InputTemplate field in the builder for each field in the struct, including nested fields.

Most of the time you will probably want to just make this helper available to your html templates via the template.Funcs() functions and the template.FuncMap type, as I did in the example above.

I don't recommend tagging domain types

It is also worth mentioning that I don't really recommend adding form struct tags to your domain types, and I typically create types specifically used to generate forms. Eg:

// This is my domain typetypeUserstruct {
IDintNamestringEmailstringPasswordHashstring
}
// Somewhere else I'll create my html-specific type:typesignupFormstruct {
Namestring`form:"..."`Emailstring`form:"type=email"`Passwordstring`form:"type=password"`Confirmationstring`form:"type=password;label=Password Confirmation"`
}

Parsing submitted forms

If you also need to parse forms created by this package, I recommend using the gorilla/schema package. This package should generate input names compliant with the gorilla/schema package by default, so as long as you don't change the names it should be pretty trivial to decode.

There is an example of this in the examples/tailwind directory.

Rendering errors

If you want to render errors, see the examples/errors/errors.go example and most notably check out the inputs_and_errors_for function provided to templates via the Builder.FuncMap() function.

TODO: Add some better examples here, but the provided code sample is a complete example.

This may have bugs

This is a very early iteration of the package, and while it appears to be working for my needs chances are it doesn't cover every use case. If you do find one that isn't covered, try to provide a PR with a breaking test.

Notes

This section is mostly for myself to jot down notes, but feel free to read away.

Potential features

Parsing forms

Long term this could also support parsing forms, but gorilla/schema does a great job of that already so I don't see any reason to at this time. It would likely be easier to just make the default input names line up with what gorilla/schema expects and provide examples for how to use the two together.

Checkboxes and other data types

Maybe allow for various templates for different types, but for now this is possible to do in the HTML templates so it isn't completely missing.

Headers on nested structs

Let's say we have this type:

typeNestedstruct {
NamestringEmailstringAddressAddress
}
typeAddressstruct {
Street1stringStreet2string// ...
}

It might make sense to make an optional way to add headers in the form when the nested Address portion is rendered, so the form looks like:

Name: [ ]
Email: [ ]
<Address Header Here>
Street1: [ ]
Street2: [ ]
...

This should be pretty easy to do with struct tags on the Address Address line.

About

Easily create HTML forms with Go structs.

Resources

Stars

401 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Form

Easily create HTML forms with Go structs.

go report cardBuild StatusMIT licenseGoDoc

Overview

The form package makes it easy to take a Go struct and turn it into an HTML form using whatever HTML format you want. Below is an example, along with the output, but first let's just look at an example of what I mean.

Let's say you have a Go struct that looks like this:

typecustomerstruct {
NamestringEmailstringAddress*address
}
typeaddressstruct {
Street1stringStreet2stringCitystringStatestringZipstring`form:"label=Postal Code"`
}

Now you want to generate an HTML form for it, but that is somewhat annoying if you want to persist user-entered values if there is an error, or if you want to support loading URL query params and auto-filling the form for the user. With this package you can very easily do both of those things simply by defining what the HTML for an input field should be:

<divclass="mb-4"><labelclass="block text-grey-darker text-sm font-bold mb-2" {{with.ID}}for="{{.}}"{{end}}>
{{.Label}}
</label><inputclass="shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker leading-tight {{if errors}}border-red{{end}}" {{with.ID}}id="{{.}}"{{end}}type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with.Value}}value="{{.}}"{{end}}>
{{range errors}}
<pclass="text-red pt-2 text-xs italic">{{.}}</p>
{{end}}
</div>

This particular example is using Tailwind CSS to style the values, along with the errors template function which is provided via this form package when it creates the inputs for each field.

Now we can render this entire struct as a form by simply using the inputs_for template function which is provided by the form.Builder's FuncMap method:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_for .Customer}}
<!-- ... add buttons here --></form>

And with it we will generate an HTML form like the one below:

Example output from the forms package

Data set in the .Customer variable in our template will also be used when rendering the form, which is why you see Michael Scott and michael@dunder.com in the screenshot - these were set in the .Customer and were thus used to set the input's value.

Error rendering is also possible, but requires the usage of the inputs_and_errors_for template function, and you need to pass in errors that implement the fieldError interface (shown below, but NOT exported):

typefieldErrorinterface {
FieldError() (field, errstring)
}

For instance, in examples/errors/errors.go we pass data similar the following into our template when executing it:

data:=struct {
FormcustomerErrors []error
}{
Form: customer{
Name: "Michael Scott",
Email: "michael@dunder.com",
Address: nil,
},
Errors: []error{
fieldError{
Field: "Email",
Issue: "is already taken",
},
fieldError{
Field: "Address.Street1",
Issue: "is required",
},
...
},
}
tpl.Execute(w, data)

And then in the template we call the inputs_and_errors_for function:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_and_errors_for .Form .Errors}}
<!-- ... buttons here --></form>

And we get an output like this:

Example output from the forms package with errors

Installation

To install this package, simply go get it:

go get github.com/joncalhoun/form

Complete Examples

This entire example can be found in the examples/readme directory. Additional examples can also be found in the examples/ directory and are a great way to see how this package could be used.

Source Code

package main
import (
"html/template""net/http""github.com/joncalhoun/form"
)
varinputTpl=`<label {{with .ID}}for="{{.}}"{{end}}>	{{.Label}}</label><input {{with .ID}}id="{{.}}"{{end}} type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with .Value}}value="{{.}}"{{end}}>{{with .Footer}} <p>{{.}}</p>{{end}}`typeAddressstruct {
Street1string`form:"label=Street;placeholder=123 Sample St"`Street2string`form:"label=Street (cont);placeholder=Apt 123"`CitystringStatestring`form:"footer=Or your Province"`Zipstring`form:"label=Postal Code"`Countrystring
}
funcmain() {
tpl:=template.Must(template.New("").Parse(inputTpl))
fb:= form.Builder{
InputTemplate: tpl,
}
pageTpl:=template.Must(template.New("").Funcs(fb.FuncMap()).Parse(` <html> <body> <form> {{inputs_for .}} </form> </body> </html>`))
http.HandleFunc("/", func(w http.ResponseWriter, r*http.Request) {
w.Header().Set("Content-Type", "text/html")
pageTpl.Execute(w, Address{
Street1: "123 Known St",
Country: "United States",
})
})
http.ListenAndServe(":3000", nil)
}

Relevant HTML trimmed for brevity

<form><label>
Street
</label><inputtype="text" name="Street1" placeholder="123 Sample St" value="123 Known St"><label>
Street (cont)
</label><inputtype="text" name="Street2" placeholder="Apt 123" ><label>
City
</label><inputtype="text" name="City" placeholder="City" ><label>
State
</label><inputtype="text" name="State" placeholder="State" ><p>Or your Province</p><label>
Postal Code
</label><inputtype="text" name="Zip" placeholder="Postal Code" ><label>
Country
</label><inputtype="text" name="Country" placeholder="Country" value="United States"></form>

How it works

The form.Builder type provides a single method - Inputs - which will parse the provided struct to determine which fields it contains, any values set for each field, and any struct tags provided for the form package. Once that information is parsed it will execute the provided InputTemplate field in the builder for each field in the struct, including nested fields.

Most of the time you will probably want to just make this helper available to your html templates via the template.Funcs() functions and the template.FuncMap type, as I did in the example above.

I don't recommend tagging domain types

It is also worth mentioning that I don't really recommend adding form struct tags to your domain types, and I typically create types specifically used to generate forms. Eg:

// This is my domain typetypeUserstruct {
IDintNamestringEmailstringPasswordHashstring
}
// Somewhere else I'll create my html-specific type:typesignupFormstruct {
Namestring`form:"..."`Emailstring`form:"type=email"`Passwordstring`form:"type=password"`Confirmationstring`form:"type=password;label=Password Confirmation"`
}

Parsing submitted forms

If you also need to parse forms created by this package, I recommend using the gorilla/schema package. This package should generate input names compliant with the gorilla/schema package by default, so as long as you don't change the names it should be pretty trivial to decode.

There is an example of this in the examples/tailwind directory.

Rendering errors

If you want to render errors, see the examples/errors/errors.go example and most notably check out the inputs_and_errors_for function provided to templates via the Builder.FuncMap() function.

TODO: Add some better examples here, but the provided code sample is a complete example.

This may have bugs

This is a very early iteration of the package, and while it appears to be working for my needs chances are it doesn't cover every use case. If you do find one that isn't covered, try to provide a PR with a breaking test.

Notes

This section is mostly for myself to jot down notes, but feel free to read away.

Potential features

Parsing forms

Long term this could also support parsing forms, but gorilla/schema does a great job of that already so I don't see any reason to at this time. It would likely be easier to just make the default input names line up with what gorilla/schema expects and provide examples for how to use the two together.

Checkboxes and other data types

Maybe allow for various templates for different types, but for now this is possible to do in the HTML templates so it isn't completely missing.

Headers on nested structs

Let's say we have this type:

typeNestedstruct {
NamestringEmailstringAddressAddress
}
typeAddressstruct {
Street1stringStreet2string// ...
}

It might make sense to make an optional way to add headers in the form when the nested Address portion is rendered, so the form looks like:

Name: [ ]
Email: [ ]
<Address Header Here>
Street1: [ ]
Street2: [ ]
...

This should be pretty easy to do with struct tags on the Address Address line.

About

Easily create HTML forms with Go structs.

Resources

Stars

401 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Form

Easily create HTML forms with Go structs.

go report cardBuild StatusMIT licenseGoDoc

Overview

The form package makes it easy to take a Go struct and turn it into an HTML form using whatever HTML format you want. Below is an example, along with the output, but first let's just look at an example of what I mean.

Let's say you have a Go struct that looks like this:

typecustomerstruct {
NamestringEmailstringAddress*address
}
typeaddressstruct {
Street1stringStreet2stringCitystringStatestringZipstring`form:"label=Postal Code"`
}

Now you want to generate an HTML form for it, but that is somewhat annoying if you want to persist user-entered values if there is an error, or if you want to support loading URL query params and auto-filling the form for the user. With this package you can very easily do both of those things simply by defining what the HTML for an input field should be:

<divclass="mb-4"><labelclass="block text-grey-darker text-sm font-bold mb-2" {{with.ID}}for="{{.}}"{{end}}>
{{.Label}}
</label><inputclass="shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker leading-tight {{if errors}}border-red{{end}}" {{with.ID}}id="{{.}}"{{end}}type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with.Value}}value="{{.}}"{{end}}>
{{range errors}}
<pclass="text-red pt-2 text-xs italic">{{.}}</p>
{{end}}
</div>

This particular example is using Tailwind CSS to style the values, along with the errors template function which is provided via this form package when it creates the inputs for each field.

Now we can render this entire struct as a form by simply using the inputs_for template function which is provided by the form.Builder's FuncMap method:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_for .Customer}}
<!-- ... add buttons here --></form>

And with it we will generate an HTML form like the one below:

Example output from the forms package

Data set in the .Customer variable in our template will also be used when rendering the form, which is why you see Michael Scott and michael@dunder.com in the screenshot - these were set in the .Customer and were thus used to set the input's value.

Error rendering is also possible, but requires the usage of the inputs_and_errors_for template function, and you need to pass in errors that implement the fieldError interface (shown below, but NOT exported):

typefieldErrorinterface {
FieldError() (field, errstring)
}

For instance, in examples/errors/errors.go we pass data similar the following into our template when executing it:

data:=struct {
FormcustomerErrors []error
}{
Form: customer{
Name: "Michael Scott",
Email: "michael@dunder.com",
Address: nil,
},
Errors: []error{
fieldError{
Field: "Email",
Issue: "is already taken",
},
fieldError{
Field: "Address.Street1",
Issue: "is required",
},
...
},
}
tpl.Execute(w, data)

And then in the template we call the inputs_and_errors_for function:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_and_errors_for .Form .Errors}}
<!-- ... buttons here --></form>

And we get an output like this:

Example output from the forms package with errors

Installation

To install this package, simply go get it:

go get github.com/joncalhoun/form

Complete Examples

This entire example can be found in the examples/readme directory. Additional examples can also be found in the examples/ directory and are a great way to see how this package could be used.

Source Code

package main
import (
"html/template""net/http""github.com/joncalhoun/form"
)
varinputTpl=`<label {{with .ID}}for="{{.}}"{{end}}>	{{.Label}}</label><input {{with .ID}}id="{{.}}"{{end}} type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with .Value}}value="{{.}}"{{end}}>{{with .Footer}} <p>{{.}}</p>{{end}}`typeAddressstruct {
Street1string`form:"label=Street;placeholder=123 Sample St"`Street2string`form:"label=Street (cont);placeholder=Apt 123"`CitystringStatestring`form:"footer=Or your Province"`Zipstring`form:"label=Postal Code"`Countrystring
}
funcmain() {
tpl:=template.Must(template.New("").Parse(inputTpl))
fb:= form.Builder{
InputTemplate: tpl,
}
pageTpl:=template.Must(template.New("").Funcs(fb.FuncMap()).Parse(` <html> <body> <form> {{inputs_for .}} </form> </body> </html>`))
http.HandleFunc("/", func(w http.ResponseWriter, r*http.Request) {
w.Header().Set("Content-Type", "text/html")
pageTpl.Execute(w, Address{
Street1: "123 Known St",
Country: "United States",
})
})
http.ListenAndServe(":3000", nil)
}

Relevant HTML trimmed for brevity

<form><label>
Street
</label><inputtype="text" name="Street1" placeholder="123 Sample St" value="123 Known St"><label>
Street (cont)
</label><inputtype="text" name="Street2" placeholder="Apt 123" ><label>
City
</label><inputtype="text" name="City" placeholder="City" ><label>
State
</label><inputtype="text" name="State" placeholder="State" ><p>Or your Province</p><label>
Postal Code
</label><inputtype="text" name="Zip" placeholder="Postal Code" ><label>
Country
</label><inputtype="text" name="Country" placeholder="Country" value="United States"></form>

How it works

The form.Builder type provides a single method - Inputs - which will parse the provided struct to determine which fields it contains, any values set for each field, and any struct tags provided for the form package. Once that information is parsed it will execute the provided InputTemplate field in the builder for each field in the struct, including nested fields.

Most of the time you will probably want to just make this helper available to your html templates via the template.Funcs() functions and the template.FuncMap type, as I did in the example above.

I don't recommend tagging domain types

It is also worth mentioning that I don't really recommend adding form struct tags to your domain types, and I typically create types specifically used to generate forms. Eg:

// This is my domain typetypeUserstruct {
IDintNamestringEmailstringPasswordHashstring
}
// Somewhere else I'll create my html-specific type:typesignupFormstruct {
Namestring`form:"..."`Emailstring`form:"type=email"`Passwordstring`form:"type=password"`Confirmationstring`form:"type=password;label=Password Confirmation"`
}

Parsing submitted forms

If you also need to parse forms created by this package, I recommend using the gorilla/schema package. This package should generate input names compliant with the gorilla/schema package by default, so as long as you don't change the names it should be pretty trivial to decode.

There is an example of this in the examples/tailwind directory.

Rendering errors

If you want to render errors, see the examples/errors/errors.go example and most notably check out the inputs_and_errors_for function provided to templates via the Builder.FuncMap() function.

TODO: Add some better examples here, but the provided code sample is a complete example.

This may have bugs

This is a very early iteration of the package, and while it appears to be working for my needs chances are it doesn't cover every use case. If you do find one that isn't covered, try to provide a PR with a breaking test.

Notes

This section is mostly for myself to jot down notes, but feel free to read away.

Potential features

Parsing forms

Long term this could also support parsing forms, but gorilla/schema does a great job of that already so I don't see any reason to at this time. It would likely be easier to just make the default input names line up with what gorilla/schema expects and provide examples for how to use the two together.

Checkboxes and other data types

Maybe allow for various templates for different types, but for now this is possible to do in the HTML templates so it isn't completely missing.

Headers on nested structs

Let's say we have this type:

typeNestedstruct {
NamestringEmailstringAddressAddress
}
typeAddressstruct {
Street1stringStreet2string// ...
}

It might make sense to make an optional way to add headers in the form when the nested Address portion is rendered, so the form looks like:

Name: [ ]
Email: [ ]
<Address Header Here>
Street1: [ ]
Street2: [ ]
...

This should be pretty easy to do with struct tags on the Address Address line.

About

Easily create HTML forms with Go structs.

Resources

Stars

401 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Form

Easily create HTML forms with Go structs.

go report cardBuild StatusMIT licenseGoDoc

Overview

The form package makes it easy to take a Go struct and turn it into an HTML form using whatever HTML format you want. Below is an example, along with the output, but first let's just look at an example of what I mean.

Let's say you have a Go struct that looks like this:

typecustomerstruct {
NamestringEmailstringAddress*address
}
typeaddressstruct {
Street1stringStreet2stringCitystringStatestringZipstring`form:"label=Postal Code"`
}

Now you want to generate an HTML form for it, but that is somewhat annoying if you want to persist user-entered values if there is an error, or if you want to support loading URL query params and auto-filling the form for the user. With this package you can very easily do both of those things simply by defining what the HTML for an input field should be:

<divclass="mb-4"><labelclass="block text-grey-darker text-sm font-bold mb-2" {{with.ID}}for="{{.}}"{{end}}>
{{.Label}}
</label><inputclass="shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker leading-tight {{if errors}}border-red{{end}}" {{with.ID}}id="{{.}}"{{end}}type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with.Value}}value="{{.}}"{{end}}>
{{range errors}}
<pclass="text-red pt-2 text-xs italic">{{.}}</p>
{{end}}
</div>

This particular example is using Tailwind CSS to style the values, along with the errors template function which is provided via this form package when it creates the inputs for each field.

Now we can render this entire struct as a form by simply using the inputs_for template function which is provided by the form.Builder's FuncMap method:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_for .Customer}}
<!-- ... add buttons here --></form>

And with it we will generate an HTML form like the one below:

Example output from the forms package

Data set in the .Customer variable in our template will also be used when rendering the form, which is why you see Michael Scott and michael@dunder.com in the screenshot - these were set in the .Customer and were thus used to set the input's value.

Error rendering is also possible, but requires the usage of the inputs_and_errors_for template function, and you need to pass in errors that implement the fieldError interface (shown below, but NOT exported):

typefieldErrorinterface {
FieldError() (field, errstring)
}

For instance, in examples/errors/errors.go we pass data similar the following into our template when executing it:

data:=struct {
FormcustomerErrors []error
}{
Form: customer{
Name: "Michael Scott",
Email: "michael@dunder.com",
Address: nil,
},
Errors: []error{
fieldError{
Field: "Email",
Issue: "is already taken",
},
fieldError{
Field: "Address.Street1",
Issue: "is required",
},
...
},
}
tpl.Execute(w, data)

And then in the template we call the inputs_and_errors_for function:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_and_errors_for .Form .Errors}}
<!-- ... buttons here --></form>

And we get an output like this:

Example output from the forms package with errors

Installation

To install this package, simply go get it:

go get github.com/joncalhoun/form

Complete Examples

This entire example can be found in the examples/readme directory. Additional examples can also be found in the examples/ directory and are a great way to see how this package could be used.

Source Code

package main
import (
"html/template""net/http""github.com/joncalhoun/form"
)
varinputTpl=`<label {{with .ID}}for="{{.}}"{{end}}>	{{.Label}}</label><input {{with .ID}}id="{{.}}"{{end}} type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with .Value}}value="{{.}}"{{end}}>{{with .Footer}} <p>{{.}}</p>{{end}}`typeAddressstruct {
Street1string`form:"label=Street;placeholder=123 Sample St"`Street2string`form:"label=Street (cont);placeholder=Apt 123"`CitystringStatestring`form:"footer=Or your Province"`Zipstring`form:"label=Postal Code"`Countrystring
}
funcmain() {
tpl:=template.Must(template.New("").Parse(inputTpl))
fb:= form.Builder{
InputTemplate: tpl,
}
pageTpl:=template.Must(template.New("").Funcs(fb.FuncMap()).Parse(` <html> <body> <form> {{inputs_for .}} </form> </body> </html>`))
http.HandleFunc("/", func(w http.ResponseWriter, r*http.Request) {
w.Header().Set("Content-Type", "text/html")
pageTpl.Execute(w, Address{
Street1: "123 Known St",
Country: "United States",
})
})
http.ListenAndServe(":3000", nil)
}

Relevant HTML trimmed for brevity

<form><label>
Street
</label><inputtype="text" name="Street1" placeholder="123 Sample St" value="123 Known St"><label>
Street (cont)
</label><inputtype="text" name="Street2" placeholder="Apt 123" ><label>
City
</label><inputtype="text" name="City" placeholder="City" ><label>
State
</label><inputtype="text" name="State" placeholder="State" ><p>Or your Province</p><label>
Postal Code
</label><inputtype="text" name="Zip" placeholder="Postal Code" ><label>
Country
</label><inputtype="text" name="Country" placeholder="Country" value="United States"></form>

How it works

The form.Builder type provides a single method - Inputs - which will parse the provided struct to determine which fields it contains, any values set for each field, and any struct tags provided for the form package. Once that information is parsed it will execute the provided InputTemplate field in the builder for each field in the struct, including nested fields.

Most of the time you will probably want to just make this helper available to your html templates via the template.Funcs() functions and the template.FuncMap type, as I did in the example above.

I don't recommend tagging domain types

It is also worth mentioning that I don't really recommend adding form struct tags to your domain types, and I typically create types specifically used to generate forms. Eg:

// This is my domain typetypeUserstruct {
IDintNamestringEmailstringPasswordHashstring
}
// Somewhere else I'll create my html-specific type:typesignupFormstruct {
Namestring`form:"..."`Emailstring`form:"type=email"`Passwordstring`form:"type=password"`Confirmationstring`form:"type=password;label=Password Confirmation"`
}

Parsing submitted forms

If you also need to parse forms created by this package, I recommend using the gorilla/schema package. This package should generate input names compliant with the gorilla/schema package by default, so as long as you don't change the names it should be pretty trivial to decode.

There is an example of this in the examples/tailwind directory.

Rendering errors

If you want to render errors, see the examples/errors/errors.go example and most notably check out the inputs_and_errors_for function provided to templates via the Builder.FuncMap() function.

TODO: Add some better examples here, but the provided code sample is a complete example.

This may have bugs

This is a very early iteration of the package, and while it appears to be working for my needs chances are it doesn't cover every use case. If you do find one that isn't covered, try to provide a PR with a breaking test.

Notes

This section is mostly for myself to jot down notes, but feel free to read away.

Potential features

Parsing forms

Long term this could also support parsing forms, but gorilla/schema does a great job of that already so I don't see any reason to at this time. It would likely be easier to just make the default input names line up with what gorilla/schema expects and provide examples for how to use the two together.

Checkboxes and other data types

Maybe allow for various templates for different types, but for now this is possible to do in the HTML templates so it isn't completely missing.

Headers on nested structs

Let's say we have this type:

typeNestedstruct {
NamestringEmailstringAddressAddress
}
typeAddressstruct {
Street1stringStreet2string// ...
}

It might make sense to make an optional way to add headers in the form when the nested Address portion is rendered, so the form looks like:

Name: [ ]
Email: [ ]
<Address Header Here>
Street1: [ ]
Street2: [ ]
...

This should be pretty easy to do with struct tags on the Address Address line.

About

Easily create HTML forms with Go structs.

Resources

Stars

401 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Form

Easily create HTML forms with Go structs.

go report cardBuild StatusMIT licenseGoDoc

Overview

The form package makes it easy to take a Go struct and turn it into an HTML form using whatever HTML format you want. Below is an example, along with the output, but first let's just look at an example of what I mean.

Let's say you have a Go struct that looks like this:

typecustomerstruct {
NamestringEmailstringAddress*address
}
typeaddressstruct {
Street1stringStreet2stringCitystringStatestringZipstring`form:"label=Postal Code"`
}

Now you want to generate an HTML form for it, but that is somewhat annoying if you want to persist user-entered values if there is an error, or if you want to support loading URL query params and auto-filling the form for the user. With this package you can very easily do both of those things simply by defining what the HTML for an input field should be:

<divclass="mb-4"><labelclass="block text-grey-darker text-sm font-bold mb-2" {{with.ID}}for="{{.}}"{{end}}>
{{.Label}}
</label><inputclass="shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker leading-tight {{if errors}}border-red{{end}}" {{with.ID}}id="{{.}}"{{end}}type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with.Value}}value="{{.}}"{{end}}>
{{range errors}}
<pclass="text-red pt-2 text-xs italic">{{.}}</p>
{{end}}
</div>

This particular example is using Tailwind CSS to style the values, along with the errors template function which is provided via this form package when it creates the inputs for each field.

Now we can render this entire struct as a form by simply using the inputs_for template function which is provided by the form.Builder's FuncMap method:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_for .Customer}}
<!-- ... add buttons here --></form>

And with it we will generate an HTML form like the one below:

Example output from the forms package

Data set in the .Customer variable in our template will also be used when rendering the form, which is why you see Michael Scott and michael@dunder.com in the screenshot - these were set in the .Customer and were thus used to set the input's value.

Error rendering is also possible, but requires the usage of the inputs_and_errors_for template function, and you need to pass in errors that implement the fieldError interface (shown below, but NOT exported):

typefieldErrorinterface {
FieldError() (field, errstring)
}

For instance, in examples/errors/errors.go we pass data similar the following into our template when executing it:

data:=struct {
FormcustomerErrors []error
}{
Form: customer{
Name: "Michael Scott",
Email: "michael@dunder.com",
Address: nil,
},
Errors: []error{
fieldError{
Field: "Email",
Issue: "is already taken",
},
fieldError{
Field: "Address.Street1",
Issue: "is required",
},
...
},
}
tpl.Execute(w, data)

And then in the template we call the inputs_and_errors_for function:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_and_errors_for .Form .Errors}}
<!-- ... buttons here --></form>

And we get an output like this:

Example output from the forms package with errors

Installation

To install this package, simply go get it:

go get github.com/joncalhoun/form

Complete Examples

This entire example can be found in the examples/readme directory. Additional examples can also be found in the examples/ directory and are a great way to see how this package could be used.

Source Code

package main
import (
"html/template""net/http""github.com/joncalhoun/form"
)
varinputTpl=`<label {{with .ID}}for="{{.}}"{{end}}>	{{.Label}}</label><input {{with .ID}}id="{{.}}"{{end}} type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with .Value}}value="{{.}}"{{end}}>{{with .Footer}} <p>{{.}}</p>{{end}}`typeAddressstruct {
Street1string`form:"label=Street;placeholder=123 Sample St"`Street2string`form:"label=Street (cont);placeholder=Apt 123"`CitystringStatestring`form:"footer=Or your Province"`Zipstring`form:"label=Postal Code"`Countrystring
}
funcmain() {
tpl:=template.Must(template.New("").Parse(inputTpl))
fb:= form.Builder{
InputTemplate: tpl,
}
pageTpl:=template.Must(template.New("").Funcs(fb.FuncMap()).Parse(` <html> <body> <form> {{inputs_for .}} </form> </body> </html>`))
http.HandleFunc("/", func(w http.ResponseWriter, r*http.Request) {
w.Header().Set("Content-Type", "text/html")
pageTpl.Execute(w, Address{
Street1: "123 Known St",
Country: "United States",
})
})
http.ListenAndServe(":3000", nil)
}

Relevant HTML trimmed for brevity

<form><label>
Street
</label><inputtype="text" name="Street1" placeholder="123 Sample St" value="123 Known St"><label>
Street (cont)
</label><inputtype="text" name="Street2" placeholder="Apt 123" ><label>
City
</label><inputtype="text" name="City" placeholder="City" ><label>
State
</label><inputtype="text" name="State" placeholder="State" ><p>Or your Province</p><label>
Postal Code
</label><inputtype="text" name="Zip" placeholder="Postal Code" ><label>
Country
</label><inputtype="text" name="Country" placeholder="Country" value="United States"></form>

How it works

The form.Builder type provides a single method - Inputs - which will parse the provided struct to determine which fields it contains, any values set for each field, and any struct tags provided for the form package. Once that information is parsed it will execute the provided InputTemplate field in the builder for each field in the struct, including nested fields.

Most of the time you will probably want to just make this helper available to your html templates via the template.Funcs() functions and the template.FuncMap type, as I did in the example above.

I don't recommend tagging domain types

It is also worth mentioning that I don't really recommend adding form struct tags to your domain types, and I typically create types specifically used to generate forms. Eg:

// This is my domain typetypeUserstruct {
IDintNamestringEmailstringPasswordHashstring
}
// Somewhere else I'll create my html-specific type:typesignupFormstruct {
Namestring`form:"..."`Emailstring`form:"type=email"`Passwordstring`form:"type=password"`Confirmationstring`form:"type=password;label=Password Confirmation"`
}

Parsing submitted forms

If you also need to parse forms created by this package, I recommend using the gorilla/schema package. This package should generate input names compliant with the gorilla/schema package by default, so as long as you don't change the names it should be pretty trivial to decode.

There is an example of this in the examples/tailwind directory.

Rendering errors

If you want to render errors, see the examples/errors/errors.go example and most notably check out the inputs_and_errors_for function provided to templates via the Builder.FuncMap() function.

TODO: Add some better examples here, but the provided code sample is a complete example.

This may have bugs

This is a very early iteration of the package, and while it appears to be working for my needs chances are it doesn't cover every use case. If you do find one that isn't covered, try to provide a PR with a breaking test.

Notes

This section is mostly for myself to jot down notes, but feel free to read away.

Potential features

Parsing forms

Long term this could also support parsing forms, but gorilla/schema does a great job of that already so I don't see any reason to at this time. It would likely be easier to just make the default input names line up with what gorilla/schema expects and provide examples for how to use the two together.

Checkboxes and other data types

Maybe allow for various templates for different types, but for now this is possible to do in the HTML templates so it isn't completely missing.

Headers on nested structs

Let's say we have this type:

typeNestedstruct {
NamestringEmailstringAddressAddress
}
typeAddressstruct {
Street1stringStreet2string// ...
}

It might make sense to make an optional way to add headers in the form when the nested Address portion is rendered, so the form looks like:

Name: [ ]
Email: [ ]
<Address Header Here>
Street1: [ ]
Street2: [ ]
...

This should be pretty easy to do with struct tags on the Address Address line.

About

Easily create HTML forms with Go structs.

Resources

Stars

401 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Form

Easily create HTML forms with Go structs.

go report cardBuild StatusMIT licenseGoDoc

Overview

The form package makes it easy to take a Go struct and turn it into an HTML form using whatever HTML format you want. Below is an example, along with the output, but first let's just look at an example of what I mean.

Let's say you have a Go struct that looks like this:

typecustomerstruct {
NamestringEmailstringAddress*address
}
typeaddressstruct {
Street1stringStreet2stringCitystringStatestringZipstring`form:"label=Postal Code"`
}

Now you want to generate an HTML form for it, but that is somewhat annoying if you want to persist user-entered values if there is an error, or if you want to support loading URL query params and auto-filling the form for the user. With this package you can very easily do both of those things simply by defining what the HTML for an input field should be:

<divclass="mb-4"><labelclass="block text-grey-darker text-sm font-bold mb-2" {{with.ID}}for="{{.}}"{{end}}>
{{.Label}}
</label><inputclass="shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker leading-tight {{if errors}}border-red{{end}}" {{with.ID}}id="{{.}}"{{end}}type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with.Value}}value="{{.}}"{{end}}>
{{range errors}}
<pclass="text-red pt-2 text-xs italic">{{.}}</p>
{{end}}
</div>

This particular example is using Tailwind CSS to style the values, along with the errors template function which is provided via this form package when it creates the inputs for each field.

Now we can render this entire struct as a form by simply using the inputs_for template function which is provided by the form.Builder's FuncMap method:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_for .Customer}}
<!-- ... add buttons here --></form>

And with it we will generate an HTML form like the one below:

Example output from the forms package

Data set in the .Customer variable in our template will also be used when rendering the form, which is why you see Michael Scott and michael@dunder.com in the screenshot - these were set in the .Customer and were thus used to set the input's value.

Error rendering is also possible, but requires the usage of the inputs_and_errors_for template function, and you need to pass in errors that implement the fieldError interface (shown below, but NOT exported):

typefieldErrorinterface {
FieldError() (field, errstring)
}

For instance, in examples/errors/errors.go we pass data similar the following into our template when executing it:

data:=struct {
FormcustomerErrors []error
}{
Form: customer{
Name: "Michael Scott",
Email: "michael@dunder.com",
Address: nil,
},
Errors: []error{
fieldError{
Field: "Email",
Issue: "is already taken",
},
fieldError{
Field: "Address.Street1",
Issue: "is required",
},
...
},
}
tpl.Execute(w, data)

And then in the template we call the inputs_and_errors_for function:

<formclass="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/" method="post">
{{inputs_and_errors_for .Form .Errors}}
<!-- ... buttons here --></form>

And we get an output like this:

Example output from the forms package with errors

Installation

To install this package, simply go get it:

go get github.com/joncalhoun/form

Complete Examples

This entire example can be found in the examples/readme directory. Additional examples can also be found in the examples/ directory and are a great way to see how this package could be used.

Source Code

package main
import (
"html/template""net/http""github.com/joncalhoun/form"
)
varinputTpl=`<label {{with .ID}}for="{{.}}"{{end}}>	{{.Label}}</label><input {{with .ID}}id="{{.}}"{{end}} type="{{.Type}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{with .Value}}value="{{.}}"{{end}}>{{with .Footer}} <p>{{.}}</p>{{end}}`typeAddressstruct {
Street1string`form:"label=Street;placeholder=123 Sample St"`Street2string`form:"label=Street (cont);placeholder=Apt 123"`CitystringStatestring`form:"footer=Or your Province"`Zipstring`form:"label=Postal Code"`Countrystring
}
funcmain() {
tpl:=template.Must(template.New("").Parse(inputTpl))
fb:= form.Builder{
InputTemplate: tpl,
}
pageTpl:=template.Must(template.New("").Funcs(fb.FuncMap()).Parse(` <html> <body> <form> {{inputs_for .}} </form> </body> </html>`))
http.HandleFunc("/", func(w http.ResponseWriter, r*http.Request) {
w.Header().Set("Content-Type", "text/html")
pageTpl.Execute(w, Address{
Street1: "123 Known St",
Country: "United States",
})
})
http.ListenAndServe(":3000", nil)
}

Relevant HTML trimmed for brevity

<form><label>
Street
</label><inputtype="text" name="Street1" placeholder="123 Sample St" value="123 Known St"><label>
Street (cont)
</label><inputtype="text" name="Street2" placeholder="Apt 123" ><label>
City
</label><inputtype="text" name="City" placeholder="City" ><label>
State
</label><inputtype="text" name="State" placeholder="State" ><p>Or your Province</p><label>
Postal Code
</label><inputtype="text" name="Zip" placeholder="Postal Code" ><label>
Country
</label><inputtype="text" name="Country" placeholder="Country" value="United States"></form>

How it works

The form.Builder type provides a single method - Inputs - which will parse the provided struct to determine which fields it contains, any values set for each field, and any struct tags provided for the form package. Once that information is parsed it will execute the provided InputTemplate field in the builder for each field in the struct, including nested fields.

Most of the time you will probably want to just make this helper available to your html templates via the template.Funcs() functions and the template.FuncMap type, as I did in the example above.

I don't recommend tagging domain types

It is also worth mentioning that I don't really recommend adding form struct tags to your domain types, and I typically create types specifically used to generate forms. Eg:

// This is my domain typetypeUserstruct {
IDintNamestringEmailstringPasswordHashstring
}
// Somewhere else I'll create my html-specific type:typesignupFormstruct {
Namestring`form:"..."`Emailstring`form:"type=email"`Passwordstring`form:"type=password"`Confirmationstring`form:"type=password;label=Password Confirmation"`
}

Parsing submitted forms

If you also need to parse forms created by this package, I recommend using the gorilla/schema package. This package should generate input names compliant with the gorilla/schema package by default, so as long as you don't change the names it should be pretty trivial to decode.

There is an example of this in the examples/tailwind directory.

Rendering errors

If you want to render errors, see the examples/errors/errors.go example and most notably check out the inputs_and_errors_for function provided to templates via the Builder.FuncMap() function.

TODO: Add some better examples here, but the provided code sample is a complete example.

This may have bugs

This is a very early iteration of the package, and while it appears to be working for my needs chances are it doesn't cover every use case. If you do find one that isn't covered, try to provide a PR with a breaking test.

Notes

This section is mostly for myself to jot down notes, but feel free to read away.

Potential features

Parsing forms

Long term this could also support parsing forms, but gorilla/schema does a great job of that already so I don't see any reason to at this time. It would likely be easier to just make the default input names line up with what gorilla/schema expects and provide examples for how to use the two together.

Checkboxes and other data types

Maybe allow for various templates for different types, but for now this is possible to do in the HTML templates so it isn't completely missing.

Headers on nested structs

Let's say we have this type:

typeNestedstruct {
NamestringEmailstringAddressAddress
}
typeAddressstruct {
Street1stringStreet2string// ...
}

It might make sense to make an optional way to add headers in the form when the nested Address portion is rendered, so the form looks like:

Name: [ ]
Email: [ ]
<Address Header Here>
Street1: [ ]
Street2: [ ]
...

This should be pretty easy to do with struct tags on the Address Address line.

About

Easily create HTML forms with Go structs.

Resources

Stars

401 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages