diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..f959a39c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +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-6.1 + - spec/gemfiles/rails-7.0 + + env: + BUNDLE_GEMFILE: ${{matrix.gemfile}} + + steps: + - uses: actions/checkout@v4 + - 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/.gitignore b/.gitignore index e4e765c7..01d5229e 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 35d16fb1..818bd47a 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.5.7 +3.0.6 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 11f2aa16..00000000 --- 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e60a34d..f4726054 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 diff --git a/Gemfile b/Gemfile index e7b799e1..a005be66 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,5 @@ -source 'https://rubygems.org' +source "https://rubygems.org" gemspec -gem "pg" - -gem "jquery-rails" - -gem "rails", "~> 5.2" -gem "sass-rails", "~> 5.0" +gem "thin" +gem "rails", "~> 7.1.1" diff --git a/README.md b/README.md index a05700bf..ed5f80b6 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 } @@ -135,13 +136,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 @@ -151,8 +145,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/app/assets/javascripts/defaults.js b/app/assets/javascripts/defaults.js new file mode 100644 index 00000000..3386fad9 --- /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 new file mode 100644 index 00000000..2cd75ee1 --- /dev/null +++ b/app/assets/javascripts/jquery-mobile-rs.js @@ -0,0 +1,20 @@ +//= require recurring_select +//= require_self + +const $ = jQuery + +$(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 2ce8b103..00000000 --- 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 00000000..c93e6bd1 --- /dev/null +++ b/app/assets/javascripts/recurring_select.js @@ -0,0 +1,96 @@ +//= require recurring_select_dialog +//= require_self + +document.addEventListener("DOMContentLoaded", () => { + document.addEventListener("focusin", (e) => { + if (e.target.matches(".recurring_select")) { + recurring_select.call(e.target, "set_initial_values") + } + }) + + document.addEventListener("input", (e) => { + if (e.target.matches(".recurring_select")) { + recurring_select.call(e.target, "changed") + } + }) +}) + +const methods = { + set_initial_values() { + 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.value == "custom") { + methods.open.call(this); + } else { + methods.set_initial_values.call(this); + } + }, + + open() { + this.setAttribute("data-recurring-select-active", true); + new RecurringSelectDialog(this); + this.blur(); + }, + + save(new_rule) { + 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 + + 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.value = new_json_val + methods.set_initial_values.apply(this) + this.dispatchEvent(new CustomEvent("recurring_select:save")) + }, + + current_rule() { + return { + str: this.getAttribute("data-initial-value-str"), + hash: JSON.parse(this.getAttribute("data-initial-value-hash")) + }; + }, + + 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.querySelectorAll("option[disabled]"); + if (separator.length === 0) { + separator = this.querySelectorAll("option"); + } + separator = separator[separator.length-1] + + 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.textContent = new_rule_str + new_option.value = new_rule_json + separator.parentNode.insertBefore(new_option, separator) + } +}; + +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 { + throw new Error( `Method ${method} does not exist on recurring_select` ); + } +} diff --git a/app/assets/javascripts/recurring_select.js.coffee b/app/assets/javascripts/recurring_select.js.coffee deleted file mode 100644 index c173d51e..00000000 --- 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 93% rename from app/assets/javascripts/recurring_select/fr.js.coffee rename to app/assets/javascripts/recurring_select/fr.js index 94804ac4..f092a389 100644 --- a/app/assets/javascripts/recurring_select/fr.js.coffee +++ 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.coffee.erb b/app/assets/javascripts/recurring_select_dialog.js.coffee.erb deleted file mode 100644 index baeccd6a..00000000 --- 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 00000000..bd60f62a --- /dev/null +++ b/app/assets/javascripts/recurring_select_dialog.js.erb @@ -0,0 +1,447 @@ +//= require utils +//= require defaults + +class RecurringSelectDialog { + constructor(recurring_selector) { + this.config = this.constructor.config + 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(); + } + } + + 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.mainEventInit(); + this.freqInit(); + this.summaryInit(); + trigger(this.outer_holder, "recurring_select:dialog_opened"); + this.freq_select.focus(); + } + + cancel() { + this.outer_holder.remove(); + this.recurring_selector.recurring_select('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); + } + +// ========================= Init Methods =============================== + + 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 && 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(); + } + } + 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") + ) + } + + 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.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'] = 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"); + } + }; + + // 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"); + } + + 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 = {} }; + 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(); + } + + 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.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; + } + +// ========================= Change callbacks =============================== + + template() { + let str = `\ +
\ +
\ +
\ +

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

\ +

\ + \ + \ +

\ + \ +
\ +

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

\ +
\ +
\ +

\ + ${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; + } +} + +RecurringSelectDialog.config = defaultConfig + +window.RecurringSelectDialog = RecurringSelectDialog diff --git a/app/assets/javascripts/utils.js b/app/assets/javascripts/utils.js new file mode 100644 index 00000000..97216877 --- /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/app/assets/stylesheets/recurring_select.scss b/app/assets/stylesheets/recurring_select.scss index 944ed9eb..da731d30 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("../images/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("../images/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,8 +110,11 @@ select { label {font-weight:normal;} } - .controls { padding:10px 0px 5px 0px; min-width:170px; text-align:center; - input[type=button] { margin:0px 5px; width:70px; + .controls { + padding:10px 0px 5px 0px; + min-width:170px; + text-align:center; + input[type=button] { margin:0px 5px; &.rs_save {color:#333; } &.rs_cancel {color:#666;} &.disabled {color:#aaa; } diff --git a/lib/helpers/recurring_select_helper.rb b/lib/helpers/recurring_select_helper.rb index 5122d4fa..223e3acc 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 = {}) @@ -65,7 +71,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 @@ -90,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/lib/recurring_select.rb b/lib/recurring_select.rb index 976d90aa..7a02c735 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 diff --git a/lib/recurring_select/engine.rb b/lib/recurring_select/engine.rb index 1989b177..27ed7fc2 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 diff --git a/lib/recurring_select/version.rb b/lib/recurring_select/version.rb index 9b8de180..2db9857e 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 diff --git a/recurring_select.gemspec b/recurring_select.gemspec index 34fdca94..21fc21de 100644 --- a/recurring_select.gemspec +++ b/recurring_select.gemspec @@ -7,18 +7,16 @@ 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." 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 "jquery-rails", ">= 3.0" + s.add_dependency "rails", ">= 6.1" 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_development_dependency "bundler", ">= 1.3.5" s.add_development_dependency "rspec-rails", ">= 2.14" diff --git a/spec/dummy/app/assets/javascripts/application.js b/spec/dummy/app/assets/javascripts/application.js index 4d3fda77..5253f3aa 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/dummy/config/application.rb b/spec/dummy/config/application.rb index e6e55311..2162cb1a 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 564fdb5f..00000000 --- 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 6d897285..8324a279 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 7b5f159d..f3f20828 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 diff --git a/spec/gemfiles/Gemfile.rails-5.2.x b/spec/gemfiles/Gemfile.rails-5.2.x deleted file mode 100644 index cb9d640c..00000000 --- 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 f5081945..00000000 --- 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-6.1 b/spec/gemfiles/rails-6.1 new file mode 100644 index 00000000..77b2168e --- /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" diff --git a/spec/gemfiles/rails-7.0 b/spec/gemfiles/rails-7.0 new file mode 100644 index 00000000..c6a3a43a --- /dev/null +++ b/spec/gemfiles/rails-7.0 @@ -0,0 +1,5 @@ +source "https://rubygems.org" +gemspec path: File.expand_path("../../..", __FILE__) + +gem "thin" +gem "rails", "~> 7.0.0"