calendrier 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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