Documentation ¶
Overview ¶
Package fpf provides form value population and error message insertion.
Value Population ¶
Value population populates HTML form elements with provided values.
This is useful when a user is sent back to a form they have previously filled out, especially in regards to one they submitted but had validation errors.
Value population is achieved by parsing the HTML node tree to discover form elements. If a value is provided for any discovered form element, then the form element is populated with the value.
The way the value is populated depends upon the element:
textarea: the text content is populated.
select: the option matching the value is given the attribute "selected".
input[type=radio], input[type=checkbox]: the input is given the "checked" attribute.
input: the input's "value" attribute is set.
Error Message Insertion ¶
Error message insertion is achieved by providing a list of "incidents". A single incident can have one or many error messages and also be associated with one or many form elements.
If a discovered form element has an associated incident, the IncidentInsertion strategy provided is invoked to insert error messages into the HTML node tree in relation to the form element and its labels.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var DefaultIncidentInserter = &GenericIncidentInserter{ ErrorClass: "error", SingleElementErrorLocation: After, MultipleElementErrorLocation: Child, Template: template.Must(template.New("error").Parse(`<ul class="errors">{{ range . }}<li>{{.}}</li>{{end}}</ul>`)), }
DefaultIncidentInserter is the default incident inserter used if no other incident inserter is provided.
Functions ¶
This section is empty.
Types ¶
type Form ¶
type Form struct { ID string Values url.Values Incidents []Incident // contains filtered or unexported fields }
Form represents a form by ID that we wish to populate with values and perform error insertion on.
type FormPopulationFilter ¶
type FormPopulationFilter struct { // The incident insertion strategy to use IncidentInsertion IncidentInserter IncludeHiddenInputs bool // Whether to populate hidden input values IncludePasswordInputs bool // Whether to populate password input values }
func New ¶
func New() *FormPopulationFilter
New returns a FormPopulationFilter with default configuration.
func (*FormPopulationFilter) Execute ¶
Execute reads from r, modifies forms matching the provided form IDs, and writes the output to w. The input is assumed to be UTF-8 encoded.
func (*FormPopulationFilter) ExecuteTemplate ¶
func (fpf *FormPopulationFilter) ExecuteTemplate(forms []Form, w io.Writer, t *template.Template, data interface{}) error
Execute executes the provided template with the provided data, modifies forms matching the provided form IDs, and writes the output to w. The template output is assumed to be UTF-8 encoded.
Example ¶
package main import ( "html/template" "log" "net/url" "os" "time" "github.com/saracen/fpf" ) func main() { const tpl = ` <html> <head> <title>Your Information</title> </head> <body> <form action="/" method="post"> <div class="form-group"> <label for="name">Name</label> <input name="name" type="text" placeholder="John Smith" /> </div> <div class="form-group"> <label>Do you like food? <input type="checkbox" name="food" /></label> </div> <div class="form-group"> <label for="month">Favourite Month</label> <select id="month" name="month"> {{- range $index, $month := .Months }} <option value="{{ $index }}">{{ $month }}</option> {{- end }} </select> </div> </form> </body> </html>` // Parse template t, err := template.New("info").Parse(tpl) if err != nil { log.Fatal(err) } // Create data we'll use with the template var data struct { Months []time.Month } for i := time.January; i <= time.December; i++ { data.Months = append(data.Months, i) } // Set the values we want populated values := url.Values{} values.Set("name", "Arran Walker") values.Set("food", "1") values.Set("month", "3") // Execute template with fpf fp := fpf.New() err = fp.ExecuteTemplate([]fpf.Form{{Values: values}}, os.Stdout, t, data) if err != nil { log.Fatal(err) } }
Output: <html><head> <title>Your Information</title> </head> <body> <form action="/" method="post"> <div class="form-group"> <label for="name">Name</label> <input name="name" type="text" placeholder="John Smith" value="Arran Walker"/> </div> <div class="form-group"> <label>Do you like food? <input type="checkbox" name="food" checked="checked"/></label> </div> <div class="form-group"> <label for="month">Favourite Month</label> <select id="month" name="month"> <option value="0">January</option> <option value="1">February</option> <option value="2">March</option> <option value="3" selected="selected">April</option> <option value="4">May</option> <option value="5">June</option> <option value="6">July</option> <option value="7">August</option> <option value="8">September</option> <option value="9">October</option> <option value="10">November</option> <option value="11">December</option> </select> </div> </form> </body></html>
Example (ErrorInsertion) ¶
package main import ( "html/template" "io" "log" "net/http" "net/http/httptest" "net/url" "os" "github.com/saracen/fpf" ) func main() { const tpl = ` <html> <head> <title>{{ .Title }}</title> </head> <body> <form action="/" method="post"> <div class="form-group"> <label for="username">Username</label> <input name="username" type="text" /> </div> <div class="form-group"> <div class="form-group-left"> <label for="password">Password</label> <input name="password" type="password" /> </div> <div class="form-group-right"> <label for="password-confirm">Confirm Password</label> <input name="password-confirm" type="password" /> </div> </div> <div class="form-group"> <label>Opt In Newsletter <input type="checkbox" name="newsletter" /></label> </div> <div class="form-group"> <label>Opt In Spam <input type="checkbox" name="spam" /></label> </div> </form> </body> </html>` // Parse template t, err := template.New("register").Parse(tpl) if err != nil { log.Fatal(err) } // Create validation function validate := func(register url.Values) (incidents []fpf.Incident) { // validate username if len(register.Get("username")) < 5 { incidents = append(incidents, fpf.Incident{ []string{"username"}, []string{"Username needs to be 5 or more characters long."}, }) } // validate password if len(register.Get("password")) < 6 { incidents = append(incidents, fpf.Incident{ []string{"password", "password-confirm"}, []string{"Password needs to be 6 or more characters long."}, }) } else if register.Get("password") != register.Get("password-confirm") { incidents = append(incidents, fpf.Incident{ []string{"password", "password-confirm"}, []string{"Passwords do not match."}, }) } return incidents } // Using httptest server for example purposes ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var incidents []fpf.Incident if r.Method == "POST" { err := r.ParseForm() if err != nil { return } // Validate posted form if incidents = validate(r.PostForm); len(incidents) == 0 { // Validated successful // Add saving / additional logic return } } // Execute template with fpf fp := fpf.New() fp.ExecuteTemplate([]fpf.Form{{Values: r.PostForm, Incidents: incidents}}, os.Stdout, t, struct{ Title string }{"Registration Page"}) })) defer ts.Close() // Emulate browser client post resp, err := http.PostForm(ts.URL, url.Values{ "username": {"sara"}, "password": {"password"}, "password-comfirm": {"password123"}, "spam": {"on"}, }) if err != nil { log.Fatal(err) } defer resp.Body.Close() io.Copy(os.Stdout, resp.Body) }
Output: <html><head> <title>Registration Page</title> </head> <body> <form action="/" method="post"> <div class="form-group"> <label for="username">Username</label> <input name="username" type="text" value="sara" class="error"/><ul class="errors"><li>Username needs to be 5 or more characters long.</li></ul> </div> <div class="form-group"> <div class="form-group-left"> <label for="password">Password</label> <input name="password" type="password" class="error"/> </div> <div class="form-group-right"> <label for="password-confirm">Confirm Password</label> <input name="password-confirm" type="password" class="error"/> </div> <ul class="errors"><li>Passwords do not match.</li></ul></div> <div class="form-group"> <label>Opt In Newsletter <input type="checkbox" name="newsletter"/></label> </div> <div class="form-group"> <label>Opt In Spam <input type="checkbox" name="spam" checked="checked"/></label> </div> </form> </body></html>
type GenericIncidentInserter ¶
type GenericIncidentInserter struct { // The error class given to labels and form input elements ErrorClass string // The location of to insert error messages SingleElementErrorLocation Location MultipleElementErrorLocation Location // The error template that will be inserted Template *template.Template }
GenericIncidentInserter provides a basic strategy for inserting error messages into the HTML node tree.
func (*GenericIncidentInserter) Insert ¶
func (i *GenericIncidentInserter) Insert(elements []LabelableElement, errors []string) error
Insert uses a basic strategy for error insertions:
If there is more than one element then error messages are added as children to the elements' lowest common ancestor.
If there is only one element, the error messages are inserted beneath it
type Incident ¶
Incident is a collection of one or more form element names and their error messages.
Multiple form element names are required when there's a group of elements that share common errors. For example, the inputs "new-password" and "new-password-confirm" can share the error "passwords do not match".
type IncidentInserter ¶
type IncidentInserter interface {
Insert(elements []LabelableElement, errors []string) error
}
IncidentInserter provides an interface for custom error message insertion strategies.
The insert method is provided with a list of the affected elements and error messages that to be inserted.
type LabelableElement ¶
LabelableElement contains a form element and its associated labels.