Solutions. For Life.

Building a unified `time_select` field

posted on: 4/10/2013, 11:10:11 AM

last updated: 4/10/2013, 11:10:11 AM

by: skamansam

Reading Time: 2 min read

Before I migrated to Rails 3.2, I used a plugin to provide a unified input field for time_select with either the standard Rails ActionView or the SimpleForm gem (SimpleForm uses ActionView under the hood). I was happy until the Rails upgrade caused my gem to inexplicably stop working. I had a deadline to meet and could not be bothered with complicated solutions. I searched and searched for alternatives to this fine gem, but none were found. Then it hit me: why not just write my own code and replace the inputs with my own code? Surely it can't take too long to build a time input based on the simple_time_select gem! So here it is: my simple version of the gem, with only a few modifications from the simple_time_select gem.

Here is my old code, utilizing simple_time_select. The gem is used only if :simple_time_select is true, and it uses the :minute_interval, :time_separator, :start_hour, :end_hour, and a host of other options for setting the times. I wish it had a way for specifying just a start_time and end_time.

<div class="field">
  <%= f.input :start_time,
              :label=>"Start Time:",
              :default=>appt.start_time,
              :wrapper=>false,
              :simple_time_select => true,
              :label_html=>{:class=>"head"},
              :minute_interval => 5,
              :time_separator => "",
              :start_hour => 06,
              :end_hour => 17
   %>
</div>

I had to search for an algorithm to use for the periodicity, and settled on the algorithm discussed in this discussion on Stack Overflow, and is inlined as the :collection option. Since the :default option removes the inital blank I wanted as the first item in the list, I has to add a blank :include_blank option.

<div class="field">
  <%- time_start = Time.now.change(:hour=>6,:minute=>0) %>
  <%- time_end = Time.now.change(:hour=>17,:minute=>55) %>
  <%- period = 5.minutes %>
  <%= f.input :start_time,
           :as=>:collection_select,
           :required=>true,
           :include_blank => '',
           :selected=>(
             f.object.start_time.blank? ?
               "" : f.object.start_time.strftime("%H:%M")
           ),
           :input_html=>{
             :name=>"appt[start_time(5i)]",
             :id=>"appt_start_time_5i"},
           :collection=> (
             [time_start].tap{
               |array| array << array.last + period
                 while array.last < time_end
             }).map{
               |t| [t.strftime("%l:%M %p"), t.strftime("%H:%M")]
             }
   %>
</div>

I am sure you can wrap this in a Helper class somewhere, and it should be easy to monkeypatch ActiveView or SimpleForm to display this instead of the default.

References: