From 07c4b0546070e6b7e6a1458723981caa5cb11052 Mon Sep 17 00:00:00 2001 From: "Gregory N. Schmit" Date: Sun, 9 Jul 2023 23:38:01 -0500 Subject: [PATCH 01/24] Adjust gemspec homepage. --- Gemfile | 2 +- recurring_select.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index e7b799e..e259b5a 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ -source 'https://rubygems.org' +source "https://rubygems.org" gemspec gem "pg" diff --git a/recurring_select.gemspec b/recurring_select.gemspec index 34fdca9..de5192e 100644 --- a/recurring_select.gemspec +++ b/recurring_select.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |s| s.version = RecurringSelect::VERSION s.authors = ["Jobber", "Forrest Zeisler", "Nathan Youngman", "Gregory Schmit"] s.email = ["schmitgreg@gmail.com"] - s.homepage = "http://github.com/getjobber/recurring_select" + s.homepage = "http://github.com/gregschmit/recurring_select" s.summary = "A select helper which gives you magical powers to generate ice_cube rules." s.description = "This gem provides a useful interface for creating recurring rules for the ice_cube gem." From 89af1439687a619765631a3e0c61eb3713be0cf9 Mon Sep 17 00:00:00 2001 From: "Gregory N. Schmit" Date: Sun, 9 Jul 2023 23:43:33 -0500 Subject: [PATCH 02/24] Fix undefined method error when :day_of_week key is integer; fixes #163. --- lib/recurring_select.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/recurring_select.rb b/lib/recurring_select.rb index 976d90a..7a02c73 100644 --- a/lib/recurring_select.rb +++ b/lib/recurring_select.rb @@ -59,7 +59,7 @@ def self.filter_params(params) # this is soooooo ugly if params[:validations][:day_of_week] params[:validations][:day_of_week] ||= {} - if params[:validations][:day_of_week].length > 0 and not params[:validations][:day_of_week].keys.first =~ /\d/ + if params[:validations][:day_of_week].length > 0 and not params[:validations][:day_of_week].keys.first.to_s =~ /\d/ params[:validations][:day_of_week].symbolize_keys! else originals = params[:validations][:day_of_week].dup From 02bb82d2781b4ccca378289732d9692c5acdda2a Mon Sep 17 00:00:00 2001 From: Jerry Walsh Date: Wed, 23 Aug 2023 03:29:34 +0000 Subject: [PATCH 03/24] fix cannot modify frozen string error in modern rubys (#165) --- lib/helpers/recurring_select_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/helpers/recurring_select_helper.rb b/lib/helpers/recurring_select_helper.rb index 5122d4f..266a098 100644 --- a/lib/helpers/recurring_select_helper.rb +++ b/lib/helpers/recurring_select_helper.rb @@ -65,7 +65,7 @@ def ice_cube_rule_to_option(supplied_rule, custom = false) ar = [rule.to_s, rule.to_hash.to_json] if custom - ar[0] << "*" + ar[0] += "*" ar << {"data-custom" => true} end From 87796bc4fc4ba685872c37bf16e3881a49811dae Mon Sep 17 00:00:00 2001 From: Aaron F Stanton Date: Tue, 22 Aug 2023 23:41:09 -0400 Subject: [PATCH 04/24] Add tag helper (#157) * Update recurring_select_helper.rb * Update engine.rb --- lib/helpers/recurring_select_helper.rb | 6 ++++++ lib/recurring_select/engine.rb | 1 + 2 files changed, 7 insertions(+) diff --git a/lib/helpers/recurring_select_helper.rb b/lib/helpers/recurring_select_helper.rb index 266a098..334458e 100644 --- a/lib/helpers/recurring_select_helper.rb +++ b/lib/helpers/recurring_select_helper.rb @@ -6,6 +6,12 @@ def select_recurring(object, method, default_schedules = nil, options = {}, html RecurringSelectTag.new(object, method, self, default_schedules, options, html_options).render end end + + module FormTagHelper + def select_recurring_tag(name, default_schedules = nil, options = {}, html_options = {}) + RecurringSelectTag.new(nil, name, self, default_schedules, options, html_options).render + end + end module FormBuilder def select_recurring(method, default_schedules = nil, options = {}, html_options = {}) diff --git a/lib/recurring_select/engine.rb b/lib/recurring_select/engine.rb index 1989b17..27ed7fc 100644 --- a/lib/recurring_select/engine.rb +++ b/lib/recurring_select/engine.rb @@ -6,6 +6,7 @@ class Engine < Rails::Engine initializer "recurring_select.extending_form_builder" do |app| ActionView::Helpers::FormHelper.send(:include, RecurringSelectHelper::FormHelper) + ActionView::Helpers::FormTagHelper.send(:include, RecurringSelectHelper::FormTagHelper) ActionView::Helpers::FormOptionsHelper.send(:include, RecurringSelectHelper::FormOptionsHelper) ActionView::Helpers::FormBuilder.send(:include, RecurringSelectHelper::FormBuilder) end From ff332d470a8c4ed45a81789dc9d0020363d6d34d Mon Sep 17 00:00:00 2001 From: glaszig Date: Fri, 1 Sep 2023 03:01:20 -0300 Subject: [PATCH 05/24] move to ruby 3.0 and rails 6.1 as everything older is eol --- .gitignore | 4 ++-- .ruby-version | 2 +- Gemfile | 6 ++---- README.md | 4 ++-- recurring_select.gemspec | 6 +++--- spec/gemfiles/Gemfile.rails-5.2.x | 9 --------- spec/gemfiles/Gemfile.rails-6.0.x | 9 --------- spec/gemfiles/rails-7 | 7 +++++++ 8 files changed, 17 insertions(+), 30 deletions(-) delete mode 100644 spec/gemfiles/Gemfile.rails-5.2.x delete mode 100644 spec/gemfiles/Gemfile.rails-6.0.x create mode 100644 spec/gemfiles/rails-7 diff --git a/.gitignore b/.gitignore index e4e765c..01d5229 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ *.gem -spec/gemfiles/Gemfile*.lock -Gemfile.lock +spec/gemfiles/*.lock +*Gemfile.lock *.gem .bundle/ diff --git a/.ruby-version b/.ruby-version index 35d16fb..818bd47 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.5.7 +3.0.6 diff --git a/Gemfile b/Gemfile index e259b5a..86083a3 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,7 @@ source "https://rubygems.org" gemspec -gem "pg" - gem "jquery-rails" -gem "rails", "~> 5.2" -gem "sass-rails", "~> 5.0" +gem "thin" +gem "rails", "~> 6.1" diff --git a/README.md b/README.md index a05700b..69a37db 100644 --- a/README.md +++ b/README.md @@ -151,8 +151,8 @@ rails s Tests can be ran against different versions of Rails like so: ``` -BUNDLE_GEMFILE=spec/gemfiles/Gemfile.rails-4.0.x bundle install -BUNDLE_GEMFILE=spec/gemfiles/Gemfile.rails-4.0.x bundle exec rspec spec +BUNDLE_GEMFILE=spec/gemfiles/rails-7 bundle install +BUNDLE_GEMFILE=spec/gemfiles/rails-7 bundle exec rspec spec ``` Feel free to open issues or send pull requests. diff --git a/recurring_select.gemspec b/recurring_select.gemspec index de5192e..e6b04ca 100644 --- a/recurring_select.gemspec +++ b/recurring_select.gemspec @@ -14,11 +14,11 @@ Gem::Specification.new do |s| s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.md"] s.test_files = Dir["test/**/*"] - s.add_dependency "rails", ">= 5.2" + s.add_dependency "rails", ">= 6.1" s.add_dependency "jquery-rails", ">= 3.0" s.add_dependency "ice_cube", ">= 0.11" - s.add_dependency "sass-rails", ">= 4.0" - s.add_dependency "coffee-rails", ">= 3.1" + s.add_dependency "sass-rails", ">= 6.0" + s.add_dependency "coffee-rails", ">= 5.0" s.add_development_dependency "bundler", ">= 1.3.5" s.add_development_dependency "rspec-rails", ">= 2.14" diff --git a/spec/gemfiles/Gemfile.rails-5.2.x b/spec/gemfiles/Gemfile.rails-5.2.x deleted file mode 100644 index cb9d640..0000000 --- a/spec/gemfiles/Gemfile.rails-5.2.x +++ /dev/null @@ -1,9 +0,0 @@ -source "https://rubygems.org" -gemspec :path => "./../.." - -gem 'pg', platform: :ruby - -gem "jquery-rails" - -gem "rails", "~> 5.2" -gem "sass-rails", "~> 5.0" diff --git a/spec/gemfiles/Gemfile.rails-6.0.x b/spec/gemfiles/Gemfile.rails-6.0.x deleted file mode 100644 index f508194..0000000 --- a/spec/gemfiles/Gemfile.rails-6.0.x +++ /dev/null @@ -1,9 +0,0 @@ -source "https://rubygems.org" -gemspec :path => "./../.." - -gem 'pg', platform: :ruby - -gem "jquery-rails" - -gem "rails", "~> 6.0" -gem "sass-rails", "~> 6.0" diff --git a/spec/gemfiles/rails-7 b/spec/gemfiles/rails-7 new file mode 100644 index 0000000..6b8eded --- /dev/null +++ b/spec/gemfiles/rails-7 @@ -0,0 +1,7 @@ +source "https://rubygems.org" +gemspec path: File.expand_path("../../..", __FILE__) + +gem "jquery-rails" + +gem "thin" +gem "rails", "~> 7.0.0" From 9dfe068f81ac9b39ac976356206a12c58d93252f Mon Sep 17 00:00:00 2001 From: glaszig Date: Fri, 1 Sep 2023 03:15:40 -0300 Subject: [PATCH 06/24] remove unused rails components from dummy app --- README.md | 7 ------- spec/dummy/config/application.rb | 6 +++++- spec/dummy/config/database.yml | 11 ----------- spec/dummy/config/environments/development.rb | 3 --- spec/dummy/config/environments/test.rb | 5 ----- 5 files changed, 5 insertions(+), 27 deletions(-) delete mode 100644 spec/dummy/config/database.yml diff --git a/README.md b/README.md index 69a37db..8a7c99e 100644 --- a/README.md +++ b/README.md @@ -135,13 +135,6 @@ $.fn.recurring_select.options = { ## Testing and Development -The dummy app uses a [Postgres](http://postgresapp.com/) database `recurring_select_development`. To get setup: - -```console -bundle -rake db:create -``` - Start the dummy server for clicking around the interface: ```console diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb index e6e5531..2162cb1 100644 --- a/spec/dummy/config/application.rb +++ b/spec/dummy/config/application.rb @@ -1,6 +1,10 @@ require File.expand_path('../boot', __FILE__) -require 'rails/all' +require "rails" +require "active_model/railtie" +require "action_controller/railtie" +require "action_view/railtie" +require "sprockets/railtie" Bundler.require(*Rails.groups(:assets => %w(development test))) require "recurring_select" diff --git a/spec/dummy/config/database.yml b/spec/dummy/config/database.yml deleted file mode 100644 index 564fdb5..0000000 --- a/spec/dummy/config/database.yml +++ /dev/null @@ -1,11 +0,0 @@ -development: - adapter: postgresql - database: recurring_select_development - username: - password: - -test: - adapter: postgresql - database: recurring_select_test - username: - password: diff --git a/spec/dummy/config/environments/development.rb b/spec/dummy/config/environments/development.rb index 6d89728..8324a27 100644 --- a/spec/dummy/config/environments/development.rb +++ b/spec/dummy/config/environments/development.rb @@ -13,9 +13,6 @@ config.consider_all_requests_local = true config.action_controller.perform_caching = false - # Don't care if the mailer can't send - config.action_mailer.raise_delivery_errors = false - # Print deprecation notices to the Rails logger config.active_support.deprecation = :log diff --git a/spec/dummy/config/environments/test.rb b/spec/dummy/config/environments/test.rb index 7b5f159..f3f2082 100644 --- a/spec/dummy/config/environments/test.rb +++ b/spec/dummy/config/environments/test.rb @@ -21,11 +21,6 @@ # Disable request forgery protection in test environment config.action_controller.allow_forgery_protection = false - # Tell Action Mailer not to deliver emails to the real world. - # The :test delivery method accumulates sent emails in the - # ActionMailer::Base.deliveries array. - config.action_mailer.delivery_method = :test - # Use SQL instead of Active Record's schema dumper when creating the test database. # This is necessary if your schema can't be completely dumped by the schema dumper, # like if you have constraints or database-specific column types From 9987a46cba508b0c4e358fc24fe5c2f08d7efa19 Mon Sep 17 00:00:00 2001 From: glaszig Date: Fri, 1 Sep 2023 03:42:15 -0300 Subject: [PATCH 07/24] major version bump --- lib/recurring_select/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/recurring_select/version.rb b/lib/recurring_select/version.rb index 9b8de18..2db9857 100644 --- a/lib/recurring_select/version.rb +++ b/lib/recurring_select/version.rb @@ -1,3 +1,3 @@ module RecurringSelect - VERSION = "3.0.1" + VERSION = "4.0.0" end From ab263eaf3ab0d74f9ad46efd849385a5eee8c8cf Mon Sep 17 00:00:00 2001 From: glaszig Date: Fri, 1 Sep 2023 04:27:22 -0300 Subject: [PATCH 08/24] decaffeinate js --- app/assets/javascripts/jquery-mobile-rs.js | 18 + .../javascripts/jquery-mobile-rs.js.coffee | 15 - app/assets/javascripts/recurring_select.js | 125 +++++ .../javascripts/recurring_select.js.coffee | 105 ---- .../recurring_select/{fr.js.coffee => fr.js} | 0 .../recurring_select_dialog.js.coffee.erb | 372 --------------- .../recurring_select_dialog.js.erb | 449 ++++++++++++++++++ 7 files changed, 592 insertions(+), 492 deletions(-) create mode 100644 app/assets/javascripts/jquery-mobile-rs.js delete mode 100644 app/assets/javascripts/jquery-mobile-rs.js.coffee create mode 100644 app/assets/javascripts/recurring_select.js delete mode 100644 app/assets/javascripts/recurring_select.js.coffee rename app/assets/javascripts/recurring_select/{fr.js.coffee => fr.js} (100%) delete mode 100644 app/assets/javascripts/recurring_select_dialog.js.coffee.erb create mode 100644 app/assets/javascripts/recurring_select_dialog.js.erb diff --git a/app/assets/javascripts/jquery-mobile-rs.js b/app/assets/javascripts/jquery-mobile-rs.js new file mode 100644 index 0000000..5cac55c --- /dev/null +++ b/app/assets/javascripts/jquery-mobile-rs.js @@ -0,0 +1,18 @@ +//= require recurring_select +//= require_self + +$(function() { + $(document).on("recurring_select:cancel recurring_select:save", ".recurring_select", function() { + $(this).selectmenu('refresh'); + }); + + $(document).on("recurring_select:dialog_opened", ".rs_dialog_holder", function() { + $(this).find("select").attr("data-theme", $('.recurring_select').data("theme")).attr("data-mini", true).selectmenu(); + $(this).find("input[type=text]").textinput(); + + $(this).on("recurring_select:dialog_positioned", ".rs_dialog", function() { + $(this).css({ + "top" : $(window).scrollTop()+"px"}); + }); + }); +}); diff --git a/app/assets/javascripts/jquery-mobile-rs.js.coffee b/app/assets/javascripts/jquery-mobile-rs.js.coffee deleted file mode 100644 index 2ce8b10..0000000 --- a/app/assets/javascripts/jquery-mobile-rs.js.coffee +++ /dev/null @@ -1,15 +0,0 @@ -#= require recurring_select -#= require_self - -$ -> - $(document).on "recurring_select:cancel recurring_select:save", ".recurring_select", -> - $(this).selectmenu('refresh') - - $(document).on "recurring_select:dialog_opened", ".rs_dialog_holder", -> - $(this).find("select").attr("data-theme", $('.recurring_select').data("theme")).attr("data-mini", true).selectmenu() - $(this).find("input[type=text]").textinput() - - $(this).on "recurring_select:dialog_positioned", ".rs_dialog", -> - $(this).css - "top" : $(window).scrollTop()+"px" - diff --git a/app/assets/javascripts/recurring_select.js b/app/assets/javascripts/recurring_select.js new file mode 100644 index 0000000..c6539c5 --- /dev/null +++ b/app/assets/javascripts/recurring_select.js @@ -0,0 +1,125 @@ +//= require recurring_select_dialog +//= require_self + +const $ = jQuery; +$(function() { + $(document).on("focus", ".recurring_select", function() { + $(this).recurring_select('set_initial_values'); + }); + + $(document).on("change", ".recurring_select", function() { + $(this).recurring_select('changed'); + }); +}); + +const methods = { + set_initial_values() { + this.data('initial-value-hash', this.val()); + this.data('initial-value-str', $(this.find("option").get()[this.prop("selectedIndex")]).text()); + }, + + changed() { + if (this.val() === "custom") { + methods.open_custom.apply(this); + } else { + methods.set_initial_values.apply(this); + } + }, + + open_custom() { + this.data("recurring-select-active", true); + new RecurringSelectDialog(this); + this.blur(); + }, + + save(new_rule) { + this.find("option[data-custom]").remove(); + const new_json_val = JSON.stringify(new_rule.hash); + + // TODO: check for matching name, and replace that value if found + + if ($.inArray(new_json_val, this.find("option").map(function() { $(this).val(); })) === -1) { + methods.insert_option.apply(this, [new_rule.str, new_json_val]); + } + + this.val(new_json_val); + methods.set_initial_values.apply(this); + this.trigger("recurring_select:save"); + }, + + current_rule() { + return { + str: this.data("initial-value-str"), + hash: $.parseJSON(this.data("initial-value-hash")) + }; + }, + + cancel() { + this.val(this.data("initial-value-hash")); + this.data("recurring-select-active", false); + this.trigger("recurring_select:cancel"); + }, + + + insert_option(new_rule_str, new_rule_json) { + let separator = this.find("option:disabled"); + if (separator.length === 0) { + separator = this.find("option"); + } + separator = separator.last(); + + const new_option = $(document.createElement("option")); + new_option.attr("data-custom", true); + + if (new_rule_str.substr(new_rule_str.length - 1) !== "*") { + new_rule_str+="*"; + } + + new_option.text(new_rule_str); + new_option.val(new_rule_json); + new_option.insertBefore(separator); + }, + + methods() { + return methods; + } +}; + +$.fn.recurring_select = function(method) { + if (method in methods) { + return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) ); + } else { + $.error( `Method ${method} does not exist on jQuery.recurring_select` ); + } +}; + +$.fn.recurring_select.options = { + monthly: { + show_week: [true, true, true, true, false, false] + } +}; + +$.fn.recurring_select.texts = { + locale_iso_code: "en", + repeat: "Repeat", + last_day: "Last Day", + frequency: "Frequency", + daily: "Daily", + weekly: "Weekly", + monthly: "Monthly", + yearly: "Yearly", + every: "Every", + days: "day(s)", + weeks_on: "week(s) on", + months: "month(s)", + years: "year(s)", + day_of_month: "Day of month", + day_of_week: "Day of week", + cancel: "Cancel", + ok: "OK", + summary: "Summary", + first_day_of_week: 0, + days_first_letter: ["S", "M", "T", "W", "T", "F", "S" ], + order: ["1st", "2nd", "3rd", "4th", "5th", "Last"], + show_week: [true, true, true, true, false, false] +}; diff --git a/app/assets/javascripts/recurring_select.js.coffee b/app/assets/javascripts/recurring_select.js.coffee deleted file mode 100644 index c173d51..0000000 --- a/app/assets/javascripts/recurring_select.js.coffee +++ /dev/null @@ -1,105 +0,0 @@ -#= require recurring_select_dialog -#= require_self - -$ = jQuery -$ -> - $(document).on "focus", ".recurring_select", -> - $(this).recurring_select('set_initial_values') - - $(document).on "change", ".recurring_select", -> - $(this).recurring_select('changed') - -methods = - set_initial_values: -> - @data 'initial-value-hash', @val() - @data 'initial-value-str', $(@find("option").get()[@.prop("selectedIndex")]).text() - - changed: -> - if @val() == "custom" - methods.open_custom.apply(@) - else - methods.set_initial_values.apply(@) - - open_custom: -> - @data "recurring-select-active", true - new RecurringSelectDialog(@) - @blur() - - save: (new_rule) -> - @find("option[data-custom]").remove() - new_json_val = JSON.stringify(new_rule.hash) - - # TODO: check for matching name, and replace that value if found - - if $.inArray(new_json_val, @find("option").map -> $(@).val()) == -1 - methods.insert_option.apply @, [new_rule.str, new_json_val] - - @val new_json_val - methods.set_initial_values.apply @ - @.trigger "recurring_select:save" - - current_rule: -> - str: @data("initial-value-str") - hash: $.parseJSON(@data("initial-value-hash")) - - cancel: -> - @val @data("initial-value-hash") - @data "recurring-select-active", false - @.trigger "recurring_select:cancel" - - - insert_option: (new_rule_str, new_rule_json) -> - separator = @find("option:disabled") - if separator.length == 0 - separator = @find("option") - separator = separator.last() - - new_option = $(document.createElement("option")) - new_option.attr "data-custom", true - - if new_rule_str.substr(new_rule_str.length - 1) != "*" - new_rule_str+="*" - - new_option.text new_rule_str - new_option.val new_rule_json - new_option.insertBefore separator - - methods: -> - methods - -$.fn.recurring_select = (method) -> - if method of methods - return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) ); - else - $.error( "Method #{method} does not exist on jQuery.recurring_select" ); - -$.fn.recurring_select.options = { - monthly: { - show_week: [true, true, true, true, false, false] - } -} - -$.fn.recurring_select.texts = { - locale_iso_code: "en" - repeat: "Repeat" - last_day: "Last Day" - frequency: "Frequency" - daily: "Daily" - weekly: "Weekly" - monthly: "Monthly" - yearly: "Yearly" - every: "Every" - days: "day(s)" - weeks_on: "week(s) on" - months: "month(s)" - years: "year(s)" - day_of_month: "Day of month" - day_of_week: "Day of week" - cancel: "Cancel" - ok: "OK" - summary: "Summary" - first_day_of_week: 0 - days_first_letter: ["S", "M", "T", "W", "T", "F", "S" ] - order: ["1st", "2nd", "3rd", "4th", "5th", "Last"] - show_week: [true, true, true, true, false, false] -} diff --git a/app/assets/javascripts/recurring_select/fr.js.coffee b/app/assets/javascripts/recurring_select/fr.js similarity index 100% rename from app/assets/javascripts/recurring_select/fr.js.coffee rename to app/assets/javascripts/recurring_select/fr.js diff --git a/app/assets/javascripts/recurring_select_dialog.js.coffee.erb b/app/assets/javascripts/recurring_select_dialog.js.coffee.erb deleted file mode 100644 index baeccd6..0000000 --- a/app/assets/javascripts/recurring_select_dialog.js.coffee.erb +++ /dev/null @@ -1,372 +0,0 @@ -window.RecurringSelectDialog = - class RecurringSelectDialog - constructor: (@recurring_selector) -> - @current_rule = @recurring_selector.recurring_select('current_rule') - @initDialogBox() - if not @current_rule.hash? or not @current_rule.hash.rule_type? - @freqChanged() - else - setTimeout @positionDialogVert, 10 # allow initial render - - initDialogBox: -> - $(".rs_dialog_holder").remove() - - open_in = $("body") - open_in = $(".ui-page-active") if $(".ui-page-active").length - open_in.append @template() - @outer_holder = $(".rs_dialog_holder") - @inner_holder = @outer_holder.find ".rs_dialog" - @content = @outer_holder.find ".rs_dialog_content" - @positionDialogVert(true) - @mainEventInit() - @freqInit() - @summaryInit() - @outer_holder.trigger "recurring_select:dialog_opened" - @freq_select.focus() - - positionDialogVert: (initial_positioning) => - window_height = $(window).height() - window_width = $(window).width() - dialog_height = @content.outerHeight() - if dialog_height < 80 - dialog_height = 80 - margin_top = (window_height - dialog_height)/2 - 30 - margin_top = 10 if margin_top < 10 - # if dialog_height > window_height - 20 - # dialog_height = window_height - 20 - - new_style_hash = - "margin-top" : margin_top+"px" - "min-height" : dialog_height+"px" - - if initial_positioning? - @inner_holder.css new_style_hash - @inner_holder.trigger "recurring_select:dialog_positioned" - else - @inner_holder.addClass "animated" - @inner_holder.animate new_style_hash, 200, => - @inner_holder.removeClass "animated" - @content.css {"width": "auto"} - @inner_holder.trigger "recurring_select:dialog_positioned" - - cancel: => - @outer_holder.remove() - @recurring_selector.recurring_select('cancel') - - outerCancel: (event) => - if $(event.target).hasClass("rs_dialog_holder") - @cancel() - - save: => - return if !@current_rule.str? - @outer_holder.remove() - @recurring_selector.recurring_select('save', @current_rule) - -# ========================= Init Methods =============================== - - mainEventInit: -> - # Tap hooks are for jQueryMobile - @outer_holder.on 'click tap', @outerCancel - @content.on 'click tap', 'h1 a', @cancel - @save_button = @content.find('input.rs_save').on "click tap", @save - @content.find('input.rs_cancel').on "click tap", @cancel - - freqInit: -> - @freq_select = @outer_holder.find ".rs_frequency" - if @current_rule.hash? && (rule_type = @current_rule.hash.rule_type)? - if rule_type.search(/Weekly/) != -1 - @freq_select.prop('selectedIndex', 1) - @initWeeklyOptions() - else if rule_type.search(/Monthly/) != -1 - @freq_select.prop('selectedIndex', 2) - @initMonthlyOptions() - else if rule_type.search(/Yearly/) != -1 - @freq_select.prop('selectedIndex', 3) - @initYearlyOptions() - else - @initDailyOptions() - @freq_select.on "change", @freqChanged - - initDailyOptions: -> - section = @content.find('.daily_options') - interval_input = section.find('.rs_daily_interval') - interval_input.val(@current_rule.hash.interval) - interval_input.on "change keyup", @intervalChanged - section.show() - - initWeeklyOptions: -> - section = @content.find('.weekly_options') - - # connect the interval field - interval_input = section.find('.rs_weekly_interval') - interval_input.val(@current_rule.hash.interval) - interval_input.on "change keyup", @intervalChanged - - # clear selected days - section.find(".day_holder a").each (index, element) -> - $(element).removeClass("selected") - - # connect the day fields - if @current_rule.hash.validations? && @current_rule.hash.validations.day? - $(@current_rule.hash.validations.day).each (index, val) -> - section.find(".day_holder a[data-value='"+val+"']").addClass("selected") - - section.off('click', '.day_holder a').on "click", ".day_holder a", @daysChanged - - section.show() - - initMonthlyOptions: -> - section = @content.find('.monthly_options') - interval_input = section.find('.rs_monthly_interval') - interval_input.val(@current_rule.hash.interval) - interval_input.on "change keyup", @intervalChanged - - @current_rule.hash.validations ||= {} - @current_rule.hash.validations.day_of_month ||= [] - @current_rule.hash.validations.day_of_week ||= {} - @init_calendar_days(section) - @init_calendar_weeks(section) - - in_week_mode = Object.keys(@current_rule.hash.validations.day_of_week).length > 0 - section.find(".monthly_rule_type_week").prop("checked", in_week_mode) - section.find(".monthly_rule_type_day").prop("checked", !in_week_mode) - @toggle_month_view() - section.find("input[name=monthly_rule_type]").on "change", @toggle_month_view - section.show() - - initYearlyOptions: -> - section = @content.find('.yearly_options') - interval_input = section.find('.rs_yearly_interval') - interval_input.val(@current_rule.hash.interval) - interval_input.on "change keyup", @intervalChanged - section.show() - - - summaryInit: -> - @summary = @outer_holder.find(".rs_summary") - @summaryUpdate() - -# ========================= render methods =============================== - - summaryUpdate: (new_string) => - @summary.width @content.width() - if @current_rule.hash? && @current_rule.str? - @summary.removeClass "fetching" - @save_button.removeClass("disabled") - rule_str = @current_rule.str.replace("*", "") - if rule_str.length < 20 - rule_str = "#{$.fn.recurring_select.texts["summary"]}: "+rule_str - @summary.find("span").html rule_str - else - @summary.addClass "fetching" - @save_button.addClass("disabled") - @summary.find("span").html "" - @summaryFetch() - - summaryFetch: -> - return if !(@current_rule.hash? && (rule_type = @current_rule.hash.rule_type)?) - @current_rule.hash['week_start'] = $.fn.recurring_select.texts["first_day_of_week"] - $.ajax - url: "<%= Rails.application.config.action_controller.relative_url_root %>/recurring_select/translate/#{$.fn.recurring_select.texts["locale_iso_code"]}", - type: "POST", - data: @current_rule.hash - success: @summaryFetchSuccess - - summaryFetchSuccess: (data) => - @current_rule.str = data - @summaryUpdate() - @content.css {"width": "auto"} - - init_calendar_days: (section) => - monthly_calendar = section.find(".rs_calendar_day") - monthly_calendar.html "" - for num in [1..31] - monthly_calendar.append (day_link = $(document.createElement("a")).text(num)) - if $.inArray(num, @current_rule.hash.validations.day_of_month) != -1 - day_link.addClass("selected") - - # add last day of month button - monthly_calendar.append (end_of_month_link = $(document.createElement("a")).text($.fn.recurring_select.texts["last_day"])) - end_of_month_link.addClass("end_of_month") - if $.inArray(-1, @current_rule.hash.validations.day_of_month) != -1 - end_of_month_link.addClass("selected") - - monthly_calendar.find("a").on "click tap", @dateOfMonthChanged - - init_calendar_weeks: (section) => - monthly_calendar = section.find(".rs_calendar_week") - monthly_calendar.html "" - row_labels = $.fn.recurring_select.texts["order"] - show_row = $.fn.recurring_select.options["monthly"]["show_week"] - cell_str = $.fn.recurring_select.texts["days_first_letter"] - - for num, index in [1, 2, 3, 4, 5, -1] - if show_row[index] - monthly_calendar.append "#{row_labels[num - 1]}" - for day_of_week in [$.fn.recurring_select.texts["first_day_of_week"]...(7 + $.fn.recurring_select.texts["first_day_of_week"])] - day_of_week = day_of_week % 7 - day_link = $("", {text: cell_str[day_of_week] }) - day_link.attr("day", day_of_week) - day_link.attr("instance", num) - monthly_calendar.append day_link - - $.each @current_rule.hash.validations.day_of_week, (key, value) -> - $.each value, (index, instance) -> - section.find("a[day='#{key}'][instance='#{instance}']").addClass("selected") - monthly_calendar.find("a").on "click tap", @weekOfMonthChanged - - toggle_month_view: => - week_mode = @content.find(".monthly_rule_type_week").prop("checked") - @content.find(".rs_calendar_week").toggle(week_mode) - @content.find(".rs_calendar_day").toggle(!week_mode) - -# ========================= Change callbacks =============================== - - freqChanged: => - @current_rule.hash = null unless $.isPlainObject(@current_rule.hash) # for custom values - - @current_rule.hash ||= {} - @current_rule.hash.interval = 1 - @current_rule.hash.until = null - @current_rule.hash.count = null - @current_rule.hash.validations = null - @content.find(".freq_option_section").hide(); - @content.find("input[type=radio], input[type=checkbox]").prop("checked", false) - switch @freq_select.val() - when "Weekly" - @current_rule.hash.rule_type = "IceCube::WeeklyRule" - @current_rule.str = $.fn.recurring_select.texts["weekly"] - @initWeeklyOptions() - when "Monthly" - @current_rule.hash.rule_type = "IceCube::MonthlyRule" - @current_rule.str = $.fn.recurring_select.texts["monthly"] - @initMonthlyOptions() - when "Yearly" - @current_rule.hash.rule_type = "IceCube::YearlyRule" - @current_rule.str = $.fn.recurring_select.texts["yearly"] - @initYearlyOptions() - else - @current_rule.hash.rule_type = "IceCube::DailyRule" - @current_rule.str = $.fn.recurring_select.texts["daily"] - @initDailyOptions() - @summaryUpdate() - @positionDialogVert() - - intervalChanged: (event) => - @current_rule.str = null - @current_rule.hash ||= {} - @current_rule.hash.interval = parseInt($(event.currentTarget).val()) - if @current_rule.hash.interval < 1 || isNaN(@current_rule.hash.interval) - @current_rule.hash.interval = 1 - @summaryUpdate() - - daysChanged: (event) => - $(event.currentTarget).toggleClass("selected") - @current_rule.str = null - @current_rule.hash ||= {} - @current_rule.hash.validations = {} - raw_days = @content.find(".day_holder a.selected").map -> parseInt($(this).data("value")) - @current_rule.hash.validations.day = raw_days.get() - @summaryUpdate() - false # this prevents default and propogation - - dateOfMonthChanged: (event) => - $(event.currentTarget).toggleClass("selected") - @current_rule.str = null - @current_rule.hash ||= {} - @current_rule.hash.validations = {} - raw_days = @content.find(".monthly_options .rs_calendar_day a.selected").map -> - res = if $(this).text() == $.fn.recurring_select.texts["last_day"] then -1 else parseInt($(this).text()) - res - @current_rule.hash.validations.day_of_week = {} - @current_rule.hash.validations.day_of_month = raw_days.get() - @summaryUpdate() - false - - weekOfMonthChanged: (event) => - $(event.currentTarget).toggleClass("selected") - @current_rule.str = null - @current_rule.hash ||= {} - @current_rule.hash.validations = {} - @current_rule.hash.validations.day_of_month = [] - @current_rule.hash.validations.day_of_week = {} - @content.find(".monthly_options .rs_calendar_week a.selected").each (index, elm) => - day = parseInt($(elm).attr("day")) - instance = parseInt($(elm).attr("instance")) - @current_rule.hash.validations.day_of_week[day] ||= [] - @current_rule.hash.validations.day_of_week[day].push instance - @summaryUpdate() - false - -# ========================= Change callbacks =============================== - - template: () -> - str = " -
-
-
-

#{$.fn.recurring_select.texts["repeat"]}

-

- - -

- -
-

- #{$.fn.recurring_select.texts["every"]} - - #{$.fn.recurring_select.texts["days"]} -

-
-
-

- #{$.fn.recurring_select.texts["every"]} - - #{$.fn.recurring_select.texts["weeks_on"]}: -

-
- " - for day_of_week in [$.fn.recurring_select.texts["first_day_of_week"]...(7 + $.fn.recurring_select.texts["first_day_of_week"])] - day_of_week = day_of_week % 7 - str += "#{$.fn.recurring_select.texts["days_first_letter"][day_of_week]}" - - str += " -
- . -
-
-

- #{$.fn.recurring_select.texts["every"]} - - #{$.fn.recurring_select.texts["months"]}: -

-

- - -

-

-

-
-
-

- #{$.fn.recurring_select.texts["every"]} - - #{$.fn.recurring_select.texts["years"]} -

-
-

- -

-
- - -
-
-
-
- " diff --git a/app/assets/javascripts/recurring_select_dialog.js.erb b/app/assets/javascripts/recurring_select_dialog.js.erb new file mode 100644 index 0000000..89c95db --- /dev/null +++ b/app/assets/javascripts/recurring_select_dialog.js.erb @@ -0,0 +1,449 @@ +window.RecurringSelectDialog = + (RecurringSelectDialog = class RecurringSelectDialog { + constructor(recurring_selector) { + this.positionDialogVert = this.positionDialogVert.bind(this); + this.cancel = this.cancel.bind(this); + this.outerCancel = this.outerCancel.bind(this); + this.save = this.save.bind(this); + this.summaryUpdate = this.summaryUpdate.bind(this); + this.summaryFetchSuccess = this.summaryFetchSuccess.bind(this); + this.init_calendar_days = this.init_calendar_days.bind(this); + this.init_calendar_weeks = this.init_calendar_weeks.bind(this); + this.toggle_month_view = this.toggle_month_view.bind(this); + this.freqChanged = this.freqChanged.bind(this); + this.intervalChanged = this.intervalChanged.bind(this); + this.daysChanged = this.daysChanged.bind(this); + this.dateOfMonthChanged = this.dateOfMonthChanged.bind(this); + this.weekOfMonthChanged = this.weekOfMonthChanged.bind(this); + this.recurring_selector = recurring_selector; + this.current_rule = this.recurring_selector.recurring_select('current_rule'); + this.initDialogBox(); + if ((this.current_rule.hash == null) || (this.current_rule.hash.rule_type == null)) { + this.freqChanged(); + } else { + setTimeout(this.positionDialogVert, 10); // allow initial render + } + } + + initDialogBox() { + $(".rs_dialog_holder").remove(); + + let open_in = $("body"); + if ($(".ui-page-active").length) { open_in = $(".ui-page-active"); } + open_in.append(this.template()); + this.outer_holder = $(".rs_dialog_holder"); + this.inner_holder = this.outer_holder.find(".rs_dialog"); + this.content = this.outer_holder.find(".rs_dialog_content"); + this.positionDialogVert(true); + this.mainEventInit(); + this.freqInit(); + this.summaryInit(); + this.outer_holder.trigger("recurring_select:dialog_opened"); + this.freq_select.focus(); + } + + positionDialogVert(initial_positioning) { + const window_height = $(window).height(); + const window_width = $(window).width(); + let dialog_height = this.content.outerHeight(); + if (dialog_height < 80) { + dialog_height = 80; + } + let margin_top = ((window_height - dialog_height)/2) - 30; + if (margin_top < 10) { margin_top = 10; } + // if dialog_height > window_height - 20 + // dialog_height = window_height - 20 + + const new_style_hash = { + "margin-top" : margin_top+"px", + "min-height" : dialog_height+"px" + }; + + if (initial_positioning != null) { + this.inner_holder.css(new_style_hash); + this.inner_holder.trigger("recurring_select:dialog_positioned"); + } else { + this.inner_holder.addClass("animated"); + this.inner_holder.animate(new_style_hash, 200, () => { + this.inner_holder.removeClass("animated"); + this.content.css({"width": "auto"}); + this.inner_holder.trigger("recurring_select:dialog_positioned"); + }); + } + } + + cancel() { + this.outer_holder.remove(); + this.recurring_selector.recurring_select('cancel'); + } + + outerCancel(event) { + if ($(event.target).hasClass("rs_dialog_holder")) { + this.cancel(); + } + } + + save() { + if ((this.current_rule.str == null)) { return; } + this.outer_holder.remove(); + this.recurring_selector.recurring_select('save', this.current_rule); + } + +// ========================= Init Methods =============================== + + mainEventInit() { + // Tap hooks are for jQueryMobile + this.outer_holder.on('click tap', this.outerCancel); + this.content.on('click tap', 'h1 a', this.cancel); + this.save_button = this.content.find('input.rs_save').on("click tap", this.save); + this.content.find('input.rs_cancel').on("click tap", this.cancel); + } + + freqInit() { + this.freq_select = this.outer_holder.find(".rs_frequency"); + const rule_type = this.current_rule.hash?.rule_type + if (this.current_rule.hash != null && rule_type != null) { + if (rule_type.search(/Weekly/) !== -1) { + this.freq_select.prop('selectedIndex', 1); + this.initWeeklyOptions(); + } else if (rule_type.search(/Monthly/) !== -1) { + this.freq_select.prop('selectedIndex', 2); + this.initMonthlyOptions(); + } else if (rule_type.search(/Yearly/) !== -1) { + this.freq_select.prop('selectedIndex', 3); + this.initYearlyOptions(); + } else { + this.initDailyOptions(); + } + } + this.freq_select.on("change", this.freqChanged); + } + + initDailyOptions() { + const section = this.content.find('.daily_options'); + const interval_input = section.find('.rs_daily_interval'); + interval_input.val(this.current_rule.hash.interval); + interval_input.on("change keyup", this.intervalChanged); + section.show(); + } + + initWeeklyOptions() { + const section = this.content.find('.weekly_options'); + + // connect the interval field + const interval_input = section.find('.rs_weekly_interval'); + interval_input.val(this.current_rule.hash.interval); + interval_input.on("change keyup", this.intervalChanged); + + // clear selected days + section.find(".day_holder a").each(function(index, element) { + $(element).removeClass("selected"); + }); + + // connect the day fields + if ((this.current_rule.hash.validations != null) && (this.current_rule.hash.validations.day != null)) { + $(this.current_rule.hash.validations.day).each(function(index, val) { + section.find(".day_holder a[data-value='"+val+"']").addClass("selected"); + }); + } + + section.off('click', '.day_holder a').on("click", ".day_holder a", this.daysChanged); + + section.show(); + } + + initMonthlyOptions() { + const section = this.content.find('.monthly_options'); + const interval_input = section.find('.rs_monthly_interval'); + interval_input.val(this.current_rule.hash.interval); + interval_input.on("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 = {} }; + this.init_calendar_days(section); + this.init_calendar_weeks(section); + + const in_week_mode = Object.keys(this.current_rule.hash.validations.day_of_week).length > 0; + section.find(".monthly_rule_type_week").prop("checked", in_week_mode); + section.find(".monthly_rule_type_day").prop("checked", !in_week_mode); + this.toggle_month_view(); + section.find("input[name=monthly_rule_type]").on("change", this.toggle_month_view); + section.show(); + } + + initYearlyOptions() { + const section = this.content.find('.yearly_options'); + const interval_input = section.find('.rs_yearly_interval'); + interval_input.val(this.current_rule.hash.interval); + interval_input.on("change keyup", this.intervalChanged); + section.show(); + } + + + summaryInit() { + this.summary = this.outer_holder.find(".rs_summary"); + this.summaryUpdate(); + } + +// ========================= render methods =============================== + + summaryUpdate(new_string) { + this.summary.width(this.content.width()); + if ((this.current_rule.hash != null) && (this.current_rule.str != null)) { + this.summary.removeClass("fetching"); + this.save_button.removeClass("disabled"); + let rule_str = this.current_rule.str.replace("*", ""); + if (rule_str.length < 20) { + rule_str = `${$.fn.recurring_select.texts["summary"]}: `+rule_str; + } + this.summary.find("span").html(rule_str); + } else { + this.summary.addClass("fetching"); + this.save_button.addClass("disabled"); + this.summary.find("span").html(""); + this.summaryFetch(); + } + } + + summaryFetch() { + if (!(this.current_rule.hash != null && this.current_rule.hash.rule_type != null)) { return; } + this.current_rule.hash['week_start'] = $.fn.recurring_select.texts["first_day_of_week"]; + $.ajax({ + url: `<%= Rails.application.config.action_controller.relative_url_root %>/recurring_select/translate/${$.fn.recurring_select.texts["locale_iso_code"]}`, + type: "POST", + data: this.current_rule.hash, + success: this.summaryFetchSuccess + }); + } + + summaryFetchSuccess(data) { + this.current_rule.str = data; + this.summaryUpdate(); + this.content.css({"width": "auto"}); + } + + init_calendar_days(section) { + const monthly_calendar = section.find(".rs_calendar_day"); + monthly_calendar.html(""); + for (let num = 1; num <= 31; num++) { + const day_link = $(document.createElement("a")).text(num); + monthly_calendar.append(day_link); + if ($.inArray(num, this.current_rule.hash.validations.day_of_month) !== -1) { + day_link.addClass("selected"); + } + }; + + // add last day of month button + const end_of_month_link = $(document.createElement("a")).text($.fn.recurring_select.texts["last_day"]) + monthly_calendar.append(end_of_month_link); + end_of_month_link.addClass("end_of_month"); + if ($.inArray(-1, this.current_rule.hash.validations.day_of_month) !== -1) { + end_of_month_link.addClass("selected"); + } + + monthly_calendar.find("a").on("click tap", this.dateOfMonthChanged); + } + + init_calendar_weeks(section) { + const monthly_calendar = section.find(".rs_calendar_week"); + monthly_calendar.html(""); + const row_labels = $.fn.recurring_select.texts["order"]; + const show_row = $.fn.recurring_select.options["monthly"]["show_week"]; + const cell_str = $.fn.recurring_select.texts["days_first_letter"]; + + const iterable = [1, 2, 3, 4, 5, -1] + for (let index = 0; index < iterable.length; index++) { + const num = iterable[index]; + if (show_row[index]) { + monthly_calendar.append(`${row_labels[num - 1]}`); + for (let i = $.fn.recurring_select.texts["first_day_of_week"], day_of_week = i, end = 7 + $.fn.recurring_select.texts["first_day_of_week"], asc = $.fn.recurring_select.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; + const day_link = $("", {text: cell_str[day_of_week] }); + day_link.attr("day", day_of_week); + day_link.attr("instance", num); + monthly_calendar.append(day_link); + }; + } + }; + + $.each(this.current_rule.hash.validations.day_of_week, function(key, value) { + $.each(value, function(index, instance) { + section.find(`a[day='${key}'][instance='${instance}']`).addClass("selected"); + }); + }); + monthly_calendar.find("a").on("click tap", this.weekOfMonthChanged); + } + + toggle_month_view() { + const week_mode = this.content.find(".monthly_rule_type_week").prop("checked"); + this.content.find(".rs_calendar_week").toggle(week_mode); + this.content.find(".rs_calendar_day").toggle(!week_mode); + } + +// ========================= Change callbacks =============================== + + freqChanged() { + if (!$.isPlainObject(this.current_rule.hash)) { this.current_rule.hash = null; } // for custom values + + if (!this.current_rule.hash) { this.current_rule.hash = {} }; + 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.find(".freq_option_section").hide();; + this.content.find("input[type=radio], input[type=checkbox]").prop("checked", false); + switch (this.freq_select.val()) { + case "Weekly": + this.current_rule.hash.rule_type = "IceCube::WeeklyRule"; + this.current_rule.str = $.fn.recurring_select.texts["weekly"]; + this.initWeeklyOptions(); + break + case "Monthly": + this.current_rule.hash.rule_type = "IceCube::MonthlyRule"; + this.current_rule.str = $.fn.recurring_select.texts["monthly"]; + this.initMonthlyOptions(); + break + case "Yearly": + this.current_rule.hash.rule_type = "IceCube::YearlyRule"; + this.current_rule.str = $.fn.recurring_select.texts["yearly"]; + this.initYearlyOptions(); + break + default: + this.current_rule.hash.rule_type = "IceCube::DailyRule"; + this.current_rule.str = $.fn.recurring_select.texts["daily"]; + this.initDailyOptions(); + }; + this.summaryUpdate(); + this.positionDialogVert(); + } + + intervalChanged(event) { + this.current_rule.str = null; + if (!this.current_rule.hash) { this.current_rule.hash = {} }; + this.current_rule.hash.interval = parseInt($(event.currentTarget).val()); + if ((this.current_rule.hash.interval < 1) || isNaN(this.current_rule.hash.interval)) { + this.current_rule.hash.interval = 1; + } + this.summaryUpdate(); + } + + daysChanged(event) { + $(event.currentTarget).toggleClass("selected"); + this.current_rule.str = null; + if (!this.current_rule.hash) { this.current_rule.hash = {} }; + this.current_rule.hash.validations = {}; + const raw_days = this.content.find(".day_holder a.selected").map(function() { return parseInt($(this).data("value")); }); + this.current_rule.hash.validations.day = raw_days.get(); + this.summaryUpdate(); + return false; // this prevents default and propogation + } + + dateOfMonthChanged(event) { + $(event.currentTarget).toggleClass("selected"); + this.current_rule.str = null; + if (!this.current_rule.hash) { this.current_rule.hash = {} }; + this.current_rule.hash.validations = {}; + const raw_days = this.content.find(".monthly_options .rs_calendar_day a.selected").map(function() { + return $(this).text() === $.fn.recurring_select.texts["last_day"] ? -1 : parseInt($(this).text()); + }); + this.current_rule.hash.validations.day_of_week = {}; + this.current_rule.hash.validations.day_of_month = raw_days.get(); + this.summaryUpdate(); + return false; + } + + weekOfMonthChanged(event) { + $(event.currentTarget).toggleClass("selected"); + this.current_rule.str = null; + 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.find(".monthly_options .rs_calendar_week a.selected").each((index, elm) => { + const day = parseInt($(elm).attr("day")); + const instance = parseInt($(elm).attr("instance")); + 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; + } + +// ========================= Change callbacks =============================== + + template() { + let str = `\ +
\ +
\ +
\ +

${$.fn.recurring_select.texts["repeat"]}

\ +

\ + \ + \ +

\ + \ +
\ +

\ + ${$.fn.recurring_select.texts["every"]} \ + \ + ${$.fn.recurring_select.texts["days"]} \ +

\ +
\ +
\ +

\ + ${$.fn.recurring_select.texts["every"]} \ + \ + ${$.fn.recurring_select.texts["weeks_on"]}: \ +

\ +
\ + `; + for (let i = $.fn.recurring_select.texts["first_day_of_week"], day_of_week = i, end = 7 + $.fn.recurring_select.texts["first_day_of_week"], asc = $.fn.recurring_select.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 += `${$.fn.recurring_select.texts["days_first_letter"][day_of_week]}`; + }; + + str += `\ +
\ + . \ +
\ +
\ +

\ + ${$.fn.recurring_select.texts["every"]} \ + \ + ${$.fn.recurring_select.texts["months"]}: \ +

\ +

\ + \ + \ +

\ +

\ +

\ +
\ +
\ +

\ + ${$.fn.recurring_select.texts["every"]} \ + \ + ${$.fn.recurring_select.texts["years"]} \ +

\ +
\ +

\ + \ +

\ +
\ + \ + \ +
\ +
\ +
\ +
\ + `; + + return str; + } + }); From 828927d4e89fa316fc455f0d367f88def85cf33f Mon Sep 17 00:00:00 2001 From: glaszig Date: Mon, 4 Sep 2023 20:49:45 -0300 Subject: [PATCH 09/24] remove coffee-rails dependency --- recurring_select.gemspec | 1 - 1 file changed, 1 deletion(-) diff --git a/recurring_select.gemspec b/recurring_select.gemspec index e6b04ca..848f36e 100644 --- a/recurring_select.gemspec +++ b/recurring_select.gemspec @@ -18,7 +18,6 @@ Gem::Specification.new do |s| s.add_dependency "jquery-rails", ">= 3.0" s.add_dependency "ice_cube", ">= 0.11" s.add_dependency "sass-rails", ">= 6.0" - s.add_dependency "coffee-rails", ">= 5.0" s.add_development_dependency "bundler", ">= 1.3.5" s.add_development_dependency "rspec-rails", ">= 2.14" From 364f156c8ac3dccfa787605c599f8c876b57a9fa Mon Sep 17 00:00:00 2001 From: glaszig Date: Fri, 1 Sep 2023 05:27:29 -0300 Subject: [PATCH 10/24] eliminate jquery --- Gemfile | 2 - README.md | 9 +- app/assets/javascripts/defaults.js | 31 + app/assets/javascripts/jquery-mobile-rs.js | 2 + app/assets/javascripts/recurring_select.js | 119 +-- app/assets/javascripts/recurring_select/fr.js | 4 +- .../recurring_select_dialog.js.erb | 846 +++++++++--------- app/assets/javascripts/utils.js | 70 ++ recurring_select.gemspec | 1 - .../app/assets/javascripts/application.js | 10 +- spec/gemfiles/rails-7 | 2 - 11 files changed, 595 insertions(+), 501 deletions(-) create mode 100644 app/assets/javascripts/defaults.js create mode 100644 app/assets/javascripts/utils.js diff --git a/Gemfile b/Gemfile index 86083a3..b811aae 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,5 @@ source "https://rubygems.org" gemspec -gem "jquery-rails" - gem "thin" gem "rails", "~> 6.1" diff --git a/README.md b/README.md index 8a7c99e..ed5f80b 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ gem 'recurring_select' - application.css: `//= require recurring_select` #### jQuery Mobile Interface: +- application.js: `//= require jquery` - application.js: `//= require jquery-mobile-rs` - application.css: `//= require jquery-mobile-rs` @@ -99,8 +100,8 @@ You have to translate JavaScript texts too by including the locale file in your For other languages include a JavaScript file like this: -```coffeescript -$.fn.recurring_select.texts = { +```js +RecurringSelectDialog.config.texts = { locale_iso_code: "fr" repeat: "Repeat" frequency: "Frequency" @@ -125,8 +126,8 @@ $.fn.recurring_select.texts = { Options include: -```coffeescript -$.fn.recurring_select.options = { +```js +RecurringSelectDialog.config.options = { monthly: { show_week: [true, true, true, true, false, false] //display week 1, 2 .... Last } diff --git a/app/assets/javascripts/defaults.js b/app/assets/javascripts/defaults.js new file mode 100644 index 0000000..3386fad --- /dev/null +++ b/app/assets/javascripts/defaults.js @@ -0,0 +1,31 @@ +const defaultConfig = { + options: { + monthly: { + show_week: [true, true, true, true, false, false] + } + }, + texts: { + locale_iso_code: "en", + repeat: "Repeat", + last_day: "Last Day", + frequency: "Frequency", + daily: "Daily", + weekly: "Weekly", + monthly: "Monthly", + yearly: "Yearly", + every: "Every", + days: "day(s)", + weeks_on: "week(s) on", + months: "month(s)", + years: "year(s)", + day_of_month: "Day of month", + day_of_week: "Day of week", + cancel: "Cancel", + ok: "OK", + summary: "Summary", + first_day_of_week: 0, + days_first_letter: ["S", "M", "T", "W", "T", "F", "S" ], + order: ["1st", "2nd", "3rd", "4th", "5th", "Last"], + show_week: [true, true, true, true, false, false] + } +} diff --git a/app/assets/javascripts/jquery-mobile-rs.js b/app/assets/javascripts/jquery-mobile-rs.js index 5cac55c..2cd75ee 100644 --- a/app/assets/javascripts/jquery-mobile-rs.js +++ b/app/assets/javascripts/jquery-mobile-rs.js @@ -1,6 +1,8 @@ //= require recurring_select //= require_self +const $ = jQuery + $(function() { $(document).on("recurring_select:cancel recurring_select:save", ".recurring_select", function() { $(this).selectmenu('refresh'); diff --git a/app/assets/javascripts/recurring_select.js b/app/assets/javascripts/recurring_select.js index c6539c5..1649127 100644 --- a/app/assets/javascripts/recurring_select.js +++ b/app/assets/javascripts/recurring_select.js @@ -1,125 +1,96 @@ //= require recurring_select_dialog //= require_self -const $ = jQuery; -$(function() { - $(document).on("focus", ".recurring_select", function() { - $(this).recurring_select('set_initial_values'); - }); +document.addEventListener("DOMContentLoaded", () => { + document.addEventListener("focusin", (e) => { + if (e.target.matches(".recurring_select")) { + recurring_select.call(e.target, "set_initial_values") + } + }) - $(document).on("change", ".recurring_select", function() { - $(this).recurring_select('changed'); - }); -}); + document.addEventListener("input", (e) => { + if (e.target.matches(".recurring_select")) { + recurring_select.call(e.target, "changed") + } + }) +}) const methods = { set_initial_values() { - this.data('initial-value-hash', this.val()); - this.data('initial-value-str', $(this.find("option").get()[this.prop("selectedIndex")]).text()); + const str = this.querySelectorAll('option')[this.selectedIndex].textContent + this.setAttribute('data-initial-value-hash', this.value); + this.setAttribute('data-initial-value-str', str); }, changed() { - if (this.val() === "custom") { - methods.open_custom.apply(this); + if (this.value == "custom") { + methods.open.call(this); } else { - methods.set_initial_values.apply(this); + methods.set_initial_values.call(this); } }, - open_custom() { - this.data("recurring-select-active", true); + open() { + this.setAttribute("data-recurring-select-active", true); new RecurringSelectDialog(this); this.blur(); }, save(new_rule) { - this.find("option[data-custom]").remove(); - const new_json_val = JSON.stringify(new_rule.hash); + this.querySelectorAll("option[data-custom]").forEach((el) => el.parentNode.removeChild(el) ) + const new_json_val = JSON.stringify(new_rule.hash) // TODO: check for matching name, and replace that value if found - if ($.inArray(new_json_val, this.find("option").map(function() { $(this).val(); })) === -1) { - methods.insert_option.apply(this, [new_rule.str, new_json_val]); + const options = Array.from(this.querySelectorAll("option")).map(() => this.value) + if (!options.includes(new_json_val)) { + methods.insert_option.apply(this, [new_rule.str, new_json_val]) } - this.val(new_json_val); - methods.set_initial_values.apply(this); - this.trigger("recurring_select:save"); + this.value = new_json_val + methods.set_initial_values.apply(this) + this.dispatchEvent(new CustomEvent("recurring_select:save")) }, current_rule() { return { - str: this.data("initial-value-str"), - hash: $.parseJSON(this.data("initial-value-hash")) + str: this.getAttribute("data-initial-value-str"), + hash: JSON.parse(this.getAttribute("data-initial-value-hash")) }; }, cancel() { - this.val(this.data("initial-value-hash")); - this.data("recurring-select-active", false); - this.trigger("recurring_select:cancel"); + this.value = this.getAttribute("data-initial-value-hash") + this.setAttribute("data-recurring-select-active", false); + this.dispatchEvent(new CustomEvent("recurring_select:cancel")) }, insert_option(new_rule_str, new_rule_json) { - let separator = this.find("option:disabled"); + let separator = this.querySelectorAll("option[disabled]"); if (separator.length === 0) { - separator = this.find("option"); + separator = this.querySelectorAll("option"); } - separator = separator.last(); + separator = separator[separator.length-1] - const new_option = $(document.createElement("option")); - new_option.attr("data-custom", true); + const new_option = document.createElement("option") + new_option.setAttribute("data-custom", true); if (new_rule_str.substr(new_rule_str.length - 1) !== "*") { new_rule_str+="*"; } - new_option.text(new_rule_str); - new_option.val(new_rule_json); - new_option.insertBefore(separator); - }, - - methods() { - return methods; + new_option.textContent = new_rule_str + new_option.value = new_rule_json + separator.parentNode.insertBefore(new_option, separator) } }; -$.fn.recurring_select = function(method) { +function recurring_select(method) { + this['recurring_select'] = this['recurring_select'] || recurring_select.bind(this) if (method in methods) { return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) ); } else { - $.error( `Method ${method} does not exist on jQuery.recurring_select` ); - } -}; - -$.fn.recurring_select.options = { - monthly: { - show_week: [true, true, true, true, false, false] + throw new Error( `Method ${method} does not exist on jQuery.recurring_select` ); } -}; - -$.fn.recurring_select.texts = { - locale_iso_code: "en", - repeat: "Repeat", - last_day: "Last Day", - frequency: "Frequency", - daily: "Daily", - weekly: "Weekly", - monthly: "Monthly", - yearly: "Yearly", - every: "Every", - days: "day(s)", - weeks_on: "week(s) on", - months: "month(s)", - years: "year(s)", - day_of_month: "Day of month", - day_of_week: "Day of week", - cancel: "Cancel", - ok: "OK", - summary: "Summary", - first_day_of_week: 0, - days_first_letter: ["S", "M", "T", "W", "T", "F", "S" ], - order: ["1st", "2nd", "3rd", "4th", "5th", "Last"], - show_week: [true, true, true, true, false, false] -}; +} diff --git a/app/assets/javascripts/recurring_select/fr.js b/app/assets/javascripts/recurring_select/fr.js index 94804ac..f092a38 100644 --- a/app/assets/javascripts/recurring_select/fr.js +++ b/app/assets/javascripts/recurring_select/fr.js @@ -1,4 +1,4 @@ -$.fn.recurring_select.texts = { +RecurringSelectDialog.config.texts = { locale_iso_code: "fr" repeat: "Récurrence" last_day: "Dernier jour" @@ -21,4 +21,4 @@ $.fn.recurring_select.texts = { days_first_letter: ["D", "L", "M", "M", "J", "V", "S" ] order: ["1er", "2ème", "3ème", "4ème", "5ème", "Dernier"] show_week: [true, true, true, true, false, false] -} \ No newline at end of file +} diff --git a/app/assets/javascripts/recurring_select_dialog.js.erb b/app/assets/javascripts/recurring_select_dialog.js.erb index 89c95db..32f58c0 100644 --- a/app/assets/javascripts/recurring_select_dialog.js.erb +++ b/app/assets/javascripts/recurring_select_dialog.js.erb @@ -1,449 +1,477 @@ -window.RecurringSelectDialog = - (RecurringSelectDialog = class RecurringSelectDialog { - constructor(recurring_selector) { - this.positionDialogVert = this.positionDialogVert.bind(this); - this.cancel = this.cancel.bind(this); - this.outerCancel = this.outerCancel.bind(this); - this.save = this.save.bind(this); - this.summaryUpdate = this.summaryUpdate.bind(this); - this.summaryFetchSuccess = this.summaryFetchSuccess.bind(this); - this.init_calendar_days = this.init_calendar_days.bind(this); - this.init_calendar_weeks = this.init_calendar_weeks.bind(this); - this.toggle_month_view = this.toggle_month_view.bind(this); - this.freqChanged = this.freqChanged.bind(this); - this.intervalChanged = this.intervalChanged.bind(this); - this.daysChanged = this.daysChanged.bind(this); - this.dateOfMonthChanged = this.dateOfMonthChanged.bind(this); - this.weekOfMonthChanged = this.weekOfMonthChanged.bind(this); - this.recurring_selector = recurring_selector; - this.current_rule = this.recurring_selector.recurring_select('current_rule'); - this.initDialogBox(); - if ((this.current_rule.hash == null) || (this.current_rule.hash.rule_type == null)) { - this.freqChanged(); - } else { - setTimeout(this.positionDialogVert, 10); // allow initial render - } +//= require utils +//= require defaults + +class RecurringSelectDialog { + static config = defaultConfig + + constructor(recurring_selector) { + this.config = this.constructor.config + this.positionDialogVert = this.positionDialogVert.bind(this); + this.cancel = this.cancel.bind(this); + this.outerCancel = this.outerCancel.bind(this); + this.save = this.save.bind(this); + this.summaryUpdate = this.summaryUpdate.bind(this); + this.summaryFetchSuccess = this.summaryFetchSuccess.bind(this); + this.init_calendar_days = this.init_calendar_days.bind(this); + this.init_calendar_weeks = this.init_calendar_weeks.bind(this); + this.toggle_month_view = this.toggle_month_view.bind(this); + this.freqChanged = this.freqChanged.bind(this); + this.intervalChanged = this.intervalChanged.bind(this); + this.daysChanged = this.daysChanged.bind(this); + this.dateOfMonthChanged = this.dateOfMonthChanged.bind(this); + this.weekOfMonthChanged = this.weekOfMonthChanged.bind(this); + this.recurring_selector = recurring_selector; + this.current_rule = this.recurring_selector.recurring_select('current_rule'); + this.initDialogBox(); + if ((this.current_rule.hash == null) || (this.current_rule.hash.rule_type == null)) { + this.freqChanged(); + } else { + setTimeout(this.positionDialogVert, 10); // allow initial render } - - initDialogBox() { - $(".rs_dialog_holder").remove(); - - let open_in = $("body"); - if ($(".ui-page-active").length) { open_in = $(".ui-page-active"); } - open_in.append(this.template()); - this.outer_holder = $(".rs_dialog_holder"); - this.inner_holder = this.outer_holder.find(".rs_dialog"); - this.content = this.outer_holder.find(".rs_dialog_content"); - this.positionDialogVert(true); - this.mainEventInit(); - this.freqInit(); - this.summaryInit(); - this.outer_holder.trigger("recurring_select:dialog_opened"); - this.freq_select.focus(); + } + + initDialogBox() { + document.querySelectorAll(".rs_dialog_holder").forEach(el => el.parentNode.removeChild(el)) + + const uiPage = document.querySelector('.ui-page-active') + const anchor = uiPage ? uiPage : document.body + + const div = document.createElement("div") + div.innerHTML = this.template() + anchor.appendChild(div.children[0]) + + this.outer_holder = document.querySelector(".rs_dialog_holder"); + this.inner_holder = this.outer_holder.querySelector(".rs_dialog"); + this.content = this.outer_holder.querySelector(".rs_dialog_content"); + + this.positionDialogVert(true); + this.mainEventInit(); + this.freqInit(); + this.summaryInit(); + trigger(this.outer_holder, "recurring_select:dialog_opened"); + this.freq_select.focus(); + } + + positionDialogVert(initial_positioning) { + const window_height = window.innerHeight; + const window_width = window.innerWidth; + const dialog_height = Math.max(80, this.content.offsetHeight) + const margin_top = Math.max(10, (window_height - dialog_height)/2 - 30) + + const new_style_hash = { + marginTop: `${margin_top}px`, + minHeight: `${dialog_height}px` } - positionDialogVert(initial_positioning) { - const window_height = $(window).height(); - const window_width = $(window).width(); - let dialog_height = this.content.outerHeight(); - if (dialog_height < 80) { - dialog_height = 80; - } - let margin_top = ((window_height - dialog_height)/2) - 30; - if (margin_top < 10) { margin_top = 10; } - // if dialog_height > window_height - 20 - // dialog_height = window_height - 20 - - const new_style_hash = { - "margin-top" : margin_top+"px", - "min-height" : dialog_height+"px" - }; - - if (initial_positioning != null) { - this.inner_holder.css(new_style_hash); - this.inner_holder.trigger("recurring_select:dialog_positioned"); - } else { - this.inner_holder.addClass("animated"); - this.inner_holder.animate(new_style_hash, 200, () => { - this.inner_holder.removeClass("animated"); - this.content.css({"width": "auto"}); - this.inner_holder.trigger("recurring_select:dialog_positioned"); - }); - } + if (initial_positioning) { + css(this.inner_holder, new_style_hash) + trigger(this.inner_holder, "recurring_select:dialog_positioned") + } else { + css(this.inner_holder, new_style_hash) + trigger(this.inner_holder, "recurring_select:dialog_positioned") + // this.inner_holder.animate(new_style_hash, 200, () => { + // this.inner_holder.classList.remove("animated"); + // this.content.css({"width": "auto"}); + // this.inner_holder.trigger("recurring_select:dialog_positioned"); + // }); } + } - cancel() { - this.outer_holder.remove(); - this.recurring_selector.recurring_select('cancel'); - } + cancel() { + this.outer_holder.remove(); + this.recurring_selector.recurring_select('cancel'); + } - outerCancel(event) { - if ($(event.target).hasClass("rs_dialog_holder")) { - this.cancel(); - } + outerCancel(event) { + if (event.target.classList.contains("rs_dialog_holder")) { + this.cancel(); } + } - save() { - if ((this.current_rule.str == null)) { return; } - this.outer_holder.remove(); - this.recurring_selector.recurring_select('save', this.current_rule); - } + save() { + if ((this.current_rule.str == null)) { return; } + this.outer_holder.remove(); + this.recurring_selector.recurring_select('save', this.current_rule); + } // ========================= Init Methods =============================== - mainEventInit() { - // Tap hooks are for jQueryMobile - this.outer_holder.on('click tap', this.outerCancel); - this.content.on('click tap', 'h1 a', this.cancel); - this.save_button = this.content.find('input.rs_save').on("click tap", this.save); - this.content.find('input.rs_cancel').on("click tap", this.cancel); - } - - freqInit() { - this.freq_select = this.outer_holder.find(".rs_frequency"); - const rule_type = this.current_rule.hash?.rule_type - if (this.current_rule.hash != null && rule_type != null) { - if (rule_type.search(/Weekly/) !== -1) { - this.freq_select.prop('selectedIndex', 1); - this.initWeeklyOptions(); - } else if (rule_type.search(/Monthly/) !== -1) { - this.freq_select.prop('selectedIndex', 2); - this.initMonthlyOptions(); - } else if (rule_type.search(/Yearly/) !== -1) { - this.freq_select.prop('selectedIndex', 3); - this.initYearlyOptions(); - } else { - this.initDailyOptions(); - } - } - this.freq_select.on("change", this.freqChanged); - } - - initDailyOptions() { - const section = this.content.find('.daily_options'); - const interval_input = section.find('.rs_daily_interval'); - interval_input.val(this.current_rule.hash.interval); - interval_input.on("change keyup", this.intervalChanged); - section.show(); - } - - initWeeklyOptions() { - const section = this.content.find('.weekly_options'); - - // connect the interval field - const interval_input = section.find('.rs_weekly_interval'); - interval_input.val(this.current_rule.hash.interval); - interval_input.on("change keyup", this.intervalChanged); - - // clear selected days - section.find(".day_holder a").each(function(index, element) { - $(element).removeClass("selected"); - }); - - // connect the day fields - if ((this.current_rule.hash.validations != null) && (this.current_rule.hash.validations.day != null)) { - $(this.current_rule.hash.validations.day).each(function(index, val) { - section.find(".day_holder a[data-value='"+val+"']").addClass("selected"); - }); + mainEventInit() { + // Tap hooks are for jQueryMobile + on(this.outer_holder, 'click tap', this.outerCancel); + on(this.content, 'click tap', 'h1 a', this.cancel); + this.save_button = this.content.querySelector('input.rs_save') + on(this.save_button, "click tap", this.save) + on(this.content.querySelector('input.rs_cancel'), "click tap", this.cancel) + } + + freqInit() { + this.freq_select = this.outer_holder.querySelector(".rs_frequency"); + const rule_type = this.current_rule.hash?.rule_type + if (this.current_rule.hash != null && rule_type != null) { + if (rule_type.search(/Weekly/) !== -1) { + this.freq_select.selectedIndex = 1 + this.initWeeklyOptions(); + } else if (rule_type.search(/Monthly/) !== -1) { + this.freq_select.selectedIndex = 2 + this.initMonthlyOptions(); + } else if (rule_type.search(/Yearly/) !== -1) { + this.freq_select.selectedIndex = 3 + this.initYearlyOptions(); + } else { + this.initDailyOptions(); } - - section.off('click', '.day_holder a').on("click", ".day_holder a", this.daysChanged); - - section.show(); } - - initMonthlyOptions() { - const section = this.content.find('.monthly_options'); - const interval_input = section.find('.rs_monthly_interval'); - interval_input.val(this.current_rule.hash.interval); - interval_input.on("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 = {} }; - this.init_calendar_days(section); - this.init_calendar_weeks(section); - - const in_week_mode = Object.keys(this.current_rule.hash.validations.day_of_week).length > 0; - section.find(".monthly_rule_type_week").prop("checked", in_week_mode); - section.find(".monthly_rule_type_day").prop("checked", !in_week_mode); - this.toggle_month_view(); - section.find("input[name=monthly_rule_type]").on("change", this.toggle_month_view); - section.show(); + on(this.freq_select, "change", this.freqChanged); + } + + initDailyOptions() { + const section = this.content.querySelector('.daily_options') + const interval_input = section.querySelector('.rs_daily_interval') + interval_input.value = this.current_rule.hash.interval + on(interval_input, "change keyup", this.intervalChanged); + section.style.display = 'block' + } + + initWeeklyOptions() { + const section = this.content.querySelector('.weekly_options'); + + // connect the interval field + const interval_input = section.querySelector('.rs_weekly_interval'); + interval_input.value = this.current_rule.hash.interval + on(interval_input, "change keyup", this.intervalChanged); + + // clear selected days + section.querySelectorAll(".day_holder a").forEach(el => + el.classList.remove("selected") + ) + + // connect the day fields + if ((this.current_rule.hash.validations != null) && (this.current_rule.hash.validations.day != null)) { + Array.from(this.current_rule.hash.validations.day).forEach((val) => + section.querySelector(".day_holder a[data-value='"+val+"']").classList.add("selected") + ) } - initYearlyOptions() { - const section = this.content.find('.yearly_options'); - const interval_input = section.find('.rs_yearly_interval'); - interval_input.val(this.current_rule.hash.interval); - interval_input.on("change keyup", this.intervalChanged); - section.show(); - } - - - summaryInit() { - this.summary = this.outer_holder.find(".rs_summary"); - this.summaryUpdate(); - } + off(section, "click") + on(section, "click", ".day_holder a", this.daysChanged) + + section.style.display = 'block' + } + + initMonthlyOptions() { + const section = this.content.querySelector('.monthly_options') + const interval_input = section.querySelector('.rs_monthly_interval') + 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 = {} }; + this.init_calendar_days(section); + this.init_calendar_weeks(section); + + const in_week_mode = Object.keys(this.current_rule.hash.validations.day_of_week).length > 0; + section.querySelector(".monthly_rule_type_week").checked = in_week_mode + section.querySelector(".monthly_rule_type_day").checked = !in_week_mode; + this.toggle_month_view(); + section.querySelectorAll("input[name=monthly_rule_type]").forEach((el) => on(el, "change", this.toggle_month_view)) + section.style.display = 'block' + } + + initYearlyOptions() { + const section = this.content.querySelector('.yearly_options'); + const interval_input = section.querySelector('.rs_yearly_interval'); + interval_input.value = this.current_rule.hash.interval + on(interval_input, "change keyup", this.intervalChanged) + section.style.display = 'block' + } + + + summaryInit() { + this.summary = this.outer_holder.querySelector(".rs_summary"); + this.summaryUpdate(); + } // ========================= render methods =============================== - summaryUpdate(new_string) { - this.summary.width(this.content.width()); - if ((this.current_rule.hash != null) && (this.current_rule.str != null)) { - this.summary.removeClass("fetching"); - this.save_button.removeClass("disabled"); - let rule_str = this.current_rule.str.replace("*", ""); - if (rule_str.length < 20) { - rule_str = `${$.fn.recurring_select.texts["summary"]}: `+rule_str; - } - this.summary.find("span").html(rule_str); - } else { - this.summary.addClass("fetching"); - this.save_button.addClass("disabled"); - this.summary.find("span").html(""); - this.summaryFetch(); + summaryUpdate(new_string) { + // this.summary.style.width = `${this.content.getBoundingClientRect().width}px`; + if ((this.current_rule.hash != null) && (this.current_rule.str != null)) { + this.summary.classList.remove("fetching"); + this.save_button.classList.remove("disabled"); + let rule_str = this.current_rule.str.replace("*", ""); + if (rule_str.length < 20) { + rule_str = `${this.config.texts["summary"]}: `+rule_str; } + this.summary.querySelector("span").textContent = rule_str + } else { + this.summary.classList.add("fetching"); + this.save_button.classList.add("disabled"); + this.summary.querySelector("span").textContent = "" + this.summaryFetch(); } - - summaryFetch() { - if (!(this.current_rule.hash != null && this.current_rule.hash.rule_type != null)) { return; } - this.current_rule.hash['week_start'] = $.fn.recurring_select.texts["first_day_of_week"]; - $.ajax({ - url: `<%= Rails.application.config.action_controller.relative_url_root %>/recurring_select/translate/${$.fn.recurring_select.texts["locale_iso_code"]}`, - type: "POST", - data: this.current_rule.hash, - success: this.summaryFetchSuccess - }); - } - - summaryFetchSuccess(data) { - this.current_rule.str = data; - this.summaryUpdate(); - this.content.css({"width": "auto"}); - } - - init_calendar_days(section) { - const monthly_calendar = section.find(".rs_calendar_day"); - monthly_calendar.html(""); - for (let num = 1; num <= 31; num++) { - const day_link = $(document.createElement("a")).text(num); - monthly_calendar.append(day_link); - if ($.inArray(num, this.current_rule.hash.validations.day_of_month) !== -1) { - day_link.addClass("selected"); - } - }; - - // add last day of month button - const end_of_month_link = $(document.createElement("a")).text($.fn.recurring_select.texts["last_day"]) - monthly_calendar.append(end_of_month_link); - end_of_month_link.addClass("end_of_month"); - if ($.inArray(-1, this.current_rule.hash.validations.day_of_month) !== -1) { - end_of_month_link.addClass("selected"); + } + + summaryFetch() { + if (!(this.current_rule.hash != null && this.current_rule.hash.rule_type != null)) { return; } + this.current_rule.hash['week_start'] = this.config.texts["first_day_of_week"]; + + const url = `<%= Rails.application.config.action_controller.relative_url_root %>/recurring_select/translate/${this.config.texts["locale_iso_code"]}` + const headers = { 'X-Requested-With' : 'XMLHttpRequest', 'Content-Type' : 'application/x-www-form-urlencoded' } + const body = serialize(this.current_rule.hash) + console.log(this.current_rule.hash, body) + + fetch(url, { method: "POST", body, headers }) + .then(r => r.text()) + .then(this.summaryFetchSuccess) + } + + summaryFetchSuccess(data) { + this.current_rule.str = data + this.summaryUpdate() + css(this.content, { width: "auto" }) + } + + init_calendar_days(section) { + const monthly_calendar = section.querySelector(".rs_calendar_day"); + monthly_calendar.innerHTML = ""; + for (let num = 1; num <= 31; num++) { + const day_link = document.createElement("a") + day_link.innerText = num + monthly_calendar.appendChild(day_link) + if (Array.from(this.current_rule.hash.validations.day_of_month).includes(num)) { + day_link.classList.add("selected"); } - - monthly_calendar.find("a").on("click tap", this.dateOfMonthChanged); + }; + + // add last day of month button + const end_of_month_link = document.createElement("a") + end_of_month_link.innerText = this.config.texts["last_day"] + monthly_calendar.appendChild(end_of_month_link); + end_of_month_link.classList.add("end_of_month"); + if (Array.from(this.current_rule.hash.validations.day_of_month).includes(-1)) { + end_of_month_link.classList.add("selected"); } - init_calendar_weeks(section) { - const monthly_calendar = section.find(".rs_calendar_week"); - monthly_calendar.html(""); - const row_labels = $.fn.recurring_select.texts["order"]; - const show_row = $.fn.recurring_select.options["monthly"]["show_week"]; - const cell_str = $.fn.recurring_select.texts["days_first_letter"]; - - const iterable = [1, 2, 3, 4, 5, -1] - for (let index = 0; index < iterable.length; index++) { - const num = iterable[index]; - if (show_row[index]) { - monthly_calendar.append(`${row_labels[num - 1]}`); - for (let i = $.fn.recurring_select.texts["first_day_of_week"], day_of_week = i, end = 7 + $.fn.recurring_select.texts["first_day_of_week"], asc = $.fn.recurring_select.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; - const day_link = $("", {text: cell_str[day_of_week] }); - day_link.attr("day", day_of_week); - day_link.attr("instance", num); - monthly_calendar.append(day_link); - }; - } - }; - - $.each(this.current_rule.hash.validations.day_of_week, function(key, value) { - $.each(value, function(index, instance) { - section.find(`a[day='${key}'][instance='${instance}']`).addClass("selected"); - }); - }); - monthly_calendar.find("a").on("click tap", this.weekOfMonthChanged); - } - - toggle_month_view() { - const week_mode = this.content.find(".monthly_rule_type_week").prop("checked"); - this.content.find(".rs_calendar_week").toggle(week_mode); - this.content.find(".rs_calendar_day").toggle(!week_mode); + off(monthly_calendar, "click tap") + on(monthly_calendar, "click tap", "a", this.dateOfMonthChanged) + } + + init_calendar_weeks(section) { + const monthly_calendar = section.querySelector(".rs_calendar_week") + monthly_calendar.innerHTML = "" + const row_labels = this.config.texts["order"]; + const show_row = this.config.options["monthly"]["show_week"]; + const cell_str = this.config.texts["days_first_letter"]; + + const iterable = [1, 2, 3, 4, 5, -1] + for (let index = 0; index < iterable.length; index++) { + const num = iterable[index]; + if (show_row[index]) { + const el = document.createElement("span") + el.innerText = row_labels[index] + monthly_calendar.appendChild(el); + 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; + const day_link = document.createElement("a") + day_link.innerText = cell_str[day_of_week] + 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) => { + section.querySelector(`a[day='${key}'][instance='${instance}']`).classList.add("selected") + }) + }) + + off(monthly_calendar, "click tap") + on(monthly_calendar, "click tap", "a", this.weekOfMonthChanged) + } + + toggle_month_view() { + const week_mode = this.content.querySelector(".monthly_rule_type_week").checked + if (week_mode) { + this.content.querySelector(".rs_calendar_week").style.display = "block" + this.content.querySelector(".rs_calendar_day").style.display = "none" + } else { + this.content.querySelector(".rs_calendar_week").style.display = "none" + this.content.querySelector(".rs_calendar_day").style.display = "block" } + } // ========================= Change callbacks =============================== - freqChanged() { - if (!$.isPlainObject(this.current_rule.hash)) { this.current_rule.hash = null; } // for custom values - - if (!this.current_rule.hash) { this.current_rule.hash = {} }; + freqChanged() { + if (isPlainObject(this.current_rule.hash)) { this.current_rule.hash = null; } // for custom values + + if (!this.current_rule.hash) { this.current_rule.hash = {} }; + 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 + switch (this.freq_select.value) { + case "Weekly": + this.current_rule.hash.rule_type = "IceCube::WeeklyRule"; + this.current_rule.str = this.config.texts["weekly"]; + this.initWeeklyOptions(); + break + case "Monthly": + this.current_rule.hash.rule_type = "IceCube::MonthlyRule"; + this.current_rule.str = this.config.texts["monthly"]; + this.initMonthlyOptions(); + break + case "Yearly": + this.current_rule.hash.rule_type = "IceCube::YearlyRule"; + this.current_rule.str = this.config.texts["yearly"]; + this.initYearlyOptions(); + break + default: + this.current_rule.hash.rule_type = "IceCube::DailyRule"; + this.current_rule.str = this.config.texts["daily"]; + this.initDailyOptions(); + }; + this.summaryUpdate(); + this.positionDialogVert(); + } + + intervalChanged(event) { + this.current_rule.str = null; + 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; - this.current_rule.hash.until = null; - this.current_rule.hash.count = null; - this.current_rule.hash.validations = null; - this.content.find(".freq_option_section").hide();; - this.content.find("input[type=radio], input[type=checkbox]").prop("checked", false); - switch (this.freq_select.val()) { - case "Weekly": - this.current_rule.hash.rule_type = "IceCube::WeeklyRule"; - this.current_rule.str = $.fn.recurring_select.texts["weekly"]; - this.initWeeklyOptions(); - break - case "Monthly": - this.current_rule.hash.rule_type = "IceCube::MonthlyRule"; - this.current_rule.str = $.fn.recurring_select.texts["monthly"]; - this.initMonthlyOptions(); - break - case "Yearly": - this.current_rule.hash.rule_type = "IceCube::YearlyRule"; - this.current_rule.str = $.fn.recurring_select.texts["yearly"]; - this.initYearlyOptions(); - break - default: - this.current_rule.hash.rule_type = "IceCube::DailyRule"; - this.current_rule.str = $.fn.recurring_select.texts["daily"]; - this.initDailyOptions(); - }; - this.summaryUpdate(); - this.positionDialogVert(); - } - - intervalChanged(event) { - this.current_rule.str = null; - if (!this.current_rule.hash) { this.current_rule.hash = {} }; - this.current_rule.hash.interval = parseInt($(event.currentTarget).val()); - if ((this.current_rule.hash.interval < 1) || isNaN(this.current_rule.hash.interval)) { - this.current_rule.hash.interval = 1; - } - this.summaryUpdate(); - } - - daysChanged(event) { - $(event.currentTarget).toggleClass("selected"); - this.current_rule.str = null; - if (!this.current_rule.hash) { this.current_rule.hash = {} }; - this.current_rule.hash.validations = {}; - const raw_days = this.content.find(".day_holder a.selected").map(function() { return parseInt($(this).data("value")); }); - this.current_rule.hash.validations.day = raw_days.get(); - this.summaryUpdate(); - return false; // this prevents default and propogation - } - - dateOfMonthChanged(event) { - $(event.currentTarget).toggleClass("selected"); - this.current_rule.str = null; - if (!this.current_rule.hash) { this.current_rule.hash = {} }; - this.current_rule.hash.validations = {}; - const raw_days = this.content.find(".monthly_options .rs_calendar_day a.selected").map(function() { - return $(this).text() === $.fn.recurring_select.texts["last_day"] ? -1 : parseInt($(this).text()); - }); - this.current_rule.hash.validations.day_of_week = {}; - this.current_rule.hash.validations.day_of_month = raw_days.get(); - this.summaryUpdate(); - return false; } - - weekOfMonthChanged(event) { - $(event.currentTarget).toggleClass("selected"); - this.current_rule.str = null; - 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.find(".monthly_options .rs_calendar_week a.selected").each((index, elm) => { - const day = parseInt($(elm).attr("day")); - const instance = parseInt($(elm).attr("instance")); + this.summaryUpdate(); + } + + daysChanged(event) { + event.target.classList.toggle("selected"); + this.current_rule.str = null; + 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)) + this.current_rule.hash.validations.day = raw_days + this.summaryUpdate(); + return false; + } + + dateOfMonthChanged(event) { + event.target.classList.toggle("selected"); + this.current_rule.str = null; + 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 => { + return el.innerText === this.config.texts["last_day"] ? -1 : parseInt(el.innerText) + }) + this.current_rule.hash.validations.day_of_week = {}; + this.current_rule.hash.validations.day_of_month = raw_days; + this.summaryUpdate(); + return false; + } + + weekOfMonthChanged(event) { + event.target.classList.toggle("selected"); + this.current_rule.str = null; + 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] = [] }; return this.current_rule.hash.validations.day_of_week[day].push(instance); - }); - this.summaryUpdate(); - return false; - } + }) + this.summaryUpdate(); + return false; + } // ========================= Change callbacks =============================== - template() { - let str = `\ -
\ -
\ -
\ -

${$.fn.recurring_select.texts["repeat"]}

\ -

\ - \ - \ + template() { + let str = `\ +

\ +
\ +
\ +

${this.config.texts["repeat"]}

\ +

\ + \ + \ +

\ + \ +
\ +

\ + ${this.config.texts["every"]} \ + \ + ${this.config.texts["days"]} \

\ - \ -
\ -

\ - ${$.fn.recurring_select.texts["every"]} \ - \ - ${$.fn.recurring_select.texts["days"]} \ -

\ -
\ -
\ -

\ - ${$.fn.recurring_select.texts["every"]} \ - \ - ${$.fn.recurring_select.texts["weeks_on"]}: \ -

\ -
\ - `; - for (let i = $.fn.recurring_select.texts["first_day_of_week"], day_of_week = i, end = 7 + $.fn.recurring_select.texts["first_day_of_week"], asc = $.fn.recurring_select.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 += `${$.fn.recurring_select.texts["days_first_letter"][day_of_week]}`; - }; - - str += `\ -
\ - . \ -
\ -
\ -

\ - ${$.fn.recurring_select.texts["every"]} \ - \ - ${$.fn.recurring_select.texts["months"]}: \ -

\ -

\ - \ - \ -

\ -

\ -

\ -
\ -
\ -

\ - ${$.fn.recurring_select.texts["every"]} \ - \ - ${$.fn.recurring_select.texts["years"]} \ -

\ -
\ -

\ - \ +

\ +
\ +

\ + ${this.config.texts["every"]} \ + \ + ${this.config.texts["weeks_on"]}: \

\ -
\ - \ - \ +
\ + `; + 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 += `\
\ + . \ +
\ +
\ +

\ + ${this.config.texts["every"]} \ + \ + ${this.config.texts["months"]}: \ +

\ +

\ + \ + \ +

\ +

\ +

\ +
\ +
\ +

\ + ${this.config.texts["every"]} \ + \ + ${this.config.texts["years"]} \ +

\ +
\ +

\ + \ +

\ +
\ + \ + \
\
\ -
\ - `; +
\ +
\ + `; - return str; - } - }); + return str; + } +} + +window.RecurringSelectDialog = RecurringSelectDialog diff --git a/app/assets/javascripts/utils.js b/app/assets/javascripts/utils.js new file mode 100644 index 0000000..9721687 --- /dev/null +++ b/app/assets/javascripts/utils.js @@ -0,0 +1,70 @@ +function css(el, styles) { + for (let rule in styles) { + el.style[rule] = styles[rule] + } +} + +function trigger(el, eventName) { + el.dispatchEvent(new CustomEvent(eventName)) +} + +function isPlainObject(obj) { + return obj && obj.toString() === "[object Object]" +} + +const eventHandlerRefsExpando = '__recurring_select_events' + +function on(el, events, sel, handler) { + let eventHandler = sel + if (handler) { + eventHandler = (e) => { + if (e.target.matches(sel)) { + if (handler.call(this, e) === false) { + e.preventDefault() + e.stopPropagation() + } + } + } + } + + el[eventHandlerRefsExpando] = el[eventHandlerRefsExpando] || [] + + events.trim().split(/ +/).forEach(type => { + el[eventHandlerRefsExpando].push([ type, eventHandler ]) + el.addEventListener(type, eventHandler) + }) +} + +function off(el, events) { + const types = events.trim().split(/ +/) + + el[eventHandlerRefsExpando] = (el[eventHandlerRefsExpando] || []) + .filter(([t, h], i) => { + if (types.includes(t)) { + el.removeEventListener(t, h) + return false + } + return true + }) +} + +function serialize(params, prefix) { + const query = Object.keys(params).map((key) => { + const value = params[key]; + + if (params.constructor === Array) + key = `${prefix}[]`; + else if (params.constructor === Object) + key = (prefix ? `${prefix}[${key}]` : key); + + if (value === null) + return `${key}=` + + if (typeof value === 'object') + return serialize(value, key); + else + return `${key}=${encodeURIComponent(value)}`; + }); + + return [].concat.apply([], query).join('&'); +} diff --git a/recurring_select.gemspec b/recurring_select.gemspec index 848f36e..21fc21d 100644 --- a/recurring_select.gemspec +++ b/recurring_select.gemspec @@ -15,7 +15,6 @@ Gem::Specification.new do |s| s.test_files = Dir["test/**/*"] s.add_dependency "rails", ">= 6.1" - s.add_dependency "jquery-rails", ">= 3.0" s.add_dependency "ice_cube", ">= 0.11" s.add_dependency "sass-rails", ">= 6.0" diff --git a/spec/dummy/app/assets/javascripts/application.js b/spec/dummy/app/assets/javascripts/application.js index 4d3fda7..5253f3a 100644 --- a/spec/dummy/app/assets/javascripts/application.js +++ b/spec/dummy/app/assets/javascripts/application.js @@ -4,13 +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 jquery -//= require jquery_ujs //= require recurring_select //= require_tree . -$.fn.recurring_select.options = { - monthly: { - show_week: [true, true, true, true, true, true] - } -}; +RecurringSelectDialog.config.options.monthly = { + show_week: [true, true, true, true, true, true] +} diff --git a/spec/gemfiles/rails-7 b/spec/gemfiles/rails-7 index 6b8eded..c6a3a43 100644 --- a/spec/gemfiles/rails-7 +++ b/spec/gemfiles/rails-7 @@ -1,7 +1,5 @@ source "https://rubygems.org" gemspec path: File.expand_path("../../..", __FILE__) -gem "jquery-rails" - gem "thin" gem "rails", "~> 7.0.0" From b974163206290b02941cd65d808ef84a981f6820 Mon Sep 17 00:00:00 2001 From: glaszig Date: Sat, 2 Sep 2023 00:50:39 -0300 Subject: [PATCH 11/24] position dialog solely via css flexbox --- .../recurring_select_dialog.js.erb | 30 ------------- app/assets/stylesheets/recurring_select.scss | 44 +++++++++++++++---- 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/app/assets/javascripts/recurring_select_dialog.js.erb b/app/assets/javascripts/recurring_select_dialog.js.erb index 32f58c0..7e280aa 100644 --- a/app/assets/javascripts/recurring_select_dialog.js.erb +++ b/app/assets/javascripts/recurring_select_dialog.js.erb @@ -6,7 +6,6 @@ class RecurringSelectDialog { constructor(recurring_selector) { this.config = this.constructor.config - this.positionDialogVert = this.positionDialogVert.bind(this); this.cancel = this.cancel.bind(this); this.outerCancel = this.outerCancel.bind(this); this.save = this.save.bind(this); @@ -25,8 +24,6 @@ class RecurringSelectDialog { this.initDialogBox(); if ((this.current_rule.hash == null) || (this.current_rule.hash.rule_type == null)) { this.freqChanged(); - } else { - setTimeout(this.positionDialogVert, 10); // allow initial render } } @@ -44,7 +41,6 @@ class RecurringSelectDialog { this.inner_holder = this.outer_holder.querySelector(".rs_dialog"); this.content = this.outer_holder.querySelector(".rs_dialog_content"); - this.positionDialogVert(true); this.mainEventInit(); this.freqInit(); this.summaryInit(); @@ -52,31 +48,6 @@ class RecurringSelectDialog { this.freq_select.focus(); } - positionDialogVert(initial_positioning) { - const window_height = window.innerHeight; - const window_width = window.innerWidth; - const dialog_height = Math.max(80, this.content.offsetHeight) - const margin_top = Math.max(10, (window_height - dialog_height)/2 - 30) - - const new_style_hash = { - marginTop: `${margin_top}px`, - minHeight: `${dialog_height}px` - } - - if (initial_positioning) { - css(this.inner_holder, new_style_hash) - trigger(this.inner_holder, "recurring_select:dialog_positioned") - } else { - css(this.inner_holder, new_style_hash) - trigger(this.inner_holder, "recurring_select:dialog_positioned") - // this.inner_holder.animate(new_style_hash, 200, () => { - // this.inner_holder.classList.remove("animated"); - // this.content.css({"width": "auto"}); - // this.inner_holder.trigger("recurring_select:dialog_positioned"); - // }); - } - } - cancel() { this.outer_holder.remove(); this.recurring_selector.recurring_select('cancel'); @@ -338,7 +309,6 @@ class RecurringSelectDialog { this.initDailyOptions(); }; this.summaryUpdate(); - this.positionDialogVert(); } intervalChanged(event) { diff --git a/app/assets/stylesheets/recurring_select.scss b/app/assets/stylesheets/recurring_select.scss index 4d5bd72..e0c66c5 100644 --- a/app/assets/stylesheets/recurring_select.scss +++ b/app/assets/stylesheets/recurring_select.scss @@ -2,7 +2,9 @@ /* -------- resets ---------------*/ -.rs_dialog_holder { font-size:14px; color:black; +.rs_dialog_holder { + font-size:14px; + color:black; a {color:black;} input[type=button] { font: small/normal Arial,sans-serif; @@ -28,10 +30,29 @@ select { option.bold {font-weight:bold; color:red;} } -.rs_dialog_holder { position:fixed; left:0px; right:0px; top:0px; bottom:0px; padding-left:50%; background-color:rgba(255,255,255,0.2); z-index:50; - .rs_dialog { background-color:#f6f6f6; border:1px solid #acacac; @include shadows(1px, 3px, 8px, rgba(0,0,0,0.25)); @include rounded_corners(7px); - display:inline-block; min-width:200px; margin-left:-125px; overflow:hidden; position:relative; - .rs_dialog_content { padding:10px; +.rs_dialog_holder { + display: flex; + justify-content: center; + align-items: center; + position: fixed; + left:0px; + right:0px; + top:0px; + bottom:0px; + background-color:rgba(255,255,255,0.2); + z-index:50; + + .rs_dialog { + background-color:#f6f6f6; + border:1px solid #acacac; + @include shadows(1px, 3px, 8px, rgba(0,0,0,0.25)); + @include rounded_corners(7px); + min-width:200px; + overflow:hidden; + + .rs_dialog_content { + padding:10px; + h1 { font-size:16px; padding:0px; margin:0 0 10px 0; a {float:right; display:inline-block; height:16px; width:16px; background-image:image-url("recurring_select/cancel.png"); background-position:center; background-repeat:no-repeat;} } @@ -42,7 +63,8 @@ select { a { -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; } - .freq_option_section { display:none; + .freq_option_section { + display:none; label { font-weight: bold; } .rs_interval {width:30px; text-align:center; display: inline-block;} @@ -77,7 +99,10 @@ select { } - .rs_summary { padding:0px; margin-top:15px; border-top:1px solid #ccc; + .rs_summary { + padding: 0px; + margin-top: 15px; + border-top: 1px solid #ccc; span {font-weight:bold; border-top:1px solid #fff; display:block; padding:10px 0 5px 0;} &.fetching {color:#999; span {background-image:image-url("recurring_select/throbber_13x13.gif"); background-position:center; background-repeat:no-repeat; display:inline-block; height:13px; width:13px; margin-top:-4px; padding-right:5px;} @@ -85,7 +110,10 @@ select { label {font-weight:normal;} } - .controls { padding:10px 0px 5px 0px; min-width:170px; text-align:center; + .controls { + padding:10px 0px 5px 0px; + min-width:170px; + text-align:center; input[type=button] { margin:0px 5px; width:70px; &.rs_save {color:#333; } &.rs_cancel {color:#666;} From 054a084730232b43d53eead9b7c984d4745832f3 Mon Sep 17 00:00:00 2001 From: glaszig Date: Sat, 2 Sep 2023 01:13:17 -0300 Subject: [PATCH 12/24] travis => ghwf --- .github/workflows/test.yml | 26 ++++++++++++++++++++++++++ .travis.yml | 10 ---------- 2 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..a5edacc --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,26 @@ +name: test + +on: [ push, pull_request ] + +jobs: + test: + name: ${{matrix.ruby}}, ${{matrix.gemfile}} + runs-on: ubuntu-latest + + strategy: + matrix: + ruby: [ '3.0', '3.1', '3.2', '3.3' ] + gemfile: + - Gemfile + - spec/gemfiles/rails-7 + + env: + BUNDLE_GEMFILE: ${{matrix.gemfile}} + + steps: + - uses: actions/checkout@v3 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{matrix.ruby}} + bundler-cache: true # 'bundle install' and cache + - run: bundle exec rspec --format documentation diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 11f2aa1..0000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: ruby -script: bundle exec rspec spec - -rvm: -- 2.5.3 -- 2.6.5 - -gemfile: -- spec/gemfiles/Gemfile.rails-5.2.x -- spec/gemfiles/Gemfile.rails-6.0.x \ No newline at end of file From 153577c9156a07ff72a2e08defc4fe7b252996c0 Mon Sep 17 00:00:00 2001 From: glaszig Date: Mon, 4 Sep 2023 20:57:37 -0300 Subject: [PATCH 13/24] update changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e60a34..f472605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 4.0.0 / upcoming + +* [BREAKING-CHANGE] Drop support for Rails < 6.1 +* [BREAKING-CHANGE] Drop support for Ruby < 3.0 +* [BREAKING-CHANGE] Convert to vanilla js, remove coffeescript dependency +* [BREAKING-CHANGE] Remove jquery dependency +* [FIX] Fix event handling when switching between frequencies +* remove databse dependency from dummy app +* move CI from Travis to GitHub + # 2.0.0 / 2015-09-24 * [BREAKING-CHANGE] Dropping support for rails 3.X - Upgrade to sass-rails 4 From 32cea9628dac7ee829c9db94d40a605c3e439d13 Mon Sep 17 00:00:00 2001 From: glaszig Date: Mon, 4 Sep 2023 21:15:52 -0300 Subject: [PATCH 14/24] stay compatible with uglify js ruby uglify gets confused with some modern js syntax --- app/assets/javascripts/recurring_select_dialog.js.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/recurring_select_dialog.js.erb b/app/assets/javascripts/recurring_select_dialog.js.erb index 7e280aa..bfcd436 100644 --- a/app/assets/javascripts/recurring_select_dialog.js.erb +++ b/app/assets/javascripts/recurring_select_dialog.js.erb @@ -2,8 +2,6 @@ //= require defaults class RecurringSelectDialog { - static config = defaultConfig - constructor(recurring_selector) { this.config = this.constructor.config this.cancel = this.cancel.bind(this); @@ -78,7 +76,7 @@ class RecurringSelectDialog { freqInit() { this.freq_select = this.outer_holder.querySelector(".rs_frequency"); - const rule_type = this.current_rule.hash?.rule_type + const rule_type = this.current_rule.hash && this.current_rule.hash.rule_type if (this.current_rule.hash != null && rule_type != null) { if (rule_type.search(/Weekly/) !== -1) { this.freq_select.selectedIndex = 1 @@ -444,4 +442,6 @@ class RecurringSelectDialog { } } +RecurringSelectDialog.config = defaultConfig + window.RecurringSelectDialog = RecurringSelectDialog From 79b32f3edf0e3b0721574e75e5da9d5934751f64 Mon Sep 17 00:00:00 2001 From: glaszig Date: Mon, 4 Sep 2023 21:55:00 -0300 Subject: [PATCH 15/24] prevent close icon button from triggering page reload --- app/assets/javascripts/recurring_select_dialog.js.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/recurring_select_dialog.js.erb b/app/assets/javascripts/recurring_select_dialog.js.erb index bfcd436..07c06e1 100644 --- a/app/assets/javascripts/recurring_select_dialog.js.erb +++ b/app/assets/javascripts/recurring_select_dialog.js.erb @@ -52,6 +52,7 @@ class RecurringSelectDialog { } outerCancel(event) { + event.preventDefault() if (event.target.classList.contains("rs_dialog_holder")) { this.cancel(); } From 163159cd3dd92349d687bcaa73b3730cc7bf00e7 Mon Sep 17 00:00:00 2001 From: glaszig Date: Mon, 4 Sep 2023 21:55:13 -0300 Subject: [PATCH 16/24] html syntax typo --- app/assets/javascripts/recurring_select_dialog.js.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/recurring_select_dialog.js.erb b/app/assets/javascripts/recurring_select_dialog.js.erb index 07c06e1..495c980 100644 --- a/app/assets/javascripts/recurring_select_dialog.js.erb +++ b/app/assets/javascripts/recurring_select_dialog.js.erb @@ -372,7 +372,7 @@ class RecurringSelectDialog {
\
\
\ -

${this.config.texts["repeat"]}

\ +

${this.config.texts["repeat"]}

\

\ \ \ From 7ed5cb242d4547b25570921322709f83b3f19b9b Mon Sep 17 00:00:00 2001 From: glaszig Date: Tue, 7 Nov 2023 23:49:35 -0300 Subject: [PATCH 19/24] support rails 7.1 mainly adjust to changes in AV::Helpers::Tags::Base see rails/rails#48574 --- .github/workflows/test.yml | 1 + Gemfile | 2 +- lib/helpers/recurring_select_helper.rb | 13 ++++--------- spec/gemfiles/rails-6.1 | 5 +++++ 4 files changed, 11 insertions(+), 10 deletions(-) create mode 100644 spec/gemfiles/rails-6.1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a5edacc..3ff4dab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,7 @@ jobs: ruby: [ '3.0', '3.1', '3.2', '3.3' ] gemfile: - Gemfile + - spec/gemfiles/rails-6.1 - spec/gemfiles/rails-7 env: diff --git a/Gemfile b/Gemfile index b811aae..d32a3c9 100644 --- a/Gemfile +++ b/Gemfile @@ -2,4 +2,4 @@ source "https://rubygems.org" gemspec gem "thin" -gem "rails", "~> 6.1" +gem "rails", "~> 7.1" diff --git a/lib/helpers/recurring_select_helper.rb b/lib/helpers/recurring_select_helper.rb index 334458e..223e3ac 100644 --- a/lib/helpers/recurring_select_helper.rb +++ b/lib/helpers/recurring_select_helper.rb @@ -96,20 +96,15 @@ def recurring_select_html_options(html_options) end end - class RecurringSelectTag < ActionView::Helpers::Tags::Base + class RecurringSelectTag < ActionView::Helpers::Tags::Select include RecurringSelectHelper::FormOptionsHelper include SelectHTMLOptions def initialize(object, method, template_object, default_schedules = nil, options = {}, html_options = {}) @default_schedules = default_schedules - @choices = @choices.to_a if @choices.is_a?(Range) - @method_name = method.to_s - @object_name = object.to_s - @html_options = recurring_select_html_options(html_options) - @template_object = template_object - add_default_name_and_id(@html_options) - - super(object, method, template_object, options) + html_options = recurring_select_html_options(html_options) + + super(object, method, template_object, @default_schedules, options, html_options) end def render diff --git a/spec/gemfiles/rails-6.1 b/spec/gemfiles/rails-6.1 new file mode 100644 index 0000000..77b2168 --- /dev/null +++ b/spec/gemfiles/rails-6.1 @@ -0,0 +1,5 @@ +source "https://rubygems.org" +gemspec path: File.expand_path("../../..", __FILE__) + +gem "thin" +gem "rails", "~> 6.1" From d98a538ace315e295e4ee20d74af03fe61280bee Mon Sep 17 00:00:00 2001 From: glaszig Date: Tue, 7 Nov 2023 23:54:34 -0300 Subject: [PATCH 20/24] more explicit gemfiles --- .github/workflows/test.yml | 2 +- Gemfile | 2 +- spec/gemfiles/{rails-7 => rails-7.0} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename spec/gemfiles/{rails-7 => rails-7.0} (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ff4dab..03102b6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: gemfile: - Gemfile - spec/gemfiles/rails-6.1 - - spec/gemfiles/rails-7 + - spec/gemfiles/rails-7.0 env: BUNDLE_GEMFILE: ${{matrix.gemfile}} diff --git a/Gemfile b/Gemfile index d32a3c9..a005be6 100644 --- a/Gemfile +++ b/Gemfile @@ -2,4 +2,4 @@ source "https://rubygems.org" gemspec gem "thin" -gem "rails", "~> 7.1" +gem "rails", "~> 7.1.1" diff --git a/spec/gemfiles/rails-7 b/spec/gemfiles/rails-7.0 similarity index 100% rename from spec/gemfiles/rails-7 rename to spec/gemfiles/rails-7.0 From 08c450debbfa034d089c8e7eb72e3970ba98ebd5 Mon Sep 17 00:00:00 2001 From: glaszig Date: Mon, 27 Nov 2023 22:31:23 -0300 Subject: [PATCH 21/24] remove jquery reference from error message Co-authored-by: Slokilla <51393687+Slokilla@users.noreply.github.com> --- app/assets/javascripts/recurring_select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/recurring_select.js b/app/assets/javascripts/recurring_select.js index 1649127..8dec2c3 100644 --- a/app/assets/javascripts/recurring_select.js +++ b/app/assets/javascripts/recurring_select.js @@ -91,6 +91,6 @@ function recurring_select(method) { if (method in methods) { return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) ); } else { - throw new Error( `Method ${method} does not exist on jQuery.recurring_select` ); + throw new Error( `Method ${method} does not exist on recurring_select` ); } } From e0bb4ea559effb3968c1a53b2c7bd858e6f1d4c9 Mon Sep 17 00:00:00 2001 From: glaszig Date: Mon, 27 Nov 2023 22:31:46 -0300 Subject: [PATCH 22/24] use actions/checkout@v4 Co-authored-by: Slokilla <51393687+Slokilla@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 03102b6..f959a39 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: BUNDLE_GEMFILE: ${{matrix.gemfile}} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} From 8e75d469df11926f8ed47b0df1459d75dc6fbe44 Mon Sep 17 00:00:00 2001 From: glaszig Date: Mon, 27 Nov 2023 22:32:13 -0300 Subject: [PATCH 23/24] remove superfluous whitespace Co-authored-by: Slokilla <51393687+Slokilla@users.noreply.github.com> --- app/assets/javascripts/recurring_select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/recurring_select.js b/app/assets/javascripts/recurring_select.js index 8dec2c3..c93e6bd 100644 --- a/app/assets/javascripts/recurring_select.js +++ b/app/assets/javascripts/recurring_select.js @@ -54,7 +54,7 @@ const methods = { current_rule() { return { - str: this.getAttribute("data-initial-value-str"), + str: this.getAttribute("data-initial-value-str"), hash: JSON.parse(this.getAttribute("data-initial-value-hash")) }; }, From 56253c91632b54fb9243473a4da1ec494b2979a9 Mon Sep 17 00:00:00 2001 From: glaszig Date: Wed, 6 Dec 2023 01:57:39 -0300 Subject: [PATCH 24/24] fix conditional the original (coffeescript) used `unless` --- app/assets/javascripts/recurring_select_dialog.js.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/recurring_select_dialog.js.erb b/app/assets/javascripts/recurring_select_dialog.js.erb index f4ecf41..bd60f62 100644 --- a/app/assets/javascripts/recurring_select_dialog.js.erb +++ b/app/assets/javascripts/recurring_select_dialog.js.erb @@ -276,7 +276,7 @@ class RecurringSelectDialog { // ========================= Change callbacks =============================== freqChanged() { - if (isPlainObject(this.current_rule.hash)) { this.current_rule.hash = null; } // for custom values + if (!isPlainObject(this.current_rule.hash)) { this.current_rule.hash = null; } // for custom values if (!this.current_rule.hash) { this.current_rule.hash = {} }; this.current_rule.hash.interval = 1;