├── .gitignore ├── .rvmrc ├── .travis.yml ├── Gemfile ├── History.rdoc ├── MIT-LICENSE ├── README.rdoc ├── Rakefile ├── app └── assets │ └── stylesheets │ └── calendar_styles │ ├── blue.css │ ├── grey.css │ └── red.css ├── calendar_helper.gemspec ├── init.rb ├── lib └── calendar_helper.rb └── test └── test_calendar_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | test/output/ 2 | tmp/* 3 | pkg/* 4 | Gemfile.lock 5 | /html/ 6 | *.sublime-* 7 | 8 | -------------------------------------------------------------------------------- /.rvmrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | environment_id="ruby-1.9.2-p136@calendar_helper" 4 | 5 | if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \ 6 | && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]] ; then 7 | \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id" 8 | else 9 | # If the environment file has not yet been created, use the RVM CLI to select. 10 | rvm --create use "$environment_id" 11 | fi 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/travis-ci/travis-ci/wiki/.travis.yml-options 2 | language: "ruby" 3 | script: "bundle exec rake" 4 | rvm: 5 | - 1.8.7 6 | - 1.9.2 7 | - 1.9.3 8 | - jruby-18mode 9 | - jruby-19mode 10 | - rbx-18mode 11 | - rbx-19mode 12 | - ree 13 | notifications: 14 | irc: "irc.freenode.org#savon" 15 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec 3 | -------------------------------------------------------------------------------- /History.rdoc: -------------------------------------------------------------------------------- 1 | === 0.2.4 / 2010-05-21 2 | 3 | * Calendar Title can be customized [Benjamin Lieb] 4 | 5 | === 0.2.3 / 2010-05-21 6 | 7 | * Don't crash if time zone is nil 8 | * Upgrade to latest Hoe 9 | * Added support to remove the month row on the table 10 | * Fixed bug where passed ID for day cell would throw error on missing class name. 11 | 12 | === 0.2.2 / 2007-08-29 13 | 14 | * Fixed missing tr tag in thead section. [Ian Struble] 15 | 16 | === 0.2.1 / 2007-07-07 17 | 18 | * Added html output to the tests for visual confirmation or 19 | for developing new stylesheets. Run 'rake' and look 20 | in the 'test/output' directory. 21 | * Adds a 'today' CSS class to the cell for the current day. 22 | Can be turned of by sending option 'show_today => false'. 23 | [Chris O'Sullivan] 24 | * Added 'accessible' option to show extra fields around 25 | days that are not in the current month. [Tom Armitage] 26 | 27 | === 0.2.0 28 | 29 | * Converted to hoe and a rubygem 30 | * Renamed to README.txt for Hoe compatibility 31 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006 Jeremy Voorhis and Geoffrey Grosenbach 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = CalendarHelper 2 | 3 | {Build Status}[http://travis-ci.org/topfunky/calendar_helper] 4 | 5 | == DESCRIPTION: 6 | 7 | A simple helper for creating an HTML calendar. The "calendar" method will be 8 | automatically available to your Rails view templates, or can be used with 9 | Sinatra or other webapps. 10 | 11 | Some example stylesheets are provided via the Rails asset pipeline. Add to 12 | your main stylesheet with one of the following: 13 | 14 | /* 15 | *= require 'calendar_styles/grey' 16 | * OR 17 | *= require 'calendar_styles/red' 18 | * OR 19 | *= require 'calendar_styles/blue' 20 | */ 21 | 22 | == SYNOPSIS: 23 | 24 | # Simple 25 | calendar(:year => 2005, :month => 6) 26 | 27 | # Set table class 28 | calendar({:year => 2005, :month => 6, :table_class => "calendar_helper"}) 29 | 30 | # Full featured 31 | calendar(:year => 2005, :month => 5) do |d| # This generates a simple calendar, but gives special days 32 | if listOfSpecialDays.include?(d) # (days that are in the array listOfSpecialDays) one CSS class, 33 | [d.mday, {:class => "specialDay"}] # "specialDay", and gives the rest of the days another CSS class, 34 | else # "normalDay". You can also use this highlight today differently 35 | [d.mday, {:class => "normalDay"}] # from the rest of the days, etc. 36 | end 37 | end 38 | 39 | If using with ERb (Rails), put in a printing tag. 40 | 41 | <%= calendar(:year => @year, :month => @month, :first_day_of_week => 1) do |d| 42 | render_calendar_cell(d) 43 | end 44 | %> 45 | 46 | With Haml, use a helper to set options for each cell. 47 | 48 | = calendar(:year => @year, :month => @month, :first_day_of_week => 1) do |d| 49 | - render_calendar_cell(d) 50 | 51 | In Sinatra, include the CalendarHelper module in your helpers: 52 | 53 | helpers do 54 | include CalendarHelper 55 | end 56 | 57 | 58 | == Accessibility & 508 Compliance: 59 | 60 | * The table tag has a summary attribute (overridable). 61 | * Each th has an id. 62 | * Each td as a headers attribute, containing the element id of the appropriate th. 63 | 64 | 65 | == AUTHORS: 66 | 67 | Jeremy Voorhis -- http://jvoorhis.com 68 | Original implementation 69 | 70 | Geoffrey Grosenbach -- http://nubyonrails.com 71 | Test suite and conversion to a Rails plugin 72 | 73 | == Contributors: 74 | 75 | * Jarkko Laine http://jlaine.net/ 76 | * Tom Armitage http://infovore.org 77 | * Bryan Larsen http://larsen.st 78 | * Eric Anderson http://saveyourcall.com 79 | 80 | == USAGE: 81 | 82 | See the RDoc (or use "rake rdoc"). 83 | 84 | To copy the CSS files, use 85 | 86 | ./script/generate calendar_styles 87 | 88 | CSS will be copied to subdirectories of public/stylesheets/calendar. 89 | 90 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks 3 | 4 | require 'rdoc/task' 5 | Rake::RDocTask.new do |rdoc| 6 | rdoc.title = 'CalendarHelper' 7 | rdoc.options << '--line-numbers' 8 | rdoc.rdoc_files.include('README.rdoc') 9 | rdoc.rdoc_files.include('lib/**/*.rb') 10 | end 11 | 12 | require 'rake/testtask' 13 | Rake::TestTask.new do |t| 14 | t.libs << 'lib' 15 | t.test_files = FileList['test/test_*.rb'] 16 | end 17 | task :default => :test -------------------------------------------------------------------------------- /app/assets/stylesheets/calendar_styles/blue.css: -------------------------------------------------------------------------------- 1 | /* 2 | A blue based theme, inspired by Blinksale and their ColorBurn widget. http://firewheeldesign.com 3 | 4 | AUTHOR: Geoffrey Grosenbach http://nubyonrails.com 5 | 6 | Colors: 7 | Light Blue: bbccff 8 | White: eeddee 9 | Turq: 003355 10 | Cream: ffffdd 11 | */ 12 | 13 | .calendar { 14 | margin: auto; 15 | } 16 | 17 | .monthName th { 18 | font-weight: normal; 19 | text-align: right; 20 | padding-top: 1em; 21 | padding-bottom: 0.7em; 22 | } 23 | 24 | .dayName th { 25 | font-size: 0.7em; 26 | padding-top: 0.6em; 27 | padding-bottom: 0.3em; 28 | background-color: #303030; 29 | color: white; 30 | } 31 | 32 | .otherMonth, .day, .specialDay { 33 | padding: 0.7em 1em; 34 | border-right: 1px solid white; 35 | 36 | } 37 | 38 | .otherMonth { 39 | color: #eeeeee; 40 | background-color: white; 41 | } 42 | 43 | .day, .specialDay { 44 | text-align: center; 45 | border-bottom: 1px dotted #bbbbbb; 46 | background-color: #bbccff; 47 | } 48 | .specialDay { 49 | background-color: #003355; 50 | color: white; 51 | } 52 | .specialDay a, .specialDay a:visited, .specialDay a:hover { 53 | color: white; 54 | text-decoration: none; 55 | padding: 1em; 56 | } 57 | .specialDay a:hover { 58 | color: white; 59 | background-color: black; 60 | } 61 | .weekendDay { 62 | background-color: #ffffdd; 63 | } 64 | .today{ 65 | background-color: #4682b4; 66 | } 67 | .weekNumber { 68 | background-color: #dedede; 69 | } 70 | -------------------------------------------------------------------------------- /app/assets/stylesheets/calendar_styles/grey.css: -------------------------------------------------------------------------------- 1 | /* 2 | A grey based theme, inspired by Blinksale and their ColorBurn widget. http://firewheeldesign.com 3 | 4 | AUTHOR: Geoffrey Grosenbach http://nubyonrails.com 5 | 6 | Colors: 7 | dk: 787888 8 | lt: 4f4f5b 9 | lter: a8a8a8 10 | white: ffffff 11 | */ 12 | 13 | /* TODO */ 14 | 15 | .calendar { 16 | margin: auto; 17 | color: white; 18 | text-align: center; 19 | } 20 | 21 | thead tr { 22 | color: black; 23 | } 24 | 25 | .monthName th { 26 | font-weight: normal; 27 | text-align: right; 28 | padding-top: 1em; 29 | padding-bottom: 0.7em; 30 | color: black; 31 | } 32 | 33 | .dayName th { 34 | font-size: 0.7em; 35 | padding-top: 0.6em; 36 | padding-bottom: 0.3em; 37 | background-color: #303030; 38 | color: white; 39 | border-bottom: 1px solid white; 40 | } 41 | 42 | .otherMonth, .day, .specialDay { 43 | padding: 0.7em 1em; 44 | border-right: 1px solid #111111; 45 | } 46 | 47 | .otherMonth { 48 | color: #999999; 49 | background-color: #4f4f5b; 50 | } 51 | 52 | .day, .specialDay { 53 | border-bottom: 1px solid #111111; 54 | background-color: #333333; 55 | } 56 | .specialDay { 57 | background-color: #a8a8a8; 58 | color: black; 59 | } 60 | .specialDay a, .specialDay a:visited, .specialDay a:hover { 61 | color: white; 62 | text-decoration: none; 63 | padding: 1em; 64 | } 65 | .specialDay a:hover { 66 | color: white; 67 | background-color: black; 68 | } 69 | .weekendDay { 70 | background-color: #787888; 71 | } 72 | .today { 73 | background-color: white; 74 | color: black; 75 | } 76 | .weekNumber { 77 | background-color: #222; 78 | } 79 | -------------------------------------------------------------------------------- /app/assets/stylesheets/calendar_styles/red.css: -------------------------------------------------------------------------------- 1 | /* 2 | A red, white, and grey theme. 3 | 4 | AUTHOR: Geoffrey Grosenbach http://nubyonrails.com 5 | */ 6 | 7 | .calendar { 8 | margin: auto; 9 | } 10 | 11 | .monthName th { 12 | font-weight: normal; 13 | text-align: right; 14 | padding-top: 1em; 15 | padding-bottom: 0.7em; 16 | } 17 | 18 | .dayName th { 19 | font-size: 0.7em; 20 | padding-top: 0.6em; 21 | padding-bottom: 0.3em; 22 | background-color: #303030; 23 | color: white; 24 | } 25 | 26 | .otherMonth, .day, .specialDay { 27 | padding: 0.7em 1em; 28 | border-right: 1px solid white; 29 | 30 | } 31 | 32 | .otherMonth { 33 | color: #eeeeee; 34 | } 35 | .weekendDay { 36 | background-color: #eeeeee; 37 | } 38 | 39 | .day, .specialDay { 40 | text-align: center; 41 | border-bottom: 1px dotted #bbbbbb; 42 | } 43 | 44 | .specialDay { 45 | background-color: #d10a21; 46 | color: white; 47 | } 48 | .specialDay a, .specialDay a:visited, .specialDay a:hover { 49 | color: white; 50 | text-decoration: none; 51 | padding: 1em; 52 | } 53 | .specialDay a:hover { 54 | color: white; 55 | background-color: black; 56 | } 57 | .today { 58 | background-color: #1e90ff; 59 | color: white; 60 | } 61 | .weekNumber { 62 | background-color: #BADA55; 63 | } 64 | -------------------------------------------------------------------------------- /calendar_helper.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | Gem::Specification.new do |s| 4 | s.name = "calendar_helper" 5 | s.version = "0.2.6" 6 | 7 | s.author = "Geoffrey Grosenbach" 8 | s.email = "boss@topfunky.com" 9 | 10 | s.summary = "A simple helper for creating an HTML calendar" 11 | s.description = <<-DESCRIPTION 12 | A simple helper for creating an HTML calendar. The "calendar" method will 13 | be automatically available to your Rails view templates, or can be used 14 | with Sinatra or other webapps. 15 | 16 | There is also a Rails generator that copies some stylesheets for use alone 17 | or alongside existing stylesheets. 18 | DESCRIPTION 19 | 20 | s.files = ["MIT-LICENSE", "README.rdoc", "History.rdoc", 'init.rb'] + 21 | Dir['lib/**/*.rb'] + Dir['app/**/*'] 22 | 23 | s.rdoc_options = ["--main", "README.rdoc"] 24 | s.extra_rdoc_files = ["README.rdoc", "History.rdoc"] 25 | 26 | s.require_paths = ["lib"] 27 | s.test_files = ["test/test_calendar_helper.rb"] 28 | 29 | s.add_runtime_dependency 'open4' 30 | 31 | s.add_development_dependency 'rake' 32 | s.add_development_dependency 'rdoc', ">= 3.10" 33 | s.add_development_dependency 'flexmock' 34 | end 35 | -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | ActionView::Base.send :include, CalendarHelper 2 | -------------------------------------------------------------------------------- /lib/calendar_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require 'date' 3 | 4 | # CalendarHelper allows you to draw a databound calendar with fine-grained CSS formatting 5 | module CalendarHelper 6 | 7 | VERSION = '0.2.6' 8 | 9 | # Returns an HTML calendar. In its simplest form, this method generates a plain 10 | # calendar (which can then be customized using CSS) for a given month and year. 11 | # However, this may be customized in a variety of ways -- changing the default CSS 12 | # classes, generating the individual day entries yourself, and so on. 13 | # 14 | # The following options are required: 15 | # :year # The year number to show the calendar for. 16 | # :month # The month number to show the calendar for. 17 | # 18 | # The following are optional, available for customizing the default behaviour: 19 | # :table_id => "calendar-2008-08" # The id for the tag. 20 | # :table_class => "calendar" # The class for the
tag. 21 | # :summary => "Calendar for August 2008" # The summary attribute for the
tag. Required for 508 compliance. 22 | # :month_name_class => "monthName" # The class for the name of the month, at the top of the table. 23 | # :other_month_class => "otherMonth" # The class for individual day cells for previous and next months. 24 | # :day_name_class => "dayName" # The class is for the names of the weekdays, at the top. 25 | # :day_class => "day" # The class for the individual day number cells. 26 | # This may or may not be used if you specify a block (see below). 27 | # :abbrev => true # This option specifies whether day names should be displayed abbrevidated (true) 28 | # or in full (false) 29 | # :first_day_of_week => 0 # Renders calendar starting on Sunday. Use 1 for Monday, and so on. 30 | # :accessible => true # Turns on accessibility mode. This suffixes dates within the 31 | # # calendar that are outside the range defined in the " 243 | end 244 | 245 | def generate_other_month_cell(date, options) 246 | unless options[:show_other_months] 247 | return generate_cell("", {}) 248 | end 249 | cell_attrs = {} 250 | cell_attrs[:headers] = th_id(date, options[:table_id]) 251 | cell_attrs[:class] = options[:other_month_class] 252 | cell_attrs[:class] += " weekendDay" if weekend?(date) 253 | cell_attrs["data-date"] = date 254 | 255 | cell_text = date.day 256 | if options[:accessible] 257 | cell_text += %() 258 | end 259 | 260 | generate_cell(date.day, cell_attrs) 261 | end 262 | 263 | # Calculates id for th element. 264 | # derived from calendar_id and dow. 265 | # 266 | # Params: 267 | # `day` can be either Date or DOW('Sunday', 'Monday') 268 | def th_id(day, calendar_id) 269 | return th_id(Date::DAYNAMES[day.wday], calendar_id) if day.is_a?(Date) 270 | "#{calendar_id}-#{day[0..2].downcase}" 271 | end 272 | 273 | def weekend?(date) 274 | [0, 6].include?(date.wday) 275 | end 276 | 277 | class Engine < Rails::Engine # :nodoc: 278 | ActiveSupport.on_load(:action_view) do 279 | include CalendarHelper 280 | end 281 | end if defined? Rails::Engine 282 | end 283 | -------------------------------------------------------------------------------- /test/test_calendar_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require 'rubygems' 3 | require 'test/unit' 4 | require 'fileutils' 5 | require File.expand_path(File.dirname(__FILE__) + "/../lib/calendar_helper") 6 | 7 | require 'flexmock/test_unit' 8 | 9 | # require 'action_controller' 10 | # require 'action_controller/assertions' 11 | # require 'active_support/inflector' 12 | 13 | class CalendarHelperTest < Test::Unit::TestCase 14 | 15 | # include Inflector 16 | # include ActionController::Assertions::SelectorAssertions 17 | include CalendarHelper 18 | 19 | 20 | def test_with_output 21 | output = [] 22 | %w(calendar_with_defaults calendar_for_this_month calendar_with_next_and_previous).each do |methodname| 23 | output << "

#{methodname}

\n" + send(methodname.to_sym) + "\n\n" 24 | end 25 | write_sample "sample.html", output 26 | end 27 | 28 | def test_simple 29 | assert_match %r{August}, calendar_with_defaults 30 | end 31 | 32 | def test_required_fields 33 | # Year and month are required 34 | assert_raises(ArgumentError) { 35 | calendar 36 | } 37 | assert_raises(ArgumentError) { 38 | calendar :year => 1 39 | } 40 | assert_raises(ArgumentError) { 41 | calendar :month => 1 42 | } 43 | end 44 | 45 | def test_default_css_classes 46 | { :table_class => "calendar", 47 | :month_name_class => "monthName", 48 | :day_name_class => "dayName", 49 | :day_class => "day", 50 | :other_month_class => "otherMonth" 51 | }.each do |key, value| 52 | assert_correct_css_class_for_default value 53 | end 54 | end 55 | 56 | def test_custom_css_classes 57 | # Uses the key name as the CSS class name 58 | [:table_class, :month_name_class, :day_name_class, :day_class, :other_month_class].each do |key| 59 | assert_correct_css_class_for_key key.to_s, key 60 | end 61 | end 62 | 63 | def test_abbrev 64 | assert_match %r{>Mon<}, calendar_with_defaults() 65 | assert_match %r{>Monday<}, calendar_with_defaults(:abbrev => false) 66 | end 67 | 68 | def test_block 69 | # Even days are special 70 | assert_match %r{class="special_day"[^>]*>2<}, calendar(:year => 2006, :month => 8) { |d| 71 | if d.mday % 2 == 0 72 | [d.mday, {:class => 'special_day'}] 73 | end 74 | } 75 | end 76 | 77 | def test_first_day_of_week 78 | assert_match %r{\s*\s*\s*.*}, html 100 | end 101 | 102 | def test_table_summary_defaults_to_calendar_period 103 | html = calendar_with_defaults(:year => 1967, :month => 4) 104 | assert_match %r{
with 32 | # # 33 | # # Defaults to false. 34 | # # You'll need to define an appropriate style in order to make this disappear. 35 | # # Choose your own method of hiding content appropriately. 36 | # 37 | # :show_today => false # Highlights today on the calendar using the CSS class 'today'. 38 | # # Defaults to true. 39 | # :previous_month_text => nil # Displayed left of the month name if set 40 | # :next_month_text => nil # Displayed right of the month name if set 41 | # :month_header => false # If you use false, the current month header will disappear. 42 | # :calendar_title => month_names[options[:month]] # Pass in a custom title for the calendar. Defaults to month name 43 | # :show_other_months => true # Do not show the days for the previous and next months 44 | # 45 | # For more customization, you can pass a code block to this method, that will get one argument, a Date object, 46 | # and return a values for the individual table cells. The block can return an array, [cell_text, cell_attrs], 47 | # cell_text being the text that is displayed and cell_attrs a hash containing the attributes for the
tag 48 | # (this can be used to change the 's class for customization with CSS). 49 | # This block can also return the cell_text only, in which case the 's class defaults to the value given in 50 | # +:day_class+. If the block returns nil, the default options are used. 51 | # 52 | # Example usage: 53 | # calendar(:year => 2005, :month => 6) # This generates the simplest possible calendar. 54 | # calendar({:year => 2005, :month => 6, :table_class => "calendar_helper"}) # This generates a calendar, as 55 | # # before, but the 's class 56 | # # is set to "calendar_helper". 57 | # calendar(:year => 2005, :month => 6, :abbrev => (0..-1)) # This generates a simple calendar but shows the 58 | # # entire day name ("Sunday", "Monday", etc.) instead 59 | # # of only the first three letters. 60 | # calendar(:year => 2005, :month => 5) do |d| # This generates a simple calendar, but gives special days 61 | # if listOfSpecialDays.include?(d) # (days that are in the array listOfSpecialDays) one CSS class, 62 | # [d.mday, {:class => "specialDay"}] # "specialDay", and gives the rest of the days another CSS class, 63 | # else # "normalDay". You can also use this highlight today differently 64 | # [d.mday, {:class => "normalDay"}] # from the rest of the days, etc. 65 | # end 66 | # end 67 | # 68 | # An additional 'weekend' class is applied to weekend days. 69 | # 70 | # For consistency with the themes provided in the calendar_styles generator, use "specialDay" as the CSS class for marked days. 71 | # 72 | # Accessibility & 508 Compliance: 73 | # The table tag has a summary attribute (overridable). 74 | # Each th has an id. 75 | # Each td has a headers attribute, containing the element id of the appropriate th. 76 | # 77 | def calendar(options = {}, &block) 78 | raise(ArgumentError, "No year given") unless options.has_key?(:year) 79 | raise(ArgumentError, "No month given") unless options.has_key?(:month) 80 | 81 | block ||= Proc.new {|d| nil} 82 | 83 | month_names = (!defined?(I18n) || I18n.t("date.month_names").include?("missing")) ? Date::MONTHNAMES.dup : I18n.t("date.month_names") 84 | 85 | defaults = { 86 | :table_id => "calendar-#{options[:year]}-#{"%02d" % options[:month]}", 87 | :table_class => 'calendar', 88 | :month_name_class => 'monthName', 89 | :other_month_class => 'otherMonth', 90 | :day_name_class => 'dayName', 91 | :day_class => 'day', 92 | :abbrev => true, 93 | :first_day_of_week => 0, 94 | :accessible => false, 95 | :show_today => true, 96 | :previous_month_text => nil, 97 | :next_month_text => nil, 98 | :month_header => true, 99 | :calendar_title => month_names[options[:month]], 100 | :summary => "Calendar for #{month_names[options[:month]]} #{options[:year]}", 101 | :show_week_numbers => false, 102 | :week_number_class => 'weekNumber', 103 | :week_number_title => 'CW', 104 | :week_number_format => :iso8601, # :iso8601 or :us_canada, 105 | :show_other_months => true 106 | } 107 | options = defaults.merge options 108 | 109 | first = Date.civil(options[:year], options[:month], 1) 110 | last = Date.civil(options[:year], options[:month], -1) 111 | 112 | first_weekday = first_day_of_week(options[:first_day_of_week]) 113 | last_weekday = last_day_of_week(options[:first_day_of_week]) 114 | 115 | day_names = (!defined?(I18n) || I18n.t("date.day_names").include?("missing")) ? Date::DAYNAMES : I18n.t("date.day_names") 116 | abbr_day_names = (!defined?(I18n) || I18n.t("date.abbr_day_names").include?("missing")) ? Date::ABBR_DAYNAMES : I18n.t("date.abbr_day_names") 117 | week_days = (0..6).to_a 118 | first_weekday.times do 119 | week_days.push(week_days.shift) 120 | end 121 | 122 | # TODO Use some kind of builder instead of straight HTML 123 | cal = %(
) 124 | cal << %() 125 | 126 | if (options[:month_header]) 127 | cal << %() 128 | if options[:previous_month_text] or options[:next_month_text] 129 | cal << %() 130 | colspan = options[:show_week_numbers] ? 4 : 3 131 | else 132 | colspan = options[:show_week_numbers] ? 8 : 7 133 | end 134 | cal << %() 135 | cal << %() if options[:next_month_text] 136 | cal << %() 137 | end 138 | 139 | cal << %() 140 | 141 | cal << %() if options[:show_week_numbers] 142 | 143 | week_days.each do |wday| 144 | cal << %() 147 | end 148 | 149 | cal << "" 150 | 151 | # previous month 152 | begin_of_week = beginning_of_week(first, first_weekday) 153 | cal << %() if options[:show_week_numbers] 154 | 155 | begin_of_week.upto(first - 1) do |d| 156 | cal << generate_other_month_cell(d, options) 157 | end unless first.wday == first_weekday 158 | 159 | first.upto(last) do |cur| 160 | cell_text, cell_attrs = block.call(cur) 161 | cell_text ||= cur.mday 162 | cell_attrs ||= {} 163 | cell_attrs["data-date"] = cur 164 | cell_attrs[:headers] = th_id(cur, options[:table_id]) 165 | cell_attrs[:class] ||= options[:day_class] 166 | cell_attrs[:class] += " weekendDay" if [0, 6].include?(cur.wday) 167 | today = (Time.respond_to?(:zone) && !(zone = Time.zone).nil? ? zone.now.to_date : Date.today) 168 | cell_attrs[:class] += " today" if (cur == today) and options[:show_today] 169 | 170 | cal << generate_cell(cell_text, cell_attrs) 171 | 172 | if cur.wday == last_weekday 173 | cal << %() 174 | if cur != last 175 | cal << %() 176 | cal << %() if options[:show_week_numbers] 177 | end 178 | end 179 | end 180 | 181 | # next month 182 | (last + 1).upto(beginning_of_week(last + 7, first_weekday) - 1) do |d| 183 | cal << generate_other_month_cell(d, options) 184 | end unless last.wday == last_weekday 185 | 186 | cal << "
#{options[:previous_month_text]}#{options[:calendar_title]}#{options[:next_month_text]}
#{options[:week_number_title]}) 145 | cal << (options[:abbrev] ? %(#{abbr_day_names[wday]}) : day_names[wday]) 146 | cal << %(
#{week_number(begin_of_week, options[:week_number_format])}
#{week_number(cur + 1, options[:week_number_format])}
" 187 | cal.respond_to?(:html_safe) ? cal.html_safe : cal 188 | end 189 | 190 | private 191 | 192 | def week_number(day, format) 193 | case format 194 | when :iso8601 195 | reference_day = seek_previous_wday(day, 1) 196 | reference_day.strftime('%V').to_i 197 | when :us_canada 198 | # US: the first day of the year defines the first calendar week 199 | first_day_of_year = Date.new((day + 7).year, 1, 1) 200 | reference_day = seek_next_wday(seek_next_wday(day, first_day_of_year.wday), 0) 201 | reference_day.strftime('%U').to_i 202 | else 203 | raise "Invalid calendar week format provided." 204 | end 205 | end 206 | 207 | def seek_previous_wday(ref_date, wday) 208 | ref_date - days_between(ref_date.wday, wday) 209 | end 210 | 211 | def seek_next_wday(ref_date, wday) 212 | ref_date + days_between(ref_date.wday, wday) 213 | end 214 | 215 | def first_day_of_week(day) 216 | day 217 | end 218 | 219 | def last_day_of_week(day) 220 | if day > 0 221 | day - 1 222 | else 223 | 6 224 | end 225 | end 226 | 227 | def days_between(first, second) 228 | if first > second 229 | second + (7 - first) 230 | else 231 | second - first 232 | end 233 | end 234 | 235 | def beginning_of_week(date, start = 1) 236 | days_to_beg = days_between(start, date.wday) 237 | date - days_to_beg 238 | end 239 | 240 | def generate_cell(cell_text, cell_attrs) 241 | cell_attrs = cell_attrs.map {|k, v| %(#{k}="#{v}") }.join(" ") 242 | "
#{cell_text}
]*scope="col">Sun}, calendar_with_defaults 79 | # testing that if the abbrev and contracted version are the same, there should be no abbreviation. 80 | assert_match %r{
]*scope="col">Sunday}, calendar_with_defaults(:abbrev => false) 81 | assert_match %r{
]*scope="col">Mon}, calendar_with_defaults(:first_day_of_week => 1) 82 | end 83 | 84 | def test_today_is_in_calendar 85 | todays_day = Date.today.day 86 | assert_match %r{class="day.+today"[^>]*>#{todays_day}<}, calendar_for_this_month 87 | end 88 | 89 | def test_should_not_show_today 90 | todays_day = Date.today.day 91 | assert_no_match %r{today}, calendar_for_this_month(:show_today => false) 92 | end 93 | 94 | # HACK Tried to use assert_select, but it's not made for free-standing 95 | # HTML parsing. 96 | def test_should_have_two_tr_tags_in_the_thead 97 | # TODO Use a validating service to make sure the rendered HTML is valid 98 | html = calendar_with_defaults 99 | assert_match %r{
]*summary="Calendar for April 1967"}, html 105 | end 106 | 107 | def test_custom_summary_attribute 108 | html = calendar_with_defaults(:summary => 'TEST SUMMARY') 109 | assert_match %r{
]*summary="TEST SUMMARY">}, html 110 | end 111 | 112 | def test_table_id_defaults_calendar_year_single_digit_month 113 | html = calendar_with_defaults(:year => 1967, :month => 4) 114 | assert_match %r{
]*id="calendar-1967-04"}, html 115 | end 116 | 117 | def test_table_id_defaults_calendar_year_double_digit_month 118 | html = calendar_with_defaults(:year => 1967, :month => 12) 119 | assert_match %r{
]*id="calendar-1967-12"}, html 120 | end 121 | 122 | def test_custom_table_id 123 | html = calendar_with_defaults(:year => 1967, :month => 4, :table_id => 'test-the-id') 124 | assert_match %r{
]*id="test-the-id"}, html 125 | end 126 | 127 | def test_th_id_defaults_calendar_year_month_dow 128 | html = calendar_with_defaults(:year => 1967, :month => 4) 129 | assert_match %r{}, html 135 | assert_match %r{}, html 136 | end 137 | 138 | def test_week_number_iso8601 139 | html = calendar_with_defaults(:year => 2011, :month => 1, :week_number_format => :iso8601, :show_week_numbers => true, :first_day_of_week => 1) 140 | [52,1,2,3,4,5].each { |cw| assert_match %r{}, html } 141 | end 142 | 143 | def test_week_number_us_canada 144 | html = calendar_with_defaults(:year => 2011, :month => 1, :week_number_format => :us_canada, :show_week_numbers => true) 145 | [1,2,3,4,5,6].each { |cw| assert_match %r{}, html } 146 | end 147 | 148 | def test_non_english_language 149 | # mock I18n.t to simulate internationalized setting 150 | CalendarHelper.const_set :I18n, Class.new { 151 | def self.t(key) 152 | if key == "date.day_names" 153 | ["Neděle", "Pondělí", "Úterý", "Středa", "Čtvrtek", "Pátek", "Sobota"] 154 | elsif key == "date.abbr_day_names" 155 | ["Ne", "Po", "Út", "St", "Čt", "Pá", "So"] 156 | elsif key == "date.month_names" 157 | ["", "Leden", "Únor", "Březen", "Duben", "Květen", "Červen", "Červenec", "Srpen", "Září", "Říjen", "Listopad", "Prosinec"] 158 | end 159 | end 160 | } 161 | 162 | html = calendar_with_defaults(:year => 2012, :month => 4) 163 | 164 | # unmock I18n.t again 165 | CalendarHelper.send(:remove_const, :I18n) 166 | 167 | # make sure all the labels are in english and don't use i18n abbreviation (Neděle) 168 | assert_no_match %r(calendar-2012-04-ned), html 169 | assert_equal 6, html.scan("calendar-2012-04-sun").size # 6 = 5 + header 170 | end 171 | 172 | 173 | private 174 | 175 | def assert_correct_css_class_for_key(css_class, key) 176 | assert_match %r{class="#{css_class}"}, calendar_with_defaults(key => css_class) 177 | end 178 | 179 | def assert_correct_css_class_for_default(css_class) 180 | assert_match %r{class="#{css_class}"}, calendar_with_defaults 181 | end 182 | 183 | def calendar_with_defaults(options={}) 184 | options = { :year => 2006, :month => 8 }.merge options 185 | calendar options 186 | end 187 | 188 | def calendar_for_this_month(options={}) 189 | options = { :year => Time.now.year, :month => Time.now.month}.merge options 190 | calendar options 191 | end 192 | 193 | def calendar_with_next_and_previous 194 | calendar_for_this_month({ 195 | :previous_month_text => "PREVIOUS", 196 | :next_month_text => "NEXT" 197 | }) 198 | end 199 | 200 | def write_sample(filename, content) 201 | FileUtils.mkdir_p "test/output" 202 | File.open("test/output/#{filename}", 'w') do |f| 203 | f.write %(Stylesheet Tester) 204 | f.write content 205 | f.write %() 206 | end 207 | end 208 | 209 | end 210 | --------------------------------------------------------------------------------
]*id=\"calendar-1967-04-sun\"}, html 130 | end 131 | 132 | def test_each_td_is_associated_with_appriopriate_th 133 | html = calendar_with_defaults(:year => 2011, :month => 8) 134 | assert_match %r{]*headers=\"calendar-2011-08-sun\"[^>]*>31]*headers=\"calendar-2011-08-mon\"[^>]*>1#{cw}#{cw}