5 Commits aadb1de035 ... b546d2a266

Author SHA1 Message Date
  Bozhin Zafirov b546d2a266 Update FormField functionality 2 years ago
  Bozhin Zafirov 9c7adcba22 Return a more descriptive error message in integer range validation. 2 years ago
  Bozhin Zafirov 09944b89d7 Add label and html class fields in FormField. 2 years ago
  Bozhin Zafirov a8482ee0e2 Allow FormField.Name to match POST form field name. 2 years ago
  Bozhin Zafirov 0ef53df57c Update comments. 2 years ago
6 changed files with 168 additions and 62 deletions
  1. 1 1
      LICENSE
  2. 26 8
      constructors.go
  3. 1 1
      errors.go
  4. 104 18
      forms.go
  5. 25 17
      validate.go
  6. 11 17
      validators.go

+ 1 - 1
LICENSE

@@ -1,6 +1,6 @@
 BSD 2-Clause License
 
-Copyright (c) 2017, Bozhin Zafirov.
+Copyright (c) 2017-2022, Bozhin Zafirov.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without

+ 26 - 8
constructors.go

@@ -1,14 +1,32 @@
 package form
 
-// Generate new CharField field with type text
-func NewCharField(Name string, Value *string, Validators *ValidatorsList) FormField {
-	if Value == nil {
-		return FormField{Name, "", nil, Validators}
+/* strFromPtr converts string pointer to a string */
+func strFromPtr(str *string) (s string) {
+	if str != nil {
+		s = *str
 	}
-	return FormField{Name, *Value, nil, Validators}
+	return
 }
 
-// Generate new CharField field with type password
-func NewPasswordField(Name string, Validators *ValidatorsList) FormField {
-	return FormField{Name, "", nil, Validators}
+/* Generate new CharField field with type text */
+func NewCharField(Name string, Value *string) *FormField {
+	return &FormField{
+		Name,
+		nil,
+		"",
+		strFromPtr(Value),
+		"form-control",
+		"text",
+		"",
+		"",
+		false,
+		false,
+		nil,
+	}
+}
+
+/* Generate new CharField field with type password */
+func NewPasswordField(Name string) *FormField {
+	field := NewCharField(Name, nil).SetType("password")
+	return &field
 }

+ 1 - 1
errors.go

@@ -4,7 +4,7 @@ import (
 	"errors"
 )
 
-// pre-defined form errors
+/* pre-defined form errors */
 var (
 	EInvalidMethod     = errors.New("Invalid method")
 	EInvalidIntValue   = errors.New("Field value must be integer.")

+ 104 - 18
forms.go

@@ -1,35 +1,96 @@
 package form
 
 import (
+	"bytes"
 	"context"
+	"html/template"
 	"strconv"
 )
 
-// ValidatorFunc defines a function for FormField data validation
+/* ValidatorFunc defines a function for FormField data validation */
 type ValidatorFunc func(FormField, context.Context) error
 
-// ValidatorsList defines a list of ValidatorFunc
+/* ValidatorsList defines a list of ValidatorFunc */
 type ValidatorsList []ValidatorFunc
 
-// A general purpose form  field struct
+/* A general purpose form  field struct */
 type FormField struct {
-	Name       string
-	Value      string
-	Error      error
-	Validators *ValidatorsList
+	Name        string
+	Error       []error
+	Value       string
+	Label       string
+	Class       string
+	Type        string
+	Placeholder string
+	Help        string
+	Required    bool
+	AutoFocus   bool
+	Validators  *ValidatorsList
 }
 
-// GetString returns FormField.Value as string
+/* SetValidators configures validators list in form field */
+func (f FormField) SetValidators(validators *ValidatorsList) FormField {
+	f.Validators = validators
+	return f
+}
+
+/* SetLabel configures form label */
+func (f FormField) SetLabel(label string) FormField {
+	f.Label = label
+	return f
+}
+
+/* SetClass configures class name in form field */
+func (f FormField) SetClass(class string) FormField {
+	f.Class = class
+	return f
+}
+
+/* SetRequired marks FormField as mandatory */
+func (f FormField) SetRequired() FormField {
+	f.Required = true
+	return f
+}
+
+/* SetAutoFocus gives focus to current FormField on page load  */
+func (f FormField) SetAutoFocus() FormField {
+	f.AutoFocus = true
+	return f
+}
+
+/* SetType specifies input field type */
+func (f FormField) SetType(t string) FormField {
+	f.Type = t
+	return f
+}
+
+/* SetPlaceholder specified a placeholder property in Formfield */
+func (f FormField) SetPlaceholder(placeholder string) FormField {
+	f.Placeholder = placeholder
+	return f
+}
+
+/* SetHelp specified a help message for current Formfield */
+func (f FormField) SetHelp(help string) FormField {
+	f.Help = help
+	return f
+}
+
+/* GetString returns FormField.Value as string */
 func (f FormField) GetString() string {
 	return f.Value
 }
 
-// GetInt returns FormField.Value as int
-func (f FormField) GetInt() (int, error) {
-	return strconv.Atoi(f.Value)
+/* GetInt returns FormField.Value as int */
+func (f FormField) GetInt() (v int, err error) {
+	v, err = strconv.Atoi(f.Value)
+	if err != nil {
+		err = EInvalidIntValue
+	}
+	return
 }
 
-// Int converts FormField.Value to integer value and ignores errors
+/* Int converts FormField.Value to integer value and ignores errors */
 func (f FormField) Int() int {
 	if result, err := strconv.Atoi(f.Value); err == nil {
 		return result
@@ -37,12 +98,12 @@ func (f FormField) Int() int {
 	return 0
 }
 
-// GetFloat returns FormField.Value as float
+/* GetFloat returns FormField.Value as float */
 func (f FormField) GetFloat() (float64, error) {
 	return strconv.ParseFloat(f.Value, 64)
 }
 
-// Float converts FormField.Value to float and ignores errors
+/* Float converts FormField.Value to float and ignores errors */
 func (f FormField) Float() float64 {
 	if result, err := strconv.ParseFloat(f.Value, 64); err == nil {
 		return result
@@ -50,14 +111,39 @@ func (f FormField) Float() float64 {
 	return 0.0
 }
 
-// GetBool returns boolean value for checkbox fields
+/* GetBool returns boolean value for checkbox fields */
 func (f FormField) GetBool() (bool, error) {
-	// placeholder
+	/* placeholder */
 	return false, nil
 }
 
-// GetChecked returns true if checkbox has been selected
-// only works if checkbox value is "on" when selected
+/* GetChecked returns true if checkbox has been checked and its value is "on" */
 func (f FormField) GetChecked() bool {
 	return f.Value == "on"
 }
+
+/* formFieldTemplate is a template to render FormField element in HTML format */
+const formFieldTemplate = `
+	{{ if .Label }}<label class="form-label" for="{{ .Name }}">{{ .Label }}</label>{{ end }}
+	<input type="{{ .Type }}" id="{{ .Name }}" name="{{ .Name }}"
+		{{- if .Class }} class="{{ .Class }}"{{ end }}
+		{{- if .Value }} value="{{ .Value }}"{{ end }}
+		{{- if .Placeholder}} placeholder="{{ .Placeholder }}"{{ end }}
+		{{- if .Help }} aria-describedby="{{ .Name }}Help"{{ end }}
+		{{- if .Required }} required{{ end }}
+		{{- if .AutoFocus }} autofocus{{ end }}>
+		{{ if .Help }}<div id="{{ .Name }}Help" class="form-text">{{ .Help }}</div>{{ end }}
+	{{ if .Error }}{{ range $e := .Error }}<div class="text-danger">{{ $e }}</div>{{ end }}{{ end }}
+`
+
+/* formTemplate is compiled template to render FormField element in HTML format */
+var formTemplate = template.Must(template.New("FormField").Parse(formFieldTemplate))
+
+/* HTML renders FormField element in html format */
+func (f FormField) HTML() template.HTML {
+	var buffer bytes.Buffer
+	if err := formTemplate.Execute(&buffer, f); err == nil {
+		return template.HTML(buffer.String())
+	}
+	return template.HTML("")
+}

+ 25 - 17
validate.go

@@ -5,54 +5,62 @@ import (
 	"reflect"
 )
 
-// ValidateForm parses a POST form into pre-defined struct
+/* ValidateForm parses a POST form into pre-defined struct */
 func ValidateForm(r *http.Request, p interface{}) error {
-	// only support POST methods
+	/* only support POST methods */
 	if r.Method != "POST" {
 		return EInvalidMethod
 	}
 
-	// parse POST data into form
+	/* parse POST data into form */
 	if err := r.ParseForm(); err != nil {
 		return err
 	}
 
 	var FormError error
-	// Parse form data into interface
+	/* Parse form data into interface */
 	formStruct := reflect.ValueOf(p).Elem()
 
-	// populate FormField value
+	/* populate FormField value */
 	for HttpFormField, HttpFormValue := range r.Form {
 		for n := 0; n < formStruct.NumField(); n++ {
 			fieldt := formStruct.Type().Field(n)
-			// only proceed if field name or tag matches that of form field
-			if fieldt.Name != HttpFormField && fieldt.Tag.Get("form") != HttpFormField {
-				continue
-			}
-			// get n-th field
+			/* get n-th field */
 			fieldn := formStruct.Field(n)
-			// set form data to field
-			// equivalent of form.Value = HttpFormValue[0]
-			fieldn.Field(1).Set(reflect.ValueOf(HttpFormValue[0]))
+			/* only proceed if field name or tag matches that of form field */
+			if fieldn.Field(0).String() != HttpFormField {
+				if fieldt.Name != HttpFormField {
+					if fieldt.Tag.Get("form") != HttpFormField {
+						continue
+					}
+				}
+			}
+			/* set form data to field
+			   equivalent of form.Value = HttpFormValue[0] */
+			fieldn.Field(2).Set(reflect.ValueOf(HttpFormValue[0]))
 		}
 	}
 
-	// run form field validators
+	/* run form field validators */
 	for n := 0; n < formStruct.NumField(); n++ {
 		fieldn := formStruct.Field(n)
 		field := fieldn.Interface().(FormField)
 		if field.Validators == nil {
 			continue
 		}
+		/* prepare list of FormField errors */
+		var errors []error
 		for _, validator := range *field.Validators {
 			if err := validator(field, r.Context()); err != nil {
-				fieldn.Field(2).Set(reflect.ValueOf(err))
+				errors = append(errors, err)
 				FormError = err
-				break
 			}
 		}
+		if len(errors) != 0 {
+			fieldn.Field(1).Set(reflect.ValueOf(errors))
+		}
 	}
 
-	// return status
+	/* return status */
 	return FormError
 }

+ 11 - 17
validators.go

@@ -7,15 +7,14 @@ import (
 	"strings"
 )
 
-// validation errors
+/* validation errors */
 var (
 	EInvalidInteger = errors.New("not a valid integer value")
 	EInvalidFloat   = errors.New("not a valid float value")
 	ERequired       = errors.New("this field is required")
 )
 
-// ValidLettersGeneric is a validator generator for checking
-// for valid letters in field
+/* ValidLettersGeneric is a validator generator for checking for valid letters in field */
 func ValidLettersGeneric(Letters string, Error error) ValidatorFunc {
 	Callback := func(field FormField, ctx context.Context) error {
 		for _, Rune := range field.GetString() {
@@ -28,8 +27,7 @@ func ValidLettersGeneric(Letters string, Error error) ValidatorFunc {
 	return Callback
 }
 
-// ValidRequired returns nil if there is text in the field.
-// If the field is empty it returns error.
+/* ValidRequired makes sure field is not empty. */
 func ValidRequired(field FormField, ctx context.Context) error {
 	if field.GetString() == "" {
 		return ERequired
@@ -37,8 +35,7 @@ func ValidRequired(field FormField, ctx context.Context) error {
 	return nil
 }
 
-// ValidLength returns a string field validator that verifies if a string
-// length is between specified min and max values.
+/* ValidLength makes sure that a string length is between specified min and max values. */
 func ValidLength(min, max int) ValidatorFunc {
 	var ELength = errors.New(
 		fmt.Sprintf("must be a string between %d and %d characters in length", min, max))
@@ -50,7 +47,7 @@ func ValidLength(min, max int) ValidatorFunc {
 	}
 }
 
-// ValidFieldIn verifies if item is within the list of items
+/* ValidFieldIn verifies if item is within the list of items */
 func ValidFieldIn(list []string) ValidatorFunc {
 	var EInvalidValue = errors.New(
 		fmt.Sprintf(
@@ -68,7 +65,7 @@ func ValidFieldIn(list []string) ValidatorFunc {
 	}
 }
 
-// ValidInt returns error if field does not contain a valid integer value
+/* ValidInt returns error if field does not contain a valid integer value */
 func ValidInt(field FormField, ctx context.Context) error {
 	_, err := field.GetInt()
 	if err != nil {
@@ -77,15 +74,14 @@ func ValidInt(field FormField, ctx context.Context) error {
 	return nil
 }
 
-// ValidBetween is a validator generator that makes sure field is
-// integer value within the specified range.
+/* ValidBetween makes sure that field is integer value within the specified range. */
 func ValidBetween(min, max int) ValidatorFunc {
 	var EInvalidInterval = errors.New(
 		fmt.Sprintf("must be integer between %d and %d", min, max))
 	return func(field FormField, ctx context.Context) error {
 		value, err := field.GetInt()
 		if err != nil {
-			return err
+			return EInvalidInteger
 		}
 		if value < min || value > max {
 			return EInvalidInterval
@@ -94,7 +90,7 @@ func ValidBetween(min, max int) ValidatorFunc {
 	}
 }
 
-// ValidFloat returns error if field does not contain a valid integer value
+/* ValidFloat returns error if field does not contain a valid integer value */
 func ValidFloat(field FormField, ctx context.Context) error {
 	_, err := field.GetFloat()
 	if err != nil {
@@ -103,8 +99,7 @@ func ValidFloat(field FormField, ctx context.Context) error {
 	return nil
 }
 
-// ValidBetweenFloat32 is a v alidator generator that makes sure field is
-// float64 value within the specified range.
+/* ValidBetweenFloat32 makes sure field is float64 value within the specified range. */
 func ValidBetweenFloat(min, max float64) ValidatorFunc {
 	var EInvalidInterval = errors.New(
 		fmt.Sprintf("must be float value between %.2f and %.2f", min, max))
@@ -120,8 +115,7 @@ func ValidBetweenFloat(min, max float64) ValidatorFunc {
 	}
 }
 
-// ValidFieldEqualTo is a general purpose validator that checks
-// if two fields have the same value for verification purposes.
+/* ValidFieldEqualTo is a validator that checks if two fields have the same value. */
 func ValidFieldEqualTo(Other *FormField, err error) ValidatorFunc {
 	return func(field FormField, ctx context.Context) error {
 		if field.GetString() != Other.GetString() {