kiss 1.7.4 → 1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- dsl_accessor :name, :type, :form, :currency, :label, :no_label, :prompt, :value, :read_only,
7
- :ignore, :save, :options, :options_value_key, :options_display_key, :options_display_transform,
8
- :required, :unique, :cancel, :columns, :style, :hidden_join, :html, :other, :other_field, :object,
9
- :format, :display_format, :key, :match, :tip, :statement, :attach_errors, :factor, :digest,
10
- :min_value_size, :max_value_size, :choose_here
11
- alias_method :option_value_key, :options_value_key
12
- alias_method :option_display_key, :options_display_key
13
- alias_method :option_display_transform, :options_display_transform
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(@_attrs)
74
+ _instance_variables_set_from_attrs(args.to_attrs)
43
75
  instance_eval(&block) if block_given?
44
76
 
45
- @_errors = []
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
- @_display_format = Kiss::Format.lookup(@_display_format)
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 any form field value
55
- # form field value is intended as default in case object value is missing
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 = case @_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/>' : '&nbsp; ') + [
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('&nbsp; ')
128
- end
129
- end
130
-
131
98
  def param
132
- @_param ||= @_form.params[@_name.to_s]
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
- v = value
107
+ return param if param
108
+ v = self.value
141
109
  v *= @_factor if v && @_factor
142
- param || value_to_s(v)
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(@_name.to_sym => value)
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( attrs ) + tip_html(attrs)
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 ? (@_columns ? '' : '<br/>') + %Q(#{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 => @_type}.merge(attrs),
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('&nbsp; ')
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/>' : '&nbsp; ') + [
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
- require 'kiss/form/field_types'
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