forme 1.2.0 → 1.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG +24 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +37 -18
- data/Rakefile +6 -5
- data/lib/forme.rb +6 -1348
- data/lib/forme/form.rb +387 -0
- data/lib/forme/input.rb +48 -0
- data/lib/forme/raw.rb +12 -0
- data/lib/forme/tag.rb +60 -0
- data/lib/forme/transformers/error_handler.rb +18 -0
- data/lib/forme/transformers/formatter.rb +511 -0
- data/lib/forme/transformers/helper.rb +17 -0
- data/lib/forme/transformers/inputs_wrapper.rb +95 -0
- data/lib/forme/transformers/labeler.rb +78 -0
- data/lib/forme/transformers/serializer.rb +171 -0
- data/lib/forme/transformers/wrapper.rb +52 -0
- data/lib/forme/version.rb +1 -1
- data/lib/sequel/plugins/forme.rb +7 -4
- data/spec/forme_spec.rb +65 -5
- data/spec/sequel_plugin_spec.rb +18 -3
- metadata +15 -4
@@ -0,0 +1,18 @@
|
|
1
|
+
module Forme
|
2
|
+
# Default error handler used by the library, using an "error" class
|
3
|
+
# for the input field and a span tag with an "error_message" class
|
4
|
+
# for the error message.
|
5
|
+
#
|
6
|
+
# Registered as :default.
|
7
|
+
class ErrorHandler
|
8
|
+
Forme.register_transformer(:error_handler, :default, new)
|
9
|
+
|
10
|
+
# Return tag with error message span tag after it.
|
11
|
+
def call(tag, input)
|
12
|
+
attr = input.opts[:error_attr]
|
13
|
+
attr = attr ? attr.dup : {}
|
14
|
+
Forme.attr_classes(attr, 'error_message')
|
15
|
+
[tag, input.tag(:span, attr, input.opts[:error])]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,511 @@
|
|
1
|
+
module Forme
|
2
|
+
# The default formatter used by the library. Any custom formatters should
|
3
|
+
# probably inherit from this formatter unless they have very special needs.
|
4
|
+
#
|
5
|
+
# Unlike most other transformers which are registered as instances and use
|
6
|
+
# a functional style, this class is registered as a class due to the large
|
7
|
+
# amount of state it uses.
|
8
|
+
#
|
9
|
+
# Registered as :default.
|
10
|
+
class Formatter
|
11
|
+
Forme.register_transformer(:formatter, :default, self)
|
12
|
+
|
13
|
+
# These options are copied directly from the options hash to the the
|
14
|
+
# attributes hash, so they don't need to be specified in the :attr
|
15
|
+
# option. However, they can be specified in both places, and if so,
|
16
|
+
# the :attr option version takes precedence.
|
17
|
+
ATTRIBUTE_OPTIONS = [:name, :id, :placeholder, :value, :style]
|
18
|
+
|
19
|
+
# Options copied from the options hash into the attributes hash,
|
20
|
+
# where a true value in the options hash sets the attribute
|
21
|
+
# value to the same name as the key.
|
22
|
+
ATTRIBUTE_BOOLEAN_OPTIONS = [:autofocus, :required, :disabled]
|
23
|
+
|
24
|
+
# Create a new instance and call it
|
25
|
+
def self.call(input)
|
26
|
+
new.call(input)
|
27
|
+
end
|
28
|
+
|
29
|
+
# The +Form+ instance for the receiver, taken from the +input+.
|
30
|
+
attr_reader :form
|
31
|
+
|
32
|
+
# The +Input+ instance for the receiver. This is what the receiver
|
33
|
+
# converts to the lower level +Tag+ form (or an array of them).
|
34
|
+
attr_reader :input
|
35
|
+
|
36
|
+
# The attributes to to set on the lower level +Tag+ form returned.
|
37
|
+
# This are derived from the +input+'s +opts+, but some processing is done on
|
38
|
+
# them.
|
39
|
+
attr_reader :attr
|
40
|
+
|
41
|
+
# The +opts+ hash of the +input+.
|
42
|
+
attr_reader :opts
|
43
|
+
|
44
|
+
# Used to specify the value of the hidden input created for checkboxes.
|
45
|
+
# Since the default for an unspecified checkbox value is 1, the default is
|
46
|
+
# 0. If the checkbox value is 't', the hidden value is 'f', since that is
|
47
|
+
# common usage for boolean values.
|
48
|
+
CHECKBOX_MAP = Hash.new(0)
|
49
|
+
CHECKBOX_MAP['t'] = 'f'
|
50
|
+
|
51
|
+
# Transform the +input+ into a +Tag+ instance (or an array of them),
|
52
|
+
# wrapping it with the +form+'s wrapper, and the form's +error_handler+
|
53
|
+
# and +labeler+ if the +input+ has an <tt>:error</tt> or <tt>:label</tt>
|
54
|
+
# options.
|
55
|
+
def call(input)
|
56
|
+
@input = input
|
57
|
+
@form = input.form
|
58
|
+
attr = input.opts[:attr]
|
59
|
+
@attr = attr ? attr.dup : {}
|
60
|
+
@opts = input.opts
|
61
|
+
normalize_options
|
62
|
+
|
63
|
+
tag = convert_to_tag(input.type)
|
64
|
+
tag = wrap_tag_with_label(tag) if input.opts[:label]
|
65
|
+
tag = wrap_tag_with_error(tag) if input.opts[:error]
|
66
|
+
tag = wrap(:helper, tag) if input.opts[:help]
|
67
|
+
wrap_tag(tag)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Dispatch to a format_<i>type</i> method if there is one that matches the
|
73
|
+
# type, otherwise, call +_format_input+ with the given +type+.
|
74
|
+
def convert_to_tag(type)
|
75
|
+
meth = :"format_#{type}"
|
76
|
+
if respond_to?(meth, true)
|
77
|
+
send(meth)
|
78
|
+
else
|
79
|
+
_format_input(type)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# If the checkbox has a name, will create a hidden input tag with the
|
84
|
+
# same name that comes before this checkbox. That way, if the checkbox
|
85
|
+
# is checked, the web app will generally see the value of the checkbox, and
|
86
|
+
# if it is not checked, the web app will generally see the value of the hidden
|
87
|
+
# input tag.
|
88
|
+
def format_checkbox
|
89
|
+
@attr[:type] = :checkbox
|
90
|
+
@attr[:checked] = :checked if @opts[:checked]
|
91
|
+
if @attr[:name] && !@opts[:no_hidden]
|
92
|
+
attr = {:type=>:hidden}
|
93
|
+
unless attr[:value] = @opts[:hidden_value]
|
94
|
+
attr[:value] = CHECKBOX_MAP[@attr[:value]]
|
95
|
+
end
|
96
|
+
attr[:id] = "#{@attr[:id]}_hidden" if @attr[:id]
|
97
|
+
attr[:name] = @attr[:name]
|
98
|
+
[tag(:input, attr), tag(:input)]
|
99
|
+
else
|
100
|
+
tag(:input)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# For radio buttons, recognizes the :checked option and sets the :checked
|
105
|
+
# attribute in the tag appropriately.
|
106
|
+
def format_radio
|
107
|
+
@attr[:checked] = :checked if @opts[:checked]
|
108
|
+
@attr[:type] = :radio
|
109
|
+
tag(:input)
|
110
|
+
end
|
111
|
+
|
112
|
+
DEFAULT_DATE_ORDER = [:year, '-'.freeze, :month, '-'.freeze, :day].freeze
|
113
|
+
# Use a date input by default. If the :as=>:select option is given,
|
114
|
+
# use a multiple select box for the options.
|
115
|
+
def format_date
|
116
|
+
if @opts[:as] == :select
|
117
|
+
values = {}
|
118
|
+
if v = @attr[:value]
|
119
|
+
v = Date.parse(v) unless v.is_a?(Date)
|
120
|
+
values[:year], values[:month], values[:day] = v.year, v.month, v.day
|
121
|
+
end
|
122
|
+
_format_date_select(values, @opts[:order] || DEFAULT_DATE_ORDER)
|
123
|
+
else
|
124
|
+
_format_input(:date)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
DEFAULT_DATETIME_ORDER = [:year, '-'.freeze, :month, '-'.freeze, :day, ' '.freeze, :hour, ':'.freeze, :minute, ':'.freeze, :second].freeze
|
129
|
+
# Use a datetime input by default. If the :as=>:select option is given,
|
130
|
+
# use a multiple select box for the options.
|
131
|
+
def format_datetime
|
132
|
+
if @opts[:as] == :select
|
133
|
+
values = {}
|
134
|
+
if v = @attr[:value]
|
135
|
+
v = DateTime.parse(v) unless v.is_a?(Time) || v.is_a?(DateTime)
|
136
|
+
values[:year], values[:month], values[:day], values[:hour], values[:minute], values[:second] = v.year, v.month, v.day, v.hour, v.min, v.sec
|
137
|
+
end
|
138
|
+
_format_date_select(values, @opts[:order] || DEFAULT_DATETIME_ORDER)
|
139
|
+
else
|
140
|
+
_format_input('datetime-local')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
DEFAULT_DATE_SELECT_OPS = {:year=>1900..2050, :month=>1..12, :day=>1..31, :hour=>0..23, :minute=>0..59, :second=>0..59}.freeze
|
145
|
+
DATE_SELECT_FORMAT = '%02i'.freeze
|
146
|
+
# Shared code for formatting dates/times as select boxes
|
147
|
+
def _format_date_select(values, order)
|
148
|
+
name = @attr[:name]
|
149
|
+
id = @attr[:id]
|
150
|
+
ops = DEFAULT_DATE_SELECT_OPS
|
151
|
+
ops = ops.merge(@opts[:select_options]) if @opts[:select_options]
|
152
|
+
first_input = true
|
153
|
+
format = DATE_SELECT_FORMAT
|
154
|
+
order.map do |x|
|
155
|
+
next x if x.is_a?(String)
|
156
|
+
opts = @opts.merge(:label=>nil, :wrapper=>nil, :error=>nil, :name=>"#{name}[#{x}]", :value=>values[x], :options=>ops[x].map{|y| [sprintf(format, y), y]})
|
157
|
+
opts[:id] = if first_input
|
158
|
+
first_input = false
|
159
|
+
id
|
160
|
+
else
|
161
|
+
"#{id}_#{x}"
|
162
|
+
end
|
163
|
+
form._input(:select, opts).format
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# The default fallback method for handling inputs. Assumes an input tag
|
168
|
+
# with the type attribute set to input.
|
169
|
+
def _format_input(type)
|
170
|
+
@attr[:type] = type
|
171
|
+
copy_options_to_attributes([:size, :maxlength])
|
172
|
+
tag(:input)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Takes a select input and turns it into a select tag with (possibly) option
|
176
|
+
# children tags.
|
177
|
+
def format_select
|
178
|
+
@attr[:multiple] = :multiple if @opts[:multiple]
|
179
|
+
copy_options_to_attributes([:size])
|
180
|
+
|
181
|
+
os = process_select_optgroups(:_format_select_optgroup) do |label, value, sel, attrs|
|
182
|
+
if value || sel
|
183
|
+
attrs = attrs.dup
|
184
|
+
attrs[:value] = value if value
|
185
|
+
attrs[:selected] = :selected if sel
|
186
|
+
end
|
187
|
+
tag(:option, attrs, [label])
|
188
|
+
end
|
189
|
+
tag(:select, @attr, os)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Use an optgroup around related options in a select tag.
|
193
|
+
def _format_select_optgroup(group, options)
|
194
|
+
group = {:label=>group} unless group.is_a?(Hash)
|
195
|
+
tag(:optgroup, group, options)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Use a fieldset/legend around related options in a checkbox or radio button set.
|
199
|
+
def _format_set_optgroup(group, options)
|
200
|
+
tag(:fieldset, {}, [tag(:legend, {}, [group])] + options)
|
201
|
+
end
|
202
|
+
|
203
|
+
def format_checkboxset
|
204
|
+
@opts[:multiple] = true unless @opts.has_key?(:multiple)
|
205
|
+
_format_set(:checkbox, :no_hidden=>true, :multiple=>true)
|
206
|
+
end
|
207
|
+
|
208
|
+
def format_radioset
|
209
|
+
_format_set(:radio)
|
210
|
+
end
|
211
|
+
|
212
|
+
def _format_set(type, tag_attrs={})
|
213
|
+
raise Error, "can't have radioset with no options" unless @opts[:optgroups] || @opts[:options]
|
214
|
+
key = @opts[:key]
|
215
|
+
name = @opts[:name]
|
216
|
+
id = @opts[:id]
|
217
|
+
if @opts[:error]
|
218
|
+
@opts[:set_error] = @opts.delete(:error)
|
219
|
+
end
|
220
|
+
if @opts[:label]
|
221
|
+
@opts[:set_label] = @opts.delete(:label)
|
222
|
+
end
|
223
|
+
tag_wrapper = @opts.delete(:tag_wrapper) || :default
|
224
|
+
wrapper = Forme.transformer(:wrapper, @opts, @input.form_opts)
|
225
|
+
|
226
|
+
tags = process_select_optgroups(:_format_set_optgroup) do |label, value, sel, attrs|
|
227
|
+
value ||= label
|
228
|
+
r_opts = attrs.merge(tag_attrs).merge(:label=>label||value, :label_attr=>{:class=>:option}, :wrapper=>tag_wrapper)
|
229
|
+
r_opts[:value] ||= value if value
|
230
|
+
r_opts[:checked] ||= :checked if sel
|
231
|
+
|
232
|
+
if name
|
233
|
+
r_opts[:name] ||= name
|
234
|
+
end
|
235
|
+
if id
|
236
|
+
r_opts[:id] ||= "#{id}_#{value}"
|
237
|
+
end
|
238
|
+
if key
|
239
|
+
r_opts[:key] ||= key
|
240
|
+
r_opts[:key_id] ||= value
|
241
|
+
end
|
242
|
+
|
243
|
+
form._input(type, r_opts)
|
244
|
+
end
|
245
|
+
|
246
|
+
if @opts[:set_error]
|
247
|
+
if (last_input = tags.last) && last_input.is_a?(Input)
|
248
|
+
last_input.opts[:error] = @opts[:set_error]
|
249
|
+
else
|
250
|
+
tags << form._tag(:span, {:class=>'error_message'}, [@opts[:set_error]])
|
251
|
+
end
|
252
|
+
end
|
253
|
+
tags.unshift(form._tag(:span, {:class=>:label}, @opts[:set_label])) if @opts[:set_label]
|
254
|
+
wrapper.call(tags, form._input(type, opts)) if wrapper
|
255
|
+
tags
|
256
|
+
end
|
257
|
+
|
258
|
+
# Formats a textarea. Respects the following options:
|
259
|
+
# :value :: Sets value as the child of the textarea.
|
260
|
+
def format_textarea
|
261
|
+
copy_options_to_attributes([:cols, :rows])
|
262
|
+
if val = @attr.delete(:value)
|
263
|
+
tag(:textarea, @attr, [val])
|
264
|
+
else
|
265
|
+
tag(:textarea)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Copy option values for given keys to the attributes unless the
|
270
|
+
# attributes already have a value for the key.
|
271
|
+
def copy_options_to_attributes(keys)
|
272
|
+
keys.each do |k|
|
273
|
+
if @opts.has_key?(k) && !@attr.has_key?(k)
|
274
|
+
@attr[k] = @opts[k]
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# Set attribute values for given keys to be the same as the key
|
280
|
+
# unless the attributes already have a value for the key.
|
281
|
+
def copy_boolean_options_to_attributes(keys)
|
282
|
+
keys.each do |k|
|
283
|
+
if @opts[k] && !@attr.has_key?(k)
|
284
|
+
@attr[k] = k
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# Normalize the options used for all input types.
|
290
|
+
def normalize_options
|
291
|
+
copy_options_to_attributes(ATTRIBUTE_OPTIONS)
|
292
|
+
copy_boolean_options_to_attributes(ATTRIBUTE_BOOLEAN_OPTIONS)
|
293
|
+
handle_key_option
|
294
|
+
|
295
|
+
Forme.attr_classes(@attr, @opts[:class]) if @opts.has_key?(:class)
|
296
|
+
Forme.attr_classes(@attr, 'error') if @opts[:error]
|
297
|
+
|
298
|
+
if data = opts[:data]
|
299
|
+
data.each do |k, v|
|
300
|
+
sym = :"data-#{k}"
|
301
|
+
@attr[sym] = v unless @attr.has_key?(sym)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# Have the :key option possibly set the name, id, and/or value attributes if not already set.
|
307
|
+
def handle_key_option
|
308
|
+
if key = @opts[:key]
|
309
|
+
unless @attr[:name] || @attr['name']
|
310
|
+
@attr[:name] = namespaced_name(key, @opts[:array] || @opts[:multiple])
|
311
|
+
if !@attr.has_key?(:value) && !@attr.has_key?('value') && (values = @form.opts[:values])
|
312
|
+
set_value_from_namespaced_values(namespaces, values, key)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
unless @attr[:id] || @attr['id']
|
316
|
+
id = namespaced_id(key)
|
317
|
+
if suffix = @opts[:key_id]
|
318
|
+
id << '_' << suffix.to_s
|
319
|
+
end
|
320
|
+
@attr[:id] = id
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# Array of namespaces to use for the input
|
326
|
+
def namespaces
|
327
|
+
input.form_opts[:namespace]
|
328
|
+
end
|
329
|
+
|
330
|
+
# Return a unique id attribute for the +field+, based on the current namespaces.
|
331
|
+
def namespaced_id(field)
|
332
|
+
"#{namespaces.join('_')}#{'_' unless namespaces.empty?}#{field}"
|
333
|
+
end
|
334
|
+
|
335
|
+
# Return a unique name attribute for the +field+, based on the current namespaces.
|
336
|
+
# If +multiple+ is true, end the name with [] so that param parsing will treat
|
337
|
+
# the name as part of an array.
|
338
|
+
def namespaced_name(field, multiple=false)
|
339
|
+
if namespaces.empty?
|
340
|
+
if multiple
|
341
|
+
"#{field}[]"
|
342
|
+
else
|
343
|
+
field
|
344
|
+
end
|
345
|
+
else
|
346
|
+
root, *nsps = namespaces
|
347
|
+
"#{root}#{nsps.map{|n| "[#{n}]"}.join}[#{field}]#{'[]' if multiple}"
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
# Set the values option based on the (possibly nested) values
|
352
|
+
# hash given, array of namespaces, and key.
|
353
|
+
def set_value_from_namespaced_values(namespaces, values, key)
|
354
|
+
namespaces.each do |ns|
|
355
|
+
v = values[ns] || values[ns.to_s]
|
356
|
+
return unless v
|
357
|
+
values = v
|
358
|
+
end
|
359
|
+
|
360
|
+
@attr[:value] = values.fetch(key){values.fetch(key.to_s){return}}
|
361
|
+
end
|
362
|
+
|
363
|
+
# If :optgroups option is present, iterate over each of the groups
|
364
|
+
# inside of it and create options for each group. Otherwise, if
|
365
|
+
# :options option present, iterate over it and create options.
|
366
|
+
def process_select_optgroups(grouper, &block)
|
367
|
+
os = if groups = @opts[:optgroups]
|
368
|
+
groups.map do |group, options|
|
369
|
+
send(grouper, group, process_select_options(options, &block))
|
370
|
+
end
|
371
|
+
else
|
372
|
+
return unless @opts[:options]
|
373
|
+
process_select_options(@opts[:options], &block)
|
374
|
+
end
|
375
|
+
|
376
|
+
if prompt = @opts[:add_blank]
|
377
|
+
unless prompt.is_a?(String)
|
378
|
+
prompt = Forme.default_add_blank_prompt
|
379
|
+
end
|
380
|
+
blank_attr = @opts[:blank_attr] || {}
|
381
|
+
os.send(@opts[:blank_position] == :after ? :push : :unshift, yield([prompt, '', false, blank_attr]))
|
382
|
+
end
|
383
|
+
|
384
|
+
os
|
385
|
+
end
|
386
|
+
|
387
|
+
# Iterate over the given options, yielding the option text, value, whether it is selected, and any attributes.
|
388
|
+
# The block should return an appropriate tag object.
|
389
|
+
def process_select_options(os)
|
390
|
+
if os
|
391
|
+
vm = @opts[:value_method]
|
392
|
+
tm = @opts[:text_method]
|
393
|
+
sel = @opts[:selected] || @attr.delete(:value)
|
394
|
+
|
395
|
+
if @opts[:multiple]
|
396
|
+
sel = Array(sel)
|
397
|
+
cmp = lambda{|v| sel.include?(v)}
|
398
|
+
else
|
399
|
+
cmp = lambda{|v| v == sel}
|
400
|
+
end
|
401
|
+
|
402
|
+
os.map do |x|
|
403
|
+
attr = {}
|
404
|
+
if tm
|
405
|
+
text = x.send(tm)
|
406
|
+
val = x.send(vm) if vm
|
407
|
+
elsif x.is_a?(Array)
|
408
|
+
text = x.first
|
409
|
+
val = x.last
|
410
|
+
|
411
|
+
if val.is_a?(Hash)
|
412
|
+
value = val[:value]
|
413
|
+
attr.merge!(val)
|
414
|
+
val = value
|
415
|
+
end
|
416
|
+
else
|
417
|
+
text = x
|
418
|
+
end
|
419
|
+
|
420
|
+
yield [text, val, val ? cmp.call(val) : cmp.call(text), attr]
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
# Create a +Tag+ instance related to the receiver's +form+ with the given
|
426
|
+
# arguments.
|
427
|
+
def tag(type, attr=@attr, children=nil)
|
428
|
+
form._tag(type, attr, children)
|
429
|
+
end
|
430
|
+
|
431
|
+
# Wrap the tag for the given transformer type.
|
432
|
+
def wrap(type, tag)
|
433
|
+
Forme.transform(type, @opts, input.form_opts, tag, input)
|
434
|
+
end
|
435
|
+
|
436
|
+
# Wrap the tag with the form's +wrapper+.
|
437
|
+
def wrap_tag(tag)
|
438
|
+
wrap(:wrapper, tag)
|
439
|
+
end
|
440
|
+
|
441
|
+
# Wrap the tag with the form's +error_handler+.
|
442
|
+
def wrap_tag_with_error(tag)
|
443
|
+
wrap(:error_handler, tag)
|
444
|
+
end
|
445
|
+
|
446
|
+
# Wrap the tag with the form's +labeler+.
|
447
|
+
def wrap_tag_with_label(tag)
|
448
|
+
wrap(:labeler, tag)
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
# Formatter that disables all input fields,
|
453
|
+
#
|
454
|
+
# Registered as :disabled.
|
455
|
+
class Formatter::Disabled < Formatter
|
456
|
+
Forme.register_transformer(:formatter, :disabled, self)
|
457
|
+
|
458
|
+
private
|
459
|
+
|
460
|
+
# Unless the :disabled option is specifically set
|
461
|
+
# to +false+, set the :disabled attribute on the
|
462
|
+
# resulting tag.
|
463
|
+
def normalize_options
|
464
|
+
if @opts[:disabled] == false
|
465
|
+
super
|
466
|
+
else
|
467
|
+
super
|
468
|
+
@attr[:disabled] = :disabled
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
# Formatter that uses span tags with text for most input types,
|
474
|
+
# and disables radio/checkbox inputs.
|
475
|
+
#
|
476
|
+
# Registered as :readonly.
|
477
|
+
class Formatter::ReadOnly < Formatter
|
478
|
+
Forme.register_transformer(:formatter, :readonly, self)
|
479
|
+
|
480
|
+
private
|
481
|
+
|
482
|
+
# Disabled checkbox inputs.
|
483
|
+
def format_checkbox
|
484
|
+
@attr[:disabled] = :disabled
|
485
|
+
super
|
486
|
+
end
|
487
|
+
|
488
|
+
# Use a span with text instead of an input field.
|
489
|
+
def _format_input(type)
|
490
|
+
tag(:span, {}, @attr[:value])
|
491
|
+
end
|
492
|
+
|
493
|
+
# Disabled radio button inputs.
|
494
|
+
def format_radio
|
495
|
+
@attr[:disabled] = :disabled
|
496
|
+
super
|
497
|
+
end
|
498
|
+
|
499
|
+
# Use a span with text of the selected values instead of a select box.
|
500
|
+
def format_select
|
501
|
+
t = super
|
502
|
+
children = [t.children.select{|o| o.attr[:selected]}.map(&:children).join(', ')] if t.children
|
503
|
+
tag(:span, {}, children)
|
504
|
+
end
|
505
|
+
|
506
|
+
# Use a span with text instead of a text area.
|
507
|
+
def format_textarea
|
508
|
+
tag(:span, {}, @attr[:value])
|
509
|
+
end
|
510
|
+
end
|
511
|
+
end
|