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
-
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
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.
The Form
instance for the receiver, taken from the input
.
The Input
instance for the receiver. This is what the receiver converts to the lower level Tag
form (or an array of them).
The opts
hash of the input
.
Public Class Methods
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
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
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
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
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
# 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
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
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
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 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
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
# 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
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
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
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
# File lib/forme/transformers/formatter.rb 218 def format_radioset 219 _format_set(:radio) 220 end
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
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
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
# 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
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
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
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
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 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
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
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
# 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 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
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 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 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 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 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