calendrier 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![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
|
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
|