kiss 1.7.4 → 1.8
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/bin/kiss +1 -1
- data/data/scaffold.tgz +0 -0
- data/lib/kiss/action.rb +1 -1
- data/lib/kiss/ext/core.rb +1 -1
- data/lib/kiss/form/field.rb +527 -110
- data/lib/kiss/form.rb +165 -88
- data/lib/kiss.rb +3 -3
- metadata +3 -5
- data/lib/kiss/form/field_types.rb +0 -239
data/lib/kiss/form/field.rb
CHANGED
@@ -2,134 +2,101 @@ require 'kiss/form/field';
|
|
2
2
|
|
3
3
|
class Kiss
|
4
4
|
class Form
|
5
|
+
### Abstract Field Classes ###
|
6
|
+
|
7
|
+
# Base class for single-value fields.
|
5
8
|
class Field
|
6
|
-
|
7
|
-
:
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
CURRENCY_SYMBOLS = {
|
10
|
+
:dollars => '$'
|
11
|
+
}
|
12
|
+
|
13
|
+
_attr_accessor :errors
|
14
|
+
|
15
|
+
dsl_accessor(
|
16
|
+
:form, # (Kiss::Form) form to which this field is attached
|
17
|
+
|
18
|
+
:object, # (Kiss::Model) object which holds data for this field
|
19
|
+
:save, # (Boolean) true iff field value should be saved to object (default: true)
|
20
|
+
|
21
|
+
:name, # (String) name attribute of field element and input param
|
22
|
+
:key, # (Symbol) key in object for the data accessed by this field
|
23
|
+
:value, # (data type depends on format) value of field
|
24
|
+
|
25
|
+
:required, # (Boolean) true iff user must supply a enter/choose for this field (default: false)
|
26
|
+
:format, # (Symbol) format for validation of field value input (see Kiss::Format)
|
27
|
+
:match, # (String) name of field whose input value must match this field's value
|
28
|
+
:unique, # (Boolean) true iff this field's value must be unique for key in the object model
|
29
|
+
|
30
|
+
:label, # (String) label to present before this field in HTML
|
31
|
+
:prompt, # (String) prompt to present above this field in HTML
|
32
|
+
:tip, # (String) tip to present following this field in HTML
|
33
|
+
# TODO: Some currencies should be prefixed, while others should be suffixed.
|
34
|
+
:currency, # (String) currency to present directly before this field in HTML
|
35
|
+
:style, # (String) style attribute for field element in HTML
|
36
|
+
:html, # (Hash or String) extra HTML attributes to add to field element
|
37
|
+
:attach_errors, # (Boolean) if true, display field errors with this field; else at top of form
|
38
|
+
|
39
|
+
:factor, # (Numeric) factor to be multiplied by value
|
40
|
+
:digest, # (Symbol) digest to be applied to value before saving to object
|
41
|
+
|
42
|
+
:min_value_size, # (Numeric) minimum size of field value
|
43
|
+
:max_value_size # (Numeric) maximum size of field value
|
44
|
+
)
|
14
45
|
alias_method :min_value_length, :min_value_size
|
15
46
|
alias_method :max_value_length, :max_value_size
|
16
47
|
|
17
48
|
def method_missing(method, *args, &block)
|
18
49
|
@_form.action.send method, *args, &block
|
19
50
|
end
|
20
|
-
|
21
|
-
def options_keys(value, display)
|
22
|
-
@_options_value_key = value
|
23
|
-
@_options_display_key = display
|
24
|
-
end
|
25
|
-
alias_method :option_keys, :options_keys
|
26
51
|
|
27
52
|
def debug(*args)
|
28
53
|
@_form.delegate.request.debug(args.first, Kernel.caller[0])
|
29
54
|
end
|
55
|
+
|
56
|
+
class << self
|
57
|
+
_attr_accessor :type
|
58
|
+
end
|
59
|
+
|
60
|
+
def type
|
61
|
+
self.class.type
|
62
|
+
end
|
30
63
|
|
31
64
|
def initialize(form, *args, &block)
|
65
|
+
# call as method to allow subclasses to override
|
66
|
+
self.form = form
|
67
|
+
|
32
68
|
# defaults
|
33
|
-
@_form = form
|
34
69
|
@_save = true
|
35
70
|
@_currency = nil
|
36
|
-
@_attrs = args.to_attrs
|
37
|
-
@_type = :text
|
38
71
|
@_object = @_form.object
|
39
|
-
@_save = true
|
40
72
|
@_attach_errors = true
|
41
73
|
|
42
|
-
_instance_variables_set_from_attrs(
|
74
|
+
_instance_variables_set_from_attrs(args.to_attrs)
|
43
75
|
instance_eval(&block) if block_given?
|
44
76
|
|
45
|
-
|
77
|
+
raise 'field must have a name' unless @_name
|
78
|
+
@_name = @_name
|
79
|
+
@_key ||= @_name.to_sym
|
80
|
+
@_label ||= @_name.titleize unless self.is_a?(SubmitField)
|
46
81
|
|
82
|
+
@_errors = []
|
47
83
|
@_format = Kiss::Format.lookup(@_format)
|
48
|
-
@
|
49
|
-
|
50
|
-
raise 'field must have a name' unless @_name
|
51
|
-
@_key ||= @_name
|
52
|
-
@_label ||= @_name.titleize unless @_type == :submit
|
84
|
+
@_tip = ((legend = @_format.legend) ? "(#{legend})" : nil) unless defined? @_tip
|
53
85
|
|
54
|
-
# object's value overrides
|
55
|
-
|
56
|
-
if @_object && (value = @_object[@_key.to_sym])
|
86
|
+
# object's value (if present) overrides default form field value
|
87
|
+
if @_object && (value = @_object[@_key])
|
57
88
|
@_value = value
|
58
89
|
end
|
59
90
|
|
60
91
|
if @_currency.is_a?(Symbol)
|
61
|
-
@_currency =
|
62
|
-
when :dollars
|
63
|
-
'$'
|
64
|
-
else
|
65
|
-
''
|
66
|
-
end
|
92
|
+
@_currency = CURRENCY_SYMBOLS[@_currency] || ''
|
67
93
|
end
|
68
94
|
|
69
|
-
if @_options
|
70
|
-
if @_options[0].is_a?(Array)
|
71
|
-
@_options_value_key ||= 0
|
72
|
-
(@_options_display_key ||= (@_options[0].size == 1) ? 0 : 1)
|
73
|
-
elsif defined?(Kiss::Model) && @_options[0].is_a?(Kiss::Model)
|
74
|
-
model_klass = @_options[0].class
|
75
|
-
@_options_value_key ||= model_klass.value_column
|
76
|
-
@_options_display_key ||= model_klass.display_column
|
77
|
-
elsif @_options[0].is_a?(Hash)
|
78
|
-
@_options_value_key ||= :id
|
79
|
-
@_options_display_key ||= :name
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
@_tip = ((legend = @_format.legend) ? "(#{legend})" : nil) unless defined? @_tip
|
84
|
-
|
85
95
|
@_form.has_required_fields ||= @_required
|
86
96
|
end
|
87
97
|
|
88
|
-
def other_field_html
|
89
|
-
return '' unless @_other
|
90
|
-
|
91
|
-
other_checked = @_value && !option_pairs.any? {|v, d| v == @_value }
|
92
|
-
|
93
|
-
(@_columns ? '<br/>' : ' ') + [
|
94
|
-
input_tag_html(
|
95
|
-
{ :value => 'other', :html => { :id => @_name+'.other' } },
|
96
|
-
other_checked ? 'checked' : ''
|
97
|
-
),
|
98
|
-
@_other[:label] || 'Other',
|
99
|
-
': ',
|
100
|
-
@_currency.to_s,
|
101
|
-
input_tag_html({
|
102
|
-
:type => :text,
|
103
|
-
:name => @_name+'.other',
|
104
|
-
:value => other_checked ? value_to_s(@_value) : nil,
|
105
|
-
:html => {
|
106
|
-
:onfocus => "document.getElementById('#{@_name}.other').checked = true"
|
107
|
-
}
|
108
|
-
}.merge(@_other))
|
109
|
-
].join
|
110
|
-
end
|
111
|
-
|
112
|
-
def column_layout(elements_html)
|
113
|
-
if elements_html.empty?
|
114
|
-
''
|
115
|
-
elsif @_columns
|
116
|
-
layout_columns = [@_columns, elements_html.size].min
|
117
|
-
num_elements_per_column = ((elements_html.size + layout_columns - 1) / layout_columns).to_i
|
118
|
-
layout_columns = ((elements_html.size + num_elements_per_column - 1) / num_elements_per_column).to_i
|
119
|
-
|
120
|
-
style = "style=\"width: #{(100 / layout_columns).to_i - 1}%\""
|
121
|
-
|
122
|
-
'<table class="kiss_field_columns"><tr>' +
|
123
|
-
(0...layout_columns).map do |i|
|
124
|
-
"<td #{style}>" + elements_html[i * num_elements_per_column, num_elements_per_column].join('<br/>') + "</td>"
|
125
|
-
end.join + '</table>'
|
126
|
-
else
|
127
|
-
elements_html.map {|h| "<nobr>#{h}</nobr>"}.join(' ')
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
98
|
def param
|
132
|
-
@_param ||= @_form.params[@_name
|
99
|
+
@_param ||= @_form.params[@_name]
|
133
100
|
end
|
134
101
|
|
135
102
|
def value
|
@@ -137,9 +104,10 @@ class Kiss
|
|
137
104
|
end
|
138
105
|
|
139
106
|
def value_string
|
140
|
-
|
107
|
+
return param if param
|
108
|
+
v = self.value
|
141
109
|
v *= @_factor if v && @_factor
|
142
|
-
|
110
|
+
value_to_s(v)
|
143
111
|
end
|
144
112
|
|
145
113
|
def add_error(message)
|
@@ -152,14 +120,10 @@ class Kiss
|
|
152
120
|
value ? @_format.value_to_s(value, @_form.context) : ''
|
153
121
|
end
|
154
122
|
|
155
|
-
def display_to_s(value)
|
156
|
-
value ? @_display_format.value_to_s(value).send(@_options_display_transform) : ''
|
157
|
-
end
|
158
|
-
|
159
123
|
def require_value(enter_verb = 'enter')
|
160
124
|
if (param !~ /\S/)
|
161
125
|
# value required
|
162
|
-
add_error("Please #{enter_verb} #{@_label}")
|
126
|
+
add_error("Please #{enter_verb} #{@_label.downcase}.")
|
163
127
|
return
|
164
128
|
end
|
165
129
|
end
|
@@ -179,7 +143,14 @@ class Kiss
|
|
179
143
|
p *= @_factor if @_factor
|
180
144
|
value = p
|
181
145
|
rescue Kiss::Format::ValidateError => e
|
182
|
-
return add_error("#{e.message.capitalize}")
|
146
|
+
return add_error("#{e.message.capitalize}.")
|
147
|
+
end
|
148
|
+
|
149
|
+
if @_min_value_size && value.size < @_min_value_size
|
150
|
+
return add_error("#{name.capitalize} must be at least #{@_min_value_size} characters long.")
|
151
|
+
end
|
152
|
+
if @_max_value_size && value.size < @_max_value_size
|
153
|
+
return add_error("#{name.capitalize} must be no more than #{@_max_value_size} characters long.")
|
183
154
|
end
|
184
155
|
|
185
156
|
if @_match
|
@@ -190,7 +161,7 @@ class Kiss
|
|
190
161
|
end
|
191
162
|
|
192
163
|
if @_save && @_unique
|
193
|
-
dataset = @_object.model.filter(@
|
164
|
+
dataset = @_object.model.filter(@_key => value)
|
194
165
|
unless (@_object.new? ? dataset : dataset.exclude(@_object.pk_hash)).empty?
|
195
166
|
return add_error("There is already another #{@_object.model.name.singularize.gsub('_', ' ')} with the same #{@_label.downcase.gsub('_', ' ')}.")
|
196
167
|
end
|
@@ -201,7 +172,7 @@ class Kiss
|
|
201
172
|
|
202
173
|
def errors_html
|
203
174
|
return nil unless @_errors.size > 0
|
204
|
-
|
175
|
+
|
205
176
|
if @_errors.size == 1
|
206
177
|
content = @_errors[0]
|
207
178
|
else
|
@@ -220,7 +191,7 @@ class Kiss
|
|
220
191
|
attrs[:style] ||= "width: #{width}px"
|
221
192
|
end
|
222
193
|
|
223
|
-
@_currency.to_s + input_tag_html(
|
194
|
+
@_currency.to_s + input_tag_html(attrs) + tip_html(attrs)
|
224
195
|
end
|
225
196
|
|
226
197
|
def table_row_html
|
@@ -233,7 +204,7 @@ class Kiss
|
|
233
204
|
|
234
205
|
def html(*args)
|
235
206
|
errors = errors_html
|
236
|
-
element_html(*args) + (errors ? (
|
207
|
+
element_html(*args) + (errors ? ('<br/>' + errors) : '')
|
237
208
|
end
|
238
209
|
|
239
210
|
def tag_start_html(tag_name, attrs = {}, extra_html = nil)
|
@@ -275,12 +246,458 @@ class Kiss
|
|
275
246
|
def input_tag_html(attrs = {}, extra_html = nil)
|
276
247
|
tag_html(
|
277
248
|
'input',
|
278
|
-
{:type =>
|
249
|
+
{:type => self.class.type}.merge(attrs),
|
279
250
|
extra_html
|
280
251
|
)
|
281
252
|
end
|
253
|
+
|
254
|
+
def set_value_to_hash(h)
|
255
|
+
h[self.name] = self.value
|
256
|
+
end
|
257
|
+
|
258
|
+
def set_value_to_object(obj)
|
259
|
+
k = self.key
|
260
|
+
v = (self.value != nil || obj.class.db_schema[k].allow_null) ?
|
261
|
+
self.value : (obj.class.db_schema[k][:default] ||= self.format.default)
|
262
|
+
|
263
|
+
v = Digest.const_get(self.digest.to_sym).hexdigest(value) if self.digest
|
264
|
+
|
265
|
+
obj[k] = v
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Subclass for single-value field types that present and take their value
|
270
|
+
# from a set of options.
|
271
|
+
class MultiChoiceField < Field
|
272
|
+
dsl_accessor(
|
273
|
+
# (Enumerable) set of options to be presented for user choice
|
274
|
+
:options,
|
275
|
+
|
276
|
+
# (Numeric or Symbol) This specifies which part of the option objects
|
277
|
+
# should be used as the field value to be submitted from this form.
|
278
|
+
# If each option is an enumerable, options_value_key should be a
|
279
|
+
# numeric index. If each option is a hash or model object, then this
|
280
|
+
# should be symbol key common to all of the option objects.
|
281
|
+
:options_value_key,
|
282
|
+
|
283
|
+
# (Numeric or Symbol) This specifies which part of the option objects
|
284
|
+
# should be displayed to the user in the form's HTML representation.
|
285
|
+
# If each option is an enumerable, options_value_key should be a
|
286
|
+
# numeric index. If each option is a hash or model object, then this
|
287
|
+
# should be symbol key common to all of the option objects.
|
288
|
+
:options_display_key,
|
289
|
+
|
290
|
+
# (Symbol) represents a Kiss::Format to be used in rendering each
|
291
|
+
# option display object in the form output.
|
292
|
+
:display_format,
|
293
|
+
|
294
|
+
# (Symbol) symbol of a method name to be called on each option display
|
295
|
+
# object to get its text or HTML representation for the form output
|
296
|
+
# (after applying the display_format). Defaults to :to_s.
|
297
|
+
:options_display_transform,
|
298
|
+
|
299
|
+
# (Numeric) Layout the options in a table grid with this many columns.
|
300
|
+
# If nil, layout the options on a single line with no grid.
|
301
|
+
:columns,
|
302
|
+
|
303
|
+
# (Boolean) iff true, create and add a text field for the user to
|
304
|
+
# specify a custom value for this field.
|
305
|
+
:other,
|
306
|
+
|
307
|
+
# (Kiss::Form::Field) The field to be presented as the 'Other' field
|
308
|
+
# for custom input from the user.
|
309
|
+
:other_field
|
310
|
+
)
|
311
|
+
alias_method :option_value_key, :options_value_key
|
312
|
+
alias_method :option_display_key, :options_display_key
|
313
|
+
alias_method :option_display_transform, :options_display_transform
|
314
|
+
|
315
|
+
def initialize(*args, &block)
|
316
|
+
@_options_display_transform = :to_s
|
317
|
+
|
318
|
+
super(*args, &block)
|
319
|
+
|
320
|
+
@_display_format = Kiss::Format.lookup(@_display_format)
|
321
|
+
|
322
|
+
if @_options
|
323
|
+
if @_options[0].is_a?(Array)
|
324
|
+
@_options_value_key ||= 0
|
325
|
+
(@_options_display_key ||= (@_options[0].size == 1) ? 0 : 1)
|
326
|
+
elsif defined?(Kiss::Model) && @_options[0].is_a?(Kiss::Model)
|
327
|
+
model_klass = @_options[0].class
|
328
|
+
@_options_value_key ||= model_klass.value_column
|
329
|
+
@_options_display_key ||= model_klass.display_column
|
330
|
+
elsif @_options[0].is_a?(Hash)
|
331
|
+
@_options_value_key ||= :id
|
332
|
+
@_options_display_key ||= :name
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
if @_other
|
337
|
+
@_other_field = @_form.create_field( { :name => @_name + '.other' }.merge(@_other) )
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def form=(new_form)
|
342
|
+
super(new_form)
|
343
|
+
@_other_field.form = new_form if @_other_field
|
344
|
+
end
|
345
|
+
|
346
|
+
def display_to_s(value)
|
347
|
+
value ? @_display_format.value_to_s(value).send(@_options_display_transform) : ''
|
348
|
+
end
|
349
|
+
|
350
|
+
def options_keys(value, display)
|
351
|
+
@_options_value_key = value
|
352
|
+
@_options_display_key = display
|
353
|
+
end
|
354
|
+
alias_method :option_keys, :options_keys
|
355
|
+
|
356
|
+
def option_pairs
|
357
|
+
pairs = if @_options_value_key
|
358
|
+
if @_options_display_key.is_a?(Proc)
|
359
|
+
@_options.map {|option| [ option[@_options_value_key], @_options_display_key.call(option) ]}
|
360
|
+
else
|
361
|
+
@_options.map {|option| [
|
362
|
+
option[@_options_value_key] || option.send(@_options_value_key),
|
363
|
+
option[@_options_display_key] || option.send(@_options_display_key)
|
364
|
+
]}
|
365
|
+
end
|
366
|
+
else
|
367
|
+
@_display_format = @_format
|
368
|
+
@_options.map {|option| [ option, option ]}
|
369
|
+
end
|
370
|
+
|
371
|
+
pairs
|
372
|
+
end
|
373
|
+
|
374
|
+
def has_option_value?(v)
|
375
|
+
!(@_options_value_key ?
|
376
|
+
@_options.select {|o| value_to_s(o[@_options_value_key]) == v } :
|
377
|
+
@_options.select {|o| value_to_s(o) == v }
|
378
|
+
).empty?
|
379
|
+
end
|
380
|
+
|
381
|
+
def column_layout(elements_html)
|
382
|
+
if elements_html.empty?
|
383
|
+
''
|
384
|
+
elsif @_columns
|
385
|
+
layout_columns = [@_columns, elements_html.size].min
|
386
|
+
num_elements_per_column = ((elements_html.size + layout_columns - 1) / layout_columns).to_i
|
387
|
+
layout_columns = ((elements_html.size + num_elements_per_column - 1) / num_elements_per_column).to_i
|
388
|
+
|
389
|
+
style = "style=\"width: #{(100 / layout_columns).to_i - 1}%\""
|
390
|
+
|
391
|
+
'<table class="kiss_field_columns"><tr>' +
|
392
|
+
(0...layout_columns).map do |i|
|
393
|
+
"<td #{style}>" + elements_html[i * num_elements_per_column, num_elements_per_column].join('<br/>') + "</td>"
|
394
|
+
end.join + '</table>'
|
395
|
+
else
|
396
|
+
elements_html.map {|h| "<nobr>#{h}</nobr>"}.join(' ')
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def validate
|
401
|
+
if @_other && param == 'other'
|
402
|
+
@_param = @_form.params[@_name+'.other']
|
403
|
+
end
|
404
|
+
super('select')
|
405
|
+
|
406
|
+
if @_value =~ /\S/ && !has_option_value?(@_value)
|
407
|
+
add_error "Invalid selection"
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def other_field_html
|
412
|
+
return '' unless @_other
|
413
|
+
|
414
|
+
other_checked = @_value && !option_pairs.any? {|v, d| v == @_value }
|
415
|
+
|
416
|
+
(@_columns ? '<br/>' : ' ') + [
|
417
|
+
input_tag_html(
|
418
|
+
{ :value => 'other', :html => { :id => @_name+'.other' } },
|
419
|
+
other_checked ? 'checked' : ''
|
420
|
+
),
|
421
|
+
@_other[:label] || 'Other',
|
422
|
+
': ',
|
423
|
+
@_currency.to_s,
|
424
|
+
input_tag_html({
|
425
|
+
:type => :text,
|
426
|
+
:name => @_name+'.other',
|
427
|
+
:value => other_checked ? value_to_s(@_value) : nil,
|
428
|
+
:html => {
|
429
|
+
:onfocus => "document.getElementById('#{@_name}.other').checked = true"
|
430
|
+
}
|
431
|
+
}.merge(@_other))
|
432
|
+
].join
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
# Subclass for field types in which multiple values can be chosen from a set
|
437
|
+
# of options.
|
438
|
+
class MultiValueField < MultiChoiceField
|
439
|
+
# (String) If not nil, then render a hidden form field specifying all of the
|
440
|
+
# values for this multi-value field, joined by this hidden_join delimiter.
|
441
|
+
# TODO: This may not be needed; remove if appropriate.
|
442
|
+
dsl_accessor :hidden_join
|
443
|
+
|
444
|
+
def param
|
445
|
+
@_form.params[@_name + '[]'] || []
|
446
|
+
end
|
447
|
+
|
448
|
+
def validate
|
449
|
+
begin
|
450
|
+
@_value = param.map { |p| @_format.validate(p) }
|
451
|
+
rescue Kiss::Format::ValidateError => e
|
452
|
+
return add_error("#{e.message.capitalize}.")
|
453
|
+
end
|
454
|
+
|
455
|
+
if @_value.empty? && @_required
|
456
|
+
return add_error "Please select at least one #{@_label.downcase.singularize}."
|
457
|
+
end
|
458
|
+
|
459
|
+
if @_min_value_size && @_value.size < @_min_value_size
|
460
|
+
return add_error "Please select at least #{@_min_value_size.of(@_label.downcase)}."
|
461
|
+
end
|
462
|
+
|
463
|
+
if @_max_value_size && @_value.size > @_max_value_size
|
464
|
+
return add_error "Please select no more than #{@_max_value_size.of(@_label.downcase)}."
|
465
|
+
end
|
466
|
+
|
467
|
+
@_value.each do |v|
|
468
|
+
unless has_option_value?(v)
|
469
|
+
return add_error "Invalid selection"
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
def selected_option_values
|
475
|
+
@_selected_option_values ||= @_value ? Hash[ *(@_value.map {|v| [value_to_s(v), true]}.flatten) ] : {}
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
### Concrete Field Types ###
|
480
|
+
|
481
|
+
class HiddenField < Field; end
|
482
|
+
class TextField < Field; end
|
483
|
+
|
484
|
+
class TextAreaField < Field
|
485
|
+
dsl_accessor :rows, :cols
|
486
|
+
|
487
|
+
def initialize(*args)
|
488
|
+
@_rows = 5
|
489
|
+
@_cols = 20
|
490
|
+
super(*args)
|
491
|
+
end
|
492
|
+
|
493
|
+
def element_html(attrs = {})
|
494
|
+
content_tag_html(
|
495
|
+
'textarea',
|
496
|
+
value_string,
|
497
|
+
attrs.merge(
|
498
|
+
:rows => @_rows ||= 1,
|
499
|
+
:cols => @_cols ||= 1
|
500
|
+
)
|
501
|
+
) + tip_html(attrs)
|
502
|
+
end
|
282
503
|
end
|
283
|
-
end
|
284
|
-
end
|
285
504
|
|
286
|
-
|
505
|
+
class PasswordField < Field
|
506
|
+
def element_html(*args)
|
507
|
+
input_tag_html(*args)
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
class FileField < Field
|
512
|
+
def element_html(attrs = {})
|
513
|
+
input_tag_html(attrs) + tip_html(attrs)
|
514
|
+
end
|
515
|
+
|
516
|
+
def get_file_name; end
|
517
|
+
|
518
|
+
def get_file_data; end
|
519
|
+
|
520
|
+
def require_value(enter_verb)
|
521
|
+
p = param
|
522
|
+
return add_error("Please choose #{label.downcase}.") unless p && p[:type]
|
523
|
+
end
|
524
|
+
|
525
|
+
def validate
|
526
|
+
require_value(nil) if @_required
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
class RangeField < Field
|
531
|
+
dsl_accessor :min_field, :max_field
|
532
|
+
|
533
|
+
def initialize(form, *args, &block)
|
534
|
+
super(form, *args, &block)
|
535
|
+
@_type = :range
|
536
|
+
|
537
|
+
attrs = args.to_attrs
|
538
|
+
text_field_attrs = attrs.merge(
|
539
|
+
:style => 'width: 60px'
|
540
|
+
)
|
541
|
+
text_field_attrs[:html] ||= {}
|
542
|
+
|
543
|
+
[:min, :max].each do |key|
|
544
|
+
text_field_name = "#{key}_#{attrs.name}"
|
545
|
+
text_field_attrs.merge!(
|
546
|
+
:name => text_field_name,
|
547
|
+
:key => text_field_name.to_sym
|
548
|
+
)
|
549
|
+
text_field_attrs[:html] = text_field_attrs[:html].clone
|
550
|
+
text_field_attrs[:html][:placeholder] = key
|
551
|
+
|
552
|
+
instance_variable_set("@_#{key}_field".to_sym,
|
553
|
+
TextField.new(form, text_field_attrs, &block))
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
def validate
|
558
|
+
@_min_field.validate
|
559
|
+
@_max_field.validate
|
560
|
+
|
561
|
+
(@_min_field.errors + @_max_field.errors).each do |error|
|
562
|
+
add_error(error)
|
563
|
+
end
|
564
|
+
nil
|
565
|
+
end
|
566
|
+
|
567
|
+
def element_html
|
568
|
+
@_min_field.element_html + ' to ' + @_max_field.element_html
|
569
|
+
end
|
570
|
+
|
571
|
+
def set_value_to_hash(h)
|
572
|
+
@_min_field.set_value_to_hash(h)
|
573
|
+
@_max_field.set_value_to_hash(h)
|
574
|
+
end
|
575
|
+
|
576
|
+
def set_value_to_object(obj)
|
577
|
+
@_min_field.set_value_to_object(obj)
|
578
|
+
@_max_field.set_value_to_object(obj)
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
class SubmitField < MultiChoiceField
|
583
|
+
dsl_accessor :cancel
|
584
|
+
|
585
|
+
def initialize(*args)
|
586
|
+
@_save = false
|
587
|
+
super(*args)
|
588
|
+
end
|
589
|
+
|
590
|
+
def element_html(*args)
|
591
|
+
elements_html(*args).join(' ')
|
592
|
+
end
|
593
|
+
|
594
|
+
def elements_html(attrs = {})
|
595
|
+
@_options.map do |option|
|
596
|
+
input_tag_html(attrs.merge( :value => value_to_s(option) ))
|
597
|
+
end
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
class SelectField < MultiChoiceField
|
602
|
+
dsl_accessor :choose_here
|
603
|
+
|
604
|
+
def element_html(attrs = {})
|
605
|
+
return 'No options' unless @_options.size > 0
|
606
|
+
|
607
|
+
@_choose_here ||= 'Choose Here'
|
608
|
+
placeholder_html = %Q(<option value="">#{@_choose_here}</option>)
|
609
|
+
|
610
|
+
options_html = option_pairs.map do |option_value, option_display|
|
611
|
+
option_value_string = value_to_s(option_value)
|
612
|
+
selected = (value_string == option_value_string) ? ' selected' : ''
|
613
|
+
%Q(<option value="#{option_value_string}"#{selected}>#{display_to_s(option_display)}</option>)
|
614
|
+
end.join
|
615
|
+
|
616
|
+
content_tag_html('select', placeholder_html + options_html, attrs) + other_field_html + tip_html(attrs)
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
class RadioField < MultiChoiceField
|
621
|
+
def element_html(attrs = {})
|
622
|
+
column_layout(elements_html(attrs)) + other_field_html + tip_html(attrs)
|
623
|
+
end
|
624
|
+
|
625
|
+
def elements_html(attrs = {})
|
626
|
+
option_pairs.map do |option_value, option_display|
|
627
|
+
option_value_string = value_to_s(option_value)
|
628
|
+
input_tag_html(
|
629
|
+
attrs.merge( :type => 'radio', :value => option_value_string ),
|
630
|
+
(value_string == option_value_string) ? 'checked' : ''
|
631
|
+
) + @_currency.to_s + display_to_s(option_display)
|
632
|
+
end
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
class BooleanField < RadioField
|
637
|
+
def initialize(*args, &block)
|
638
|
+
@_options = [[1, 'Yes'], [0, 'No']]
|
639
|
+
super(*args, &block)
|
640
|
+
end
|
641
|
+
end
|
642
|
+
|
643
|
+
class CheckboxField < MultiValueField
|
644
|
+
def element_html(attrs = {})
|
645
|
+
hidden_options = @_hidden_join ? input_tag_html(
|
646
|
+
:type => 'hidden',
|
647
|
+
:name => "#{@_name}_options",
|
648
|
+
:value => option_pairs.map {|option_value, option_display| value_to_s(option_value) }.join(@_hidden_join)
|
649
|
+
) : ''
|
650
|
+
|
651
|
+
column_layout(elements_html(attrs)) + other_field_html + hidden_options + tip_html(attrs)
|
652
|
+
end
|
653
|
+
|
654
|
+
def elements_html(attrs = {})
|
655
|
+
name = @_name + '[]'
|
656
|
+
option_pairs.map do |option_value, option_display|
|
657
|
+
option_value_string = value_to_s(option_value)
|
658
|
+
|
659
|
+
input_tag_html(
|
660
|
+
attrs.merge( :name => name, :value => option_value_string ),
|
661
|
+
selected_option_values[option_value_string] ? 'checked' : ''
|
662
|
+
) + @_currency.to_s + display_to_s(option_display)
|
663
|
+
end
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
667
|
+
class MultiSelectField < MultiValueField
|
668
|
+
def element_html(attrs = {})
|
669
|
+
options_html = option_pairs.map do |option_value, option_display|
|
670
|
+
option_value_string = value_to_s(option_value)
|
671
|
+
selected = selected_option_values[option_value_string] ? ' selected' : ''
|
672
|
+
%Q(<option value="#{option_value_string}"#{selected}>#{display_to_s(option_display)}</option>)
|
673
|
+
end.join
|
674
|
+
|
675
|
+
content_tag_html(
|
676
|
+
'select',
|
677
|
+
options_html,
|
678
|
+
attrs,
|
679
|
+
'multiple'
|
680
|
+
)
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
684
|
+
# Component type symbols used to create form definition DSL
|
685
|
+
COMPONENT_TYPES = {
|
686
|
+
:text => TextField,
|
687
|
+
:hidden => HiddenField,
|
688
|
+
:textarea => TextAreaField,
|
689
|
+
:password => PasswordField,
|
690
|
+
:boolean => BooleanField,
|
691
|
+
:range => RangeField,
|
692
|
+
:file => FileField,
|
693
|
+
:select => SelectField,
|
694
|
+
:radio => RadioField,
|
695
|
+
:checkbox => CheckboxField,
|
696
|
+
:multiselect => MultiSelectField,
|
697
|
+
:submit => SubmitField
|
698
|
+
}
|
699
|
+
COMPONENT_TYPES.each_pair do |type, klass|
|
700
|
+
klass.type = type
|
701
|
+
end
|
702
|
+
end
|
703
|
+
end
|