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.
- data/.gitignore +18 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE +661 -0
- data/README.md +158 -0
- data/Rakefile +13 -0
- data/calendrier.gemspec +23 -0
- data/lib/calendrier.rb +21 -0
- data/lib/calendrier/calendrier_builder.rb +74 -0
- data/lib/calendrier/controllers/event_extension.rb +57 -0
- data/lib/calendrier/helpers/calendrier_helper.rb +94 -0
- data/lib/calendrier/helpers/event_helper.rb +63 -0
- data/lib/calendrier/version.rb +3 -0
- data/test/lib/calendrier/calendrier_builder_test.rb +175 -0
- data/test/lib/calendrier/controllers/event_extension_test.rb +40 -0
- data/test/lib/calendrier/helpers/calendrier_helper_test.rb +60 -0
- data/test/lib/calendrier/helpers/event_helper_test.rb +94 -0
- data/test/lib/calendrier/version_test.rb +9 -0
- data/test/minitest_helper.rb +19 -0
- data/test/test_helper.rb +52 -0
- metadata +138 -0
data/README.md
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
# Calendrier
|
2
|
+
|
3
|
+
[](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
|
data/Rakefile
ADDED
@@ -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
|
data/calendrier.gemspec
ADDED
@@ -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
|
data/lib/calendrier.rb
ADDED
@@ -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
|