Ruby on Rails
CalendarHelper (Version #57)

by MichaelSchuerig

These helper methods are intended to make the use of the DHTML/JavaScript calendar from http://www.dynarch.com/projects/calendar/ as easy as possible.

Lest I forget it. The calendar has a bug that keeps it currently from working correctly in “flat” (in-page) mode. Please have a look at this patch

—MichaelSchuerig


S2: Tested with Internet Explorer 7 Beta 2 and Firefox

Michael, could you preface each block of code with a suggestion of which file in our project it is intended to be merged with?

—Tried to do this- SteveJernigan

I would also appreciate some more pre-explanation. such as what this module can do exactly. does it allow me to color certain dates? does it allow me to easily set onclick event on dates. I want to allow the user to browse through my apps via the application.

—Thinking of trying- Somekool

If you want to load the helper functions as an ActionView Helper plugin, replace

module CalendarHelper
with
module ActionView::Helpers::CalendarHelper
and add the line
ActionView::Base.send(:include, ActionView::Helpers::CalendarHelper)
at the end of the code. Now put the code into the the file vendor/plugins/calendar_helper/lib/calendar_helper.rb and put an init.rb file into vendors/plugins/calendar_helper. This file should have the following content:
require 'calendar_helper'



vendor/plugins/calendar_helper/lib/calendar_helper.rb


# The Calendar Helper methods create HTML code for different variants of the
# Dynarch DHTML/JavaScript Calendar.
#
# Author: Michael Schuerig, <a href="mailto:michael@schuerig.de">michael@schuerig.de</a>, 2005
# Free for all uses. No warranty or anything. Comments welcome.
#
# Version 0.02:
# Always set calendar_options's ifFormat value to '%Y/%m/%d %H:%M:%S'
# so that the calendar recieves the object's time of day.  Previously,
# the '%c' formating used to set the initial date would be parsed by
# the JavaScript calendar correctly to find the date, but it would not
# pick up the time of day.
#
# Version 0.01:
# Original version by Michael Schuerig.
#

#
#
# == Prerequisites
#
# Get the latest version of the calendar package from
# <a href="http://www.dynarch.com/projects/calendar/">http://www.dynarch.com/projects/calendar/</a>
#
# == Installation
#
# You need to install at least these files from the jscalendar distribution
# in the +public+ directory of your project
#
#  public/
#      images/
#          calendar.gif [copied from img.gif]
#      javascripts/
#          calendar.js
#          calendar-setup.js
#          calendar-en.js
#      stylesheets/
#          calendar-system.css
#
# Then, in the head section of your page templates, possibly in a layout,
# include the necessary files like this:
#
#  <%= stylesheet_link_tag 'calendar-system.css' %>
#  <%= javascript_include_tag 'calendar', 'calendar-en', 'calendar-setup' %>
#
# == Common Options
#
# The +html_options+ argument is passed through mostly verbatim to the
# +text_field+, +hidden_field+, and +image_tag+ helpers.
# The +title+ attributes are handled specially, +field_title+ and
# +button_title+ appear only on the respective elements as +title+.
#
# The +calendar_options+ argument accepts all the options of the
# JavaScript +Calendar.setup+ method defined in +calendar-setup.js+.
# The ifFormat option for +Calendar.setup+ is set up with a default
# value that sets the calendar's date and time to the object's value,
# so only set it if you need to send less specific times to the
# calendar, such as not setting the number of seconds.

#
#
module CalendarHelper

  # Returns HTML code for a calendar that pops up when the calendar image is
  # clicked.
  #
  # _Example:_
  #
  #  <%= popup_calendar 'person', 'birthday',
  #        { :class => 'date',
  #          :field_title => 'Birthday',
  #          :button_image => 'calendar.gif',
  #          :button_title => 'Show calendar' },
  #        { :firstDay => 1,
  #          :range => [1920, 1990],
  #          :step => 1,
  #          :showOthers => true,
  #          :cache => true }
  #  %>
  #
  def popup_calendar(object, method, html_options = {}, calendar_options = {})
    _calendar(object, method, false, true, html_options, calendar_options)
  end

  # Returns HTML code for a flat calendar.
  #
  # _Example:_
  #
  #  <%= calendar 'person', 'birthday',
  #        { :class => 'date' },
  #        { :firstDay => 1,
  #          :range => [1920, 1990],
  #          :step => 1,
  #          :showOthers => true }
  #  %>
  #
  def calendar(object, method, html_options = {}, calendar_options = {})
    _calendar(object, method, false, false, html_options, calendar_options)
  end

  # Returns HTML code for a date field and calendar that pops up when the
  # calendar image is clicked.
  #
  # _Example:_
  #
  #  <%= calendar_field 'person', 'birthday',
  #        { :class => 'date',
  #          :field_title => 'Birthday',
  #          :button_title => 'Show calendar' },
  #        { :firstDay => 1,
  #          :range => [1920, 1990],
  #          :step => 1,
  #          :showOthers => true,
  #          :cache => true }
  #  %>
  #
  def calendar_field(object, method, html_options = {}, calendar_options = {})
    _calendar(object, method, true, true, html_options, calendar_options)
  end

  private

  def _calendar(object, method, show_field = true, popup = true, html_options = {}, calendar_options = {})
    button_image = html_options[:button_image] || 'calendar'
    date = value(object, method)

    input_field_id = "#{object}_#{method}" 
    calendar_id = "#{object}_#{method}_calendar" 

    add_defaults(calendar_options, :ifFormat => '%Y/%m/%d %H:%M:%S')

    field_options = html_options.dup
    add_defaults(field_options,
      :value => date && date.strftime(calendar_options[:ifFormat]),
      :size => 12
    )
    rename_option(field_options, :field_title, :title)
    remove_option(field_options, :button_title)
    if show_field
      field = text_field(object, method, field_options)
    else
      field = hidden_field(object, method, field_options)
    end

    if popup
      button_options = html_options.dup
      add_mandatories(button_options, :id => calendar_id)
      rename_option(button_options, :button_title, :title)
      remove_option(button_options, :field_title)
      calendar = image_tag(button_image, button_options)
    else
      calendar = "<div id=\"#{calendar_id}\" class=\"#{html_options[:class]}\"></div>" 
    end

    calendar_setup = calendar_options.dup
    add_mandatories(calendar_setup,
      :inputField => input_field_id,
      (popup ? :button : :flat) => calendar_id
    )

<<END
#{field}
#{calendar}
<script type="text/javascript">
  Calendar.setup({ #{format_js_hash(calendar_setup)} })
</script>
END
  end

  def value(object_name, method_name)
    if object = self.instance_variable_get("@#{object_name}")
      object.send(method_name)
    else
      nil
    end
  end

  def add_mandatories(options, mandatories)
    options.merge!(mandatories)
  end

  def add_defaults(options, defaults)
    options.merge!(defaults) { |key, old_val, new_val| old_val }
  end

  def remove_option(options, key)
    options.delete(key)
  end

  def rename_option(options, old_key, new_key)
    if options.has_key?(old_key)
      options[new_key] = options.delete(old_key)
    end
    options
  end

  def format_js_hash(options)
    options.collect { |key,value| key.to_s + ':' + value.inspect }.join(',')
  end

end

  

vendor/plugins/calendar_helper/lib/boiler_plate.rb

There’s still a problem that ActiveRecord doesn’t understand dates in their locale-dependend string form. So we need to make it know about dates.


require 'active_record'
require 'date'

module BoilerPlate # :nodoc:
  module Model # :nodoc:

    # I18n support for ActiveRecord.
    # Currently, all that it does is define a class variable
    #
    #   ActiveRecord::Base.date_format
    #
    # and redefines +write_attribute+ to convert string values to dates
    # according to this format.
    module I18n

      def self.included(base)
        base.class_eval do
          unless method_defined?(:write_attribute_with_date_cast)
            alias_method :write_attribute_without_date_cast, :write_attribute

            def write_attribute_with_date_cast(attr, value)
              if column_for_attribute(attr).type == :date
                value = cast_to_date(value)
              end
              write_attribute_without_date_cast(attr, value)
            end

            alias_method :write_attribute, :write_attribute_with_date_cast

            cattr_accessor :date_format
            ActiveRecord::Base.date_format = '%Y-%m-%d'
          end
        end
      end

      protected

      def cast_to_date(value)
        case value
        when String
          Date.strptime(value, ActiveRecord::Base.date_format) rescue nil ### FIXME rescue better
        when Date, Time
          value
        else
          raise ArgumentError, "Argument for cast_to_date must be a String, Date, or Time; was: #{value.inspect}" 
        end
      end

    end
  end
end

  


{SRJ- I had to add an extra condition to check for nil to avoid failures on optional dates where I wasn’t using the calendar. ‘when nil return 0’}
  

vendor/plugins/calendar_helper/init.rb



require 'calendar_helper'

  

config/environment.rb


Add this to end of file

ActiveRecord::Base.class_eval do
  include BoilerPlate::Model::I18n
end


  

app/controller/application.rb


Then, preferrably in a superclass of our controller, we need to set a before_filter that sets the date format.

class ApplicationController < ActionController::Base
  prepend_before_filter :localize

  def localize
    # determine locale and set other relevant stuff
    ActiveRecord::Base.date_format = date_format
  end
end

Using this method results in the following error: undefined local variable or method `date_format' for #<HomeController:0x26e89fc> Is the value on the RHS of the assignment in localize above supposed to be a string literal? If not, where is the date_format variable declared and initialized? I’m sure I’m just a rails newbie who doesn’t understand how somethings are fitting together, but perhaps someone can clarify the instructions on this page?

I get the same error, replaced with a string literal, ”%Y-%m-%d” and now it runs. However, that isn’t the format of the returned date.

Q: Is there a place where we can download it, or do we have to copy & paste?

A1: :http://wiki.rubyonrails.org/rails/pages/DynamicCalendarHelper
calendar_helper is similar but unrelated project that creates an in-line calendar and installs as a nice plugin. It doesn’t do the DHTML form widget thing though, which is probably what this page and hence your question is trying to be about.

A2: A plugin can be found at DhtmlCalendar .

A3: this documentation is a perfect example of why Wiki’s are usually awful.
—Just another AC adding cruft and noise to a page that was probably minimally helpful to begin with but now does more harm than good

by MichaelSchuerig

These helper methods are intended to make the use of the DHTML/JavaScript calendar from http://www.dynarch.com/projects/calendar/ as easy as possible.

Lest I forget it. The calendar has a bug that keeps it currently from working correctly in “flat” (in-page) mode. Please have a look at this patch

—MichaelSchuerig


S2: Tested with Internet Explorer 7 Beta 2 and Firefox

Michael, could you preface each block of code with a suggestion of which file in our project it is intended to be merged with?

—Tried to do this- SteveJernigan

I would also appreciate some more pre-explanation. such as what this module can do exactly. does it allow me to color certain dates? does it allow me to easily set onclick event on dates. I want to allow the user to browse through my apps via the application.

—Thinking of trying- Somekool

If you want to load the helper functions as an ActionView Helper plugin, replace

module CalendarHelper
with
module ActionView::Helpers::CalendarHelper
and add the line
ActionView::Base.send(:include, ActionView::Helpers::CalendarHelper)
at the end of the code. Now put the code into the the file vendor/plugins/calendar_helper/lib/calendar_helper.rb and put an init.rb file into vendors/plugins/calendar_helper. This file should have the following content:
require 'calendar_helper'



vendor/plugins/calendar_helper/lib/calendar_helper.rb


# The Calendar Helper methods create HTML code for different variants of the
# Dynarch DHTML/JavaScript Calendar.
#
# Author: Michael Schuerig, <a href="mailto:michael@schuerig.de">michael@schuerig.de</a>, 2005
# Free for all uses. No warranty or anything. Comments welcome.
#
# Version 0.02:
# Always set calendar_options's ifFormat value to '%Y/%m/%d %H:%M:%S'
# so that the calendar recieves the object's time of day.  Previously,
# the '%c' formating used to set the initial date would be parsed by
# the JavaScript calendar correctly to find the date, but it would not
# pick up the time of day.
#
# Version 0.01:
# Original version by Michael Schuerig.
#

#
#
# == Prerequisites
#
# Get the latest version of the calendar package from
# <a href="http://www.dynarch.com/projects/calendar/">http://www.dynarch.com/projects/calendar/</a>
#
# == Installation
#
# You need to install at least these files from the jscalendar distribution
# in the +public+ directory of your project
#
#  public/
#      images/
#          calendar.gif [copied from img.gif]
#      javascripts/
#          calendar.js
#          calendar-setup.js
#          calendar-en.js
#      stylesheets/
#          calendar-system.css
#
# Then, in the head section of your page templates, possibly in a layout,
# include the necessary files like this:
#
#  <%= stylesheet_link_tag 'calendar-system.css' %>
#  <%= javascript_include_tag 'calendar', 'calendar-en', 'calendar-setup' %>
#
# == Common Options
#
# The +html_options+ argument is passed through mostly verbatim to the
# +text_field+, +hidden_field+, and +image_tag+ helpers.
# The +title+ attributes are handled specially, +field_title+ and
# +button_title+ appear only on the respective elements as +title+.
#
# The +calendar_options+ argument accepts all the options of the
# JavaScript +Calendar.setup+ method defined in +calendar-setup.js+.
# The ifFormat option for +Calendar.setup+ is set up with a default
# value that sets the calendar's date and time to the object's value,
# so only set it if you need to send less specific times to the
# calendar, such as not setting the number of seconds.

#
#
module CalendarHelper

  # Returns HTML code for a calendar that pops up when the calendar image is
  # clicked.
  #
  # _Example:_
  #
  #  <%= popup_calendar 'person', 'birthday',
  #        { :class => 'date',
  #          :field_title => 'Birthday',
  #          :button_image => 'calendar.gif',
  #          :button_title => 'Show calendar' },
  #        { :firstDay => 1,
  #          :range => [1920, 1990],
  #          :step => 1,
  #          :showOthers => true,
  #          :cache => true }
  #  %>
  #
  def popup_calendar(object, method, html_options = {}, calendar_options = {})
    _calendar(object, method, false, true, html_options, calendar_options)
  end

  # Returns HTML code for a flat calendar.
  #
  # _Example:_
  #
  #  <%= calendar 'person', 'birthday',
  #        { :class => 'date' },
  #        { :firstDay => 1,
  #          :range => [1920, 1990],
  #          :step => 1,
  #          :showOthers => true }
  #  %>
  #
  def calendar(object, method, html_options = {}, calendar_options = {})
    _calendar(object, method, false, false, html_options, calendar_options)
  end

  # Returns HTML code for a date field and calendar that pops up when the
  # calendar image is clicked.
  #
  # _Example:_
  #
  #  <%= calendar_field 'person', 'birthday',
  #        { :class => 'date',
  #          :field_title => 'Birthday',
  #          :button_title => 'Show calendar' },
  #        { :firstDay => 1,
  #          :range => [1920, 1990],
  #          :step => 1,
  #          :showOthers => true,
  #          :cache => true }
  #  %>
  #
  def calendar_field(object, method, html_options = {}, calendar_options = {})
    _calendar(object, method, true, true, html_options, calendar_options)
  end

  private

  def _calendar(object, method, show_field = true, popup = true, html_options = {}, calendar_options = {})
    button_image = html_options[:button_image] || 'calendar'
    date = value(object, method)

    input_field_id = "#{object}_#{method}" 
    calendar_id = "#{object}_#{method}_calendar" 

    add_defaults(calendar_options, :ifFormat => '%Y/%m/%d %H:%M:%S')

    field_options = html_options.dup
    add_defaults(field_options,
      :value => date && date.strftime(calendar_options[:ifFormat]),
      :size => 12
    )
    rename_option(field_options, :field_title, :title)
    remove_option(field_options, :button_title)
    if show_field
      field = text_field(object, method, field_options)
    else
      field = hidden_field(object, method, field_options)
    end

    if popup
      button_options = html_options.dup
      add_mandatories(button_options, :id => calendar_id)
      rename_option(button_options, :button_title, :title)
      remove_option(button_options, :field_title)
      calendar = image_tag(button_image, button_options)
    else
      calendar = "<div id=\"#{calendar_id}\" class=\"#{html_options[:class]}\"></div>" 
    end

    calendar_setup = calendar_options.dup
    add_mandatories(calendar_setup,
      :inputField => input_field_id,
      (popup ? :button : :flat) => calendar_id
    )

<<END
#{field}
#{calendar}
<script type="text/javascript">
  Calendar.setup({ #{format_js_hash(calendar_setup)} })
</script>
END
  end

  def value(object_name, method_name)
    if object = self.instance_variable_get("@#{object_name}")
      object.send(method_name)
    else
      nil
    end
  end

  def add_mandatories(options, mandatories)
    options.merge!(mandatories)
  end

  def add_defaults(options, defaults)
    options.merge!(defaults) { |key, old_val, new_val| old_val }
  end

  def remove_option(options, key)
    options.delete(key)
  end

  def rename_option(options, old_key, new_key)
    if options.has_key?(old_key)
      options[new_key] = options.delete(old_key)
    end
    options
  end

  def format_js_hash(options)
    options.collect { |key,value| key.to_s + ':' + value.inspect }.join(',')
  end

end

  

vendor/plugins/calendar_helper/lib/boiler_plate.rb

There’s still a problem that ActiveRecord doesn’t understand dates in their locale-dependend string form. So we need to make it know about dates.


require 'active_record'
require 'date'

module BoilerPlate # :nodoc:
  module Model # :nodoc:

    # I18n support for ActiveRecord.
    # Currently, all that it does is define a class variable
    #
    #   ActiveRecord::Base.date_format
    #
    # and redefines +write_attribute+ to convert string values to dates
    # according to this format.
    module I18n

      def self.included(base)
        base.class_eval do
          unless method_defined?(:write_attribute_with_date_cast)
            alias_method :write_attribute_without_date_cast, :write_attribute

            def write_attribute_with_date_cast(attr, value)
              if column_for_attribute(attr).type == :date
                value = cast_to_date(value)
              end
              write_attribute_without_date_cast(attr, value)
            end

            alias_method :write_attribute, :write_attribute_with_date_cast

            cattr_accessor :date_format
            ActiveRecord::Base.date_format = '%Y-%m-%d'
          end
        end
      end

      protected

      def cast_to_date(value)
        case value
        when String
          Date.strptime(value, ActiveRecord::Base.date_format) rescue nil ### FIXME rescue better
        when Date, Time
          value
        else
          raise ArgumentError, "Argument for cast_to_date must be a String, Date, or Time; was: #{value.inspect}" 
        end
      end

    end
  end
end

  


{SRJ- I had to add an extra condition to check for nil to avoid failures on optional dates where I wasn’t using the calendar. ‘when nil return 0’}
  

vendor/plugins/calendar_helper/init.rb



require 'calendar_helper'

  

config/environment.rb


Add this to end of file

ActiveRecord::Base.class_eval do
  include BoilerPlate::Model::I18n
end


  

app/controller/application.rb


Then, preferrably in a superclass of our controller, we need to set a before_filter that sets the date format.

class ApplicationController < ActionController::Base
  prepend_before_filter :localize

  def localize
    # determine locale and set other relevant stuff
    ActiveRecord::Base.date_format = date_format
  end
end

Using this method results in the following error: undefined local variable or method `date_format' for #<HomeController:0x26e89fc> Is the value on the RHS of the assignment in localize above supposed to be a string literal? If not, where is the date_format variable declared and initialized? I’m sure I’m just a rails newbie who doesn’t understand how somethings are fitting together, but perhaps someone can clarify the instructions on this page?

I get the same error, replaced with a string literal, ”%Y-%m-%d” and now it runs. However, that isn’t the format of the returned date.

Q: Is there a place where we can download it, or do we have to copy & paste?

A1: :http://wiki.rubyonrails.org/rails/pages/DynamicCalendarHelper
calendar_helper is similar but unrelated project that creates an in-line calendar and installs as a nice plugin. It doesn’t do the DHTML form widget thing though, which is probably what this page and hence your question is trying to be about.

A2: A plugin can be found at DhtmlCalendar .

A3: this documentation is a perfect example of why Wiki’s are usually awful.
—Just another AC adding cruft and noise to a page that was probably minimally helpful to begin with but now does more harm than good