Calendar

Simple UTC based calendar with events

June 2020

Sunday Monday Tuesday Wednesday Thursday Friday Saturday
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
12 AM
test Edit...
12 AM
çlkçjçij Edit...
30
1
2
3
4

Code (176 LOC)

JavaScript (12 LOC)
app/javascript/controllers/button_group_controller.js (12 LOC)
import ApplicationController from './application_controller'
export default class extends ApplicationController {
static targets = ['input']
select (event) {
this.buttons.forEach(el => el.classList.remove('active'))
event.target.classList.add('active')
this.inputTarget.value = event.target.innerText
}
get buttons () {
return this.element.querySelectorAll('.btn')
}
}
ERB (96 LOC)
app/views/calendars/_demo.html.erb (56 LOC)
<div class="d-flex justify-content-between align-items-center">
<h2><%= @start_date.strftime("%B %Y") %></h2>
<div class="text-success" hidden data-activity-indicator>
<div class="spinner-border spinner-border-sm"></div>
<span>Loading...</span>
</div>
<div>
<div class="btn-group btn-group-toggle" data-toggle="buttons">
<%= link_to calendar_path(@start_date.advance(months: -1).iso8601), title: "Previous", class: "btn btn-light" do %>
<i class="fas fa-chevron-left"></i>
<% end %>
<%= link_to calendar_path(@start_date.advance(months: 1).iso8601), title: "Next", class: "btn btn-light" do %>
<i class="fas fa-chevron-right"></i>
<% end %>
</div>
<%= link_to "Today", calendar_path(Date.current.beginning_of_month.iso8601), class: "btn btn-light" %>
</div>
</div>
<table id="calendar-<%= @start_date.iso8601 %>" class="table table-bordered mt-2">
<thead class="thead-dark">
<tr class="text-center">
<% Date::DAYNAMES.each do |name| %>
<th style="width:14%" ><%= name %></th>
<% end %>
</tr>
</thead>
<tbody>
<% @dates.each_slice(7) do |week| %>
<tr>
<% week.each do |date| %>
<%= tag.td class: css("", "bg-warning-lighter": date.today?, "bg-secondary-lightest": @start_date.month != date.month),
data: { reflex: "click->CalendarReflex#new_calendar_event", date: date.iso8601 } do %>
<%= tag.span date.day, class: "lead" %>
<div class="mt-2" style="height:65px; overflow-y:scroll">
<% (@calendar_events[date] || []).each do |calendar_event| %>
<div class="bg-white py-1 px-2 border mb-1" data-controller="application" data-action="click->application#default_reflex">
<%= link_to "#", class: "float-right text-danger", data: { reflex: "click->CalendarReflex#destroy_calendar_event", id: calendar_event.id} do %>
<i class="fas fa-window-close"></i>
<% end %>
<small class="d-block text-secondary">
<%= calendar_event.occurs_at.strftime "%l %p" %>
</small>
<div>
<%= truncate calendar_event.description, length: 24 %>
<small><%= link_to "Edit...", "#", class: "ml-2", data: { reflex: "click->CalendarReflex#edit_calendar_event", id: calendar_event.id } %></small>
</div>
</div>
<% end %>
</div>
<% end %>
<% end %>
</tr>
<% end %>
</tbody>
</table>
<%= render "/calendars/calendar_event_modal" if @calendar_event %>
app/views/calendars/_calendar_event_modal.html.erb (40 LOC)
<div class="modal" tabindex="-1" style="display:block">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><%= @calendar_event.occurs_at.strftime "%A %B, %e %Y" %></h5>
<button type="button" class="close" data-reflex="click->CalendarReflex#default_reflex">
<span>&times;</span>
</button>
</div>
<%= form_with model: @calendar_event, html: { data: { controller: "application", action: "ajax:success->application#reload" } } do |f| %>
<%= f.hidden_field :occurs_at %>
<div class="modal-body">
<div class="btn-group mt-3" data-controller="button-group" data-reflex-permanent>
<%= f.hidden_field :hour, data: { target: "button-group.input" } %>
<% (1..12).each do |i| %>
<%= tag.button i, type: "button",
class: css("btn btn-light", "active": @calendar_event.occurs_at.strftime("%I").to_i == i),
data: { action: "click->button-group#select" } %>
<% end %>
</div>
<div class="btn-group mt-2" data-controller="button-group" data-reflex-permanent>
<%= f.hidden_field :meridian, data: { target: "button-group.input" } %>
<%= tag.button "AM", type: "button",
class: css("btn btn-light", "active": @calendar_event.occurs_at.strftime("%p") == "AM"),
data: { action: "click->button-group#select" } %>
<%= tag.button "PM", type: "button",
class: css("btn btn-light", "active": @calendar_event.occurs_at.strftime("%p") == "PM"),
data: { action: "click->button-group#select" } %>
</div>
<div class="form-group my-3">
<%= f.text_area :description, placeholder: "Description for this event...", class: "form-control", rows: 3,
data: { reflex: "input->CalendarReflex#validate_calendar_event", id: @calendar_event.id, date: @calendar_event.occurs_at.to_date.iso8601 } %>
</div>
<%= f.submit "Save", class: "btn btn-primary", disabled: @calendar_event.invalid? %>
<button type="button" class="btn btn-link" data-reflex="click->CalendarReflex#default_reflex">Cancel</button>
</div>
<% end %>
</div>
</div>
</div>
Ruby (68 LOC)
app/controllers/calendars_controller.rb (11 LOC)
class CalendarsController < ApplicationController
def show
@start_date ||= Date.parse(params[:date])
@start_date = @start_date.to_date.beginning_of_month
date_range = (@start_date..@start_date.end_of_month)
@dates = date_range.to_a
@dates.prepend(@dates.first - 1) until @dates.first.sunday?
@dates.append(@dates.last + 1) until @dates.last.saturday?
@calendar_events = CalendarEvent.where(occurs_at: date_range).order(:occurs_at).group_by(&:occurs_at_date)
end
end
app/controllers/calendar_events_controller.rb (26 LOC)
class CalendarEventsController < ApplicationController
after_action :cleanup
def create
calendar_event = CalendarEvent.new(calendar_event_params)
calendar_event.assign_hour hour_params
calendar_event.save
redirect_to calendar_path(calendar_event.occurs_at.beginning_of_month.iso8601)
end
def update
calendar_event = CalendarEvent.find(params[:id])
calendar_event.assign_attributes calendar_event_params
calendar_event.assign_hour hour_params
calendar_event.save
redirect_to calendar_path(calendar_event.occurs_at.beginning_of_month.iso8601)
end
private
def calendar_event_params
params.require(:calendar_event).permit(:occurs_at, :description).merge(session_id: session.id)
end
def hour_params
params.require(:calendar_event).permit(:hour, :meridian)
end
def cleanup
CalendarEvent.old.delete_all
end
end
app/models/calendar_event.rb (14 LOC)
class CalendarEvent < ApplicationRecord
validates :description, presence: true
validates :description, length: {maximum: 240}
validates :occurs_at, presence: true
scope :old, -> { where arel_table[:created_at].lt(1.month.ago) }
def occurs_at_date
occurs_at.to_date
end
def assign_hour(params)
hour = params[:hour].to_i
hour += 12 if params[:meridian] == "PM"
self.occurs_at = occurs_at.change(hour: hour)
end
end
app/reflexes/calendar_reflex.rb (17 LOC)
class CalendarReflex < ApplicationReflex
def new_calendar_event
@calendar_event = CalendarEvent.new(session_id: session.id, occurs_at: Date.parse(element.dataset["date"]))
session[:calendar_event_attributes] = @calendar_event.attributes
end
def edit_calendar_event
@calendar_event = CalendarEvent.find(element.dataset[:id])
end
def destroy_calendar_event
CalendarEvent.find(element.dataset[:id]).destroy
end
def validate_calendar_event
@calendar_event = CalendarEvent.where(id: element.dataset[:id]).first_or_initialize(session_id: session.id, occurs_at: Date.parse(element.dataset["date"]))
@calendar_event.description = element[:value]
@calendar_event.validate
end
end