module Formtastic::Inputs::Base::Timeish
Timeish inputs (`:date_select`, `:datetime_select`, `:time_select`) are similar to the Rails date and time helpers (`date_select`, `datetime_select`, `time_select`), rendering a series of `<select>` tags for each fragment (year, month, day, hour, minute, seconds). The fragments are then re-combined to a date by ActiveRecord through multi-parameter assignment.
The mark-up produced by Rails is simple but far from ideal, with no way to label the individual fragments for accessibility, no fieldset to group the related fields, and no legend describing the group. Formtastic addresses this within the standard `<li>` wrapper with a `<fieldset>` with a `<legend>` as a label, followed by an ordered list (`<ol>`) of list items (`<li>`), one for each fragment (year, month, …). Each `<li>` fragment contains a `<label>` (eg “Year”) for the fragment, and a `<select>` containing `<option>`s (eg a range of years).
In the supplied formtastic.css file, the resulting mark-up is styled to appear a lot like a standard Rails date time select by:
-
styling the legend to look like the other labels (to the left hand side of the selects)
-
floating the `<li>` fragments against each other as a single line
-
hiding the `<label>` of each fragment with `display:none`
@example `:date_select` input with full form context and sample HTMl output
<%= semantic_form_for(@post) do |f| %> <%= f.inputs do %> ... <%= f.input :publish_at, :as => :date_select %> <% end %> <% end %> <form...> <fieldset class="inputs"> <ol> <li class="date"> <fieldset class="fragments"> <ol class="fragments-group"> <li class="fragment"> <label for="post_publish_at_1i">Year</label> <select id="post_publish_at_1i" name="post[publish_at_1i]">...</select> </li> <li class="fragment"> <label for="post_publish_at_2i">Month</label> <select id="post_publish_at_2i" name="post[publish_at_2i]">...</select> </li> <li class="fragment"> <label for="post_publish_at_3i">Day</label> <select id="post_publish_at_3i" name="post[publish_at_3i]">...</select> </li> </ol> </fieldset> </li> </ol> </fieldset> </form>
@example `:time_select` input
<%= f.input :publish_at, :as => :time_select %>
@example `:datetime_select` input
<%= f.input :publish_at, :as => :datetime_select %>
@example Change the labels for each fragment
<%= f.input :publish_at, :as => :date_select, :labels => { :year => "Y", :month => "M", :day => "D" } %>
@example Suppress the labels for all fragments
<%= f.input :publish_at, :as => :date_select, :labels => false %>
@example Skip a fragment (defaults to 1, skips all following fragments)
<%= f.input :publish_at, :as => :datetime_select, :discard_minute => true %> <%= f.input :publish_at, :as => :datetime_select, :discard_hour => true %> <%= f.input :publish_at, :as => :datetime_select, :discard_day => true %> <%= f.input :publish_at, :as => :datetime_select, :discard_month => true %> <%= f.input :publish_at, :as => :datetime_select, :discard_year => true %>
@example Change the order
<%= f.input :publish_at, :as => :date_select, :order => [:month, :day, :year] %>
@example Include seconds with times (excluded by default)
<%= f.input :publish_at, :as => :time_select, :include_seconds => true %>
@example Specify if there should be a blank option at the start of each select or not. Note that, unlike select inputs, :include_blank does not accept a string value.
<%= f.input :publish_at, :as => :time_select, :include_blank => true %> <%= f.input :publish_at, :as => :time_select, :include_blank => false %>
@todo Document i18n @todo Check what other Rails options are supported (`start_year`, `end_year`, `use_month_numbers`, `use_short_month`, `add_month_numbers`, `prompt`), write tests for them, and otherwise support them @todo Could we take the rendering from Rails' helpers and inject better HTML in and around it rather than re-inventing the whee?
Public Instance Methods
# File lib/formtastic/inputs/base/timeish.rb, line 119 def date_fragments options[:order] || i18n_date_fragments || default_date_fragments end
# File lib/formtastic/inputs/base/timeish.rb, line 123 def default_date_fragments [:year, :month, :day] end
# File lib/formtastic/inputs/base/timeish.rb, line 146 def fragment_id(fragment) "#{input_html_options[:id]}_#{position(fragment)}i" end
# File lib/formtastic/inputs/base/timeish.rb, line 163 def fragment_input_html(fragment) opts = input_options.merge(:prefix => fragment_prefix, :field_name => fragment_name(fragment), :default => value, :include_blank => include_blank?) template.send(:"select_#{fragment}", value, opts, input_html_options.merge(:id => fragment_id(fragment))) end
# File lib/formtastic/inputs/base/timeish.rb, line 135 def fragment_label(fragment) labels_from_options = options.key?(:labels) ? options[:labels] : {} if !labels_from_options '' elsif labels_from_options.key?(fragment) labels_from_options[fragment] else ::I18n.t(fragment.to_s, :default => fragment.to_s.humanize, :scope => [:datetime, :prompts]) end end
# File lib/formtastic/inputs/base/timeish.rb, line 154 def fragment_label_html(fragment) text = fragment_label(fragment) text.blank? ? "".html_safe : template.content_tag(:label, text, :for => fragment_id(fragment)) end
# File lib/formtastic/inputs/base/timeish.rb, line 150 def fragment_name(fragment) "#{method}(#{position(fragment)}i)" end
# File lib/formtastic/inputs/base/timeish.rb, line 168 def fragment_prefix if builder.options.key?(:index) object_name + "[#{builder.options[:index]}]" else object_name end end
# File lib/formtastic/inputs/base/timeish.rb, line 127 def fragment_wrapping(&block) template.content_tag(:li, template.capture(&block), fragment_wrapping_html_options) end
# File lib/formtastic/inputs/base/timeish.rb, line 131 def fragment_wrapping_html_options { :class => 'fragment' } end
# File lib/formtastic/inputs/base/timeish.rb, line 111 def fragments date_fragments + time_fragments end
# File lib/formtastic/inputs/base/timeish.rb, line 220 def fragments_inner_wrapping(&block) template.content_tag(:ol, template.capture(&block) ) end
# File lib/formtastic/inputs/base/timeish.rb, line 209 def fragments_label if render_label? template.content_tag(:legend, builder.label(method, label_text, :for => fragment_id(fragments.first)), :class => "label" ) else "".html_safe end end
# File lib/formtastic/inputs/base/timeish.rb, line 198 def fragments_wrapping(&block) template.content_tag(:fieldset, template.capture(&block).html_safe, fragments_wrapping_html_options ) end
# File lib/formtastic/inputs/base/timeish.rb, line 205 def fragments_wrapping_html_options { :class => "fragments" } end
# File lib/formtastic/inputs/base/timeish.rb, line 189 def i18n_date_fragments order = ::I18n.t(:order, :scope => [:date]) if order.is_a?(Array) order.map &:to_sym else nil end end
TODO extract to BlankOptions or similar – Select uses similar code
# File lib/formtastic/inputs/base/timeish.rb, line 177 def include_blank? options.key?(:include_blank) ? options[:include_blank] : builder.include_blank_for_select_by_default end
# File lib/formtastic/inputs/base/timeish.rb, line 185 def position(fragment) positions[fragment] end
# File lib/formtastic/inputs/base/timeish.rb, line 181 def positions { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 } end
# File lib/formtastic/inputs/base/timeish.rb, line 115 def time_fragments options[:include_seconds] ? [:hour, :minute, :second] : [:hour, :minute] end
# File lib/formtastic/inputs/base/timeish.rb, line 93 def to_html input_wrapping do fragments_wrapping do hidden_fragments << fragments_label << template.content_tag(:ol, fragments.map do |fragment| fragment_wrapping do fragment_label_html(fragment) << fragment_input_html(fragment) end end.join.html_safe, # TODO is this safe? { :class => 'fragments-group' } # TODO refactor to fragments_group_wrapping ) end end end
# File lib/formtastic/inputs/base/timeish.rb, line 159 def value object.send(method) if object && object.respond_to?(method) end