class Forme::Formatter

The default formatter used by the library. Any custom formatters should probably inherit from this formatter unless they have very special needs.

Unlike most other transformers which are registered as instances and use a functional style, this class is registered as a class due to the large amount of state it uses.

Registered as :default.

Constants

ATTRIBUTE_BOOLEAN_OPTIONS

Options copied from the options hash into the attributes hash, where a true value in the options hash sets the attribute value to the same name as the key.

ATTRIBUTE_OPTIONS

These options are copied directly from the options hash to the the attributes hash, so they don’t need to be specified in the :attr option. However, they can be specified in both places, and if so, the :attr option version takes precedence.

CHECKBOX_MAP

Used to specify the value of the hidden input created for checkboxes. Since the default for an unspecified checkbox value is 1, the default is

  1. If the checkbox value is ‘t’, the hidden value is ‘f’, since that is

common usage for boolean values.

DATE_SELECT_FORMAT
DEFAULT_DATETIME_ORDER
DEFAULT_DATE_ORDER
DEFAULT_DATE_SELECT_OPS

Attributes

attr[R]

The attributes to to set on the lower level Tag form returned. This are derived from the input‘s opts, but some processing is done on them.

form[R]

The Form instance for the receiver, taken from the input.

input[R]

The Input instance for the receiver. This is what the receiver converts to the lower level Tag form (or an array of them).

opts[R]

The opts hash of the input.

Public Class Methods

call(input) click to toggle source

Create a new instance and call it

   # File lib/forme/transformers/formatter.rb
27 def self.call(input)
28   new.call(input)
29 end

Public Instance Methods

call(input) click to toggle source

Transform the input into a Tag instance (or an array of them), wrapping it with the form‘s wrapper, and the form’s error_handler and labeler if the input has an :error or :label options.

   # File lib/forme/transformers/formatter.rb
57 def call(input)
58   @input = input
59   @form = input.form
60   attr = input.opts[:attr]
61   @attr = attr ? attr.dup : {}
62   @opts = input.opts
63   normalize_options
64   tag = if html = input.opts[:html]
65     html = html.call(input) if html.respond_to?(:call)
66     form.raw(html)
67   else
68     convert_to_tag(input.type)
69   end
70   tag = wrap_tag_with_label(tag) if @opts[:label]
71   tag = wrap_tag_with_error(tag) if @opts[:error]
72   tag = wrap(:helper, tag) if input.opts[:help]
73   wrap_tag(tag)
74 end

Private Instance Methods

_format_date_select(values, order) click to toggle source

Shared code for formatting dates/times as select boxes

    # File lib/forme/transformers/formatter.rb
153 def _format_date_select(values, order)
154   name = @attr[:name]
155   id = @attr[:id]
156   ops = DEFAULT_DATE_SELECT_OPS
157   ops = ops.merge(@opts[:select_options]) if @opts[:select_options]
158   first_input = true
159   format = DATE_SELECT_FORMAT
160   @opts[:select_labels] ||= {}
161   order.map do |x|
162     next x if x.is_a?(String)
163     options = ops[x].map do |value, text|
164       [text || sprintf(format, value), value]
165     end
166     opts = @opts.merge(:label=>@opts[:select_labels][x], :wrapper=>nil, :error=>nil, :name=>"#{name}[#{x}]", :value=>values[x], :options=>options)
167     opts[:id] = if first_input
168       first_input = false
169       id
170     else
171       "#{id}_#{x}"
172     end
173     form._input(:select, opts).format
174   end
175 end
_format_input(type) click to toggle source

The default fallback method for handling inputs. Assumes an input tag with the type attribute set to input.

    # File lib/forme/transformers/formatter.rb
179 def _format_input(type)
180   @attr[:type] = type
181   copy_options_to_attributes([:size, :maxlength, :minlength])
182   tag(:input)
183 end
_format_select_optgroup(group, options) click to toggle source

Use an optgroup around related options in a select tag.

    # File lib/forme/transformers/formatter.rb
203 def _format_select_optgroup(group, options)
204   group = {:label=>group} unless group.is_a?(Hash)
205   tag(:optgroup, group, options)
206 end
_format_set(type, tag_attrs={}) click to toggle source
    # File lib/forme/transformers/formatter.rb
222 def _format_set(type, tag_attrs={})
223   raise Error, "can't have radioset or checkboxset with no options" unless @opts[:optgroups] || @opts[:options]
224   key = @opts[:key]
225   name = @opts[:name]
226   id = @opts[:id]
227   @opts[:labeler] ||= :span
228   @opts[:error_handler] ||= :set
229 
230   tag_wrapper = Forme.transformer(:tag_wrapper, @opts.delete(:tag_wrapper), @input.form_opts) || :default
231   tag_labeler = Forme.transformer(:labeler, @opts.delete(:tag_labeler), @input.form_opts) || :default
232   wrapper = @opts.fetch(:wrapper){@opts[:wrapper] = @input.form_opts[:set_wrapper] || @input.form_opts[:wrapper]}
233   wrapper = Forme.transformer(:wrapper, wrapper)
234   tag_label_attr = @opts[:tag_label_attr] || @opts[:label_attr]
235 
236   first_input = nil
237   last_input = nil
238   ret = process_select_optgroups(:_format_set_optgroup) do |label, value, sel, attrs|
239     value = label if value.nil?
240     label_attr = {:class=>:option}
241     label_attr.merge!(tag_label_attr) if tag_label_attr
242     r_opts = attrs.merge(tag_attrs).merge(:label=>label||value, :label_attr=>label_attr, :wrapper=>tag_wrapper, :labeler=>tag_labeler, :error=>nil, :error_attr=>nil)
243     if r_opts[:value].nil?
244       r_opts[:value] = value unless value.nil?
245     end
246     r_opts[:checked] ||= :checked if sel
247     r_opts[:formatter] = @opts[:formatter] if @opts[:formatter]
248 
249     if name
250       r_opts[:name] ||= name
251     end
252     if id
253       r_opts[:id] ||= "#{id}_#{value}"
254     end
255     if key
256       r_opts[:key] ||= key
257       r_opts[:key_id] ||= value
258     end
259 
260     input = form._input(type, r_opts)
261     first_input ||= input
262     last_input = input
263   end
264 
265   @opts[:first_input] = first_input
266   @opts[:last_input] = last_input
267 
268   ret
269 end
_format_set_optgroup(group, options) click to toggle source

Use a fieldset/legend around related options in a checkbox or radio button set.

    # File lib/forme/transformers/formatter.rb
209 def _format_set_optgroup(group, options)
210   tag(:fieldset, {}, [tag(:legend, {}, [group])] + options)
211 end
convert_to_tag(type) click to toggle source

Dispatch to a format_type method if there is one that matches the type, otherwise, call _format_input with the given type.

   # File lib/forme/transformers/formatter.rb
80 def convert_to_tag(type)
81   meth = :"format_#{type}"
82   if respond_to?(meth, true)
83     send(meth)
84   else
85     _format_input(type)
86   end
87 end
copy_boolean_options_to_attributes(keys) click to toggle source

Set attribute values for given keys to be the same as the key unless the attributes already have a value for the key.

    # File lib/forme/transformers/formatter.rb
300 def copy_boolean_options_to_attributes(keys)
301   keys.each do |k|
302     if @opts[k] && !@attr.has_key?(k)
303       @attr[k] = k
304     end
305   end
306 end
copy_options_to_attributes(keys) click to toggle source

Copy option values for given keys to the attributes unless the attributes already have a value for the key.

    # File lib/forme/transformers/formatter.rb
290 def copy_options_to_attributes(keys)
291   keys.each do |k|
292     if @opts.has_key?(k) && !@attr.has_key?(k)
293       @attr[k] = @opts[k]
294     end
295   end
296 end
format_checkbox() click to toggle source

If the checkbox has a name, will create a hidden input tag with the same name that comes before this checkbox. That way, if the checkbox is checked, the web app will generally see the value of the checkbox, and if it is not checked, the web app will generally see the value of the hidden input tag.

    # File lib/forme/transformers/formatter.rb
 94 def format_checkbox
 95   @attr[:type] = :checkbox
 96   @attr[:checked] = :checked if @opts[:checked]
 97   if @attr[:name] && !@opts[:no_hidden]
 98     attr = {:type=>:hidden}
 99     unless attr[:value] = @opts[:hidden_value]
100       attr[:value] = CHECKBOX_MAP[@attr[:value]]
101     end
102     attr[:id] = "#{@attr[:id]}_hidden" if @attr[:id]
103     attr[:name] = @attr[:name]
104     [tag(:input, attr), tag(:input)]
105   else
106     tag(:input)
107   end
108 end
format_checkboxset() click to toggle source
    # File lib/forme/transformers/formatter.rb
213 def format_checkboxset
214   @opts[:multiple] = true unless @opts.has_key?(:multiple)
215   _format_set(:checkbox, :no_hidden=>true, :multiple=>true)
216 end
format_date() click to toggle source

Use a date input by default. If the :as=>:select option is given, use a multiple select box for the options.

    # File lib/forme/transformers/formatter.rb
121 def format_date
122   if @opts[:as] == :select
123     values = {}
124     if v = @attr[:value]
125       v = Date.parse(v) unless v.is_a?(Date)
126       values[:year], values[:month], values[:day] = v.year, v.month, v.day
127     end
128     _format_date_select(values, @opts[:order] || DEFAULT_DATE_ORDER)
129   else
130     _format_input(:date)
131   end
132 end
format_datetime() click to toggle source

Use a datetime input by default. If the :as=>:select option is given, use a multiple select box for the options.

    # File lib/forme/transformers/formatter.rb
137 def format_datetime
138   if @opts[:as] == :select
139     values = {}
140     if v = @attr[:value]
141       v = DateTime.parse(v) unless v.is_a?(Time) || v.is_a?(DateTime)
142       values[:year], values[:month], values[:day], values[:hour], values[:minute], values[:second] = v.year, v.month, v.day, v.hour, v.min, v.sec
143     end
144     _format_date_select(values, @opts[:order] || DEFAULT_DATETIME_ORDER)
145   else
146     _format_input('datetime-local')
147   end
148 end
format_radio() click to toggle source

For radio buttons, recognizes the :checked option and sets the :checked attribute in the tag appropriately.

    # File lib/forme/transformers/formatter.rb
112 def format_radio
113   @attr[:checked] = :checked if @opts[:checked]
114   @attr[:type] = :radio
115   tag(:input)
116 end
format_radioset() click to toggle source
    # File lib/forme/transformers/formatter.rb
218 def format_radioset
219   _format_set(:radio)
220 end
format_select() click to toggle source

Takes a select input and turns it into a select tag with (possibly) option children tags.

    # File lib/forme/transformers/formatter.rb
187 def format_select
188   @attr[:multiple] = :multiple if @opts[:multiple]
189   copy_options_to_attributes([:size])
190 
191   os = process_select_optgroups(:_format_select_optgroup) do |label, value, sel, attrs|
192     if !value.nil? || sel
193       attrs = attrs.dup
194       attrs[:value] = value unless value.nil?
195       attrs[:selected] = :selected if sel
196     end
197     tag(:option, attrs, [label])
198   end
199   tag(:select, @attr, os)
200 end
format_submit() click to toggle source

Formats a submit input. Respects :formaction option.

    # File lib/forme/transformers/formatter.rb
272 def format_submit
273   copy_options_to_attributes([:formaction])
274   _format_input(:submit)
275 end
format_textarea() click to toggle source

Formats a textarea. Respects the following options:

:value

Sets value as the child of the textarea.

    # File lib/forme/transformers/formatter.rb
279 def format_textarea
280   copy_options_to_attributes([:cols, :rows, :maxlength, :minlength])
281   if val = @attr.delete(:value)
282     tag(:textarea, @attr, [val])
283   else
284     tag(:textarea)
285   end
286 end
handle_errors_option() click to toggle source
    # File lib/forme/transformers/formatter.rb
360 def handle_errors_option
361   if key = @opts[:key]
362     if !@attr.has_key?(:error) && !@attr.has_key?("error") && (errors = @form.opts[:errors])
363       set_error_from_namespaced_errors(namespaces, errors, key)
364     end
365   end
366 end
handle_key_option() click to toggle source

Have the :key option possibly set the name, id, and/or value attributes if not already set.

    # File lib/forme/transformers/formatter.rb
342 def handle_key_option
343   if key = @opts[:key]
344     unless @attr[:name] || @attr['name']
345       @attr[:name] = namespaced_name(key, @opts[:array] || @opts[:multiple])
346       if !@attr.has_key?(:value) && !@attr.has_key?('value') && (values = @form.opts[:values])
347         set_value_from_namespaced_values(namespaces, values, key)
348       end
349     end
350     unless @attr[:id] || @attr['id']
351       id = namespaced_id(key)
352       if suffix = @opts[:key_id]
353         id += "_#{suffix}"
354       end
355       @attr[:id] = id
356     end
357   end
358 end
namespaced_id(field) click to toggle source

Return a unique id attribute for the field, based on the current namespaces.

    # File lib/forme/transformers/formatter.rb
374 def namespaced_id(field)
375   "#{namespaces.join('_')}#{'_' unless namespaces.empty?}#{field}"
376 end
namespaced_name(field, multiple=false) click to toggle source

Return a unique name attribute for the field, based on the current namespaces. If multiple is true, end the name with [] so that param parsing will treat the name as part of an array.

    # File lib/forme/transformers/formatter.rb
381 def namespaced_name(field, multiple=false)
382   if namespaces.empty?
383     if multiple
384       "#{field}[]"
385     else
386       field
387     end
388   else
389     root, *nsps = namespaces
390     "#{root}#{nsps.map{|n| "[#{n}]"}.join}[#{field}]#{'[]' if multiple}"
391   end
392 end
namespaces() click to toggle source

Array of namespaces to use for the input

    # File lib/forme/transformers/formatter.rb
369 def namespaces
370   input.form_opts[:namespace]
371 end
normalize_options() click to toggle source

Normalize the options used for all input types.

    # File lib/forme/transformers/formatter.rb
309 def normalize_options
310   copy_options_to_attributes(ATTRIBUTE_OPTIONS)
311   copy_boolean_options_to_attributes(ATTRIBUTE_BOOLEAN_OPTIONS)
312   handle_key_option
313   handle_errors_option
314 
315   Forme.attr_classes(@attr, @opts[:class]) if @opts.has_key?(:class)
316 
317   if @opts[:error]
318     Forme.attr_classes(@attr, 'error')
319     @attr["aria-invalid"] = "true"
320     if @opts.fetch(:error_handler, true)
321       if @opts[:error_id]
322         @attr['aria-describedby'] ||= @opts[:error_id]
323       else
324         if id = @attr[:id] || @attr['id']
325           error_id = @attr['aria-describedby'] ||= "#{id}_error_message"
326           @opts[:error_id] = error_id
327         end
328       end
329     end
330   end
331 
332   if data = opts[:data]
333     data.each do |k, v|
334       k = k.to_s.tr("_", "-") if k.is_a?(Symbol) && input.opts[:dasherize_data]
335       sym = :"data-#{k}"
336       @attr[sym] = v unless @attr.has_key?(sym)
337     end
338   end
339 end
process_select_optgroups(grouper) { |prompt, '', false, blank_attr| ... } click to toggle source

If :optgroups option is present, iterate over each of the groups inside of it and create options for each group. Otherwise, if :options option present, iterate over it and create options.

    # File lib/forme/transformers/formatter.rb
417 def process_select_optgroups(grouper, &block)
418   os = if groups = @opts[:optgroups]
419     groups.map do |group, options|
420       send(grouper, group, process_select_options(options, &block))
421     end
422   else
423     return unless @opts[:options]
424     process_select_options(@opts[:options], &block)
425   end
426   @attr.delete(:value)
427 
428   if prompt = @opts[:add_blank]
429     unless prompt.is_a?(String)
430       prompt = Forme.default_add_blank_prompt
431     end
432     blank_attr = @opts[:blank_attr] || {}
433     os.send(@opts[:blank_position] == :after ? :push : :unshift, yield([prompt, '', false, blank_attr]))
434   end
435 
436   os
437 end
process_select_options(os) { |text, val, !nil? ? call: call, attr| ... } click to toggle source

Iterate over the given options, yielding the option text, value, whether it is selected, and any attributes. The block should return an appropriate tag object.

    # File lib/forme/transformers/formatter.rb
441 def process_select_options(os)
442   vm = @opts[:value_method]
443   tm = @opts[:text_method]
444   sel = @opts[:selected] || @attr[:value]
445 
446   if @opts[:multiple]
447     sel = Array(sel)
448     cmp = lambda{|v| sel.include?(v)}
449   else
450     cmp = lambda{|v| v == sel}
451   end
452 
453   os.map do |x|
454     attr = {}
455     if x == :hr
456       next form._tag(:hr, {})
457     elsif tm
458       text = x.send(tm)
459       val = x.send(vm) if vm
460     elsif x.is_a?(Array)
461       text = x.first
462       val = x.last
463 
464       if val.is_a?(Hash)
465         value = val[:value]
466         attr.merge!(val)
467         val = value
468       end
469     else
470       text = x
471     end
472 
473     yield [text, val, !val.nil? ? cmp.call(val) : cmp.call(text), attr]
474   end
475 end
set_error_from_namespaced_errors(namespaces, errors, key) click to toggle source
    # File lib/forme/transformers/formatter.rb
406 def set_error_from_namespaced_errors(namespaces, errors, key)
407   namespaces.each do |ns|
408     return unless errors = errors[ns] || errors[ns.to_s]
409   end
410 
411   @opts[:error] = errors.fetch(key){errors.fetch(key.to_s){return}}
412 end
set_value_from_namespaced_values(namespaces, values, key) click to toggle source

Set the values option based on the (possibly nested) values hash given, array of namespaces, and key.

    # File lib/forme/transformers/formatter.rb
396 def set_value_from_namespaced_values(namespaces, values, key)
397   namespaces.each do |ns|
398     v = values[ns] || values[ns.to_s]
399     return unless v
400     values = v
401   end
402 
403   @attr[:value] = values.fetch(key){values.fetch(key.to_s){return}}
404 end
tag(type, attr=@attr, children=nil) click to toggle source

Create a Tag instance related to the receiver’s form with the given arguments.

    # File lib/forme/transformers/formatter.rb
479 def tag(type, attr=@attr, children=nil)
480   form._tag(type, attr, children)
481 end
wrap(type, tag) click to toggle source

Wrap the tag for the given transformer type.

    # File lib/forme/transformers/formatter.rb
484 def wrap(type, tag)
485   Forme.transform(type, @opts, input.form_opts, tag, input)
486 end
wrap_tag(tag) click to toggle source

Wrap the tag with the form’s wrapper.

    # File lib/forme/transformers/formatter.rb
489 def wrap_tag(tag)
490   wrap(:wrapper, tag)
491 end
wrap_tag_with_error(tag) click to toggle source

Wrap the tag with the form’s error_handler.

    # File lib/forme/transformers/formatter.rb
494 def wrap_tag_with_error(tag)
495   wrap(:error_handler, tag)
496 end
wrap_tag_with_label(tag) click to toggle source

Wrap the tag with the form’s labeler.

    # File lib/forme/transformers/formatter.rb
499 def wrap_tag_with_label(tag)
500   wrap(:labeler, tag)
501 end