form_input 0.9.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/LICENSE +19 -0
- data/README.md +3160 -0
- data/Rakefile +19 -0
- data/example/controllers/ramaze/press_release.rb +104 -0
- data/example/controllers/ramaze/profile.rb +38 -0
- data/example/controllers/sinatra/press_release.rb +114 -0
- data/example/controllers/sinatra/profile.rb +39 -0
- data/example/forms/change_password_form.rb +17 -0
- data/example/forms/login_form.rb +14 -0
- data/example/forms/lost_password_form.rb +14 -0
- data/example/forms/new_password_form.rb +15 -0
- data/example/forms/password_form.rb +18 -0
- data/example/forms/press_release_form.rb +153 -0
- data/example/forms/profile_form.rb +21 -0
- data/example/forms/signup_form.rb +25 -0
- data/example/views/press_release.slim +65 -0
- data/example/views/profile.slim +28 -0
- data/example/views/snippets/form_block.slim +27 -0
- data/example/views/snippets/form_chunked.slim +25 -0
- data/example/views/snippets/form_hidden.slim +21 -0
- data/example/views/snippets/form_panel.slim +89 -0
- data/form_input.gemspec +32 -0
- data/lib/form_input/core.rb +1165 -0
- data/lib/form_input/localize.rb +49 -0
- data/lib/form_input/r18n/cs.yml +97 -0
- data/lib/form_input/r18n/en.yml +70 -0
- data/lib/form_input/r18n/pl.yml +122 -0
- data/lib/form_input/r18n/sk.yml +120 -0
- data/lib/form_input/r18n.rb +163 -0
- data/lib/form_input/steps.rb +365 -0
- data/lib/form_input/types.rb +176 -0
- data/lib/form_input/version.rb +12 -0
- data/lib/form_input.rb +5 -0
- data/test/helper.rb +21 -0
- data/test/localize/en.yml +63 -0
- data/test/r18n/cs.yml +60 -0
- data/test/r18n/xx.yml +51 -0
- data/test/reference/cs.txt +352 -0
- data/test/reference/cs.yml +14 -0
- data/test/reference/en.txt +76 -0
- data/test/reference/en.yml +8 -0
- data/test/reference/pl.txt +440 -0
- data/test/reference/pl.yml +16 -0
- data/test/reference/sk.txt +352 -0
- data/test/reference/sk.yml +14 -0
- data/test/test_core.rb +1272 -0
- data/test/test_localize.rb +27 -0
- data/test/test_r18n.rb +373 -0
- data/test/test_steps.rb +482 -0
- data/test/test_types.rb +307 -0
- metadata +145 -0
@@ -0,0 +1,365 @@
|
|
1
|
+
# Support for multi-step forms.
|
2
|
+
|
3
|
+
class FormInput
|
4
|
+
|
5
|
+
# Turn this form into multi-step form using given steps.
|
6
|
+
# Returns self for chaining.
|
7
|
+
def self.define_steps( steps )
|
8
|
+
@steps = steps = steps.to_hash.dup.freeze
|
9
|
+
|
10
|
+
self.send( :include, StepMethods )
|
11
|
+
|
12
|
+
opts = { filter: ->{ steps.keys.find{ |x| x.to_s == self } }, class: Symbol }
|
13
|
+
|
14
|
+
param :step, opts, type: :hidden
|
15
|
+
param :next, opts, type: :ignore
|
16
|
+
param :last, opts, type: :hidden
|
17
|
+
param :seen, opts, type: :hidden
|
18
|
+
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get hash mapping defined steps to their names, or nil if there are none.
|
23
|
+
def self.form_steps
|
24
|
+
@steps
|
25
|
+
end
|
26
|
+
|
27
|
+
# Additional methods used for multi-step form processing.
|
28
|
+
module StepMethods
|
29
|
+
|
30
|
+
# Initialize new instance.
|
31
|
+
def initialize( *args )
|
32
|
+
super
|
33
|
+
self.seen = last_step( seen, step )
|
34
|
+
self.step ||= steps.first
|
35
|
+
self.next ||= step
|
36
|
+
self.last ||= step
|
37
|
+
if correct_step?
|
38
|
+
self.step = self.next
|
39
|
+
self.seen = last_step( seen, previous_step( step ) )
|
40
|
+
end
|
41
|
+
self.last = last_step( step, last )
|
42
|
+
end
|
43
|
+
|
44
|
+
# Make all steps instantly available.
|
45
|
+
# Returns self for chaining.
|
46
|
+
def unlock_steps
|
47
|
+
self.last = self.seen = steps.last
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get parameters relevant for given step.
|
52
|
+
def step_params( step )
|
53
|
+
fail( ArgumentError, "invalid step name #{step}" ) unless form_steps.key?( step )
|
54
|
+
tagged_params( step )
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get the parameters relevant for the current step.
|
58
|
+
def current_params
|
59
|
+
tagged_params( step )
|
60
|
+
end
|
61
|
+
|
62
|
+
# Get the parameters irrelevant for the current step.
|
63
|
+
def other_params
|
64
|
+
untagged_params( step )
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get hash mapping defined steps to their names.
|
68
|
+
# Note that this is never localized. See step_names instead.
|
69
|
+
def form_steps
|
70
|
+
self.class.form_steps
|
71
|
+
end
|
72
|
+
|
73
|
+
# Get allowed form steps as list of symbols.
|
74
|
+
def steps
|
75
|
+
form_steps.keys
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get name of current or given step, if any.
|
79
|
+
def step_name( step = self.step )
|
80
|
+
form_steps[ step ]
|
81
|
+
end
|
82
|
+
alias raw_step_name step_name
|
83
|
+
|
84
|
+
# Get hash of steps along with their names, for use in a sidebar.
|
85
|
+
def step_names
|
86
|
+
form_steps.reject{ |k,v| v.nil? }
|
87
|
+
end
|
88
|
+
alias raw_step_names step_names
|
89
|
+
|
90
|
+
# Get index of given/current step among all steps.
|
91
|
+
def step_index( step = self.step )
|
92
|
+
steps.index( step ) or fail( ArgumentError, "invalid step name #{step}" )
|
93
|
+
end
|
94
|
+
|
95
|
+
# Test if current step is before given step.
|
96
|
+
def step_before?( step )
|
97
|
+
step_index < step_index( step )
|
98
|
+
end
|
99
|
+
|
100
|
+
# Test if current step is after given step.
|
101
|
+
def step_after?( step )
|
102
|
+
step_index > step_index( step )
|
103
|
+
end
|
104
|
+
|
105
|
+
# Get first step, or first step among given list of steps, if any.
|
106
|
+
def first_step( *args )
|
107
|
+
if args.empty?
|
108
|
+
steps.first
|
109
|
+
else
|
110
|
+
args.flatten.compact.min_by{ |x| step_index( x ) }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Get last step, or last step among given list of steps, if any.
|
115
|
+
def last_step( *args )
|
116
|
+
if args.empty?
|
117
|
+
steps.last
|
118
|
+
else
|
119
|
+
args.flatten.compact.max_by{ |x| step_index( x ) }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Test if given/current step is the first step.
|
124
|
+
def first_step?( step = self.step )
|
125
|
+
step == first_step
|
126
|
+
end
|
127
|
+
|
128
|
+
# Test if given/current step is the last step.
|
129
|
+
def last_step?( step = self.step )
|
130
|
+
step == last_step
|
131
|
+
end
|
132
|
+
|
133
|
+
# Get steps before given/current step.
|
134
|
+
def previous_steps( step = self.step )
|
135
|
+
index = steps.index( step ) || 0
|
136
|
+
steps.first( index )
|
137
|
+
end
|
138
|
+
|
139
|
+
# Get steps after given/current step.
|
140
|
+
def next_steps( step = self.step )
|
141
|
+
index = steps.index( step ) || -1
|
142
|
+
steps[ index + 1 .. -1 ]
|
143
|
+
end
|
144
|
+
|
145
|
+
# Get the next step, or nil if there is none.
|
146
|
+
def next_step( step = self.step )
|
147
|
+
next_steps( step ).first
|
148
|
+
end
|
149
|
+
|
150
|
+
# Get the previous step, or nil if there is none.
|
151
|
+
def previous_step( step = self.step )
|
152
|
+
previous_steps( step ).last
|
153
|
+
end
|
154
|
+
|
155
|
+
# Get name of the next step, if any.
|
156
|
+
def next_step_name
|
157
|
+
step_name( next_step )
|
158
|
+
end
|
159
|
+
|
160
|
+
# Get name of the previous step, if any.
|
161
|
+
def previous_step_name
|
162
|
+
step_name( previous_step )
|
163
|
+
end
|
164
|
+
|
165
|
+
# Test if the current/given step has no parameters defined.
|
166
|
+
def extra_step?( step = self.step )
|
167
|
+
step_params( step ).empty?
|
168
|
+
end
|
169
|
+
|
170
|
+
# Test if the current/given step has some parameters defined.
|
171
|
+
def regular_step?( step = self.step )
|
172
|
+
not extra_step?( step )
|
173
|
+
end
|
174
|
+
|
175
|
+
# Get steps with no parameters defined.
|
176
|
+
def extra_steps
|
177
|
+
steps.select{ |step| extra_step?( step ) }
|
178
|
+
end
|
179
|
+
|
180
|
+
# Get steps with some parameters defined.
|
181
|
+
def regular_steps
|
182
|
+
steps.select{ |step| regular_step?( step ) }
|
183
|
+
end
|
184
|
+
|
185
|
+
# Filter steps by testing their corresponding parameters with given block. Excludes steps without parameters.
|
186
|
+
def filter_steps
|
187
|
+
steps.select do |step|
|
188
|
+
params = step_params( step )
|
189
|
+
yield params unless params.empty?
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Get steps which have required parameters. Excludes steps without parameters.
|
194
|
+
def required_steps
|
195
|
+
filter_steps{ |params| params.any?{ |p| p.required? } }
|
196
|
+
end
|
197
|
+
|
198
|
+
# Get steps which have no required parameters. Excludes steps without parameters.
|
199
|
+
def optional_steps
|
200
|
+
filter_steps{ |params| params.none?{ |p| p.required? } }
|
201
|
+
end
|
202
|
+
|
203
|
+
# Test if given/current has some required parameters. Considered false for steps without parameters.
|
204
|
+
def required_step?( step = self.step )
|
205
|
+
step_params( step ).any?{ |p| p.required? }
|
206
|
+
end
|
207
|
+
|
208
|
+
# Test if given/current step has no required parameters. Considered true for steps without parameters.
|
209
|
+
def optional_step?( step = self.step )
|
210
|
+
not required_step?( step )
|
211
|
+
end
|
212
|
+
|
213
|
+
# Get steps which have some data filled in. Excludes steps without parameters.
|
214
|
+
def filled_steps
|
215
|
+
filter_steps{ |params| params.any?{ |p| p.filled? } }
|
216
|
+
end
|
217
|
+
|
218
|
+
# Get steps which have no data filled in. Excludes steps without parameters.
|
219
|
+
def unfilled_steps
|
220
|
+
filter_steps{ |params| params.none?{ |p| p.filled? } }
|
221
|
+
end
|
222
|
+
|
223
|
+
# Test if given/current step has some data filled in. Considered true for steps without parameters.
|
224
|
+
def filled_step?( step = self.step )
|
225
|
+
params = step_params( step )
|
226
|
+
params.empty? or params.any?{ |p| p.filled? }
|
227
|
+
end
|
228
|
+
|
229
|
+
# Test if given/current step has no data filled in. Considered false for steps without parameters.
|
230
|
+
def unfilled_step?( step = self.step )
|
231
|
+
not filled_step?( step )
|
232
|
+
end
|
233
|
+
|
234
|
+
# Get steps which have all data filled in correctly. Excludes steps without parameters.
|
235
|
+
def correct_steps
|
236
|
+
filter_steps{ |params| valid?( params ) }
|
237
|
+
end
|
238
|
+
|
239
|
+
# Get steps which have some invalid data filled in. Excludes steps without parameters.
|
240
|
+
def incorrect_steps
|
241
|
+
filter_steps{ |params| invalid?( params ) }
|
242
|
+
end
|
243
|
+
|
244
|
+
# Get first step with invalid data, or nil if there is none.
|
245
|
+
def incorrect_step
|
246
|
+
incorrect_steps.first
|
247
|
+
end
|
248
|
+
|
249
|
+
# Test if the current/given step has all data filled in correctly. Considered true for steps without parameters.
|
250
|
+
def correct_step?( step = self.step )
|
251
|
+
valid?( step_params( step ) )
|
252
|
+
end
|
253
|
+
|
254
|
+
# Test if the current/given step has some invalid data filled in. Considered false for steps without parameters.
|
255
|
+
def incorrect_step?( step = self.step )
|
256
|
+
invalid?( step_params( step ) )
|
257
|
+
end
|
258
|
+
|
259
|
+
# Get steps with some parameters enabled. Excludes steps without parameters.
|
260
|
+
def enabled_steps
|
261
|
+
filter_steps{ |params| params.any?{ |p| p.enabled? } }
|
262
|
+
end
|
263
|
+
|
264
|
+
# Get steps with all parameters disabled. Excludes steps without parameters.
|
265
|
+
def disabled_steps
|
266
|
+
filter_steps{ |params| params.all?{ |p| p.disabled? } }
|
267
|
+
end
|
268
|
+
|
269
|
+
# Test if given/current step has some parameters enabled. Considered true for steps without parameters.
|
270
|
+
def enabled_step?( step = self.step )
|
271
|
+
params = step_params( step )
|
272
|
+
params.empty? or params.any?{ |p| p.enabled? }
|
273
|
+
end
|
274
|
+
|
275
|
+
# Test if given/current step has all parameters disabled. Considered false for steps without parameters.
|
276
|
+
def disabled_step?( step = self.step )
|
277
|
+
not enabled_step?( step )
|
278
|
+
end
|
279
|
+
|
280
|
+
# Get unfinished steps, those we have not yet visited or visited for the first time.
|
281
|
+
def unfinished_steps
|
282
|
+
next_steps( seen )
|
283
|
+
end
|
284
|
+
|
285
|
+
# Get finished steps, those we have visited or skipped over before.
|
286
|
+
def finished_steps
|
287
|
+
steps - unfinished_steps
|
288
|
+
end
|
289
|
+
|
290
|
+
# Test if given/current step was not yet visited or was visited for the first time.
|
291
|
+
def unfinished_step?( step = self.step )
|
292
|
+
index = seen ? step_index( seen ) : -1
|
293
|
+
step_index( step ) > index
|
294
|
+
end
|
295
|
+
|
296
|
+
# Test if given/current step was visited or skipped over before.
|
297
|
+
def finished_step?( step = self.step )
|
298
|
+
not unfinished_step?( step )
|
299
|
+
end
|
300
|
+
|
301
|
+
# Get yet inaccessible steps, excluding the last accessed step.
|
302
|
+
def inaccessible_steps
|
303
|
+
next_steps( last )
|
304
|
+
end
|
305
|
+
|
306
|
+
# Get already accessible steps, including the last accessed step.
|
307
|
+
def accessible_steps
|
308
|
+
steps - inaccessible_steps
|
309
|
+
end
|
310
|
+
|
311
|
+
# Test if given/current step is inaccessible.
|
312
|
+
def inaccessible_step?( step = self.step )
|
313
|
+
step_index( step ) > step_index( last )
|
314
|
+
end
|
315
|
+
|
316
|
+
# Test if given/current step is accessible.
|
317
|
+
def accessible_step?( step = self.step )
|
318
|
+
not inaccessible_step?( step )
|
319
|
+
end
|
320
|
+
|
321
|
+
# Get correct finished steps. Includes finished steps without parameters.
|
322
|
+
def complete_steps
|
323
|
+
finished_steps.select{ |step| correct_step?( step ) }
|
324
|
+
end
|
325
|
+
|
326
|
+
# Get incorrect finished steps. Excludes steps without parameters.
|
327
|
+
def incomplete_steps
|
328
|
+
finished_steps.select{ |step| incorrect_step?( step ) }
|
329
|
+
end
|
330
|
+
|
331
|
+
# Test if given/current step is one of the complete steps.
|
332
|
+
def complete_step?( step = self.step )
|
333
|
+
finished_step?( step ) and correct_step?( step )
|
334
|
+
end
|
335
|
+
|
336
|
+
# Test if given/current step is one of the incomplete steps.
|
337
|
+
def incomplete_step?( step = self.step )
|
338
|
+
finished_step?( step ) and incorrect_step?( step )
|
339
|
+
end
|
340
|
+
|
341
|
+
# Get steps which shell be displayed as correct.
|
342
|
+
def good_steps
|
343
|
+
steps.select{ |step| good_step?( step ) }
|
344
|
+
end
|
345
|
+
|
346
|
+
# Get steps which shell be displayed as incorrect.
|
347
|
+
def bad_steps
|
348
|
+
steps.select{ |step| bad_step?( step ) }
|
349
|
+
end
|
350
|
+
|
351
|
+
# Test if given/current step shell be displayed as correct.
|
352
|
+
def good_step?( step = self.step )
|
353
|
+
complete_step?( step ) and filled_step?( step ) and regular_step?( step )
|
354
|
+
end
|
355
|
+
|
356
|
+
# Test if given/current step shell be displayed as incorrect.
|
357
|
+
def bad_step?( step = self.step )
|
358
|
+
incomplete_step?( step )
|
359
|
+
end
|
360
|
+
|
361
|
+
end
|
362
|
+
|
363
|
+
end
|
364
|
+
|
365
|
+
# EOF #
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# Common form types.
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
# Extend forms with arguments for form parameter types which are used often.
|
7
|
+
class FormInput
|
8
|
+
|
9
|
+
# Regular expressions commonly used to validate input arguments.
|
10
|
+
|
11
|
+
# Matches names using latin alphabet.
|
12
|
+
LATIN_NAMES_RE = /\A[\p{Latin}\-\. ]+\z/u
|
13
|
+
|
14
|
+
# Matches common email addresses. Note that it doesn't match all addresses allowed by RFC, though.
|
15
|
+
SIMPLE_EMAIL_RE = /\A[-_.=+%a-z0-9]+@(?:[-_a-z0-9]+\.)+[a-z]{2,4}\z/i
|
16
|
+
|
17
|
+
# Matches generic ZIP code. Note that the real format changes for each country.
|
18
|
+
ZIP_CODE_RE = /\A[A-Z\d]++(?:[- ]?[A-Z\d]+)*+\z/i
|
19
|
+
|
20
|
+
# Filter for phone numbers.
|
21
|
+
PHONE_NUMBER_FILTER = ->{ gsub( /\s*[-\/\.]\s*/, '-' ).gsub( /\s+/, ' ' ).strip }
|
22
|
+
|
23
|
+
# Matches generic phone number.
|
24
|
+
PHONE_NUMBER_RE = /\A\+?\d++(?:[- ]?(?:\d+|\(\d+\)))*+(?:[- ]?[A-Z\d]+)*+\z/i
|
25
|
+
|
26
|
+
# Basic types.
|
27
|
+
|
28
|
+
# Integer number.
|
29
|
+
INTEGER_ARGS = {
|
30
|
+
filter: ->{ ( Integer( self, 10 ) rescue self ) unless empty? },
|
31
|
+
class: Integer,
|
32
|
+
}
|
33
|
+
|
34
|
+
# Float number.
|
35
|
+
FLOAT_ARGS = {
|
36
|
+
filter: ->{ ( Float( self ) rescue self ) unless empty? },
|
37
|
+
class: Float,
|
38
|
+
}
|
39
|
+
|
40
|
+
# Boolean value, displayed as a select menu.
|
41
|
+
BOOL_ARGS = {
|
42
|
+
type: :select,
|
43
|
+
data: [ [ true, 'Yes' ], [ false, 'No' ] ],
|
44
|
+
filter: ->{ self == 'true' unless empty? },
|
45
|
+
class: [ TrueClass, FalseClass ],
|
46
|
+
}
|
47
|
+
|
48
|
+
# Boolean value, displayed as a checkbox.
|
49
|
+
CHECKBOX_ARGS = {
|
50
|
+
type: :checkbox,
|
51
|
+
filter: ->{ not empty? },
|
52
|
+
format: ->{ self if self },
|
53
|
+
class: [ TrueClass, FalseClass ],
|
54
|
+
}
|
55
|
+
|
56
|
+
# Address fields.
|
57
|
+
|
58
|
+
# Email.
|
59
|
+
EMAIL_ARGS = {
|
60
|
+
match: SIMPLE_EMAIL_RE,
|
61
|
+
}
|
62
|
+
|
63
|
+
# Zip code.
|
64
|
+
ZIP_ARGS = {
|
65
|
+
match: ZIP_CODE_RE,
|
66
|
+
}
|
67
|
+
|
68
|
+
# Phone number.
|
69
|
+
PHONE_ARGS = {
|
70
|
+
filter: PHONE_NUMBER_FILTER,
|
71
|
+
match: PHONE_NUMBER_RE,
|
72
|
+
}
|
73
|
+
|
74
|
+
# Date and time.
|
75
|
+
|
76
|
+
# Full time format.
|
77
|
+
TIME_FORMAT = "%Y-%m-%d %H:%M:%S".freeze
|
78
|
+
# Full time format example.
|
79
|
+
TIME_FORMAT_EXAMPLE = "YYYY-MM-DD HH:MM:SS".freeze
|
80
|
+
|
81
|
+
# Full time.
|
82
|
+
TIME_ARGS = {
|
83
|
+
placeholder: TIME_FORMAT_EXAMPLE,
|
84
|
+
filter: ->{ ( FormInput.parse_time!( self, TIME_FORMAT ) rescue self ) unless empty? },
|
85
|
+
format: ->{ utc.strftime( TIME_FORMAT ) rescue self },
|
86
|
+
class: Time,
|
87
|
+
}
|
88
|
+
|
89
|
+
# US date format.
|
90
|
+
US_DATE_FORMAT = "%m/%d/%Y".freeze
|
91
|
+
# US date format example.
|
92
|
+
US_DATE_FORMAT_EXAMPLE = "MM/DD/YYYY".freeze
|
93
|
+
|
94
|
+
# Time in US date format.
|
95
|
+
US_DATE_ARGS = {
|
96
|
+
placeholder: US_DATE_FORMAT_EXAMPLE,
|
97
|
+
filter: ->{ ( FormInput.parse_time!( self, US_DATE_FORMAT ) rescue self ) unless empty? },
|
98
|
+
format: ->{ utc.strftime( US_DATE_FORMAT ) rescue self },
|
99
|
+
class: Time,
|
100
|
+
}
|
101
|
+
|
102
|
+
# UK date format.
|
103
|
+
UK_DATE_FORMAT = "%d/%m/%Y".freeze
|
104
|
+
# UK date format example.
|
105
|
+
UK_DATE_FORMAT_EXAMPLE = "DD/MM/YYYY".freeze
|
106
|
+
|
107
|
+
# Time in UK date format.
|
108
|
+
UK_DATE_ARGS = {
|
109
|
+
placeholder: UK_DATE_FORMAT_EXAMPLE,
|
110
|
+
filter: ->{ ( FormInput.parse_time!( self, UK_DATE_FORMAT ) rescue self ) unless empty? },
|
111
|
+
format: ->{ utc.strftime( UK_DATE_FORMAT ) rescue self },
|
112
|
+
class: Time,
|
113
|
+
}
|
114
|
+
|
115
|
+
# EU date format.
|
116
|
+
EU_DATE_FORMAT = "%-d.%-m.%Y".freeze
|
117
|
+
# EU date format example.
|
118
|
+
EU_DATE_FORMAT_EXAMPLE = "D.M.YYYY".freeze
|
119
|
+
|
120
|
+
# Time in EU date format.
|
121
|
+
EU_DATE_ARGS = {
|
122
|
+
placeholder: EU_DATE_FORMAT_EXAMPLE,
|
123
|
+
filter: ->{ ( FormInput.parse_time!( self, EU_DATE_FORMAT ) rescue self ) unless empty? },
|
124
|
+
format: ->{ utc.strftime( EU_DATE_FORMAT ) rescue self },
|
125
|
+
class: Time,
|
126
|
+
}
|
127
|
+
|
128
|
+
# Hours format.
|
129
|
+
HOURS_FORMAT = "%H:%M".freeze
|
130
|
+
# Hours format example.
|
131
|
+
HOURS_FORMAT_EXAMPLE = "HH:MM".freeze
|
132
|
+
|
133
|
+
# Seconds since midnight in hours:minutes format.
|
134
|
+
HOURS_ARGS = {
|
135
|
+
placeholder: HOURS_FORMAT_EXAMPLE,
|
136
|
+
filter: ->{ ( FormInput.parse_time( self, HOURS_FORMAT ).to_i % 86400 rescue self ) unless empty? },
|
137
|
+
format: ->{ Time.at( self ).utc.strftime( HOURS_FORMAT ) rescue self },
|
138
|
+
class: Integer,
|
139
|
+
}
|
140
|
+
|
141
|
+
# Parse time like Time#strptime but raise on trailing garbage.
|
142
|
+
# Also ignores -, _ and ^ % modifiers, so the same format can be used for both parsing and formatting.
|
143
|
+
def self.parse_time( string, format )
|
144
|
+
format = format.gsub( /%[-_^]?(.)/, '%\1' )
|
145
|
+
# Rather than using _strptime and checking the leftover field,
|
146
|
+
# add required trailing character to both the string and format parameters.
|
147
|
+
suffix = ( string[ -1 ] == "\1" ? "\2" : "\1" )
|
148
|
+
Time.strptime( "+0000 #{string}#{suffix}", "%z #{format}#{suffix}" ).utc
|
149
|
+
end
|
150
|
+
|
151
|
+
# Like parse_time, but falls back to DateTime.parse heuristics when the date/time can't be parsed.
|
152
|
+
def self.parse_time!( string, format )
|
153
|
+
parse_time( string, format )
|
154
|
+
rescue
|
155
|
+
DateTime.parse( string ).to_time.utc
|
156
|
+
end
|
157
|
+
|
158
|
+
# Transformation which drops empty values from hashes and arrays and turns empty string into nil.
|
159
|
+
PRUNED_ARGS = {
|
160
|
+
transform: ->{
|
161
|
+
case self
|
162
|
+
when Array
|
163
|
+
reject{ |v| v.nil? or ( v.respond_to?( :empty? ) && v.empty? ) }
|
164
|
+
when Hash
|
165
|
+
reject{ |k,v| v.nil? or ( v.respond_to?( :empty? ) && v.empty? ) }
|
166
|
+
when String
|
167
|
+
self unless empty?
|
168
|
+
else
|
169
|
+
self
|
170
|
+
end
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
# EOF #
|
data/lib/form_input.rb
ADDED
data/test/helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Test helper.
|
2
|
+
|
3
|
+
# Test coverage if enabled.
|
4
|
+
|
5
|
+
def jruby?
|
6
|
+
defined?( RUBY_ENGINE ) and RUBY_ENGINE == 'jruby'
|
7
|
+
end
|
8
|
+
|
9
|
+
if ENV[ 'COVERAGE' ]
|
10
|
+
require 'simplecov'
|
11
|
+
SimpleCov.start
|
12
|
+
end
|
13
|
+
|
14
|
+
begin
|
15
|
+
require 'codeclimate-test-reporter'
|
16
|
+
CodeClimate::TestReporter.start
|
17
|
+
ENV[ 'COVERAGE' ] = 'on'
|
18
|
+
rescue LoadError
|
19
|
+
end unless jruby?
|
20
|
+
|
21
|
+
# EOF #
|
@@ -0,0 +1,63 @@
|
|
1
|
+
---
|
2
|
+
forms:
|
3
|
+
test_form:
|
4
|
+
email:
|
5
|
+
title: Email
|
6
|
+
form_title: Your email
|
7
|
+
error_title: email address
|
8
|
+
password:
|
9
|
+
title: Password
|
10
|
+
msg: Password must contain one lowercase and one uppercase letter and one digit
|
11
|
+
test_inflection_form:
|
12
|
+
name:
|
13
|
+
title: Name
|
14
|
+
address:
|
15
|
+
title: Address
|
16
|
+
state:
|
17
|
+
title: State
|
18
|
+
author:
|
19
|
+
title: Author
|
20
|
+
friends:
|
21
|
+
title: Friends
|
22
|
+
chars:
|
23
|
+
title: Characters
|
24
|
+
keywords:
|
25
|
+
title: Keywords
|
26
|
+
notes:
|
27
|
+
title: Notes
|
28
|
+
test_localize_form:
|
29
|
+
simple:
|
30
|
+
title: String
|
31
|
+
utf:
|
32
|
+
title: "ěščřžýáíéúůďťň"
|
33
|
+
yaml:
|
34
|
+
title: "%"
|
35
|
+
msg: "{}"
|
36
|
+
test_localized_steps_form:
|
37
|
+
steps:
|
38
|
+
one: First
|
39
|
+
two: Second
|
40
|
+
three: Third
|
41
|
+
test_r18n_form:
|
42
|
+
msg:
|
43
|
+
title: Message
|
44
|
+
msg2:
|
45
|
+
title: Second Message
|
46
|
+
test_steps_form:
|
47
|
+
steps:
|
48
|
+
intro: Intro
|
49
|
+
email: Email
|
50
|
+
name: Name
|
51
|
+
address: Address
|
52
|
+
message: Message
|
53
|
+
test_time_types_form:
|
54
|
+
time:
|
55
|
+
placeholder: YYYY-MM-DD HH:MM:SS
|
56
|
+
us_date:
|
57
|
+
placeholder: MM/DD/YYYY
|
58
|
+
uk_date:
|
59
|
+
placeholder: DD/MM/YYYY
|
60
|
+
eu_date:
|
61
|
+
placeholder: D.M.YYYY
|
62
|
+
hours:
|
63
|
+
placeholder: HH:MM
|
data/test/r18n/cs.yml
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# Form input localization testing messages.
|
2
|
+
|
3
|
+
forms:
|
4
|
+
test_r18n_form:
|
5
|
+
msg:
|
6
|
+
title: Zpráva
|
7
|
+
form_title: Vaše zpráva
|
8
|
+
error_title: Parametr Zpráva
|
9
|
+
msg: '%p není správně vyplněn'
|
10
|
+
match_msg: '%p není ve správném tvaru'
|
11
|
+
reject_msg: '%p obsahuje nepovolené znaky'
|
12
|
+
required_msg: '%p musí být vyplněn'
|
13
|
+
msg2:
|
14
|
+
title: Druhá zpráva
|
15
|
+
error_title: Parametr druhá zpráva
|
16
|
+
msg3:
|
17
|
+
title: Třetí zpráva
|
18
|
+
test_msg: Argument %1
|
19
|
+
inflected_msg: !!inflect
|
20
|
+
s: Singular
|
21
|
+
p: Plural
|
22
|
+
texts:
|
23
|
+
test: Test
|
24
|
+
|
25
|
+
test_inflection_form:
|
26
|
+
name:
|
27
|
+
title: Jméno
|
28
|
+
gender: n
|
29
|
+
address:
|
30
|
+
title: Adresa
|
31
|
+
gender: f
|
32
|
+
state:
|
33
|
+
title: Stát
|
34
|
+
gender: mi
|
35
|
+
author:
|
36
|
+
title: Autor
|
37
|
+
gender: ma
|
38
|
+
keywords:
|
39
|
+
title: Klíčová slova
|
40
|
+
gender: n
|
41
|
+
notes:
|
42
|
+
title: Poznámky
|
43
|
+
gender: f
|
44
|
+
chars:
|
45
|
+
title: Znaky
|
46
|
+
gender: mi
|
47
|
+
friends:
|
48
|
+
title: Přátelé
|
49
|
+
gender: ma
|
50
|
+
test:
|
51
|
+
inflect: invalid
|
52
|
+
|
53
|
+
test_localized_steps_form:
|
54
|
+
steps:
|
55
|
+
one: První
|
56
|
+
two: Druhý
|
57
|
+
three: Třetí
|
58
|
+
four: Should not be here.
|
59
|
+
|
60
|
+
# EOF #
|