require 'date' require 'time' module UnobtrusiveDatePicker # == Unobtrusive Date-Picker Helper # # This Module helps to create date and date-time fields that use the # Unobtrusive Date-Picker Javascript Widget. # # They also use the 12-hour AM/PM time format. # module UnobtrusiveDatePickerHelper 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' ## # 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, nil, options.delete(:object)).to_datepicker_date_select_tag(options, html_options) end ## # Creates the date-time picker with the calendar widget, and AM/PM select. # def unobtrusive_datetime_picker(object_name, method, options = {}, html_options = {}) ActionView::Helpers::InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datepicker_datetime_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, nil, options.delete(:object)).to_datepicker_text_tag(options, html_options) end ## # Creates the date picker with the calendar widget without a model object. # def unobtrusive_date_picker_tags(date = Date.today, options = {}, html_options = {}) options[:prefix] = html_options.delete(:id) options[:order] ||= [] [:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) } date_picker = '' options[:order].each do |o| date_picker << self.send("datepicker_select_#{o}", date, options, html_options) end date_picker end ## # Creates the date-time picker with the calendar widget, and AM/PM select without a model object # def unobtrusive_datetime_picker_tags(datetime = Time.now, options = {}, html_options = {}) options[:prefix] = html_options.delete(:id) separator = options[:datetime_separator] || '' datetime_picker = unobtrusive_date_picker_tags(datetime, options, html_options) + separator datetime_picker << datepicker_select_hour(datetime, options, html_options) + ':' datetime_picker << datepicker_select_minute(datetime, options, html_options) + ' ' datetime_picker << datepicker_select_ampm(datetime, options, html_options) datetime_picker 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.today, options = {}, html_options = {}) options, html_options = datepicker_text_field_options(options, html_options) value = format_date_value_for_text_field(options[:format], options[:divider], date) text_field_tag(name, value, html_options) end def datepicker_select_ampm(datetime, options = {}, html_options = {}) ampm = %w(AM PM) val = datetime ? (ampm.include?(datetime) ? datetime : datetime.strftime("%p")) : '' ampm_options = [] ampm.each { |meridiem| ampm_options << ((val == meridiem) ? %(\n) : %(\n) ) } select_html(options[:field_name] || 'ampm', ampm_options, options, html_options) end def datepicker_select_hour(datetime, options = {}, html_options = {}) val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.strftime("%I").to_i) : '' hour_options = [] 1.upto(12) do |hour| hour_options << ((val == hour) ? %(\n) : %(\n) ) end select_html(options[:field_name] || 'hour', hour_options, options, html_options) end ## # Can except a "minute_step" option which will specify how many minutes to move forward on each loop. # # i.e. if set to 15: 0, 15, 30, and 45 will be the options supplied in the minute select # def datepicker_select_minute(datetime, options = {}, html_options = {}) val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.strftime("%M").to_i) : '' minute_options = [] 0.step(59, options[:minute_step] || 1) do |minute| minute_options << ((val == minute) ? %(\n) : %(\n) ) end select_html(options[:field_name] || 'minute', minute_options, options, html_options) end def datepicker_select_day(date, options = {}, html_options = {}) val = date ? (date.kind_of?(Fixnum) ? date : date.day) : '' day_options = [] 1.upto(31) do |day| day_options << ((val == day) ? %(\n) : %(\n) ) end datepicker_select_html(:day, day_options, options, html_options) end ## # This method will accept a hash for the option "use_month_names" where the key is the month number # and the value is the month name. It will also accept a boolean option "use_short_mont" that will # allow the use of short names instead of full names for the option text. Will also take "use_month_numbers", # and "add_month_numbers" which use or add month numbers to the option item text. # def datepicker_select_month(date, options = {}, html_options = {}) val = date ? (date.kind_of?(Fixnum) ? date : date.month) : '' month_options = [] month_names = options[:use_month_names] || (options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES) month_names.unshift(nil) if month_names.size < 13 1.upto(12) do |month_number| month_name = if options[:use_month_numbers] month_number elsif options[:add_month_numbers] month_number.to_s + ' - ' + month_names[month_number] else month_names[month_number] end month_options << ((val == month_number) ? %(\n) : %(\n) ) end datepicker_select_html(:month, month_options, options, html_options) end ## # This method will accept a "start_year" and "end_year" options. # def datepicker_select_year(date, options = {}, html_options = {}) year_html_options = html_options.dup val = date ? (date.kind_of?(Fixnum) ? date : date.year) : '' y = date ? (date.kind_of?(Fixnum) ? (date == 0 ? Date.today.year : date) : date.year) : Date.today.year start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5) step_val = start_year < end_year ? 1 : -1 year_options = [] start_year.step(end_year, step_val) do |year| year_options << ((val == year) ? %(\n) : %(\n) ) end html_classes = make_date_picker_class_options(options).push('split-date') year_html_options[:class] = year_html_options[:class] ? "#{year_html_options[:class]} #{html_classes.join(' ')}" : html_classes.join(' ') datepicker_select_html(:year, year_options, options, year_html_options) end protected def datepicker_select_html(type, html_options, options, select_tag_options = {}) # :nodoc: datepicker_name_and_id(type, options) select_options = {:id => options[:id], :name => options[:name]} select_options.merge!(:disabled => 'disabled') if options[:disabled] select_options.merge!(select_tag_options) unless select_tag_options.empty? select_html = "\n" select_html << content_tag(:option, '', :value => '') + "\n" if options[:include_blank] select_html << html_options.to_s content_tag(:select, select_html, select_options) + "\n" end def datepicker_name_and_id(type, html_options) # :nodoc: if html_options[:id_prefix] html_options[:id] = (type == :year) ? "#{html_options[:id_prefix]}" : "#{html_options[:id_prefix]}-#{DATEPICKER_DEFAULT_NAME_ID_SUFFIXES[type][:id]}" else html_options[:prefix] ||= ActionView::Helpers::DateHelper::DEFAULT_PREFIX html_options[:id] = ((type == :year) ? html_options[:prefix] : html_options[:prefix] + "-#{DATEPICKER_DEFAULT_NAME_ID_SUFFIXES[type][:id]}" ) html_options[:name] = html_options[:prefix] + "[#{DATEPICKER_DEFAULT_NAME_ID_SUFFIXES[type][:name]}]" end end def datepicker_text_field_options(options, html_options) defaults = {:format => 'm-d-y', :divider => 'slash'} options = defaults.merge(options) html_classes = make_date_picker_class_options(options) html_options[:class] = html_options[:class] ? "#{html_options[:class]} #{html_classes.join(' ')}" : html_classes.join(' ') [options, html_options] end def format_date_value_for_text_field(format, divider_option, value) 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 make_date_picker_class_options(options = {}) # :nodoc: 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) # :nodoc: 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) # :nodoc: 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 def parse_divider_option(option) if DATEPICKER_DIVIDERS.keys.include?(option) option else DATEPICKER_DIVIDERS.find {|name, value| option == value}.first end end end module AssetTagHelper ## # This will add the necessary and