Created
October 30, 2013 21:02
-
-
Save toxaq/7240207 to your computer and use it in GitHub Desktop.
Calendar builder helper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module CalendarHelper | |
# Generates a calendar (as a table) for an array of objects placing each of them on the corresponding date. | |
# | |
# **TODO: fully document this method, the current documentation is far from done.** | |
# | |
# @param [Hash] options extra options | |
# | |
# :row_header if true, each row will have an extra cell at the beginning, as a row header. A typical usage would be | |
# to output week numbers. When the block is called, it will get the date that would normally be passed to the | |
# first day of the week (to give you some context) and a nil list of objects (and that's how you recognize it as | |
# a header, because empty days get an empty array, not nil). | |
def calendar_for(objects, *args) | |
raise ArgumentError, "Missing block" unless block_given? | |
options = args.last.is_a?(Hash) ? args.pop : {} | |
html_options = options[:html] || {} | |
builder = options[:builder] || CalendarBuilder | |
calendar = options[:calendar] || Calendar | |
#html_options.update({:class => 'table table-timesheet'}) | |
content_tag(:table, nil, html_options) do | |
yield builder.new(objects || [], self, calendar, options) | |
end | |
end | |
class CalendarBuilder < TableHelper::TableBuilder | |
def initialize(objects, template, calendar, options) | |
super(objects, template, options) | |
@calendar = calendar.new(options) | |
@today = options[:today] || Time.zone.now | |
@row_header = options[:row_header] || false | |
end | |
def week_head(*args) | |
raise ArgumentError, "Missing block" unless block_given? | |
concat(tag(:thead, {}, true)) | |
concat(tag(:tr, {}, true)) | |
@calendar.days.each do |c| | |
concat(tag(:th, {}, true)) | |
yield(c) | |
concat("</th>") | |
end | |
concat("</tr>") | |
concat("</thead>") | |
end | |
def day(*args) | |
raise ArgumentError, "Missing block" unless block_given? | |
options = options_from_hash(args) | |
day_method = options.delete(:day_method) || :date | |
id_pattern = options.delete(:id) | |
tbody do | |
@calendar.objects_for_days(@objects, day_method).to_a.sort { |a1, a2| a1.first <=> a2.first }.each do |o| | |
key, array = o | |
day, objects = array | |
concat(tag(:tr, options, true)) if (day.wday == @calendar.first_weekday) | |
if @row_header && day.wday == @calendar.first_weekday | |
row_header_options = {} #td_options(day, id_pattern) | |
row_header_options[:class] ||= "" | |
row_header_options[:class] << " row_header" | |
concat(tag(:td, row_header_options, true)) | |
yield(day, nil) | |
concat("</td>") | |
end | |
concat(tag(:td, td_options(day, id_pattern), true)) | |
yield(day, objects) | |
concat('</td>') | |
concat('</tr>') if (day.wday == @calendar.last_weekday) | |
end | |
end | |
end | |
private | |
def objects_for_days | |
@calendar.objects_for_days(@objects) | |
end | |
def td_options(day, id_pattern) | |
options = {} | |
css_classes = [] | |
css_classes << 'today' if day.strftime("%Y-%m-%d") == @today.strftime("%Y-%m-%d") | |
css_classes << 'notmonth' if day.month != @calendar.month | |
css_classes << 'weekend' if day.wday == 0 or day.wday == 6 | |
css_classes << 'future' if day > @today.to_date | |
options[:class] = css_classes.join(' ') unless css_classes.empty? | |
options[:id] = day.strftime(id_pattern) if id_pattern | |
options | |
end | |
end | |
# Override methods for bootstrap styling | |
class BootstrapCalendarBuilder < CalendarBuilder | |
#<thead><tr><th class="prev" style="visibility: visible; "><i class="icon-arrow-left"></i></th><th colspan="5" class="switch">October 2012</th><th class="next" style="visibility: visible; "><i class="icon-arrow-right"></i></th></tr><tr><th class="dow">Su</th><th class="dow">Mo</th><th class="dow">Tu</th><th class="dow">We</th><th class="dow">Th</th><th class="dow">Fr</th><th class="dow">Sa</th></tr></thead> | |
def head(*args) | |
@num_of_columns = args.size | |
@template.content_tag(:thead) do | |
@template.content_tag(:tr) do | |
@template.content_tag(:th, :class=>'prev') do | |
@template.content_tag(:i, nil, :class=>"icon-arrow-left" ) | |
end + | |
@template.content_tag(:th, :colspan=> @row_header ? 6 : 5) do | |
DateTime.new(@calendar.year, @calendar.month, 1).strftime("%B %Y") | |
end + | |
@template.content_tag(:th, :class=>'next') do | |
@template.content_tag(:i, nil, :class=>"icon-arrow-right" ) | |
end | |
end + | |
content_tag(:tr, | |
(@row_header ? @template.content_tag(:th) : '') + | |
args.collect { |c| content_tag(:th, c.html_safe) }.join('').html_safe | |
) | |
end | |
end | |
private | |
def td_options(day, id_pattern) | |
options = {} | |
css_classes = ['day'] | |
css_classes << 'active' if day.strftime("%Y-%m-%d") == @today.strftime("%Y-%m-%d") | |
css_classes << 'old' if day.month < @calendar.month | |
css_classes << 'new' if day.month > @calendar.month | |
css_classes << 'weekend' if day.wday == 0 or day.wday == 6 | |
css_classes << 'future' if day > @today.to_date | |
css_classes << "week_#{day.strftime('%V')}" | |
css_classes << "day_#{day.strftime('%e').lstrip}" | |
options[:class] = css_classes.join(' ')# unless css_classes.empty? | |
options[:data] = {'date' => day.strftime("%Y-%m-%d")} | |
options[:id] = day.strftime(id_pattern) if id_pattern | |
options | |
end | |
end | |
class Calendar | |
attr_accessor :first_weekday, :last_weekday, :month, :year | |
# :first lets you set the first day to start the calendar on (default is the first day of the given :month and :year). | |
# :first => :today will use Date.today | |
# :last lets you set the last day of the calendar (default is the last day of the given :month and :year). | |
# :last => :thirty will show 30 days from :first | |
# :last => :week will show one week | |
def initialize(options={}) | |
@year = options[:year] || Time.now.year | |
@month = options[:month] || Time.now.month | |
@first_day_of_week = options[:first_day_of_week] || 1 | |
@first_weekday = first_day_of_week(@first_day_of_week) | |
@last_weekday = last_day_of_week(@first_day_of_week) | |
@first = options[:first]==:today ? Date.today : options[:first] || Date.civil(@year, @month, 1) | |
if options[:last] == :thirty_days || options[:last] == :thirty | |
@last = @first + 30 | |
elsif options[:last] == :one_week || options[:last] == :week | |
@last = @first | |
elsif options[:last] == :three_days | |
@last = @first | |
@first_weekday = @first.wday | |
@last_weekday = (@first + 2.days).wday | |
else | |
@last = options[:last] || Date.civil(@year, @month, -1) | |
end | |
end | |
def each_day | |
#Rails.logger.debug("First day: #{first_day}") | |
#Rails.logger.debug("Last day: #{last_day}") | |
first_day.upto(last_day) do |day| | |
yield(day) | |
end | |
end | |
def last_day | |
last = @last | |
while (last.wday % 7 != @last_weekday % 7) | |
last = last.next | |
end | |
last | |
end | |
def first_day | |
first = @first - 6 | |
while (first.wday % 7 != (@first_weekday) % 7) | |
first = first.next | |
end | |
first | |
end | |
def objects_for_days(objects, day_method) | |
unless @objects_for_days | |
@objects_for_days = {} | |
days.each { |day| @objects_for_days[day.strftime("%Y-%m-%d")] = [day, []] } | |
objects.each do |o| | |
date = o.send(day_method.to_sym).strftime("%Y-%m-%d") | |
if @objects_for_days[date] | |
@objects_for_days[date][1] << o | |
end | |
end | |
end | |
@objects_for_days | |
end | |
def days | |
unless @days | |
@days = [] | |
each_day { |day| @days << day } | |
end | |
@days | |
end | |
def mjdays | |
unless @mjdays | |
@mdays = [] | |
each_day { |day| @days << day } | |
end | |
@days | |
end | |
def first_day_of_week(day) | |
day | |
end | |
def last_day_of_week(day) | |
if day > 0 | |
day - 1 | |
else | |
6 | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment