kiss 1.7.4 → 1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|