-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfield.go
226 lines (183 loc) · 7.19 KB
/
field.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package formulate
import (
"reflect"
"strings"
"github.com/fatih/camelcase"
)
// StructField is a wrapper around the reflect.StructField type. The rendering behavior of form elements is controlled
// by Struct Tags. The following tags are currently available:
//
// - name (e.g. name:"Phone Number") - this overwrites the name used in the label. This value can be left empty.
// - help (e.g. help:"Enter your phone number, including area code") - this text is displayed alongside the input field as a prompt.
// - show (e.g. show:"adminOnly") - controls visibility of elements. See HTMLEncoder.AddShowCondition for more details.
// If "contents" is used, the field is shown and the parent fieldset (if any) will be omitted.
// If "fieldset" is used, anonymous structs will be built as fieldsets too, if their name is also set.
// - type (e.g. type:"tel", type:"hidden") - sets the HTML input "type" attribute. type:"hidden" will be rendered without labels and help text.
// - elem (elem:"textarea") - used to specify that a text input should use a <textarea> rather than an input field.
// - min (e.g. min:"0") - minimum value for number inputs, minimum length for text inputs
// - max (e.g. max:"10") - maximum value for number inputs, maximum length for text inputs
// - step (e.g. step:"0.1") - step size for number inputs
// - pattern (e.g. pattern:"[a-z]+" - regex pattern for text inputs
// - required (true/false) - adds the required attribute to the element.
// - placeholder (e.g. placeholder:"phone number") - indicates a placeholder for the element.
// - validators (e.g. "email,notempty") - which registered Validators to use.
//
// These can all be used in combination with one another in a struct field. A full example of the above types is:
//
// type Address struct {
// HouseNumber int `min:"0" help:"Please enter your house number" name:"House Number (if any)"
// AddressLine1 string
// DeliveryInstructions string `elem:"textarea"`
// CountryCode string `pattern:"[A-Za-z]{3}"`
// }
type StructField struct {
reflect.StructField
// ValidationErrors are the errors present for the StructField. They are only set on an encode.
ValidationErrors []ValidationError
}
// GetName returns the name of the StructField, taking into account tag name overrides.
func (sf StructField) GetName() string {
tagName := sf.Tag.Get("name")
if tagName == "-" {
return ""
}
if tagName != "" {
return tagName
}
return camelCase(sf.Name)
}
// GetHelpText returns the help text for the field.
func (sf StructField) GetHelpText() string {
return sf.Tag.Get("help")
}
// Hidden determines if a StructField is hidden based on the showConditions.
// If multiple show conditions are specified, they must all pass for the field to be visible.
func (sf StructField) Hidden(showConditions ShowConditions) bool {
showTag := sf.Tag.Get("show")
if showTag == "-" {
return true
}
visible := true
showTags := strings.Split(showTag, ",")
if _, ok := showConditions[showConditionAllFields]; ok {
showTags = append(showTags, showConditionAllFields)
}
for _, tag := range showTags {
if conditionFuncs, ok := showConditions[tag]; ok {
for _, fn := range conditionFuncs {
visible = visible && fn(sf)
}
}
}
return !visible
}
func camelCase(s string) string {
return strings.Join(camelcase.Split(s), " ")
}
// InputType returns the HTML <input> element type attribute
func (sf StructField) InputType(original string) string {
t := sf.Tag.Get("type")
if t != "" {
return t
}
return original
}
// Elem returns the element to be used. Currently, the only supported value is <textarea>.
// <input> will be used if not specified.
func (sf StructField) Elem() string {
return sf.Tag.Get("elem")
}
// HasMin determines if a StructField has a minimum value
func (sf StructField) HasMin() bool {
return sf.Tag.Get("min") != ""
}
// Min is the minimum value of the StructField
func (sf StructField) Min() string {
return sf.Tag.Get("min")
}
// HasMax determines if a StructField has a maximum value
func (sf StructField) HasMax() bool {
return sf.Tag.Get("max") != ""
}
// Max is the maximum value of the StructField
func (sf StructField) Max() string {
return sf.Tag.Get("max")
}
// HasStep determines if a StructField has a step value
func (sf StructField) HasStep() bool {
return sf.Tag.Get("step") != ""
}
// Step value of the StructField
func (sf StructField) Step() string {
return sf.Tag.Get("step")
}
// Pattern is the regex for the input field.
func (sf StructField) Pattern() string {
return sf.Tag.Get("pattern")
}
// Placeholder defines the placeholder attribute for the input field
func (sf StructField) Placeholder() string {
return sf.Tag.Get("placeholder")
}
// Required indicates that an input field must be filled in.
func (sf StructField) Required() bool {
return sf.Tag.Get("required") == "true"
}
func (sf StructField) IsExported() bool {
return sf.StructField.PkgPath == ""
}
// BuildFieldset determines whether a given struct should be inside its own fieldset. Use the Struct Tag
// show:"contents" to indicate that a fieldset should not be built for this struct. Use show:"fieldset"
// to indicate that anonymous structs should be built in a fieldset.
func (sf StructField) BuildFieldset() bool {
showTag := sf.Tag.Get("show")
for _, tag := range strings.Split(showTag, ",") {
if tag == "contents" {
return false
} else if tag == "fieldset" {
// allow anonymous structs to be built in a fieldset
return true
}
}
return !sf.StructField.Anonymous
}
// Validators are the TagNames of the registered Validators. Multiple Validators may be specified, separated by a comma.
func (sf StructField) Validators() []ValidatorKey {
split := strings.Split(sf.Tag.Get("validators"), ",")
var keys []ValidatorKey
for _, key := range split {
keys = append(keys, ValidatorKey(key))
}
return keys
}
// ShowConditionFunc is a function which determines whether to show a form element. See: HTMLEncoder.AddShowCondition
type ShowConditionFunc func(field StructField) bool
type ShowConditions map[string][]ShowConditionFunc
// AddShowCondition allows you to determine visibility of certain form elements.
// For example, given the following struct:
//
// type Example struct {
// Name string
// SecretOption bool `show:"adminOnly"`
// }
//
// If you wanted to make the SecretOption field only show to admins, you would call AddShowCondition as follows:
//
// AddShowCondition("adminOnly", func(field StructField) bool {
// // some code that determines if we are 'admin'
// })
//
// You can add multiple ShowConditions for the same key.
//
// It is also possible to add ShowConditionFuncs to be used on every StructField. See AddGlobalShowCondition.
//
// Note: ShowConditions should be added to both the Encoder and Decoder.
func (s ShowConditions) AddShowCondition(key string, fn ShowConditionFunc) {
s[key] = append(s[key], fn)
}
// AddGlobalShowCondition adds a ShowConditionFunc to be called on all StructFields.
func (s ShowConditions) AddGlobalShowCondition(fn ShowConditionFunc) {
s[showConditionAllFields] = append(s[showConditionAllFields], fn)
}
// showConditionAllFields is a special key for a ShowConditionFunc that is used on all fields.
const showConditionAllFields = "*"