+
+
diff --git a/.hugo_build.lock b/.hugo_build.lock new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..5e6a065 --- /dev/null +++ b/404.html @@ -0,0 +1,39 @@ + + + +
+The validation is designed to validate data against constraints.
+import (
+ "gitnet.fr/deblan/go-form/validation"
+)Validate the length of an array, a slice or a string
+c := validation.NewLength()
+
+// Define minimum
+c.WithMin(1)
+
+// Define minimum
+c.WithMax(100)
+
+// Define min and max
+// Equivalent to c.WithMin(50).WithMax(50)
+c.WithExact(50)validation.NewMail()validation.NewNotBlank()Validate a number
+c := validation.NewRange()
+
+// Define minimum
+c.WithMin(1)
+
+// Define minimum
+c.WithMax(100)
+
+// Define min and max
+// Equivalent to c.WithMin(1).WithMax(100)
+c.WithRange(1, 100)Validate a string with a regex
+c := validation.NewRegex(`expression`)
+
+// The value must match
+c.MustMatch()
+
+// The value must not match
+c.MustNotMatch()Validate that a number is even.
+validation.NewIsEven()Validate that a number is odd.
+validation.NewIsOdd()Use case: you want to validate that the data equals “example”
+package validation
+
+import (
+ "reflect"
+
+ v "gitnet.fr/deblan/go-form/validation"
+)
+
+// Define a struct
+type IsExample struct {
+ Message string
+ TypeErrorMessage string
+}
+
+// Create a factory
+func NewIsExample() IsEven {
+ return IsEven{
+ Message: "This value does not equal \"example\".",
+ TypeErrorMessage: "This value can not be processed.",
+ }
+}
+
+// Implement the validation
+func (c IsExample) Validate(data any) []Error {
+ errors := []Error{}
+
+ // Should not validate blank data
+ if len(v.NewNotBlank().Validate(data)) != 0 {
+ return []Error{}
+ }
+
+ t := reflect.TypeOf(data)
+
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+
+ switch t.Kind() {
+ case reflect.String:
+ if data.(string) != "example" {
+ errors = append(errors, Error(c.Message))
+ }
+
+ default:
+ errors = append(errors, Error(c.TypeErrorMessage))
+ }
+
+ return errors
+}A field represents a field in a form.
+func NewFieldCheckbox(name string) *Fieldfield := form.NewFieldCheckbox("Foo")
+Generates an input[type=checkbox]
+func NewFieldChoice(name string) *Field
+field := form.NewFieldChoice("Foo")
+
+Generates inputs (checkbox or radio) or selects
+func NewFieldCsrf(name string) *Field
+field := form.NewFieldCsrf("Foo")
+
+func NewFieldDate(name string) *Field
+field := form.NewFieldDate("Foo")
+
+Generates an input[type=date] with default transformers
+func NewFieldDatetime(name string) *Field
+field := form.NewFieldDatetime("Foo")
+
+Generates an input[type=datetime] with default transformers
+func NewFieldDatetimeLocal(name string) *Field
+field := form.NewFieldDatetimeLocal("Foo")
+
+Generates an input[type=datetime-local] with default transformers
+func NewFieldHidden(name string) *Field
+field := form.NewFieldHidden("Foo")
+
+Generates an input[type=hidden]
+func NewFieldMail(name string) *Field
+field := form.NewFieldMail("Foo")
+
+Generates an input[type=email]
+func NewFieldNumber(name string) *Field
+field := form.NewFieldNumber("Foo")
+
+Generates an input[type=number] with default transformers
+func NewFieldPassword(name string) *Field
+field := form.NewFieldPassword("Foo")
+
+Generates an input[type=password]
+func NewFieldRange(name string) *Field
+field := form.NewFieldRange("Foo")
+
+Generates an input[type=range]
+func NewFieldSubForm(name string) *Field
+field := form.NewFieldSubForm("Foo")
+
+Alias:
+func NewSubForm(name string) *FieldGenerates a sub form
+func NewFieldText(name string) *Field
+field := form.NewFieldText("Foo")
+
+Generates an input[type=text]
+func NewFieldTextarea(name string) *Field
+field := form.NewFieldTextarea("Foo")
+
+Generates a textarea
+func NewFieldTime(name string) *Field
+field := form.NewFieldTime("Foo")
+
+Generates an input[type=time] with default transformers
+func NewSubmit(name string) *Field
+field := form.NewSubmit("Foo")
+
+Generates an input[type=submit]
+func (f *Field) Add(children ...*Field) *FieldAppends children
+func (f *Field) Bind(data map[string]any, key *string) errorBind the data into the given map
+func (f *Field) GetChild(name string) *FieldReturns a child using its name
+func (f *Field) GetId() stringComputes the id of the field
+func (f *Field) GetName() stringComputes the name of the field
+func (f *Field) GetOption(name string) *OptionReturns an option using its name
+func (f *Field) HasChild(name string) boolChecks if the field contains a child using its name
+func (f *Field) HasOption(name string) boolChecks if the field contains an option using its name
+func (f *Field) Mount(data any) errorPopulates the field with data
+func (f *Field) ResetErrors() *FieldResets the field errors
+func (f *Field) WithBeforeBind(callback func(data any) (any, error)) *FieldSets a transformer applied to the data of a field before defining it in a structure
+func (f *Field) WithBeforeMount(callback func(data any) (any, error)) *FieldSets a transformer applied to the structure data before displaying it in a field
+func (f *Field) WithConstraints(constraints ...validation.Constraint) *FieldAppends constraints
+func (f *Field) WithData(data any) *FieldSets data the field
+func (f *Field) WithFixedName() *FieldSets that the name of the field is not computed
+func (f *Field) WithOptions(options ...*Option) *Field| Name | +type | +description | +Info | +
|---|---|---|---|
required |
+ bool |
+ Add required="true" |
+ Does not apply a constraint | +
attr |
+ form.Attrs |
+ List of extra attributes of the field | ++ |
row_attr |
+ form.Attrs |
+ List of extra attributes of the field’s top container | ++ |
label |
+ string |
+ The label of the field | +Usually show before the field | +
label_attr |
+ form.Attrs |
+ List of extra attributes of the label | ++ |
help |
+ string |
+ Helper of the field | ++ |
help_attr |
+ form.Attrs |
+ List of extra attributes of the help | ++ |
Appends options to the field
+func (f *Field) WithSlice() *FieldSets that the field represents a data slice
+ +import (
+ "gitnet.fr/deblan/go-form/form"
+ "gitnet.fr/deblan/go-form/validation"
+)
+
+type Person struct {
+ Name string
+ Age int
+}myForm := form.NewForm(
+ form.NewFieldText("Name").
+ WithConstraints(
+ validation.NewNotBlank(),
+ ),
+ form.NewFieldNumber("Age").
+ WithConstraints(
+ validation.NewNotBlank(),
+ validation.NewRange().WithMin(18),
+ ),
+).End()data := Person{}
+
+myForm.Mount(data)
+myForm.IsValid() // false
+
+data = Person{
+ Name: "Alice",
+ Age: 42,
+}
+
+myForm.Mount(data)
+myForm.IsValid() // trueimport (
+ "net/http"
+)
+
+myForm.WithMethod(http.MethodPost)
+
+// req *http.Request
+if req.Method == myForm.Method {
+ myForm.HandleRequest(req)
+
+ if myForm.IsSubmitted() && myForm.IsValid() {
+ myForm.Bind(&data)
+ }
+}type Form struct {
+ Fields []*Field
+ GlobalFields []*Field
+ Errors []validation.Error
+ Method string
+ Action string
+ Name string
+ Options []*Option
+ RequestData *url.Values
+}func NewForm(fields ...*Field) *FormGenerates a new form with default properties
+func (f *Form) Add(fields ...*Field)Appends children
+func (f *Form) AddGlobalField(field *Field)Configures its children deeply
+func (f *Form) Bind(data any) errorCopies datas from the form to a struct
+func (f *Form) End() *FormConfigures its children deeply This function must be called after adding all
+fields
+func (f *Form) GetField(name string) *FieldReturns a child using its name
+func (f *Form) GetOption(name string) *OptionReturns an option using its name
+func (f *Form) HandleRequest(req *http.Request)Processes a request
+func (f *Form) HasField(name string) boolChecks if the form contains a child using its name
+func (f *Form) HasOption(name string) boolChecks if the form contains an option using its name
+func (f *Form) IsSubmitted() boolChecks if the form is submitted
+func (f *Form) IsValid() boolChecks the a form is valid
+func (f *Form) Mount(data any) errorCopies datas from a struct to the form
+func (f *Form) ResetErrors() *FormResets the form errors
+func (f *Form) WithAction(v string) *FormSets the action of the form (eg: “/”)
+func (f *Form) WithMethod(v string) *FormSets the method of the format (http.MethodPost, http.MethodGet, …)
+func (f *Form) WithName(v string) *FormSets the name of the form (used to compute name of fields)
+func (f *Form) WithOptions(options ...*Option) *FormAppends options to the form
+| Name | +Type | +Description | +
|---|---|---|
attr |
+ map[string]string |
+ List of extra attributes | +
help |
+ string |
+ Helper | +
Creating and processing HTML forms is hard and repetitive. You need to deal with rendering HTML form fields, validating submitted data, mapping the form data into objects and a lot more. go-form includes a powerful form feature that provides all these features.
+go-form is heavily influenced by Symfony Form. It includes:
+go get gitnet.fr/deblan/go-formgo-form allows you to render a form using Go’s built-in template engine. +Here is a simple example that displays a form:
+myForm := form.NewForm(...)
+
+render := theme.NewRenderer(theme.Bootstrap5)
+
+tpl, _ := template.New("page").Funcs(render.FuncMap()).Parse(`
+ <html>
+ <head>
+ <title>My form</title>
+ </head>
+ <body>
+ {{ form .Form }}
+ </body>
+ </html>
+`)
+
+b := new(strings.Builder)
+
+tpl.Execute(w, map[string]any{
+ "Form": myForm,
+})
+
+fmt.Println(b.String())
+@import "fmt"
+@import "html/template"
+@import "strings"
+@import
+@import "github.com/yosssi/gohtml"
+@import "gitnet.fr/deblan/go-form/example"
+@import "gitnet.fr/deblan/go-form/theme"
+
+form := example.CreateDataForm()
+render := theme.NewRenderer(theme.Html5)
+// render := theme.NewRenderer(theme.Bootstrap5)
+
+tpl, _ := template.New("page").Funcs(render.FuncMap()).Parse(`{{ form .Form }}`)
+
+buff := new(strings.Builder)
+
+tpl.Execute(buff, map[string]any{
+ "Form": form,
+})
+
+fmt.Println(gohtml.Format(buff.String()))
+
+Other helper functions are available to render specific parts of the form:
+form_errors: displays the form’s global errorsform_row : renders the label, errors, and widget of a fieldform_label: renders only the label of a fieldform_widget: renders only the widget of a fieldform_widget_errors: renders only the errors of a specific fieldgo-form provides 2 themes:
+theme.Html5: a basic view without classestheme.Bootstrap5: a theme for Bootstrap 5You can add a custom theme. Learn by reading the Bootstrap5 theme.
+ +import (
+ "html/template"
+ "net/http"
+
+ "gitnet.fr/deblan/go-form/form"
+ "gitnet.fr/deblan/go-form/theme"
+)// Let's create a new form
+// You can pass *form.Field as arguments
+myForm := form.NewForm(field1, field2, ...)
+
+// Add somes fields
+myForm.Add(field3, field4, ...)
+
+// Set the method
+// <form method="POST" ...>
+myForm.WithMethod(http.MethodPost)
+
+// Define the action
+// <form action="/" ...>
+myForm.WithAction("/")
+
+// Set a name
+myForm.WithName("myForm")
+
+// Add options
+myForm.WithOptions(option1, option2, ...)
+
+// When all fields are added, call End()
+myForm.End()Some options are natively supported in go-form themes.
+myForm.WithOptions(
+ form.NewOption("help", "A help for the form"),
+ // <form data-foo="bar" data-bar="bar" ...
+ form.NewOption("attr", form.Attrs{
+ "data-foo": "foo",
+ "data-bar": "bar",
+ }),
+)This step is not required when you does not want to pre-fill the form. +Your struct can be complexe and the form only map existing properties.
+type Person struct {
+ Name string
+ Age int
+}
+
+data := Person{
+ Name: "Alice",
+ Age: 42,
+}
+
+// Assuming 2 fields named "Name" and "Age" exist
+myForm.Mount(data)http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ // myForm := form.NewForm(...)
+
+ render := theme.NewRenderer(theme.Html5)
+ tpl, _ := template.New("page").Funcs(render.FuncMap()).Parse(`{{ form .Form }}`)
+
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+
+ tpl.Execute(w, map[string]any{
+ "Form": myForm,
+ })
+}This is the final step. After the form handles the request, you can check if the form has been submitted, check the values are valid and finally populate your struct.
+http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ // data := Person{...}
+ // myForm := form.NewForm(...)
+
+ if r.Method == myForm.Method {
+ myForm.HandleRequest(r)
+
+ if myForm.IsSubmitted() && myForm.IsValid() {
+ myForm.Bind(&data)
+ }
+ }
+}){{ .dump }}
- {{ .json }}
- {{ .dump }}
- {{ .json }}
- The form is valid!
- {{else}} -The form is invalid!
- {{end}} - - {{ form .form }} - - - - - diff --git a/favicon-16x16.png b/favicon-16x16.png new file mode 100644 index 0000000..0f2dd2b Binary files /dev/null and b/favicon-16x16.png differ diff --git a/favicon-32x32.png b/favicon-32x32.png new file mode 100644 index 0000000..5c1aea5 Binary files /dev/null and b/favicon-32x32.png differ diff --git a/favicon-dark.svg b/favicon-dark.svg new file mode 100644 index 0000000..3b49e35 --- /dev/null +++ b/favicon-dark.svg @@ -0,0 +1,13 @@ + diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..553fa15 Binary files /dev/null and b/favicon.ico differ diff --git a/favicon.svg b/favicon.svg new file mode 100644 index 0000000..6a08d10 --- /dev/null +++ b/favicon.svg @@ -0,0 +1,13 @@ + diff --git a/form/field.go b/form/field.go deleted file mode 100644 index fe63326..0000000 --- a/form/field.go +++ /dev/null @@ -1,423 +0,0 @@ -package form - -// @license GNU AGPL version 3 or any later version -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see