Skip to content

Commit

Permalink
add support for recurrence termination options based on changes by rf…
Browse files Browse the repository at this point in the history
…lorence (gregschmit#117)
  • Loading branch information
JimmyToor committed Aug 5, 2024
1 parent 340dd76 commit 648c41c
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 18 deletions.
5 changes: 5 additions & 0 deletions app/assets/javascripts/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ const defaultConfig = {
years: "year(s)",
day_of_month: "Day of month",
day_of_week: "Day of week",
ends: "Ends",
never: "Never",
after: "After",
occurrences: "occurrences",
on: "On",
cancel: "Cancel",
ok: "OK",
summary: "Summary",
Expand Down
1 change: 1 addition & 0 deletions app/assets/javascripts/recurring_select.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ document.addEventListener("DOMContentLoaded", () => {
recurring_select.call(e.target, "changed")
}
})

})

const methods = {
Expand Down
114 changes: 99 additions & 15 deletions app/assets/javascripts/recurring_select_dialog.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class RecurringSelectDialog {
this.daysChanged = this.daysChanged.bind(this);
this.dateOfMonthChanged = this.dateOfMonthChanged.bind(this);
this.weekOfMonthChanged = this.weekOfMonthChanged.bind(this);
this.terminationChanged = this.terminationChanged.bind(this);
this.recurring_selector = recurring_selector;
this.current_rule = this.recurring_selector.recurring_select('current_rule');
this.initDialogBox();
Expand All @@ -41,9 +42,12 @@ class RecurringSelectDialog {

this.mainEventInit();
this.freqInit();
this.terminationInit();
this.summaryInit();

trigger(this.outer_holder, "recurring_select:dialog_opened");
this.freq_select.focus();

}

cancel() {
Expand Down Expand Up @@ -134,9 +138,9 @@ class RecurringSelectDialog {
interval_input.value = this.current_rule.hash.interval
on(interval_input, "change keyup", this.intervalChanged)

if (!this.current_rule.hash.validations) { this.current_rule.hash.validations = {} };
if (!this.current_rule.hash.validations.day_of_month) { this.current_rule.hash.validations.day_of_month = [] };
if (!this.current_rule.hash.validations.day_of_week) { this.current_rule.hash.validations.day_of_week = {} };
if (!this.current_rule.hash.validations) { this.current_rule.hash.validations = {}; }
if (!this.current_rule.hash.validations.day_of_month) { this.current_rule.hash.validations.day_of_month = []; }
if (!this.current_rule.hash.validations.day_of_week) { this.current_rule.hash.validations.day_of_week = {}; }
this.init_calendar_days(section);
this.init_calendar_weeks(section);

Expand All @@ -156,6 +160,38 @@ class RecurringSelectDialog {
section.style.display = 'block'
}

terminationInit() {
const section = this.outer_holder.querySelector(".rs_termination_section");
this.until_date = section.querySelector("#rs_until_date");
this.until_date.flatpickr({
enableTime: true,
dateFormat: "Y-m-d H:i"
});

if (this.current_rule.hash && this.current_rule.hash.count) {
this.count_option = section.querySelector("input[name=rs_termination][value=count]");
this.count_option.checked = true;
this.occurence_count = section.querySelector("#rs_occurrence_count");
this.occurence_count.value = this.current_rule.hash.count;
} else if (this.current_rule.hash && this.current_rule.hash.until) {
this.until_option = section.querySelector("input[name=rs_termination][value=until]");
this.until_option.checked = true;
// IceCube::TimeUtil will serialize a TimeWithZone into a hash, such as:
// {time: Thu, 04 Sep 2014 06:59:59 +0000, zone: "Pacific Time (US & Canada)"}
// If we're initializing from an unsaved rule, until will be a string
if (this.current_rule.hash.until.time) {
this.until_val = new Date(this.current_rule.hash.until.time);
this.until_date.value = (this.until_val.getFullYear() + "-" + (this.until_val.getMonth() + 1) + "-" + this.until_val.getDate() + " " + this.until_val.getHours() + ":" + this.until_val.getMinutes());
} else {
this.until_date.value = this.current_rule.hash.until;
}
} else {
this.never_option = section.querySelector("input[name=rs_termination][value=never]");
this.never_option.checked = true;
}

section.addEventListener("change", this.terminationChanged.bind(this));
}

summaryInit() {
this.summary = this.outer_holder.querySelector(".rs_summary");
Expand Down Expand Up @@ -212,7 +248,7 @@ class RecurringSelectDialog {
if (Array.from(this.current_rule.hash.validations.day_of_month).includes(num)) {
day_link.classList.add("selected");
}
};
}

// add last day of month button
const end_of_month_link = document.createElement("a")
Expand Down Expand Up @@ -248,9 +284,9 @@ class RecurringSelectDialog {
day_link.setAttribute("day", day_of_week);
day_link.setAttribute("instance", num);
monthly_calendar.appendChild(day_link);
};
}
}
};
}

Object.entries(this.current_rule.hash.validations.day_of_week).forEach(([key, value]) => {
Array.from(value).forEach((instance, index) => {
Expand Down Expand Up @@ -278,7 +314,8 @@ class RecurringSelectDialog {
freqChanged() {
if (!isPlainObject(this.current_rule.hash)) { this.current_rule.hash = null; } // for custom values

if (!this.current_rule.hash) { this.current_rule.hash = {} };
if (!this.current_rule.hash) { this.current_rule.hash = {} }
this.current_rule.str = null;
this.current_rule.hash.interval = 1;
this.current_rule.hash.until = null;
this.current_rule.hash.count = null;
Expand All @@ -305,13 +342,13 @@ class RecurringSelectDialog {
this.current_rule.hash.rule_type = "IceCube::DailyRule";
this.current_rule.str = this.config.texts["daily"];
this.initDailyOptions();
};
}
this.summaryUpdate();
}

intervalChanged(event) {
this.current_rule.str = null;
if (!this.current_rule.hash) { this.current_rule.hash = {} };
if (!this.current_rule.hash) { this.current_rule.hash = {}; }
this.current_rule.hash.interval = parseInt(event.currentTarget.value);
if ((this.current_rule.hash.interval < 1) || isNaN(this.current_rule.hash.interval)) {
this.current_rule.hash.interval = 1;
Expand All @@ -322,7 +359,7 @@ class RecurringSelectDialog {
daysChanged(event) {
event.target.classList.toggle("selected");
this.current_rule.str = null;
if (!this.current_rule.hash) { this.current_rule.hash = {} };
if (!this.current_rule.hash) { this.current_rule.hash = {}; }
this.current_rule.hash.validations = {};
const raw_days = Array.from(this.content.querySelectorAll(".day_holder a.selected"))
.map(el => parseInt(el.dataset.value))
Expand All @@ -334,7 +371,7 @@ class RecurringSelectDialog {
dateOfMonthChanged(event) {
event.target.classList.toggle("selected");
this.current_rule.str = null;
if (!this.current_rule.hash) { this.current_rule.hash = {} };
if (!this.current_rule.hash) { this.current_rule.hash = {}; }
this.current_rule.hash.validations = {};
const raw_days = Array.from(this.content.querySelectorAll(".monthly_options .rs_calendar_day a.selected"))
.map(el => {
Expand All @@ -349,21 +386,44 @@ class RecurringSelectDialog {
weekOfMonthChanged(event) {
event.target.classList.toggle("selected");
this.current_rule.str = null;
if (!this.current_rule.hash) { this.current_rule.hash = {} };
if (!this.current_rule.hash) { this.current_rule.hash = {}; }
this.current_rule.hash.validations = {};
this.current_rule.hash.validations.day_of_month = [];
this.current_rule.hash.validations.day_of_week = {};
this.content.querySelectorAll(".monthly_options .rs_calendar_week a.selected")
.forEach((elm, index) => {
const day = parseInt(elm.getAttribute("day"));
const instance = parseInt(elm.getAttribute("instance"));
if (!this.current_rule.hash.validations.day_of_week[day]) { this.current_rule.hash.validations.day_of_week[day] = [] };
if (!this.current_rule.hash.validations.day_of_week[day]) { this.current_rule.hash.validations.day_of_week[day] = []; }
return this.current_rule.hash.validations.day_of_week[day].push(instance);
})
this.summaryUpdate();
return false;
}

terminationChanged() {
this.selected_termination_type = this.outer_holder.querySelector(".rs_termination_section input[type='radio']:checked");
if (!this.selected_termination_type) { return; }
this.current_rule.str = null;
if (!this.current_rule.hash) { this.current_rule.hash = {}; }
switch (this.selected_termination_type.value) {
case "count":
this.current_rule.hash.count = parseInt(this.occurence_count ? this.occurence_count.value : this.outer_holder.querySelector("#rs_occurrence_count").value);
if ((this.current_rule.hash.count < 1) || isNaN(this.current_rule.hash.count)) {
this.current_rule.hash.count = 1;
}
this.current_rule.hash.until = null;
break
case "until":
this.current_rule.hash.until = this.until_date ? this.until_date.value : this.outer_holder.querySelector("#rs_until_date").value;
this.current_rule.hash.count = null;
break
default:
this.current_rule.hash.count = null;
this.current_rule.hash.until = null;
}
this.summaryUpdate();
}
// ========================= Change callbacks ===============================

template() {
Expand All @@ -381,7 +441,6 @@ class RecurringSelectDialog {
<option value='Yearly'>${this.config.texts["yearly"]}</option> \
</select> \
</p> \
\
<div class='daily_options freq_option_section'> \
<p> \
${this.config.texts["every"]} \
Expand All @@ -400,7 +459,7 @@ class RecurringSelectDialog {
for (let i = this.config.texts["first_day_of_week"], day_of_week = i, end = 7 + this.config.texts["first_day_of_week"], asc = this.config.texts["first_day_of_week"] <= end; asc ? i < end : i > end; asc ? i++ : i--, day_of_week = i) {
day_of_week = day_of_week % 7;
str += `<a href='#' data-value='${day_of_week}'>${this.config.texts["days_first_letter"][day_of_week]}</a>`;
};
}

str += `\
</div> \
Expand All @@ -426,6 +485,31 @@ class RecurringSelectDialog {
${this.config.texts["years"]} \
</p> \
</div> \
<div class='rs_termination_section'>
<table>
<tr>
<td>
<label class='rs_termination_label'>${this.config.texts["ends"]}:</label>
</td>
<td>
<label>
<input type='radio' name='rs_termination' value='never' />
${this.config.texts["never"]}
</label><br>
<label>
<input type='radio' name='rs_termination' value='count' />
${this.config.texts["after"]}
<input type='text' data-wrapper-class='ui-recurring-select' id='rs_occurrence_count' class='rs_count' value='10' size='2' />
${this.config.texts["occurrences"]}
</label><br>
<label>
<input type='radio' name='rs_termination' value='until' />
${this.config.texts["on"]}
<input type='text' data-wrapper-class='ui-recurring-select' class='rs_datepicker' id='rs_until_date' />
</label>
</td>
</table>
</div>
<p class='rs_summary'> \
<span></span> \
</p> \
Expand Down
74 changes: 73 additions & 1 deletion app/assets/stylesheets/recurring_select.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@import "utilities.scss";

/* -------- resets ---------------*/

.rs_dialog_holder {
Expand Down Expand Up @@ -98,6 +97,41 @@ select {
}
}

// New styles for termination section
.rs_termination_section {
table {
margin: 0;
padding-top: 5px;
td {
padding: 0;
vertical-align: top;
}
}
.rs_termination_label {margin-right:10px;}
.rs_count {width:30px; text-align:center; display: inline-block;}
}

.rs_datepicker {
width: 80px;
text-align: center;
}

// New styles for date range inputs
.date_range {
margin-bottom: 10px;
display: flex;
align-items: center;

label {
margin-right: 5px;
}

input[type="text"] {
width: 150px;
padding: 5px;
margin-right: 10px;
}
}

.rs_summary {
padding: 0px;
Expand Down Expand Up @@ -129,3 +163,41 @@ select {

}
}

// Flatpickr styles
.flatpickr-calendar {
background: #fff;
border: 1px solid #e6e6e6;
box-shadow: 0 3px 13px rgba(0,0,0,0.08);
}

.flatpickr-day {
&.selected,
&.startRange,
&.endRange,
&.selected.inRange,
&.startRange.inRange,
&.endRange.inRange,
&.selected:focus,
&.startRange:focus,
&.endRange:focus,
&.selected:hover,
&.startRange:hover,
&.endRange:hover,
&.selected.prevMonthDay,
&.startRange.prevMonthDay,
&.endRange.prevMonthDay,
&.selected.nextMonthDay,
&.startRange.nextMonthDay,
&.endRange.nextMonthDay {
background: #89a;
border-color: #89a;
}
}

.flatpickr-time input:hover,
.flatpickr-time .flatpickr-am-pm:hover,
.flatpickr-time input:focus,
.flatpickr-time .flatpickr-am-pm:focus {
background: #eee;
}
18 changes: 18 additions & 0 deletions lib/recurring_select.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def self.filter_params(params)

params[:interval] = params[:interval].to_i if params[:interval]
params[:week_start] = params[:week_start].to_i if params[:week_start]
params[:count] = params[:count].to_i if params[:count]

params[:validations] ||= {}
params[:validations].symbolize_keys!
Expand Down Expand Up @@ -77,6 +78,23 @@ def self.filter_params(params)
params[:validations][:day_of_year] = params[:validations][:day_of_year].collect(&:to_i)
end

begin
# IceCube::TimeUtil will serialize a TimeWithZone into a hash, such as:
# {time: Thu, 04 Sep 2014 06:59:59 +0000, zone: "Pacific Time (US & Canada)"}
# So don't try to DateTime.parse the hash. IceCube::TimeUtil will deserialize this for us.
if (until_param = params[:until])
if until_param.is_a?(String)
# Set to 23:59:59 (in current TZ) to encompass all events on until day
params[:until] = Time.zone.parse(until_param).change(hour: 23, min: 59, sec: 59)
elsif until_param.is_a?(Hash) # ex: {time: Thu, 28 Aug 2014 06:59:590000, zone: "Pacific Time (US & Canada)"}
until_param = until_param.symbolize_keys
params[:until] = until_param[:time].in_time_zone(until_param[:zone])
end
end
rescue ArgumentError
# Invalid date given, attempt to assign :until will fail silently
end

params
end
end
2 changes: 1 addition & 1 deletion lib/recurring_select/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module RecurringSelect
VERSION = "4.0.0"
VERSION = "4.0.1"
end
1 change: 1 addition & 0 deletions recurring_select.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Gem::Specification.new do |s|
s.add_dependency "rails", ">= 6.1"
s.add_dependency "ice_cube", ">= 0.11"
s.add_dependency "sass-rails", ">= 6.0"
s.add_dependency "flatpickr", ">= 4.6.6"

s.add_development_dependency "bundler", ">= 1.3.5"
s.add_development_dependency "rspec-rails", ">= 2.14"
Expand Down
2 changes: 1 addition & 1 deletion spec/dummy/app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
//= require flatpickr/dist/flatpickr.js
//= require recurring_select
//= require_tree .

RecurringSelectDialog.config.options.monthly = {
show_week: [true, true, true, true, true, true]
}
Loading

0 comments on commit 648c41c

Please sign in to comment.