simple_calendar 1.1.10 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.travis.yml +5 -0
- data/README.md +122 -156
- data/Rakefile +7 -0
- data/app/views/simple_calendar/_calendar.html.erb +27 -0
- data/app/views/simple_calendar/_month_calendar.html.erb +27 -0
- data/app/views/simple_calendar/_week_calendar.html.erb +27 -0
- data/bin/console +8 -0
- data/bin/setup +5 -0
- data/lib/generators/simple_calendar/views_generator.rb +13 -0
- data/lib/simple_calendar.rb +0 -10
- data/lib/simple_calendar/calendar.rb +57 -121
- data/lib/simple_calendar/month_calendar.rb +5 -11
- data/lib/simple_calendar/railtie.rb +1 -1
- data/lib/simple_calendar/version.rb +1 -1
- data/lib/simple_calendar/view_helpers.rb +3 -3
- data/lib/simple_calendar/week_calendar.rb +10 -38
- data/simple_calendar.gemspec +1 -0
- data/spec/calendar_spec.rb +69 -0
- data/spec/calendars/month_calendar_spec.rb +7 -0
- data/spec/simple_calendar_spec.rb +8 -0
- data/spec/spec_helper.rb +96 -0
- data/spec/views_generators_spec.rb +7 -0
- metadata +38 -6
- data/lib/simple_calendar/model_additions.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8dfc97c340a63812f227e879a6756c2802c50488
|
4
|
+
data.tar.gz: ebf3dad29eae132666ecc41060996ca812e59d9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c7fcff14b9f734bd872120d693bd3b71434cf82d439d92a11767c3cc6fbc862a05d2b297b7937b3704b5c5adf5d0dee6a2390b1bbb0a58a0d9003ad53d0fa9d8
|
7
|
+
data.tar.gz: 32501ddee6d5535fd55a1af0487b66ba9a3b23c3e464e48181e1bb7956de0efacc599decff45c131b9f8ba9f7b4865eb81ea0b8bf7a9256a66d022894332942f
|
data/.rspec
CHANGED
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
![travis ci](https://travis-ci.org/excid3/simple_calendar.svg?branch=2.0)
|
2
|
+
|
1
3
|
Simple Calendar
|
2
4
|
===============
|
3
5
|
|
@@ -19,12 +21,12 @@ Installation
|
|
19
21
|
|
20
22
|
Just add this into your Gemfile followed by a bundle install:
|
21
23
|
|
22
|
-
gem "simple_calendar", "~>
|
24
|
+
gem "simple_calendar", "~> 2.0"
|
23
25
|
|
24
26
|
Usage
|
25
27
|
-----
|
26
28
|
|
27
|
-
Generating calendars is extremely simple with simple_calendar
|
29
|
+
Generating calendars is extremely simple with simple_calendar.
|
28
30
|
|
29
31
|
The first parameter is a symbol that looks up the current date in
|
30
32
|
`params`. If no date is found, it will use the current date.
|
@@ -77,26 +79,26 @@ model called Meeting, but you can add this to any model or Ruby object.
|
|
77
79
|
Here's an example model:
|
78
80
|
|
79
81
|
```bash
|
80
|
-
rails g scaffold Meeting name
|
82
|
+
rails g scaffold Meeting name start_time:datetime
|
81
83
|
```
|
82
84
|
|
83
|
-
|
84
|
-
and sort the meetings on the different calendar days. This should be the
|
85
|
-
start date/time of your meeting. By default it uses `starts_at` as the
|
86
|
-
attribute name.
|
85
|
+
By default it uses `start_time` as the attribute name.
|
87
86
|
|
88
|
-
|
89
|
-
|
90
|
-
extend SimpleCalendar
|
91
|
-
has_calendar
|
87
|
+
**If you'd like to use another attribute other than start_time, just
|
88
|
+
pass it in as the `attribute` option**
|
92
89
|
|
93
|
-
|
94
|
-
|
95
|
-
|
90
|
+
```erb
|
91
|
+
<%= month_calendar(attribute: :starts_at) do |date| %>
|
92
|
+
<%= day %>
|
93
|
+
<% end %>
|
96
94
|
```
|
97
95
|
|
98
96
|
In your controller, query for these meetings and store them in an instance
|
99
|
-
variable.
|
97
|
+
variable. Normally you'll want to search for the ones that only show up
|
98
|
+
inside the calendar view (for example, you may only want to grab the events for
|
99
|
+
the current month).
|
100
|
+
|
101
|
+
We'll just load up all the meetings for this example.
|
100
102
|
|
101
103
|
```ruby
|
102
104
|
def index
|
@@ -125,13 +127,23 @@ do custom filtering however you want.
|
|
125
127
|
|
126
128
|
## Customizing The Calendar
|
127
129
|
|
128
|
-
|
129
|
-
|
130
|
+
There are a handful of configuration options that you can use in
|
131
|
+
simple_calendar.
|
130
132
|
|
131
|
-
|
132
|
-
|
133
|
+
### Customizing Views
|
134
|
+
|
135
|
+
You can customize the layouts for each of the calendars by running the
|
136
|
+
generators for simple_calendar:
|
137
|
+
|
138
|
+
```bash
|
139
|
+
rails g simple_calendar:views
|
133
140
|
```
|
134
141
|
|
142
|
+
This will generate a folder in app/views called simple_calendar that you
|
143
|
+
edit to your heart's desire.
|
144
|
+
|
145
|
+
### Time Zones
|
146
|
+
|
135
147
|
Setting `Time.zone` will make sure the calendar start days are correctly computed
|
136
148
|
in the right timezone. You can set this globally in your `application.rb` file or
|
137
149
|
if you have a User model with a time_zone attribute, you can set it on every request by using
|
@@ -153,12 +165,6 @@ class ApplicationController < ActionController::Base
|
|
153
165
|
end
|
154
166
|
end
|
155
167
|
```
|
156
|
-
On the other hand, you can always pass a ``ActiveSupport::TimeZone`` object as an option to avoid possible timezone pollution:
|
157
|
-
|
158
|
-
```erb
|
159
|
-
<%= calendar timezone: ActiveSupport::TimeZone.new('Taipei') do |date, events| %>
|
160
|
-
<% end %>
|
161
|
-
```
|
162
168
|
|
163
169
|
If you want to set the time zone globally, you can set the following in
|
164
170
|
`config/application.rb`:
|
@@ -167,6 +173,8 @@ If you want to set the time zone globally, you can set the following in
|
|
167
173
|
config.time_zone = 'Central Time (US & Canada)'
|
168
174
|
```
|
169
175
|
|
176
|
+
### Beginning Of Week
|
177
|
+
|
170
178
|
You can also change the beginning day of the week by setting
|
171
179
|
`Date.beginning_of_week` in a `before_filter` just like in the previous
|
172
180
|
example. If you want to set this globally, you can put this line in
|
@@ -176,188 +184,142 @@ example. If you want to set this globally, you can put this line in
|
|
176
184
|
config.beginning_of_week = :sunday
|
177
185
|
```
|
178
186
|
|
187
|
+
### Custom CSS Classes
|
188
|
+
|
179
189
|
Setting classes on the table and elements are pretty easy.
|
180
190
|
|
181
|
-
|
182
|
-
|
191
|
+
You can simply run the following command to install the calendar views
|
192
|
+
and then add your own helpers to the table, rows, headers, and days.
|
183
193
|
|
184
|
-
|
194
|
+
simple_calendar comes with a handful of useful classes for each day in
|
195
|
+
the calendar that you can use:
|
185
196
|
|
186
|
-
|
187
|
-
|
188
|
-
|
197
|
+
```scss
|
198
|
+
.simple-calendar {
|
199
|
+
.day {}
|
189
200
|
|
190
|
-
|
191
|
-
|
201
|
+
.wday-0 {}
|
202
|
+
.wday-1 {}
|
203
|
+
.wday-2 {}
|
204
|
+
.wday-3 {}
|
205
|
+
.wday-4 {}
|
206
|
+
.wday-5 {}
|
207
|
+
.wday-6 {}
|
192
208
|
|
193
|
-
|
209
|
+
.today {}
|
210
|
+
.past {}
|
211
|
+
.future {}
|
194
212
|
|
195
|
-
|
196
|
-
generated. By default, simple_calendar renders the following classes for
|
197
|
-
any given day in a calendar:
|
213
|
+
.start-date {}
|
198
214
|
|
215
|
+
.prev-month {}
|
216
|
+
.next-month { }
|
217
|
+
.current-month {}
|
199
218
|
|
200
|
-
|
201
|
-
|
202
|
-
td_class << "today" if today == current_calendar_date
|
203
|
-
td_class << "past" if today > current_calendar_date
|
204
|
-
td_class << "future" if today < current_calendar_date
|
205
|
-
td_class << "prev-month" if start_date.month != current_calendar_date.month && current_calendar_date < start_date
|
206
|
-
td_class << "next-month" if start_date.month != current_calendar_date.month && current_calendar_date > start_date
|
207
|
-
td_class << "current-month" if start_date.month == current_calendar_date.month
|
208
|
-
td_class << "wday-#{current_calendar_date.wday.to_s}"
|
219
|
+
.has-events {}
|
220
|
+
}
|
209
221
|
```
|
210
222
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
content_tag options. If you wish to set a class or data attributes, just
|
216
|
-
set them as you normally would in a content_tag call.
|
223
|
+
Just paste this into a CSS file and add your styles and they will be
|
224
|
+
applied to the calendar. All of these classes are inside of the
|
225
|
+
simple-calendar class so you can scope your own classes with similar
|
226
|
+
names.
|
217
227
|
|
218
|
-
|
219
|
-
<%= month_calendar td: ->(start_date, current_calendar_date) { {class: "calendar-date", data: {day: current_calendar_date}} } do |day| %>
|
220
|
-
<% end %>
|
221
|
-
```
|
222
|
-
|
223
|
-
This generate each day in the calendar like this:
|
228
|
+
### Custom Header Title And Links
|
224
229
|
|
225
|
-
|
226
|
-
|
227
|
-
</td>
|
228
|
-
```
|
230
|
+
Header and title links are easily adjusted by generating views and
|
231
|
+
modifying them inside your application.
|
229
232
|
|
230
|
-
|
231
|
-
|
232
|
-
adding this to one of your helpers:
|
233
|
+
For example, if you'd like to use abbreviated month names, you can modify
|
234
|
+
the views from this:
|
233
235
|
|
234
|
-
```
|
235
|
-
|
236
|
-
->(start_date, current_calendar_date) {
|
237
|
-
{class: "calendar-date", data: {day: current_calendar_date}}
|
238
|
-
}
|
239
|
-
end
|
236
|
+
```erb
|
237
|
+
<%= I18n.t("date.month_names")[start_date.month] %> <%= start_date.year %>
|
240
238
|
```
|
241
239
|
|
242
|
-
|
240
|
+
To
|
243
241
|
|
244
242
|
```erb
|
245
|
-
<%=
|
246
|
-
<% end %>
|
243
|
+
<%= I18n.t("date.abbr_month_names")[start_date.month] %> <%= start_date.year %>
|
247
244
|
```
|
248
245
|
|
249
|
-
|
246
|
+
Your calendar will now display "Sep 2015" instead of "September 2015" at
|
247
|
+
the top! :)
|
250
248
|
|
251
|
-
|
252
|
-
previous and next views. The `month_calendar` also includes a header
|
253
|
-
with a title that tells you the current month and year that you are viewing.
|
254
|
-
|
255
|
-
To change these, you can pass in the `previous_link`, `title`, and
|
256
|
-
`next_link` options into the calendar methods.
|
249
|
+
### AJAX Calendars
|
257
250
|
|
258
|
-
|
259
|
-
|
251
|
+
Rendering calendars that update with AJAX is pretty simple. You'll need
|
252
|
+
to wrap your calendar in a div, overwrite the `next_link` and `previous_link` options, and setup your
|
253
|
+
controller to respond to JS requests. The response can simply replace
|
254
|
+
the HTML of the div with the newly rendered calendar.
|
260
255
|
|
261
|
-
|
262
|
-
month calendars, this is the Month and Year (May 2014)
|
256
|
+
Take a look at **[excid3/simple_calendar-ajax-example](https://github.com/excid3/simple_calendar-ajax-example)** to see how it is done.
|
263
257
|
|
264
|
-
```erb
|
265
|
-
<%= calendar title: ->(start_date) { content_tag :span, "#{I18n.t("date.month_names")[start_date.month]} #{start_date.year}", class: "calendar-title" } do |date, events| %>
|
266
|
-
<% end %>
|
267
|
-
```
|
268
258
|
|
269
|
-
|
270
|
-
with the current url having `?start_date=2014-04-30` appended to it as
|
271
|
-
a date in the previous view of the calendar.
|
259
|
+
## Custom Calendars
|
272
260
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
```
|
261
|
+
The three main calendars available should take care of most of your
|
262
|
+
needs, but simple_calendar makes it easy to create completely custom
|
263
|
+
calendars (like maybe you only want business weeks).
|
277
264
|
|
278
|
-
|
279
|
-
|
280
|
-
|
265
|
+
If you'd like to make a completely custom calendar, you can create a new
|
266
|
+
class that inherits from `SimpleCalendar::Calendar`. The name you give
|
267
|
+
it will correspond to the name of the template it will try to render.
|
281
268
|
|
282
|
-
```erb
|
283
|
-
<%= calendar next_link: ->(param, date_range) { link_to raw("»"), {param => date_range.last + 1.day} } do |date, events| %>
|
284
|
-
<% end %>
|
285
|
-
```
|
286
269
|
|
287
|
-
|
270
|
+
The main method you'll need to implement is the `date_range` so that
|
271
|
+
your calendar can have a custom length.
|
288
272
|
|
289
|
-
```erb
|
290
|
-
<%= calendar header: {class: "calendar-header"} do |date, events| %>
|
291
|
-
<% end %>
|
292
273
|
```
|
274
|
+
class SimpleCalendar::BusinessWeekCalendar
|
275
|
+
private
|
293
276
|
|
294
|
-
|
295
|
-
|
277
|
+
def date_range
|
278
|
+
beginning = start_date.beginning_of_week + 1.day
|
279
|
+
ending = start_date.end_of_week - 1.day
|
280
|
+
(beginning..ending)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
```
|
296
284
|
|
297
|
-
|
285
|
+
To render this in the view, you can do:
|
298
286
|
|
299
287
|
```erb
|
300
|
-
<%=
|
288
|
+
<%= SimpleCalendar::BusinessWeekCalendar.new(self).render do |date| %>
|
289
|
+
<%= day %>
|
301
290
|
<% end %>
|
302
291
|
```
|
303
292
|
|
304
|
-
|
293
|
+
And this will render the
|
294
|
+
`app/views/simple_calendar/_business_week_calendar.html.erb` partial.
|
305
295
|
|
306
|
-
|
307
|
-
|
308
|
-
validate I18n array.
|
296
|
+
You can copy one of the existing templates to use for the partial for
|
297
|
+
your new calendar.
|
309
298
|
|
310
|
-
|
311
|
-
<%= calendar day_names: "date.day_names" do |date, events| %>
|
312
|
-
<% end %>
|
313
|
-
```
|
314
|
-
|
315
|
-
Which renders:
|
316
|
-
|
317
|
-
```html
|
318
|
-
<thead>
|
319
|
-
<tr>
|
320
|
-
<th>Sunday</th>
|
321
|
-
<th>Monday</th>
|
322
|
-
<th>Tuesday</th>
|
323
|
-
<th>Wednesday</th>
|
324
|
-
</tr>
|
325
|
-
</thead>
|
326
|
-
```
|
299
|
+
## View Specs and Tests
|
327
300
|
|
328
|
-
|
329
|
-
header names.
|
301
|
+
If you're running view specs against views with calendars, you may run into route generation errors like the following:
|
330
302
|
|
331
|
-
```erb
|
332
|
-
<%= calendar day_names: "date.abbr_day_names" do |date, events| %>
|
333
|
-
<% end %>
|
334
303
|
```
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
```html
|
339
|
-
<thead>
|
340
|
-
<tr>
|
341
|
-
<th>Sun</th>
|
342
|
-
<th>Mon</th>
|
343
|
-
<th>Tue</th>
|
344
|
-
<th>Wed</th>
|
345
|
-
</tr>
|
346
|
-
</thead>
|
304
|
+
Failure/Error: render
|
305
|
+
ActionView::Template::Error:
|
306
|
+
No route matches {:action=>"show", :controller=>"controller_name", :start_date=>Sun, 29 Mar 2015}
|
347
307
|
```
|
348
308
|
|
349
|
-
|
309
|
+
If so, you can stub out the appropriate method like so (rspec 3 and up):
|
350
310
|
|
351
|
-
|
352
|
-
to
|
353
|
-
|
354
|
-
the HTML of the div with the newly rendered calendar.
|
311
|
+
```
|
312
|
+
expect_any_instance_of(SimpleCalendar::Calendar).to receive(:link_to).at_least(:once).and_return("")
|
313
|
+
```
|
355
314
|
|
356
|
-
|
315
|
+
With modifications as appropriate.
|
357
316
|
|
358
317
|
## TODO
|
359
318
|
|
360
|
-
- Multi-day events
|
319
|
+
- Multi-day events
|
320
|
+
- Rspec tests for Calendar
|
321
|
+
- Rspec tests for MonthCalendar
|
322
|
+
- Rspec tests for WeekCalendar
|
361
323
|
|
362
324
|
## Author
|
363
325
|
|
@@ -366,3 +328,7 @@ Chris Oliver <chris@gorails.com>
|
|
366
328
|
[https://gorails.com](https://gorails.com)
|
367
329
|
|
368
330
|
[@excid3](https://twitter.com/excid3)
|
331
|
+
|
332
|
+
## Support
|
333
|
+
|
334
|
+
Need help
|
data/Rakefile
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
<div class="simple-calendar">
|
2
|
+
<%= link_to "Previous", start_date: date_range.first - 1.day %>
|
3
|
+
<%= I18n.t("date.month_names")[start_date.month] %> <%= start_date.year %>
|
4
|
+
<%= link_to "Next", start_date: date_range.last + 1.day %>
|
5
|
+
|
6
|
+
<table class="table table-striped">
|
7
|
+
<thead>
|
8
|
+
<tr>
|
9
|
+
<% date_range.slice(0, 7).each do |day| %>
|
10
|
+
<th><%= I18n.t("date.abbr_day_names")[day.wday] %></th>
|
11
|
+
<% end %>
|
12
|
+
</tr>
|
13
|
+
</thead>
|
14
|
+
|
15
|
+
<tbody>
|
16
|
+
<% date_range.each_slice(7) do |week| %>
|
17
|
+
<tr>
|
18
|
+
<% week.each do |day| %>
|
19
|
+
<%= content_tag :td, class: calendar.td_classes_for(day) do %>
|
20
|
+
<%= block.call day, sorted_events.fetch(day, []) %>
|
21
|
+
<% end %>
|
22
|
+
<% end %>
|
23
|
+
</tr>
|
24
|
+
<% end %>
|
25
|
+
</tbody>
|
26
|
+
</table>
|
27
|
+
</div>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<div class="simple-calendar">
|
2
|
+
<%= link_to "Previous", start_date: date_range.first - 1.day %>
|
3
|
+
<%= I18n.t("date.month_names")[start_date.month] %> <%= start_date.year %>
|
4
|
+
<%= link_to "Next", start_date: date_range.last + 1.day %>
|
5
|
+
|
6
|
+
<table class="table table-striped">
|
7
|
+
<thead>
|
8
|
+
<tr>
|
9
|
+
<% date_range.slice(0, 7).each do |day| %>
|
10
|
+
<th><%= I18n.t("date.abbr_day_names")[day.wday] %></th>
|
11
|
+
<% end %>
|
12
|
+
</tr>
|
13
|
+
</thead>
|
14
|
+
|
15
|
+
<tbody>
|
16
|
+
<% date_range.each_slice(7) do |week| %>
|
17
|
+
<tr>
|
18
|
+
<% week.each do |day| %>
|
19
|
+
<%= content_tag :td, class: calendar.td_classes_for(day) do %>
|
20
|
+
<%= block.call day, sorted_events.fetch(day, []) %>
|
21
|
+
<% end %>
|
22
|
+
<% end %>
|
23
|
+
</tr>
|
24
|
+
<% end %>
|
25
|
+
</tbody>
|
26
|
+
</table>
|
27
|
+
</div>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<div class="simple-calendar">
|
2
|
+
<%= link_to "Previous", start_date: date_range.first - 1.day %>
|
3
|
+
Week <%= start_date.strftime("%U").to_i %>
|
4
|
+
<%= link_to "Next", start_date: date_range.last + 1.day %>
|
5
|
+
|
6
|
+
<table class="table table-striped">
|
7
|
+
<thead>
|
8
|
+
<tr>
|
9
|
+
<% date_range.slice(0, 7).each do |day| %>
|
10
|
+
<th><%= I18n.t("date.abbr_day_names")[day.wday] %></th>
|
11
|
+
<% end %>
|
12
|
+
</tr>
|
13
|
+
</thead>
|
14
|
+
|
15
|
+
<tbody>
|
16
|
+
<% date_range.each_slice(7) do |week| %>
|
17
|
+
<tr>
|
18
|
+
<% week.each do |day| %>
|
19
|
+
<%= content_tag :td, class: calendar.td_classes_for(day) do %>
|
20
|
+
<%= block.call day, sorted_events.fetch(day, []) %>
|
21
|
+
<% end %>
|
22
|
+
<% end %>
|
23
|
+
</tr>
|
24
|
+
<% end %>
|
25
|
+
</tbody>
|
26
|
+
</table>
|
27
|
+
</div>
|
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module SimpleCalendar
|
4
|
+
module Generators
|
5
|
+
class ViewsGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path("../../../..", __FILE__)
|
7
|
+
|
8
|
+
def copy_views
|
9
|
+
directory 'app/views/simple_calendar', 'app/views/simple_calendar'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/simple_calendar.rb
CHANGED
@@ -1,17 +1,7 @@
|
|
1
1
|
require "simple_calendar/calendar"
|
2
2
|
require "simple_calendar/month_calendar"
|
3
3
|
require "simple_calendar/week_calendar"
|
4
|
-
require "simple_calendar/model_additions"
|
5
4
|
require "simple_calendar/railtie"
|
6
5
|
require "simple_calendar/version"
|
7
6
|
require "simple_calendar/view_helpers"
|
8
7
|
|
9
|
-
module SimpleCalendar
|
10
|
-
def self.extended(model_class)
|
11
|
-
return if model_class.respond_to? :has_calendar
|
12
|
-
|
13
|
-
model_class.class_eval do
|
14
|
-
extend ModelAdditions
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,147 +1,83 @@
|
|
1
|
+
require 'rails'
|
2
|
+
|
1
3
|
module SimpleCalendar
|
2
4
|
class Calendar
|
3
|
-
|
4
|
-
|
5
|
-
attr_reader :block, :events, :options, :view_context
|
5
|
+
attr_accessor :view_context, :options
|
6
6
|
|
7
7
|
def initialize(view_context, opts={})
|
8
8
|
@view_context = view_context
|
9
|
-
@
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
9
|
+
@options = opts
|
10
|
+
end
|
11
|
+
|
12
|
+
def render(&block)
|
13
|
+
view_context.render(
|
14
|
+
partial: partial_name,
|
15
|
+
locals: {
|
16
|
+
block: block,
|
17
|
+
calendar: self,
|
18
|
+
date_range: date_range,
|
19
|
+
start_date: start_date,
|
20
|
+
sorted_events: sorted_events
|
21
|
+
}
|
19
22
|
)
|
20
|
-
|
21
|
-
@options = opts
|
22
23
|
end
|
23
24
|
|
24
|
-
def
|
25
|
-
|
25
|
+
def td_classes_for(day)
|
26
|
+
today = Time.zone.now.to_date
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
td_class = ["day"]
|
29
|
+
td_class << "wday-#{day.wday.to_s}"
|
30
|
+
td_class << "today" if today == day
|
31
|
+
td_class << "past" if today > day
|
32
|
+
td_class << "future" if today < day
|
33
|
+
td_class << 'start-date' if day.to_date == start_date.to_date
|
34
|
+
td_class << "prev-month" if start_date.month != day.month && day < start_date
|
35
|
+
td_class << "next-month" if start_date.month != day.month && day > start_date
|
36
|
+
td_class << "current-month" if start_date.month == day.month
|
37
|
+
td_class << "has-events" if sorted_events.fetch(day, []).any?
|
32
38
|
|
33
|
-
|
34
|
-
capture do
|
35
|
-
content_tag :header, get_option(:header) do
|
36
|
-
concat get_option(:previous_link, param_name, date_range)
|
37
|
-
concat get_option(:title, start_date)
|
38
|
-
concat get_option(:next_link, param_name, date_range)
|
39
|
-
end
|
40
|
-
end
|
39
|
+
td_class
|
41
40
|
end
|
42
41
|
|
43
|
-
|
44
|
-
content_tag :table, get_option(:table) do
|
45
|
-
capture do
|
46
|
-
concat get_option(:thead, date_range.to_a.slice(0, 7))
|
47
|
-
concat content_tag(:tbody, render_weeks, get_option(:tbody))
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def render_weeks
|
53
|
-
capture do
|
54
|
-
date_range.each_slice(7) do |week|
|
55
|
-
concat content_tag(:tr, render_week(week), get_option(:tr, week))
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
42
|
+
private
|
59
43
|
|
60
|
-
|
61
|
-
|
62
|
-
content_tag :td, get_option(:td, start_date, day) do
|
63
|
-
block.call(day, events_for_date(day))
|
64
|
-
end
|
44
|
+
def partial_name
|
45
|
+
self.class.name.underscore
|
65
46
|
end
|
66
|
-
safe_join results
|
67
|
-
end
|
68
|
-
|
69
|
-
def param_name
|
70
|
-
@param_name ||= options.fetch(:param_name, :start_date)
|
71
|
-
end
|
72
47
|
|
73
|
-
|
74
|
-
|
75
|
-
events.select do |e|
|
76
|
-
current_date == e.send(:simple_calendar_start_time).in_time_zone(@timezone).to_date
|
77
|
-
end.sort_by(&:simple_calendar_start_time)
|
78
|
-
else
|
79
|
-
events
|
48
|
+
def attribute
|
49
|
+
options.fetch(:attribute, :start_time).to_sym
|
80
50
|
end
|
81
|
-
end
|
82
51
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
def default_thead
|
96
|
-
->(dates) {
|
97
|
-
content_tag(:thead) do
|
98
|
-
content_tag(:tr) do
|
99
|
-
capture do
|
100
|
-
dates.each do |date|
|
101
|
-
concat content_tag(:th, I18n.t(options.fetch(:day_names, "date.abbr_day_names"))[date.wday])
|
102
|
-
end
|
103
|
-
end
|
52
|
+
def sorted_events
|
53
|
+
events = options.fetch(:events, [])
|
54
|
+
sorted = {}
|
55
|
+
|
56
|
+
events.each do |event|
|
57
|
+
start_time = event.send(attribute)
|
58
|
+
if start_time.present?
|
59
|
+
date = start_time.to_date
|
60
|
+
sorted[date] ||= []
|
61
|
+
sorted[date] << event
|
62
|
+
sorted[date] = sorted[date].sort_by(&attribute)
|
104
63
|
end
|
105
64
|
end
|
106
|
-
}
|
107
|
-
end
|
108
65
|
|
109
|
-
|
110
|
-
@start_date ||= (get_option(:start_date) || params[param_name] || Time.zone.now).to_date
|
111
|
-
end
|
66
|
+
# TODO: move sorting by start_time to after the event loop
|
112
67
|
|
113
|
-
|
114
|
-
|
115
|
-
number_of_days = options.fetch(:number_of_days, 4) - 1
|
116
|
-
start_date..(start_date + number_of_days.days)
|
117
|
-
end
|
118
|
-
end
|
68
|
+
sorted
|
69
|
+
end
|
119
70
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
td_class = ["day"]
|
124
|
-
td_class << "today" if today == current_calendar_date
|
125
|
-
td_class << "past" if today > current_calendar_date
|
126
|
-
td_class << "future" if today < current_calendar_date
|
127
|
-
td_class << "prev-month" if start_date.month != current_calendar_date.month && current_calendar_date < start_date
|
128
|
-
td_class << "next-month" if start_date.month != current_calendar_date.month && current_calendar_date > start_date
|
129
|
-
td_class << "current-month" if start_date.month == current_calendar_date.month
|
130
|
-
td_class << "wday-#{current_calendar_date.wday.to_s}"
|
131
|
-
td_class << "has-events" if events_for_date(current_calendar_date).any?
|
132
|
-
|
133
|
-
{ class: td_class.join(" ") }
|
134
|
-
}
|
135
|
-
end
|
71
|
+
def start_date
|
72
|
+
view_context.params.fetch(:start_date, Date.today).to_date
|
73
|
+
end
|
136
74
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
option.respond_to?(:call) ? option.call(*params) : option
|
75
|
+
def date_range
|
76
|
+
(start_date..(start_date + additional_days.days)).to_a
|
77
|
+
end
|
78
|
+
|
79
|
+
def additional_days
|
80
|
+
options.fetch(:number_of_days, 4) - 1
|
144
81
|
end
|
145
|
-
end
|
146
82
|
end
|
147
83
|
end
|
@@ -1,16 +1,10 @@
|
|
1
1
|
module SimpleCalendar
|
2
|
-
class MonthCalendar < Calendar
|
3
|
-
|
4
|
-
@date_range ||= start_date.beginning_of_month.beginning_of_week..start_date.end_of_month.end_of_week
|
5
|
-
end
|
2
|
+
class MonthCalendar < SimpleCalendar::Calendar
|
3
|
+
private
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def month_name(start_date)
|
12
|
-
"#{I18n.t("date.month_names")[start_date.month]} #{start_date.year}"
|
13
|
-
end
|
5
|
+
def date_range
|
6
|
+
(start_date.beginning_of_month.beginning_of_week..start_date.end_of_month.end_of_week).to_a
|
7
|
+
end
|
14
8
|
end
|
15
9
|
end
|
16
10
|
|
@@ -2,17 +2,17 @@ module SimpleCalendar
|
|
2
2
|
module ViewHelpers
|
3
3
|
def calendar(options={}, &block)
|
4
4
|
raise 'calendar requires a block' unless block_given?
|
5
|
-
SimpleCalendar::Calendar.new(self, options).render(block)
|
5
|
+
SimpleCalendar::Calendar.new(self, options).render(&block)
|
6
6
|
end
|
7
7
|
|
8
8
|
def month_calendar(options={}, &block)
|
9
9
|
raise 'month_calendar requires a block' unless block_given?
|
10
|
-
SimpleCalendar::MonthCalendar.new(self, options).render(block)
|
10
|
+
SimpleCalendar::MonthCalendar.new(self, options).render(&block)
|
11
11
|
end
|
12
12
|
|
13
13
|
def week_calendar(options={}, &block)
|
14
14
|
raise 'week_calendar requires a block' unless block_given?
|
15
|
-
SimpleCalendar::WeekCalendar.new(self, options).render(block)
|
15
|
+
SimpleCalendar::WeekCalendar.new(self, options).render(&block)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -1,44 +1,16 @@
|
|
1
1
|
module SimpleCalendar
|
2
|
-
class WeekCalendar < Calendar
|
3
|
-
|
4
|
-
@date_range ||= begin
|
5
|
-
number_of_weeks = options.fetch(:number_of_weeks, 1)
|
6
|
-
number_of_days = (number_of_weeks * 7) - 1
|
7
|
-
starting_day = start_date.beginning_of_week.to_date
|
8
|
-
ending_day = starting_day + number_of_days.days
|
9
|
-
starting_day..ending_day
|
10
|
-
end
|
11
|
-
end
|
2
|
+
class WeekCalendar < SimpleCalendar::Calendar
|
3
|
+
private
|
12
4
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
month_name(start_date) .
|
17
|
-
concat year_number(start_date)
|
18
|
-
else
|
19
|
-
month_name(start_date) .
|
20
|
-
concat " into " .
|
21
|
-
concat month_name(@date_range.last) .
|
22
|
-
concat year_number @date_range.last
|
23
|
-
end
|
24
|
-
}
|
25
|
-
end
|
5
|
+
def date_range
|
6
|
+
starting = start_date.beginning_of_week
|
7
|
+
ending = (starting + (number_of_weeks - 1).weeks).end_of_week
|
26
8
|
|
27
|
-
|
28
|
-
|
29
|
-
end
|
9
|
+
(starting..ending).to_a
|
10
|
+
end
|
30
11
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
def month_name(date)
|
36
|
-
"#{I18n.t("date.month_names")[date.month]}"
|
37
|
-
end
|
38
|
-
|
39
|
-
def year_number(date)
|
40
|
-
" #{date.year}"
|
41
|
-
end
|
12
|
+
def number_of_weeks
|
13
|
+
options.fetch(:number_of_weeks, 1)
|
14
|
+
end
|
42
15
|
end
|
43
16
|
end
|
44
|
-
|
data/simple_calendar.gemspec
CHANGED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'simple_calendar/calendar'
|
3
|
+
|
4
|
+
class ViewContext
|
5
|
+
attr_accessor :start_date
|
6
|
+
|
7
|
+
def initialize(start_date=nil)
|
8
|
+
@start_date = start_date
|
9
|
+
end
|
10
|
+
|
11
|
+
def params
|
12
|
+
if @start_date.present?
|
13
|
+
{start_date: @start_date}
|
14
|
+
else
|
15
|
+
{}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe SimpleCalendar::Calendar do
|
21
|
+
let(:calendar) { SimpleCalendar::Calendar.new(nil) }
|
22
|
+
|
23
|
+
it 'renders a partial with the same name as the class' do
|
24
|
+
expect(calendar.send(:partial_name)).to eq("simple_calendar/calendar")
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'event sorting attribute' do
|
28
|
+
it 'has start_time as the default attribute' do
|
29
|
+
expect(calendar.send(:attribute)).to eq(:start_time)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'allows you to override the default attribute' do
|
33
|
+
expect(SimpleCalendar::Calendar.new(nil, attribute: :starts_at).send(:attribute)).to eq(:starts_at)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#sorted_events" do
|
38
|
+
it 'converts an array of events to a hash sorted by days'
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#start_date" do
|
42
|
+
it "defaults to today's date" do
|
43
|
+
view_context = ViewContext.new()
|
44
|
+
calendar = SimpleCalendar::Calendar.new(view_context)
|
45
|
+
expect(calendar.send(:start_date)).to eq(Date.today)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "uses the params start_date to override" do
|
49
|
+
view_context = ViewContext.new(Date.yesterday)
|
50
|
+
calendar = SimpleCalendar::Calendar.new(view_context)
|
51
|
+
expect(calendar.send(:start_date)).to eq(Date.yesterday)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'has a param that determines the start date of the calendar'
|
56
|
+
it 'generates a default date if no start date is present'
|
57
|
+
it 'has a range of dates'
|
58
|
+
|
59
|
+
it 'can split the range of dates into weeks'
|
60
|
+
it 'has a title'
|
61
|
+
it 'has a next view link'
|
62
|
+
it 'has a previous view link'
|
63
|
+
|
64
|
+
it 'accepts an array of events'
|
65
|
+
it 'sorts the events'
|
66
|
+
it 'yields the events for each day'
|
67
|
+
|
68
|
+
it "doesn't crash when an event has a nil start_time"
|
69
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
4
|
+
# this file to always be loaded, without a need to explicitly require it in any
|
5
|
+
# files.
|
6
|
+
#
|
7
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
8
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
9
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
10
|
+
# individual file that may not need all of that loaded. Instead, consider making
|
11
|
+
# a separate helper file that requires the additional dependencies and performs
|
12
|
+
# the additional setup, and require it from the spec files that actually need
|
13
|
+
# it.
|
14
|
+
#
|
15
|
+
# The `.rspec` file also contains a few flags that are not defaults but that
|
16
|
+
# users commonly want.
|
17
|
+
#
|
18
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
19
|
+
RSpec.configure do |config|
|
20
|
+
# rspec-expectations config goes here. You can use an alternate
|
21
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
22
|
+
# assertions if you prefer.
|
23
|
+
config.expect_with :rspec do |expectations|
|
24
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
25
|
+
# and `failure_message` of custom matchers include text for helper methods
|
26
|
+
# defined using `chain`, e.g.:
|
27
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
28
|
+
# # => "be bigger than 2 and smaller than 4"
|
29
|
+
# ...rather than:
|
30
|
+
# # => "be bigger than 2"
|
31
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
32
|
+
end
|
33
|
+
|
34
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
35
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
36
|
+
config.mock_with :rspec do |mocks|
|
37
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
38
|
+
# a real object. This is generally recommended, and will default to
|
39
|
+
# `true` in RSpec 4.
|
40
|
+
mocks.verify_partial_doubles = true
|
41
|
+
end
|
42
|
+
|
43
|
+
# The settings below are suggested to provide a good initial experience
|
44
|
+
# with RSpec, but feel free to customize to your heart's content.
|
45
|
+
=begin
|
46
|
+
# These two settings work together to allow you to limit a spec run
|
47
|
+
# to individual examples or groups you care about by tagging them with
|
48
|
+
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
49
|
+
# get run.
|
50
|
+
config.filter_run :focus
|
51
|
+
config.run_all_when_everything_filtered = true
|
52
|
+
|
53
|
+
# Allows RSpec to persist some state between runs in order to support
|
54
|
+
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
55
|
+
# you configure your source control system to ignore this file.
|
56
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
57
|
+
|
58
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
59
|
+
# recommended. For more details, see:
|
60
|
+
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
61
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
62
|
+
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
|
63
|
+
config.disable_monkey_patching!
|
64
|
+
|
65
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
66
|
+
# be too noisy due to issues in dependencies.
|
67
|
+
config.warnings = true
|
68
|
+
|
69
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
70
|
+
# file, and it's useful to allow more verbose output when running an
|
71
|
+
# individual spec file.
|
72
|
+
if config.files_to_run.one?
|
73
|
+
# Use the documentation formatter for detailed output,
|
74
|
+
# unless a formatter has already been configured
|
75
|
+
# (e.g. via a command-line flag).
|
76
|
+
config.default_formatter = 'doc'
|
77
|
+
end
|
78
|
+
|
79
|
+
# Print the 10 slowest examples and example groups at the
|
80
|
+
# end of the spec run, to help surface which specs are running
|
81
|
+
# particularly slow.
|
82
|
+
config.profile_examples = 10
|
83
|
+
|
84
|
+
# Run specs in random order to surface order dependencies. If you find an
|
85
|
+
# order dependency and want to debug it, you can fix the order by providing
|
86
|
+
# the seed, which is printed after each run.
|
87
|
+
# --seed 1234
|
88
|
+
config.order = :random
|
89
|
+
|
90
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
91
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
92
|
+
# test failures related to randomization by passing the same `--seed` value
|
93
|
+
# as the one that triggered the failure.
|
94
|
+
Kernel.srand config.seed
|
95
|
+
=end
|
96
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_calendar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Oliver
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -24,27 +24,54 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
description: A simple Rails 3 and Rails 4 calendar
|
28
42
|
email:
|
29
43
|
- excid3@gmail.com
|
30
|
-
executables:
|
44
|
+
executables:
|
45
|
+
- console
|
46
|
+
- setup
|
31
47
|
extensions: []
|
32
48
|
extra_rdoc_files: []
|
33
49
|
files:
|
34
50
|
- ".gitignore"
|
35
51
|
- ".rspec"
|
52
|
+
- ".travis.yml"
|
36
53
|
- Gemfile
|
37
54
|
- README.md
|
38
55
|
- Rakefile
|
56
|
+
- app/views/simple_calendar/_calendar.html.erb
|
57
|
+
- app/views/simple_calendar/_month_calendar.html.erb
|
58
|
+
- app/views/simple_calendar/_week_calendar.html.erb
|
59
|
+
- bin/console
|
60
|
+
- bin/setup
|
61
|
+
- lib/generators/simple_calendar/views_generator.rb
|
39
62
|
- lib/simple_calendar.rb
|
40
63
|
- lib/simple_calendar/calendar.rb
|
41
|
-
- lib/simple_calendar/model_additions.rb
|
42
64
|
- lib/simple_calendar/month_calendar.rb
|
43
65
|
- lib/simple_calendar/railtie.rb
|
44
66
|
- lib/simple_calendar/version.rb
|
45
67
|
- lib/simple_calendar/view_helpers.rb
|
46
68
|
- lib/simple_calendar/week_calendar.rb
|
47
69
|
- simple_calendar.gemspec
|
70
|
+
- spec/calendar_spec.rb
|
71
|
+
- spec/calendars/month_calendar_spec.rb
|
72
|
+
- spec/simple_calendar_spec.rb
|
73
|
+
- spec/spec_helper.rb
|
74
|
+
- spec/views_generators_spec.rb
|
48
75
|
homepage: https://github.com/excid3/simple_calendar
|
49
76
|
licenses: []
|
50
77
|
metadata: {}
|
@@ -64,8 +91,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
91
|
version: '0'
|
65
92
|
requirements: []
|
66
93
|
rubyforge_project: simple_calendar
|
67
|
-
rubygems_version: 2.
|
94
|
+
rubygems_version: 2.4.5.1
|
68
95
|
signing_key:
|
69
96
|
specification_version: 4
|
70
97
|
summary: A simple Rails 3 and Rails 4 calendar
|
71
|
-
test_files:
|
98
|
+
test_files:
|
99
|
+
- spec/calendar_spec.rb
|
100
|
+
- spec/calendars/month_calendar_spec.rb
|
101
|
+
- spec/simple_calendar_spec.rb
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
- spec/views_generators_spec.rb
|
@@ -1,16 +0,0 @@
|
|
1
|
-
module SimpleCalendar
|
2
|
-
module ModelAdditions
|
3
|
-
def has_calendar(options={})
|
4
|
-
config = { :attribute => :starts_at }
|
5
|
-
|
6
|
-
# Override default config
|
7
|
-
config.update(options) if options.is_a?(Hash)
|
8
|
-
|
9
|
-
class_eval <<-EOV
|
10
|
-
def simple_calendar_start_time
|
11
|
-
#{config[:attribute]}
|
12
|
-
end
|
13
|
-
EOV
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|