-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathform.go
154 lines (127 loc) · 3.38 KB
/
form.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package forms
import (
"fmt"
"html/template"
"net/url"
"reflect"
)
// Attributes is structure that contains forms or fields attributes
type Attributes map[string]interface{}
// Data is structure in which we store cleaned and initial data
type Data map[string]interface{}
// Form is structure that hold all fields, data, and
type Form struct {
// Keeps all the fields
Fields map[string]*Field
// Form attributes
Attributes Attributes
Errors []string
// Data that are used in validation
IncomingData url.Values
// After validation is done we put data here
CleanedData Data
// Initial data are used before form validation
InitialData Data
}
// Clear clears error and data on fields in form
func (f *Form) Clear() {
f.CleanedData = nil
for _, field := range f.Fields {
field.Errors = []string{}
}
}
// SetInitial sets initial data on form
func (f *Form) SetInitial(data Data) {
f.InitialData = data
for name, field := range f.Fields {
if values, ok := data[name]; ok {
field.InitialValue = values
}
}
}
// IsValid validate all fields and if all is correct assign cleaned data from
// every field to forms CleanedData attribute
func (f *Form) IsValid(data url.Values) bool {
f.Clear()
f.IncomingData = data
isValid := true
cleanedData := Data{}
for name, field := range f.Fields {
values, _ := data[name]
field.Value = values
result := field.IsValid(values)
if result {
cleanedData[name] = field.Type.CleanData(values)
} else {
isValid = false
}
}
if isValid {
f.CleanedData = cleanedData
}
return isValid
}
// IsValidMap populates data from map.
// It accepts map of string/strings with keys as field names.
func (f *Form) IsValidMap(values map[string]interface{}) bool {
data := url.Values{}
for k, v := range values {
if isSlice(v) {
s := reflect.ValueOf(v)
for i := 0; i < s.Len(); i++ {
data.Add(k, s.Index(i).String())
}
} else {
str, _ := v.(string)
data.Set(k, str)
}
}
return f.IsValid(data)
}
// OpenTag render opening tag of the form with given attributes
func (f *Form) OpenTag() template.HTML {
return template.HTML(fmt.Sprintf("<form%s>", prepareAttributes(f.Attributes, nil)))
}
// CloseTag render closing tag for form
func (f *Form) CloseTag() template.HTML {
return "</form>"
}
// HasErrors returns information if there are errors in form.
func (f *Form) HasErrors() bool {
return len(f.Errors) > 0
}
// AddError adds new error string to form.
func (f *Form) AddError(error string) {
f.Errors = append(f.Errors, error)
}
// RenderErrors render all errors as list (<ul>) with class "errors".
func (f *Form) RenderErrors() template.HTML {
if !f.HasErrors() {
return ""
}
rendered := ""
for _, err := range f.Errors {
rendered += fmt.Sprintf("<li>%s</li>\n", err)
}
return template.HTML(fmt.Sprintf("<ul class=\"errors\">\n%s</ul>", rendered))
}
// New is shorthand, and preferred way, to create new form.
// Main difference is that, this approach add field name, basing on key in map,
// to a field instance
// Example
// form := forms.New(
// map[string]*forms.Field{
// "field1": &forms.Field{},
// "field2": &forms.Field{},
// },
// forms.Attributes{"id": "test"},
// )
func New(fields map[string]*Field, attrs Attributes) *Form {
for fieldName, field := range fields {
field.Name = fieldName
}
return &Form{
Fields: fields,
Attributes: attrs,
}
}