simple_calendar 1.1.10 → 2.0.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.
- 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
|
+

|
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
|