lib/unobtrusive_date_picker/unobtrusive_date_picker.rb
# originally a plugin from https://github.com/brianjlandau/unobtrusive_date_picker
module UnobtrusiveDatePicker
DATEPICKER_DEFAULT_NAME_ID_SUFFIXES = { :year => {:id => '', :name => 'year'},
:month => {:id => 'mm', :name => 'month'},
:day => {:id => 'dd', :name => 'day'} }
DATEPICKER_DAYS_OF_WEEK = { :Monday => '0',
:Tuesday => '1',
:Wednesday => '2',
:Thursday => '3',
:Friday => '4',
:Saturday => '5',
:Sunday => '6'}
DATEPICKER_DIVIDERS = { 'slash' => '/',
'dash' => '-',
'dot' => '.',
'space' => ' ' }
RANGE_DATE_FORMAT = '%Y-%m-%d'
# == Unobtrusive Date-Picker Helper
#
# This Module helps to create date and date-time fields that use the
# Unobtrusive Date-Picker Javascript Widget.
#
#
module UnobtrusiveDatePickerHelper
##
# Creates the date picker with the calendar widget.
#
def unobtrusive_date_picker(object_name, method, options = {}, html_options = {})
ActionView::Helpers::InstanceTag.new(object_name, method, self, options.delete(:object)).to_datepicker_date_select_tag(options, html_options)
end
##
# Creates the date picker with the calendar widget.
#
def unobtrusive_date_text_picker(object_name, method, options = {}, html_options = {})
ActionView::Helpers::InstanceTag.new(object_name, method, self, options.delete(:object)).to_datepicker_text_tag(options, html_options)
end
def unobtrusive_date_picker_tags(date = Date.current, options = {}, html_options = {})
date ||= Date.current
DateTimePickerSelector.new(date, options, html_options).select_date
end
##
# Creates the text field based date picker with the calendar widget without a model object.
#
def unobtrusive_date_text_picker_tag(name, date = Date.current, options = {}, html_options = {})
date ||= Date.current
options = merge_defaults_for_text_picker(options)
DateTimePickerSelector.new(date, options, html_options).text_date_picker(name)
end
private
def merge_defaults_for_text_picker(options)
defaults = {:format => 'm-d-y', :divider => 'slash'}
options = defaults.merge(options)
end
end
module AssetTagHelper
##
# This will add the necessary <link> and <script> tags to include the necessary stylesheet and
# javascripts.
#
def unobtrusive_datepicker_includes(options = {})
tags = []
tags << javascript_include_tag('datepicker', options)
tags << stylesheet_link_tag('datepicker', options)
tags * "\n"
end
end
module OptionParser
protected
def parse_divider_option(option)
if DATEPICKER_DIVIDERS.keys.include?(option)
option
else
DATEPICKER_DIVIDERS.find {|name, value| option == value}.first
end
end
def format_date_value_for_text_field(value, format, divider_option)
divider = DATEPICKER_DIVIDERS[parse_divider_option(divider_option)]
format_string = format.downcase.gsub(/(m|d)/, '%\1').gsub(/y/, '%Y').gsub('-', divider)
value.strftime(format_string)
end
def get_html_classes_for_datepicker(options, html_options_class, extra_class = nil)
html_classes = make_date_picker_class_options(options)
html_classes << extra_class if extra_class
html_options_class.blank? ?
html_classes.join(' ') : "#{html_options_class} #{html_classes.join(' ')}"
end
def make_date_picker_class_options(options)
html_classes = []
if options[:highlight_days]
highlight_days = parse_days_of_week(options[:highlight_days])
if !highlight_days.blank?
html_classes << "highlight-days-#{highlight_days}"
end
end
if options[:range_low]
range_low = parse_range_option(options[:range_low], 'low')
if !range_low.blank?
html_classes << range_low
end
end
if options[:range_high]
range_high = parse_range_option(options[:range_high], 'high')
if !range_high.blank?
html_classes << range_high
end
end
if options[:disable_days]
disable_days = parse_days_of_week(options[:disable_days])
if !disable_days.blank?
html_classes << "disable-days-#{disable_days}"
end
end
if options[:no_transparency]
html_classes << 'no-transparency'
end
if options[:format] && %W(d-m-y m-d-y y-m-d).include?(options[:format].downcase)
html_classes << "format-#{options[:format].downcase}"
end
if options[:divider]
html_classes << "divider-#{parse_divider_option(options[:divider])}"
end
html_classes
end
def parse_days_of_week(option)
if option.is_a? String
option
elsif option.is_a? Symbol
DATEPICKER_DAYS_OF_WEEK[option]
elsif option.is_a? Array
days = ''
option.each do |day|
days << DATEPICKER_DAYS_OF_WEEK[day]
end
days
end
end
def parse_range_option(option, direction)
if option.is_a? Symbol
case option
when :today
range_class = 'today'
when :tomorrow
range_class = Date.tomorrow.strftime(RANGE_DATE_FORMAT)
when :yesterday
range_class = Date.yesterday.strftime(RANGE_DATE_FORMAT)
end
elsif option.is_a? String
if !option.blank?
range_class = Date.parse(option).strftime(RANGE_DATE_FORMAT)
else
range_class = nil
end
elsif (option.is_a?(Date) || option.is_a?(DateTime) || option.is_a?(Time))
range_class = option.strftime(RANGE_DATE_FORMAT)
else
range_class = nil
end
if !range_class.blank?
range_class = 'range-' + direction + '-' + range_class
else
nil
end
end
end
class DateTimePickerSelector < ActionView::Helpers::DateTimeSelector
include ActionView::Helpers::FormTagHelper
include OptionParser
POSITION = {
:year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5,
:second => 6
}
# XXX would like to do this, but it's frozen
# We give them negative values so can differentiate between normal
# date/time values. The way the multi param stuff works, from what I
# can see, results in a variable number of fields (if you tell it to
# include seconds, for example). So we expect the AM/PM field, if
# present, to be last and have a negative value.
def text_date_picker(name)
value = format_date_value_for_text_field(@datetime, @options[:format], @options[:divider])
@html_options[:class] = get_html_classes_for_datepicker(@options, @html_options[:class])
text_field_tag(name, value, @html_options)
end
private
def build_select(type, select_options_as_html)
select_options = @html_options.merge(
:id => input_id_from_type(type, @html_options[:id]),
:name => input_name_from_type(type)
)
select_options.merge!(:disabled => 'disabled') if @options[:disabled]
if type.to_sym == :year
select_options[:class] = get_html_classes_for_datepicker(@options, select_options[:class], 'split-date')
end
select_html = "\n"
select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
select_html << select_options_as_html.to_s
content_tag(:select, select_html.html_safe, select_options) + "\n"
end
def build_hidden(type, value)
hidden_html_options = {
:type => "hidden",
:id => input_id_from_type(type, @html_options[:id]),
:name => input_name_from_type(type),
:value => value
}
if type.to_sym == :year
hidden_html_options[:class] = get_html_classes_for_datepicker(@options, hidden_html_options[:class], 'split-date')
end
tag(:input, hidden_html_options) + "\n"
end
def input_id_from_type(type, html_options_id = nil)
if html_options_id.blank?
prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX
prefix += "_#{@options[:index]}" if @options.has_key?(:index)
prefix += "_#{@options[:field_name]}" if @options.has_key?(:field_name)
else
prefix = html_options_id
end
case type.to_sym
when :year
prefix
when :month
prefix + '-mm'
when :day
prefix + '-dd'
else
super(type)
end
end
end
end
# /UnobtrusiveDatePicker
module ActionView # :nodoc: all
module Helpers
class InstanceTag
include UnobtrusiveDatePicker::UnobtrusiveDatePickerHelper
include UnobtrusiveDatePicker::OptionParser
def to_datepicker_date_select_tag(options = {}, html_options = {})
datepicker_selector(options, html_options).select_date
end
def to_datepicker_text_tag(options = {}, html_options = {})
options = merge_defaults_for_text_picker(options)
html_options[:class] = get_html_classes_for_datepicker(options, html_options[:class])
html_options[:value] = format_date_value_for_text_field(value(object), options[:format], options[:divider])
to_input_field_tag('text', html_options)
end
private
def datepicker_selector(options, html_options)
datetime = value(object) || default_datetime(options)
options = options.dup
options[:field_name] = @method_name
options[:include_position] = true
options[:prefix] ||= @object_name
options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
options[:datetime_separator] ||= ' — '
options[:time_separator] ||= ' : '
UnobtrusiveDatePicker::DateTimePickerSelector.new(datetime, options.merge(:tag => true), html_options)
end
end
end
end
module ActionView::Helpers::PrototypeHelper
class JavaScriptGenerator
module GeneratorMethods
def unobtrusive_date_picker_create(id = nil)
if id
call "datePickerController.create", "$(#{id})"
else
record "datePickerController.create"
end
end
def unobtrusive_date_picker_cleanup(id = nil)
record "datePickerController.cleanUp"
end
end
end
end
module ActionView # :nodoc: all
module Helpers
class FormBuilder
def unobtrusive_date_picker(method, options = {}, html_options = {})
@template.unobtrusive_date_picker(@object_name, method, objectify_options(options), html_options)
end
def unobtrusive_date_text_picker(method, options = {}, html_options = {})
@template.unobtrusive_date_text_picker(@object_name, method, objectify_options(options), html_options)
end
end
end
end