Rails datetime picker

In Rails, when making a form with a datetime field in it, the scaffolding will generate a datetimepicker consisting of six dropdowns. One for year, month, date, hours, minutes and seconds. You can tweak these selects, by passing arguments to the form builder, that the minutes for example increments by 10 instead of 1, thus getting a more simplified input.

But for some interfaces you want something simpler. Anyways, if you need to input a lot of dates, having to click six times is annoying. There are other options, though.

For a date field, it’s simple. Just use the datepicker in jQuery-UI. Generate a text field in the form. You can then give it a class, and this class you can use for generating the datepicker with jQuery.

Something like this:

$(function() {
    $('.datepicker').datepicker({dateFormat: 'yy-mm-dd'});
});

Simple and clean.

But the datepicker in jQuery-UI doesn’t support time input. There are extensions to jQuery, which add timepicking functionality, like this one or this one, but I disagree with the interface of both. I like to have a simple text field for the time, and not sliders or knobs. Maybe it just me :).

Anyways, here is my solution:

Create a new rails app:

rails new datetime
cd datetime

Lets generate a simple blog posts model, where the published_at datetime field can be edited through a form.

rails g scaffold post name:string content:text date:datetime

That should get us started. The scaffolding will build a select based datetime input for the form view (app/views/posts/_form.html.erb):

<div class="field">
  <%= f.label :published_at %><br />
  <%= f.datetime_select :published_at %>
</div>

The goal here is to make two textfields out of this. One for the date (which will use the jQuery datepicker), and one for the time (which will just accept normal input via the keyboard). Let’s add some extra attributes to the model (don’t forget to add them to attr_accessible as well, otherwise it won’t work):

class Post < ActiveRecord::Base
  attr_accessible :content, :name, :published_at, :published_at_date, :published_at_time  #remember to add the fields here as well!

  # add the accessors for the two fields
  attr_accessor :published_at_date, :published_at_time
end

To convert the database datetime to out two fields, and vice versa, we add some callbacks to the model (app/models/post.rb):

class Post < ActiveRecord::Base
  attr_accessible :content, :name, :published_at

  # add the accessors for the two fields
  attr_accessor :published_at_date, :published_at_time

  # add some callbacks
  after_initialize :get_datetimes # convert db format to accessors
  before_validation :set_datetimes # convert accessors back to db format

  def get_datetimes
    self.published_at ||= Time.now  # if the published_at time is not set, set it to now

    self.published_at_date ||= self.published_at.to_date.to_s(:db) # extract the date is yyyy-mm-dd format
    self.published_at_time ||= "#{'%02d' % self.published_at.hour}:#{'%02d' % self.published_at.min}" # extract the time
  end

  def set_datetimes
    self.published_at = "#{self.published_at_date} #{self.published_at_time}:00" # convert the two fields back to db
  end  
end

Let’s show the two accessors instead of the datetime select in the form (app/views/posts/_form.html.erb):

<div class="field">
  <%= f.label :published_at %><br />
  <%= f.text_field :published_at_date, :class => 'datepicker', :size => 10, :maxlength => 10 %> <%= f.text_field :published_at_time, :size => 5, :maxlength => 5 %> 
</div>

include jQuery UI in app/assets/javascripts/application.js:

//= require jquery-ui

Don’t forget to include css and images for jQuery UI, as it doesn’t come included standard. You can choose your own theme at jQuery Theme Roller. It is a bit tricky with the assets pipeline, because the css you get have wrong paths for images. It is easily solved with a search and replace in the css file.

The only things we need to do now is to add some jQuery sauce over the date field. You can also do this in app/assets/application.js and it will be application-wide:

$(function() {
    $('.datepicker').datepicker({dateFormat: 'yy-mm-dd'});
});

You will get a datetime picker that looks like this:

I like this solution for apps where the users have some level of affinity with using webapps. They understand this type of input.

As a last precaution, let’s add a validation to check the time format (app/models/post.rb):

validates_format_of :published_at_time, :with => /\d{1,2}:\d{2}/

Gotchas

If the user inputs a number higher than 24 as hours or higher than 59 as minutes, the current time will be used. This could also easily be solved. You could add a custom validator, that parses the date, and gives an error on an exception. Your users should be able to deal with this type of inputs. I think it is friendlier than six dropdowns though. I hope you found this post useful.

Comments

comments powered by Disqus