As a training company we have courses. Each course has its own schedule.
Example :
- Course duration : 4 days
- Schedule :
- day 1 : 9am - 12am, 2pm - 6pm
- day 2 : 9am - 12am, 2pm - 6pm
- day 3 : 9am - 12am, 2pm - 6pm
- day 4 : 9am - 12am, 2pm - 5pm
We want to be able to manage each days schedule based on the course duration.
Note : we'd like to shorten that code, but materialize expects specific wrappers and classes that we could avoid.
Please feel free to enhance or comment the following code.
app/inputs/schedule_input.rb
# Custom input for schedules as multi-dimensional arrays
# Posting params will like the following :
#
# > "schedule"=>{"0"=>["09:30", "19:00"], "1"=>["09:30", "17:40"], "2"=>… }
#
# Unfortunately multi-dimensional arrays cannot be built from forms.
#
# That's the reason why you'll have to convert schedule params as a 2d array
# in your controller.
# Here is an example :
# ```
# if params[:course][:schedule]
# params[:course][:schedule] = params[:course][:schedule].values
# end
# ```
# Because strong parameters won't accept multi-dimensional array, you'll have
# to manage it outside of strong parameters.
# ```
# schedule_format = params[:course][:schedule].try(:keys).inject({}) { |acc, k| acc[k] = []; acc }
# params.require(:course).permit(…, schedule: schedule_format)
# ```
#
class ScheduleInput < SimpleForm::Inputs::StringInput
# 2 display modes can be set depending on 'entry_type' option
# Default is full (4 text fields / day)
# You can otherwise use 'dual' as for 2 text fields (starting time, ending time / day)
def input(wrapper_options = nil)
input_html_options[:type] ||= input_type
classes = input_html_options.delete(:class)
values = object.public_send(attribute_name)
if options[:entry_type]
dual_type = 'dual' == options[:entry_type].to_s
else
dual_type = 2 == (values || []).first.try(:size).to_i
end
elements = values.blank? ? [[nil]] : values
hide_day_label = elements.one?
elements.each_with_index.map do |elem, index|
if dual_type
schedule_dual_entry(elem, index + 1, classes, hide_day_label)
else
schedule_full_entry(elem, index + 1, classes, hide_day_label)
end
end.join.html_safe
end
# Builds a single schedule entry (ie one day schedule)
def schedule_full_entry(elem, counter, classes, hide_day_label)
day_label = unless hide_day_label
template.content_tag(:span, class: 'day-label') {
I18n.t("simple_form.labels.schedule.day", num: counter)
}
end
template.content_tag(:div, class: 'day') do
day_label.to_s.html_safe +
[:am, :pm].each_with_index.map do |wkey, windex|
template.content_tag(:div, class: 'day-part') do
template.content_tag(:span, class: 'when-label') {
I18n.t("simple_form.labels.schedule.#{wkey}")
} + single_entry(elem, classes, counter, windex.zero? ? 0 : 2)
end
end.join.html_safe
end
end
def schedule_dual_entry(elem, counter, classes, hide_day_label)
day_label = unless hide_day_label
template.content_tag(:span, class: 'day-label') {
I18n.t("simple_form.labels.schedule.day", num: counter)
}
end
template.content_tag(:div, class: 'day') do
day_label.to_s.html_safe +
single_entry(elem, classes, counter)
end
end
# Display a single entry with two text fields : 'from', 'to'
def single_entry(elem, classes, counter, position = 0)
[:from, :to].each_with_index.map do |key, index|
template.content_tag(:div, class: classes) do
name = "#{object_name}[#{attribute_name}][#{counter}][]"
value = elem[index + position]
# Use default values when needed
if value.blank? && options[:defaults] && object.new_record?
value = (options[:defaults] || []).flatten[index + position]
end
component = @builder.label(name, I18n.t("simple_form.labels.schedule.#{key}")) +
@builder.text_field(nil, input_html_options.merge(value: value, name: name))
component
end
end.join.html_safe
end
def input_type
:time
end
end
app/assets/stylesheets/…/components/schedule-input.scss
.course_schedule {
margin-bottom: 15px;
.day {
@extend .valign-wrapper;
&:nth-child(odd) {
background-color: color('grey', 'lighten-5');
}
}
.day-part, .time {
display: inline-block;
}
.day-label, .day-part, .when-label, .time {
padding: 0 0.75rem;
}
.time {
position: relative;
}
@media #{$small-and-down} {
.day, .day-part, .day-label {
display: block;
}
.day-label {
text-align: center;
padding: 10px;
font-weight: bold;
}
.when-label {
display: inline-block;
min-width: 90px;
}
}
}
config/simple_form.fr.yml
fr:
simple_form:
labels:
schedule:
am: matin
day: "Jour %{num}"
from: de
pm: après-midi
to: à