-
Notifications
You must be signed in to change notification settings - Fork 6
/
integer.go
253 lines (226 loc) · 7.93 KB
/
integer.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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
package textbox
import (
"context"
"encoding/gob"
"fmt"
"strconv"
"github.com/goradd/goradd/pkg/page"
)
type IntegerI interface {
TextboxI
SetMinValue(minValue int, invalidMessage string) IntegerI
SetMaxValue(maxValue int, invalidMessage string) IntegerI
}
// IntegerTextbox is a textbox that only permits integers.
// It does server-side validation on a minimum and maximum value when set.
// It sets the inputmode attribute on the html textbox to "numeric" to inform mobile browsers
// that numeric data is expected. If you would like client-side validation and up and down spinner arrows,
// call SetType(NumberType), and set the min and max attributes.
type IntegerTextbox struct {
Textbox
minValue *int
maxValue *int
}
func NewIntegerTextbox(parent page.ControlI, id string) *IntegerTextbox {
t := &IntegerTextbox{}
t.Self = t
t.Init(parent, id)
return t
}
func (t *IntegerTextbox) Init(parent page.ControlI, id string) {
t.Textbox.Init(parent, id)
t.ValidateWith(IntValidator{})
t.SetAttribute("inputmode", "numeric") // set inputmode for mobile input, but do it here so programmer could cancel this if desired.
}
func (t *IntegerTextbox) this() IntegerI {
return t.Self.(IntegerI)
}
// SetMinValue creates a validator that makes sure the value of the text box is at least the
// given value. Specify your own error message, or leave the error message blank and a standard error message will
// be presented if the value is not valid.
func (t *IntegerTextbox) SetMinValue(minValue int, invalidMessage string) IntegerI {
t.ValidateWith(MinIntValidator{minValue, invalidMessage})
t.minValue = new(int)
*t.minValue = minValue
return t.this()
}
// SetMaxValue creates a validator that makes sure the value of the text box is at most the
// given value. Specify your own error message, or leave the error message blank and a standard error message will
// be presented if the value is not valid.
func (t *IntegerTextbox) SetMaxValue(maxValue int, invalidMessage string) IntegerI {
t.ValidateWith(MaxIntValidator{maxValue, invalidMessage})
t.maxValue = new(int)
*t.maxValue = maxValue
return t.this()
}
func (t *IntegerTextbox) SetValue(v interface{}) page.ControlI {
t.Textbox.SetValue(v)
newValue := t.Int()
if t.minValue != nil && *t.minValue > newValue {
panic("Setting IntegerTextbox to a value less than minimum value.")
}
if t.maxValue != nil && *t.maxValue < newValue {
panic("Setting IntegerTextbox to a value greater than the maximum value.")
}
return t.this()
}
func (t *IntegerTextbox) SetInt(v int) IntegerI {
t.Textbox.SetValue(v)
if t.minValue != nil && *t.minValue > v {
panic("Setting IntegerTextbox to a value less than minimum value.")
}
if t.maxValue != nil && *t.maxValue < v {
panic("Setting IntegerTextbox to a value greater than the maximum value.")
}
return t.this()
}
func (t *IntegerTextbox) Value() interface{} {
return t.Int()
}
func (t *IntegerTextbox) Int() int {
text := t.Textbox.Text()
v, _ := strconv.Atoi(text)
return v
}
func (t *IntegerTextbox) Int64() int64 {
text := t.Textbox.Text()
i64, _ := strconv.ParseInt(text, 10, 0)
return i64
}
type IntValidator struct {
Message string
}
func (v IntValidator) Validate(c page.ControlI, s string) (msg string) {
if s == "" {
return "" // empty textbox is checked elsewhere
}
if _, err := strconv.Atoi(s); err != nil {
if v.Message == "" {
return c.T("Please enter an integer.")
} else {
return v.Message
}
}
return
}
type MinIntValidator struct {
MinValue int
Message string
}
func (v MinIntValidator) Validate(c page.ControlI, s string) (msg string) {
if s == "" {
return "" // empty textbox is checked elsewhere
}
if val, _ := strconv.Atoi(s); val < v.MinValue {
if v.Message == "" {
return fmt.Sprintf(c.GT("Enter at least %d"), v.MinValue)
} else {
return v.Message
}
}
return
}
type MaxIntValidator struct {
MaxValue int
Message string
}
func (v MaxIntValidator) Validate(c page.ControlI, s string) (msg string) {
if s == "" {
return "" // empty textbox is checked elsewhere
}
if val, _ := strconv.Atoi(s); val > v.MaxValue {
if v.Message == "" {
return fmt.Sprintf(c.GT("Enter at most %d"), v.MaxValue)
} else {
return v.Message
}
}
return
}
type IntegerLimit struct {
Value int
InvalidMessage string
}
// IntegerTextboxCreator creates an integer textbox.
// Pass it to AddControls of a control, or as a Child of
// a FormFieldWrapper.
type IntegerTextboxCreator struct {
// ID is the control id of the html widget and must be unique to the page
ID string
// Placeholder is the placeholder attribute of the textbox and shows as help text inside the field
Placeholder string
// Type is the type attribute of the textbox
Type string
// MinLength is the minimum number of characters that the user is required to enter. If the
// length is less than this number, a validation error will be shown.
MinLength int
// MaxLength is the maximum number of characters that the user is required to enter. If the
// length is more than this number, a validation error will be shown.
MaxLength int
// ColumnCount is the number of characters wide the textbox will be, and becomes the width attribute in the tag.
// The actual width is browser dependent. For better control, use a width style property.
ColumnCount int
// RowCount creates a multi-line textarea with the given number of rows. By default the
// textbox will expand vertically by this number of lines. Use a height style property for
// better control of the height of a textbox.
RowCount int
// ReadOnly sets the readonly attribute of the textbox, which prevents it from being changed by the user.
ReadOnly bool
// SaveState will save the text in the textbox, to be restored if the user comes back to the page.
// It is particularly helpful when the textbox is being used to filter the results of a query, so that
// when the user comes back to the page, he does not have to type the filter text again.
SaveState bool
// MinValue is the minimum value the user can enter. If the user does not
// enter at least this amount, or enters something that is not an integer, it will fail validation
// and the FormFieldWrapper will show an error.
MinValue *IntegerLimit
// MaxValue is the maximum value the user can enter. If the user enter more
// than this amount, or enters something that is not an integer, it will fail validation
// and the FormFieldWrapper will show an error.
MaxValue *IntegerLimit
// Value is the initial value of the textbox. Often its best to load the value in a separate Load step after creating the control.
Value interface{}
page.ControlOptions
}
// Create is called by the framework to create a new control from the Creator. You
// do not normally need to call this.
func (c IntegerTextboxCreator) Create(ctx context.Context, parent page.ControlI) page.ControlI {
ctrl := NewIntegerTextbox(parent, c.ID)
c.Init(ctx, ctrl)
return ctrl
}
// Init is called by implementations of Textboxes to initialize a control with the
// creator. You do not normally need to call this.
func (c IntegerTextboxCreator) Init(ctx context.Context, ctrl IntegerI) {
if c.MinValue != nil {
ctrl.SetMinValue(c.MinValue.Value, c.MinValue.InvalidMessage)
}
if c.MaxValue != nil {
ctrl.SetMaxValue(c.MaxValue.Value, c.MaxValue.InvalidMessage)
}
if c.Value != nil {
ctrl.SetValue(c.Value)
}
// Reuse subclass
sub := TextboxCreator{
Placeholder: c.Placeholder,
Type: c.Type,
MinLength: c.MinLength,
MaxLength: c.MaxLength,
RowCount: c.RowCount,
ReadOnly: c.ReadOnly,
ControlOptions: c.ControlOptions,
SaveState: c.SaveState,
}
sub.Init(ctx, ctrl)
}
// GetIntegerTextbox is a convenience method to return the control with the given id from the page.
func GetIntegerTextbox(c page.ControlI, id string) *IntegerTextbox {
return c.Page().GetControl(id).(*IntegerTextbox)
}
func init() {
gob.Register(MaxIntValidator{})
gob.Register(MinIntValidator{})
gob.Register(IntValidator{})
page.RegisterControl(&IntegerTextbox{})
}