calendrier 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,158 @@
1
+ # Calendrier
2
+
3
+ [![travis](https://secure.travis-ci.org/lafourmi/calendrier.png?branch=master)](http://travis-ci.org/lafourmi/calendrier)
4
+
5
+ ##DESCRIPTION
6
+
7
+ A simple helper for creating calendars. It includes a method to sort objects by date and an helpers to display events/meetings and other objects.
8
+
9
+ ##SYNOPSIS:
10
+
11
+ The gem provides `calendrier` helper to display calendars
12
+
13
+ # Simple example
14
+ <%= calendrier(:year => 2012, :month => 5, :day => 25, :start_on_monday => true, :display => :week, :title => "My calendar title") %>
15
+
16
+ # Complex example
17
+ <%= calendrier(:year => 2012, :month => 5, :day => 25, :start_on_monday => true, :display => :month) do |cell_begin_time, cell_end_time| %>
18
+ <!-- this code is run for every cell of the calendar -->
19
+
20
+ <!-- you have access to `cell_begin_time` and `cell_end_time` which let you create links or custom content -->
21
+ <%= content_tag(:span, "Add meeting/event at #{l(cell_begin_time)} ?") %>
22
+ <% end %>
23
+
24
+ Now you have a calendar, but you may need to display events inside. If you have many events to display, the gem provides a method `sort_events` for the controller to create a hash of events.
25
+ This avoid the calendar to check every events of the month to display every single cell.
26
+ To use that method, you could pass as argument a mix of any objects which `respond_to?` one of the following method sets :
27
+
28
+ * `year`, `month`, `day`
29
+ * `begin_time`, `end_time`
30
+
31
+ This method will return an array like this :
32
+
33
+ events_by_date => {"2012"=>{"5"=>{"21"=>[#<Event>, #<Event>],
34
+ "22"=>[#<Event>, #<Event>, #<Event>],
35
+ "23"=>[#<Meeting>],
36
+ "25"=>[#<Event>, #<Meeting>],
37
+ "26"=>[#<Event>],
38
+ "27"=>[#<Meeting>, #<Event>]}}}
39
+
40
+ In your controller :
41
+
42
+ class HomeController < ApplicationController
43
+ include Calendrier::EventExtension
44
+
45
+ def index
46
+ arr = Meeting.all
47
+ arr << Event.all
48
+ @events_by_date = sort_events(arr)
49
+ end
50
+ end
51
+
52
+ In your view :
53
+
54
+ <%= calendrier(:year => 2012, :month => 5, :day => 25, :start_on_monday => true, :display => :month) do |cell_begin_time, cell_end_time| %>
55
+ <!-- this code is run for every cell of the calendar -->
56
+
57
+ <% if count_sorted_events(@events_by_date, cell_begin_time, cell_end_time) > 0 %>
58
+ <!-- this code is run only there is at least one event between cell begin and end -->
59
+
60
+ <ul>
61
+ <% yield_sorted_events(@events_by_date, cell_begin_time, cell_end_time) do |obj| %>
62
+ <!-- you may handle event/meeting/... with the obj variable -->
63
+ <li><%= obj.title %></li>
64
+ <% end %>
65
+ </ul>
66
+ <% end %>
67
+
68
+ <% end %>
69
+
70
+
71
+
72
+ ##Custom builder
73
+
74
+ If you need a more complex calendar, you'll need to define a custom builder. To create such builder, add a file like the following.
75
+
76
+ # /lib/calendrier/calendrier_builder/custom_builder.rb
77
+ module Calendrier
78
+ module CalendrierBuilder
79
+ class CustomBuilder < Builder
80
+
81
+ def render(header, content)
82
+ # header is an array like this, having Date object of each columns
83
+ # [Mon, 21 May 2012,
84
+ # Tue, 22 May 2012,
85
+ # Wed, 23 May 2012,
86
+ # Thu, 24 May 2012,
87
+ # Fri, 25 May 2012,
88
+ # Sat, 26 May 2012,
89
+ # Sun, 27 May 2012]
90
+ #
91
+ # content is a double array like that, containing one Hash for each cell : {:time => Time.utc(<cell_begin_time>), :content => '<block content of this cell>' }
92
+ # [ [ 7 Hash for one week ], [ 7 Hash for the next week ], ... ]
93
+ #
94
+ # [[{:time=>nil, :content=>nil}, # time could be nil on monthly display if week do not starts on monday/sunday (first day of week)
95
+ # {:time=>2012-05-01 00:00:00 +0200, :content=>nil},
96
+ # {:time=>2012-05-02 00:00:00 +0200, :content=>nil},
97
+ # {:time=>2012-05-03 00:00:00 +0200, :content=>nil},
98
+ # {:time=>2012-05-04 00:00:00 +0200, :content=>nil},
99
+ # {:time=>2012-05-05 00:00:00 +0200, :content=>nil},
100
+ # {:time=>2012-05-06 00:00:00 +0200, :content=>nil}],
101
+ # ...
102
+ #
103
+ end
104
+
105
+ end
106
+ end
107
+ end
108
+
109
+ And do not forget to add /lib to rails autoload_paths by adding the following line.
110
+
111
+ # config/application.rb
112
+ module MyNiceRailsApplication
113
+ class Application < Rails::Application
114
+
115
+ ...
116
+
117
+ # Custom directories with classes and modules you want to be autoloadable.
118
+ # config.autoload_paths += %W(#{config.root}/extras)
119
+ config.autoload_paths += %W( #{config.root}/lib )
120
+
121
+ ...
122
+
123
+ end
124
+ end
125
+
126
+ Use your new builder by adding the builder option to the renderer.
127
+
128
+ <%= calendrier(:year => 2012, :month => 5, :day => 25, :start_on_monday => true, :display => :month, :builder => Calendrier::CalendrierBuilder::CustomBuilder) %>
129
+
130
+
131
+ ##INSTALLATION
132
+
133
+ Add this line to your application's Gemfile :
134
+
135
+ gem 'calendrier', :git => "git://github.com/lafourmi/calendrier.git", :branch => "master"
136
+
137
+ And then execute :
138
+
139
+ $ bundle install
140
+
141
+ Or install it yourself as :
142
+
143
+ $ gem install calendrier
144
+
145
+
146
+ ##AUTHORS
147
+
148
+ Romain Castel
149
+
150
+ Thomas Kienlen
151
+
152
+ ##USAGE
153
+
154
+ 1. Fork it
155
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
156
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
157
+ 4. Push to the branch (`git push origin my-new-feature`)
158
+ 5. Create new Pull Request
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'lib/calendrier'
7
+ t.libs << 'test' # to find test_helper
8
+ #t.test_files = FileList['test/lib/calendrier/*_test.rb']
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ #t.verbose = !!ENV["VERBOSE"]
11
+ t.verbose = true
12
+ end
13
+ task :default => :test
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/calendrier/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Romain Castel", "Thomas Kienlen"]
6
+ gem.email = ["thomas.kienlen@lafourmi-immo.com"]
7
+ gem.description = %q{simple calendar}
8
+ gem.summary = %q{simple calendar gem, including helpers to display objects inside cells}
9
+ gem.homepage = "https://github.com/lafourmi/calendrier"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "calendrier"
15
+ gem.require_paths = ["lib"]
16
+ gem.add_dependency 'rails', ['>= 3.0']
17
+ gem.version = Calendrier::VERSION
18
+ gem.add_development_dependency 'rake'
19
+ gem.add_development_dependency 'nokogiri'
20
+ unless ENV["CI"]
21
+ gem.add_development_dependency "turn", "~> 0.9" if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9'
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ require "calendrier/version"
2
+ require "calendrier/calendrier_builder"
3
+ require "calendrier/controllers/event_extension"
4
+ require "calendrier/helpers/calendrier_helper"
5
+ require "calendrier/helpers/event_helper"
6
+
7
+ module Calendrier
8
+ # including our calendar
9
+ ActiveSupport.on_load(:action_view) do
10
+ ::ActionView::Base.send :include, Calendrier::CalendrierHelper
11
+ # this one is crapy
12
+ ::ActionView::Base.send :include, Calendrier::EventHelper
13
+ end
14
+ # no automatic load
15
+ # you need to include manually in the controllers where it is needed
16
+ #ActiveSupport.on_load(:action_controller) do
17
+ # ::ActionController::Base.send :include, Calendrier::EventExtension
18
+ #end
19
+ end
20
+
21
+
@@ -0,0 +1,74 @@
1
+ require 'pp'
2
+
3
+ module Calendrier
4
+ module CalendrierBuilder
5
+
6
+ class Builder
7
+ def initialize(context, options = {})
8
+ @context, @options = context, options
9
+ @options[:display] ||= :month
10
+
11
+ unless @options.include? :title
12
+ month = @options[:month] || nil
13
+ year = @options[:year] || nil
14
+ date = (month.nil? || year.nil?) ? Time.now.to_date : Date.new(year, month)
15
+ @options[:title] = "#{I18n.l(date)}"
16
+ end
17
+ end
18
+
19
+ def render(&block)
20
+ raise NotImplementedError
21
+ end
22
+ end
23
+
24
+ class SimpleBuilder < Builder
25
+ def render(header, content)
26
+ display = @options[:display]
27
+ title = @options[:title] || ''
28
+
29
+ @context.content_tag(:div, nil, :class => "calendar #{display.to_s}") do
30
+ cal = @context.content_tag(:span, title)
31
+ cal << @context.content_tag(:table, nil) do
32
+
33
+ unless header.nil?
34
+ thead = @context.content_tag(:thead, nil) do
35
+ ths = "".html_safe
36
+ ths << @context.content_tag(:th, 'Horaires') if display == :week
37
+ header.each do |cell_date|
38
+ ths << @context.content_tag(:th, I18n.l(cell_date)) if display == :week
39
+ ths << @context.content_tag(:th, I18n.l(cell_date, :format => '%A')) if display == :month
40
+ end
41
+ ths
42
+ end
43
+ end
44
+
45
+ unless content.nil?
46
+ tbody = @context.content_tag(:tbody, nil) do
47
+ trs = "".html_safe
48
+ content.each_with_index do |row, index|
49
+ trs << @context.content_tag(:tr, nil) do
50
+ tds = "".html_safe
51
+ tds << @context.content_tag(:td, index) if display == :week
52
+ row.collect do |cell|
53
+ cell_content = "".html_safe
54
+ cell_time = cell[:time]
55
+ cell_content << @context.content_tag(:span, cell_time.day) if display == :month && !cell_time.nil?
56
+ cell_content << cell[:content]
57
+ tds << @context.content_tag(:td, cell_content)
58
+ end
59
+ tds
60
+ end
61
+ end
62
+ @context.concat trs
63
+ end
64
+ end
65
+
66
+ thead.concat(tbody) unless thead.nil?
67
+ end
68
+ cal
69
+ end
70
+ end
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,57 @@
1
+ module Calendrier
2
+ module EventExtension
3
+
4
+ def sort_events(events)
5
+ events_by_date = {}
6
+
7
+ events.sort_by { |obj| get_event_stamp(obj) }.each do |event|
8
+
9
+ begin_date = Time.at(get_event_stamp(event)).to_date
10
+ end_date = Time.at(get_event_stamp(event, :end_time => true)).to_date
11
+
12
+ duration_in_days = (end_date - begin_date).to_i + 1
13
+
14
+ duration_in_days.times do |index|
15
+ current_date = begin_date + index
16
+ date_arr = [current_date.year.to_s, current_date.month.to_s, current_date.day.to_s]
17
+ exist = begin
18
+ true if events_by_date[current_date.year.to_s][current_date.month.to_s][current_date.day.to_s]
19
+ rescue NoMethodError
20
+ false
21
+ end
22
+ # create recursive hash {"2012"=>{"5"=>{"21"=>[]}}}
23
+ events_by_date = events_rmerge(events_by_date, date_arr.reverse.inject([]) { |a, n| {n=>a} }) unless exist
24
+
25
+ unless events_by_date[current_date.year.to_s][current_date.month.to_s][current_date.day.to_s].include? event
26
+ events_by_date[current_date.year.to_s][current_date.month.to_s][current_date.day.to_s] << event
27
+ end
28
+ end
29
+ end
30
+
31
+ return events_by_date
32
+ end
33
+
34
+ protected
35
+
36
+ def events_rmerge(hash, other_hash)
37
+ r = {}
38
+ hash.merge(other_hash) do |key, oldval, newval|
39
+ r[key] = oldval.class == hash.class ? events_rmerge(oldval, newval) : newval
40
+ end
41
+ end
42
+
43
+ def get_event_stamp(event, options = {})
44
+ end_time = options[:end_time]
45
+ ret = nil
46
+
47
+ if event.respond_to?(:year) && event.respond_to?(:month) && event.respond_to?(:day)
48
+ ret = Time.utc(event.year, event.month, event.day).to_i
49
+ elsif event.respond_to?(:begin_time) && event.respond_to?(:end_time)
50
+ ret = end_time ? event.end_time : event.begin_time
51
+ end
52
+
53
+ return ret
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,94 @@
1
+ module Calendrier
2
+ module CalendrierHelper
3
+ DAYS_IN_WEEK = 7
4
+ HOURS_IN_DAY = 24
5
+
6
+ DIMANCHE = 0
7
+ LUNDI = 1
8
+
9
+ def calendrier(options = {}, &block)
10
+ year = options[:year] || Time.now.year
11
+ month = options[:month] || Time.now.month
12
+ day = options[:day] || Time.now.day
13
+ display = options[:display] || :month
14
+
15
+ builder_options = options
16
+ builder_options[:display] = display unless builder_options.include? :display
17
+
18
+ builder = (options.delete(:builder) || CalendrierBuilder::SimpleBuilder).new(self, options)
19
+ start_on_monday = options[:start_on_monday].nil? ? true : options[:start_on_monday]
20
+
21
+ first_day_of_month = Time.utc(year, month, 1).wday
22
+ first_day_of_month = shift_week_days(first_day_of_month, 1) if start_on_monday
23
+
24
+ days_in_month = Time.utc(year, month, 1).end_of_month.day
25
+ days = (days_in_month + first_day_of_month)
26
+ weeks_in_month = (days / DAYS_IN_WEEK) + (days % DAYS_IN_WEEK != 0 ? 1 : 0)
27
+
28
+ days_arr = []
29
+ selected_calendar_date = Date.new(year, month, day)
30
+
31
+ day_shift = (start_on_monday ? LUNDI : DIMANCHE)
32
+ first_day_of_week = selected_calendar_date - (selected_calendar_date.wday - day_shift)
33
+
34
+
35
+ if display == :week
36
+ table_head = (0...DAYS_IN_WEEK).map { |index| first_day_of_week + index }
37
+ table_content = []
38
+ (0...HOURS_IN_DAY).each do |hour_index|
39
+ table_content_row = []
40
+ DAYS_IN_WEEK.times do |index|
41
+ this_day = (first_day_of_week + index)
42
+ cell_begin_time = Time.utc(this_day.year, this_day.month, this_day.day, hour_index)
43
+ cell_content = nil
44
+ cell_end_time = cell_begin_time + 3600
45
+ cell_content = capture(cell_begin_time, cell_end_time, &block) if block_given?
46
+ table_content_row << { time: cell_begin_time, content: cell_content}
47
+ end
48
+ table_content << table_content_row
49
+ end
50
+ else # :month
51
+ table_head = (0...DAYS_IN_WEEK).map { |index| first_day_of_week + index }
52
+ day_counter = 0
53
+ weeks_in_month.times do |week_index|
54
+ (0...DAYS_IN_WEEK).each do |day_index|
55
+ day_counter += 1 if (day_index == first_day_of_month || day_counter != 0)
56
+ days_arr << nil if (day_counter == 0 && day_index != first_day_of_month) || (day_counter != 0 && day_counter > days_in_month)
57
+ days_arr << day_counter if (day_counter == 0 && day_index == first_day_of_month) || (day_counter != 0 && day_counter <= days_in_month)
58
+ end
59
+ end
60
+
61
+ table_content = []
62
+ while days_arr.length > 0
63
+ table_content_row = []
64
+ one_week = days_arr.slice!(0, DAYS_IN_WEEK)
65
+ one_week.each do |one_day|
66
+ cell_content = nil
67
+ cell_begin_time = nil
68
+
69
+ if one_day.is_a?(Integer)
70
+ cell_begin_time = Time.utc(year, month, one_day)
71
+ cell_end_time = cell_begin_time + 3600*24
72
+ cell_content = capture(cell_begin_time, cell_end_time, &block) if block_given?
73
+ end
74
+
75
+ table_content_row << { time: cell_begin_time, content: cell_content}
76
+ end
77
+ table_content << table_content_row
78
+ end
79
+ end
80
+
81
+ builder.render(table_head, table_content).html_safe
82
+ end
83
+
84
+ protected
85
+
86
+ def shift_week_days(wday, index)
87
+ wday -= index
88
+ wday += DAYS_IN_WEEK if wday < 0
89
+
90
+ return wday
91
+ end
92
+
93
+ end
94
+ end