BinaryWebPark

How to Add jQuery UI Datepicker To Your Rails 4 Application With Formtastic and Cocoon

September 27, 2014

What is Formtastic, Cocoon, and jQuery UI Datepicker?

Formtastic is a gem that lets you add forms more easily to your Rails application. Cocoon allows you to add dynamic nested forms to your Rails application using jQuery. jQuery-ui Datepicker allows you to present the user with a nice date selection calendar when entering date information.

In this article I will give you the recipe for adding jquery-ui datepicker with formtastic and cocoon in your Rails 4 application.

Step 1 – Add the formtastic, jquery-ui-rails, and cocoon gems to your Gemfile and then install them using bundle install command

gem 'formtastic', '~> 3.0.0rc'
gem 'jquery-ui-rails'
gem "cocoon"

Step 1a – Run the formtastic generator

rails generate formtastic:install

Step 2 – Add the Cocoon markup to your views along with the Formtastic markup

These are my rails models, Person and Report

class Person < ActiveRecord::Base
  has_many :reports, dependent: :destroy
end
class Report < ActiveRecord::Base
  belongs_to :person
end

This is the formtastic and cocoon code in my Rails view

= semantic_form_for @person, url: {action: "update_it" } do |f|
  - if @person.errors.any?
    #error_explanation
    %h2= "#{pluralize(@person.errors.count, "error")} prohibited this person and their reports from being displayed:"
      %ul
        - @person.errors.full_messages.each do |msg|
          %li= msg

%h4 Reports

#reports

  = f.semantic_fields_for :reports do |report|
    = render 'report_fields', :f => report
    .links
      = link_to_add_association 'Add Report', f, :reports

This is the ‘report_fields’ partial called above

.nested-fields
  = f.inputs :name => "Report(s)" do
  = f.input :title
  = f.input :date_published, :as => :date_picker, :input_html => { :class=> "ui-datepicker", :value => (f.object.date_published.try(:strftime,'%m/%d/%Y'))}
  = f.input :report_type
  = link_to_remove_association "Remove Report", f

Notice in the reportfields partial how I am calling the _try method. This is a Rails method that tries calling a method on an object, but returns nil instead of raising an exception if that object is nil.

For example, suppose you had an object called person that had a nil assignment (as in person=nil). If you tried calling person.firstname, you would get an error because person is a nil instance. Using try, as in person.try(:firstname) simply results in a nil value returned.

Step 3 – Add the required calls in your assets library

In application.js

//= require cocoon
//= require jquery-ui

Note: If you don’t want to include the entire jquery-ui library as above in application.js and would rather include only jquery-ui datepicker, you can require specific modules by substituting the following require directive:

//= require jquery-ui/datepicker

For Formtastic and jquery-ui-rails, you’ll need to add some require directives for css

In app/assets/stylesheets/application.css

/*
*= require formtastic
*= require jquery-ui
*/

For Formtastic, the gem’s author Justin French, says:

A proof-of-concept set of stylesheets are provided which you can include in your layout. Customization is best achieved by overriding these styles in an additional stylesheet. In the above application.css, I elected not to do any customization.

Step 4 – Add the javascript (I use coffeescript) in your assets/javascripts folder

This is my javascript file for adding the datepicker. Notice that I’m selecting the element with the class ui-datepicker since this is what I gave my html view markup via :inputhtml => { :class=> “ui-datepicker”}_ in the ‘report_fields’ partial above.

Below is my file formtastic_datepicker.js.coffee:

$(document).ready ->
  $(".ui-datepicker").each ->
    $(@).datepicker
      dateFormat: "mm/dd/yy"

$("#communications").on "cocoon:after-insert", ->
  $(".ui-datepicker").datepicker
    dateFormat: "mm/dd/yy"

Step 5 – Parse dates in your Rails controller in the correct format using Date.strptime

In my controller, I have:

def update_it

  parse_datetime

  if @person.update(person_params)
    redirect_to edit_survey_person_path(@person, survey_id: @survey.id, survey_type: params[:survey_type]), notice: 'Survey was successfully filled in.'
  else
    render :template => "people/edit_survey_saved"
  end

end

def parse_datetime
  params["person"].each do |nested_model_attrs, ids|
    case nested_model_attrs
    when "reports_attributes"
      ids.each do |id, attrs|
        attrs["date\_published"] = DateTime.strptime(attrs["date\_published"], '%m/%d/%Y') if attrs["date_published"].present?
      end
    end
  end
end

def person_params
  params.require(:person).permit(:id, reports_attributes: [:id, :title, :date_published, :report_type, :person_id, :_destroy])
end

Notice in the controller, I call parse_datetime so I can convert the date (which is passed in the params hash in month-day-year format since that is what people are used to seeing, but Rails stores DateTime objects in UTC format). If I didn’t do this datetime parsing on the backend, Rails ends up flipping the day and month every time you save it, and you wind up getting back a date you didn’t expect. For example, if you put in 10/2/14 (or October 2, 2014) via the datepicker in the browser, when you returned it to the Rails view, the date would show as 2/10/14 (or February 2, 2014). It’s not the prettiest format, but it works.

Consider also using text fields instead of DateTime fields in Rails. This means you wouldn’t have to parse the date in the params hash to make it compatible with the way Rails stores DateTime objects.

In another interesting side note, looking through the code, I found Formtastic gives you a custom datepicker but it only works with Google Chrome.

If anyone has a better way of doing this (especially parsing the dates on the backend), I’d love to hear about it.