From 4115d06ef12af73103092eb013ed8d9faede8ef4 Mon Sep 17 00:00:00 2001
From: Jimmy
Date: Mon, 5 Aug 2024 14:33:46 -0700
Subject: [PATCH] add support for recurrence termination options based on
changes by rflorence
(https://github.com/gregschmit/recurring_select/pull/117)
---
app/assets/javascripts/defaults.js | 5 +
app/assets/javascripts/recurring_select.js | 1 +
.../recurring_select_dialog.js.erb | 126 +++++++++++++++---
app/assets/stylesheets/recurring_select.scss | 20 ++-
lib/recurring_select.rb | 18 +++
lib/recurring_select/version.rb | 2 +-
recurring_select.gemspec | 1 +
.../app/assets/javascripts/application.js | 2 +-
.../app/assets/stylesheets/application.scss | 1 +
spec/dummy/config/application.rb | 2 +
10 files changed, 156 insertions(+), 22 deletions(-)
diff --git a/app/assets/javascripts/defaults.js b/app/assets/javascripts/defaults.js
index 3386fad9..aa906d75 100644
--- a/app/assets/javascripts/defaults.js
+++ b/app/assets/javascripts/defaults.js
@@ -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",
diff --git a/app/assets/javascripts/recurring_select.js b/app/assets/javascripts/recurring_select.js
index c93e6bd1..c4498c16 100644
--- a/app/assets/javascripts/recurring_select.js
+++ b/app/assets/javascripts/recurring_select.js
@@ -13,6 +13,7 @@ document.addEventListener("DOMContentLoaded", () => {
recurring_select.call(e.target, "changed")
}
})
+
})
const methods = {
diff --git a/app/assets/javascripts/recurring_select_dialog.js.erb b/app/assets/javascripts/recurring_select_dialog.js.erb
index bd60f62a..5a970eb0 100644
--- a/app/assets/javascripts/recurring_select_dialog.js.erb
+++ b/app/assets/javascripts/recurring_select_dialog.js.erb
@@ -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();
@@ -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() {
@@ -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);
@@ -156,6 +160,40 @@ 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",
+ altInput: true,
+ altFormat: "F j, Y h:i K",
+ });
+
+ 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");
@@ -212,7 +250,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")
@@ -248,9 +286,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) => {
@@ -278,10 +316,9 @@ 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;
this.current_rule.hash.validations = null;
this.content.querySelectorAll(".freq_option_section").forEach(el => el.style.display = 'none')
this.content.querySelector("input[type=radio], input[type=checkbox]").checked = false
@@ -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();
+ }
+ this.summaryFetch();
}
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;
@@ -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))
@@ -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 => {
@@ -349,7 +386,7 @@ 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 = {};
@@ -357,13 +394,36 @@ class RecurringSelectDialog {
.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() {
@@ -381,7 +441,6 @@ class RecurringSelectDialog {
\
\
\
- \
\
\
${this.config.texts["every"]} \
@@ -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 += `${this.config.texts["days_first_letter"][day_of_week]}`;
- };
+ }
str += `\
\
@@ -426,6 +485,37 @@ class RecurringSelectDialog {
${this.config.texts["years"]} \
\
\
+
\
\
\
diff --git a/app/assets/stylesheets/recurring_select.scss b/app/assets/stylesheets/recurring_select.scss
index 67d7154d..294678db 100644
--- a/app/assets/stylesheets/recurring_select.scss
+++ b/app/assets/stylesheets/recurring_select.scss
@@ -1,5 +1,4 @@
@import "utilities.scss";
-
/* -------- resets ---------------*/
.rs_dialog_holder {
@@ -98,6 +97,23 @@ select {
}
}
+ .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: 150px;
+ text-align: center;
+ }
.rs_summary {
padding: 0px;
@@ -128,4 +144,4 @@ select {
}
}
-}
+}
\ No newline at end of file
diff --git a/lib/recurring_select.rb b/lib/recurring_select.rb
index 7a02c735..05f707bb 100644
--- a/lib/recurring_select.rb
+++ b/lib/recurring_select.rb
@@ -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!
@@ -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
diff --git a/lib/recurring_select/version.rb b/lib/recurring_select/version.rb
index 2db9857e..22b67d5d 100644
--- a/lib/recurring_select/version.rb
+++ b/lib/recurring_select/version.rb
@@ -1,3 +1,3 @@
module RecurringSelect
- VERSION = "4.0.0"
+ VERSION = "4.0.1"
end
diff --git a/recurring_select.gemspec b/recurring_select.gemspec
index 21fc21de..5126d58b 100644
--- a/recurring_select.gemspec
+++ b/recurring_select.gemspec
@@ -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"
diff --git a/spec/dummy/app/assets/javascripts/application.js b/spec/dummy/app/assets/javascripts/application.js
index 5253f3aa..71eeff16 100644
--- a/spec/dummy/app/assets/javascripts/application.js
+++ b/spec/dummy/app/assets/javascripts/application.js
@@ -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.min.js
//= require recurring_select
//= require_tree .
-
RecurringSelectDialog.config.options.monthly = {
show_week: [true, true, true, true, true, true]
}
diff --git a/spec/dummy/app/assets/stylesheets/application.scss b/spec/dummy/app/assets/stylesheets/application.scss
index 37f51c9e..39ce3ea0 100644
--- a/spec/dummy/app/assets/stylesheets/application.scss
+++ b/spec/dummy/app/assets/stylesheets/application.scss
@@ -4,6 +4,7 @@
* the top of the compiled file, but it's generally better to create a new file per style scope.
*= require_self
*= require recurring_select
+ *= require flatpickr
*/
diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb
index 2162cb1a..1efa5893 100644
--- a/spec/dummy/config/application.rb
+++ b/spec/dummy/config/application.rb
@@ -44,6 +44,8 @@ class Application < Rails::Application
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
+
+ config.assets.paths << Rails.root.join('../../node_modules')
end
end