recurring_select 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +20 -0
- data/README.md +83 -0
- data/Rakefile +39 -0
- data/app/assets/images/recurring_select/cancel.png +0 -0
- data/app/assets/images/recurring_select/throbber_13x13.gif +0 -0
- data/app/assets/javascripts/jquery-mobile-rs.js.coffee +15 -0
- data/app/assets/javascripts/recurring_select.js.coffee +74 -0
- data/app/assets/javascripts/recurring_select_dialog.js.coffee.erb +300 -0
- data/app/assets/stylesheets/jquery-mobile-rs.css.scss.erb +33 -0
- data/app/assets/stylesheets/recurring_select.css.scss.erb +101 -0
- data/app/assets/stylesheets/utilities.scss +26 -0
- data/app/assets/templates/recurring_select/dialog_template.html +77 -0
- data/app/helpers/recurring_select_helper.rb +107 -0
- data/app/middleware/recurring_select_middleware.rb +26 -0
- data/config/routes.rb +2 -0
- data/lib/recurring_select.rb +90 -0
- data/lib/recurring_select/engine.rb +16 -0
- data/lib/recurring_select/version.rb +3 -0
- metadata +163 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2013 Jobber (OctopusApp Inc)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
RecurringSelect
|
2
|
+
=============
|
3
|
+
|
4
|
+
This is a gem to add a number of selectors and helpers for working with recurring schedules in a rails app.
|
5
|
+
It uses the [IceCube](https://github.com/seejohnrun/ice_cube) recurring scheduling gem.
|
6
|
+
|
7
|
+
Created by the [Jobber](http://getjobber.com) team for Jobber, the leading business management tool for field service companies.
|
8
|
+
|
9
|
+
Check out the [live demo](http://recurring-select-demo.herokuapp.com/)
|
10
|
+
|
11
|
+
|
12
|
+
Usage
|
13
|
+
-----
|
14
|
+
|
15
|
+
Basic selector:
|
16
|
+
|
17
|
+
Load the gem:
|
18
|
+
`gem 'recurring_select`
|
19
|
+
|
20
|
+
Require assets
|
21
|
+
Desktop view
|
22
|
+
application.js
|
23
|
+
`//= require recurring_select`
|
24
|
+
application.css
|
25
|
+
`//= require recurring_select`
|
26
|
+
|
27
|
+
or jQueryMobile interface
|
28
|
+
application.js
|
29
|
+
`//= require jquery-mobile-rs`
|
30
|
+
application.css
|
31
|
+
`//= require jquery-mobile-rs`
|
32
|
+
|
33
|
+
|
34
|
+
In the form view call the helper:
|
35
|
+
`<%= f.select_recurring :recurring_rule_column %>`
|
36
|
+
|
37
|
+
Options
|
38
|
+
-------
|
39
|
+
|
40
|
+
Defaults Values
|
41
|
+
```
|
42
|
+
f.select_recurring :current_existing_rule, [
|
43
|
+
IceCube::Rule.weekly.day(:monday, :wednesday, :friday),
|
44
|
+
IceCube::Rule.monthly.day_of_month(-1)
|
45
|
+
]
|
46
|
+
```
|
47
|
+
|
48
|
+
:allow_blank let's you pick if there is a "not recurring" value
|
49
|
+
```
|
50
|
+
f.select_recurring :current_existing_rule, :allow_blank => true
|
51
|
+
```
|
52
|
+
|
53
|
+
|
54
|
+
Additional Helpers
|
55
|
+
------------------
|
56
|
+
|
57
|
+
RecurringSelect also comes with several helpers for parsing up the
|
58
|
+
parameters when they hit your application.
|
59
|
+
|
60
|
+
You can send the column into the `is_valid_rule?` method to check the
|
61
|
+
validity of the input.
|
62
|
+
`RecurringSelect.is_valid_rule?(possible_rule)`
|
63
|
+
|
64
|
+
There is also a `dirty_hash_to_rule` method for sanitizing the inputs
|
65
|
+
for IceCube. This is sometimes needed based on if you're receiving strings, fixed
|
66
|
+
numbers, strings vs symbols, etc.
|
67
|
+
`RecurringSelect.dirty_hash_to_rule(params)`
|
68
|
+
|
69
|
+
|
70
|
+
Testing and development
|
71
|
+
----------------------
|
72
|
+
|
73
|
+
Start the dummy server for clicking around the interface:
|
74
|
+
`rails s`
|
75
|
+
|
76
|
+
Use [Guard](https://github.com/guard/guard) and RSpec for all tests. I'd
|
77
|
+
love to get jasmine running also, but haven't had time yet.
|
78
|
+
|
79
|
+
Feel free to open issues or send pull requests.
|
80
|
+
|
81
|
+
Licensing
|
82
|
+
---------
|
83
|
+
This project rocks and uses MIT-LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'RecurringSelect'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
24
|
+
load 'rails/tasks/engine.rake'
|
25
|
+
|
26
|
+
|
27
|
+
Bundler::GemHelper.install_tasks
|
28
|
+
|
29
|
+
require 'rake/testtask'
|
30
|
+
|
31
|
+
Rake::TestTask.new(:test) do |t|
|
32
|
+
t.libs << 'lib'
|
33
|
+
t.libs << 'test'
|
34
|
+
t.pattern = 'test/**/*_test.rb'
|
35
|
+
t.verbose = false
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
task :default => :test
|
Binary file
|
Binary file
|
@@ -0,0 +1,15 @@
|
|
1
|
+
//= require recurring_select
|
2
|
+
//= require_self
|
3
|
+
|
4
|
+
$ ->
|
5
|
+
$(document).on "recurring_select:cancel recurring_select:save", ".recurring_select", ->
|
6
|
+
$(this).selectmenu('refresh')
|
7
|
+
|
8
|
+
$(document).on "recurring_select:dialog_opened", ".rs_dialog_holder", ->
|
9
|
+
$(this).find("select").attr("data-theme", $('.recurring_select').data("theme")).attr("data-mini", true).selectmenu()
|
10
|
+
$(this).find("input[type=text]").textinput()
|
11
|
+
|
12
|
+
$(this).on "recurring_select:dialog_positioned", ".rs_dialog", ->
|
13
|
+
$(this).css
|
14
|
+
"top" : $(window).scrollTop()+"px"
|
15
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
//= require recurring_select_dialog
|
2
|
+
//= require_self
|
3
|
+
|
4
|
+
$ = jQuery
|
5
|
+
$ ->
|
6
|
+
$(document).on "focus", ".recurring_select", ->
|
7
|
+
$(this).recurring_select('set_initial_values')
|
8
|
+
|
9
|
+
$(document).on "change", ".recurring_select", ->
|
10
|
+
$(this).recurring_select('changed')
|
11
|
+
|
12
|
+
methods =
|
13
|
+
set_initial_values: ->
|
14
|
+
@data 'initial-value-hash', @val()
|
15
|
+
@data 'initial-value-str', $(@find("option").get()[@.prop("selectedIndex")]).text()
|
16
|
+
|
17
|
+
changed: ->
|
18
|
+
if @val() == "custom"
|
19
|
+
methods.open_custom.apply(@)
|
20
|
+
else
|
21
|
+
methods.set_initial_values.apply(@)
|
22
|
+
|
23
|
+
open_custom: ->
|
24
|
+
@data "recurring-select-active", true
|
25
|
+
new RecurringSelectDialog(@)
|
26
|
+
@blur()
|
27
|
+
|
28
|
+
save: (new_rule) ->
|
29
|
+
@find("option[data-custom]").remove()
|
30
|
+
new_json_val = JSON.stringify(new_rule.hash)
|
31
|
+
|
32
|
+
# TODO: check for matching name, and replace that value if found
|
33
|
+
|
34
|
+
if $.inArray(new_json_val, @find("option").map -> $(@).val()) == -1
|
35
|
+
methods.insert_option.apply @, [new_rule.str, new_json_val]
|
36
|
+
|
37
|
+
@val new_json_val
|
38
|
+
methods.set_initial_values.apply @
|
39
|
+
@.trigger "recurring_select:save"
|
40
|
+
|
41
|
+
current_rule: ->
|
42
|
+
str: @data("initial-value-str")
|
43
|
+
hash: $.parseJSON(@data("initial-value-hash"))
|
44
|
+
|
45
|
+
cancel: ->
|
46
|
+
@val @data("initial-value-hash")
|
47
|
+
@data "recurring-select-active", false
|
48
|
+
@.trigger "recurring_select:cancel"
|
49
|
+
|
50
|
+
|
51
|
+
insert_option: (new_rule_str, new_rule_json) ->
|
52
|
+
seperator = @find("option:disabled")
|
53
|
+
if seperator.length == 0
|
54
|
+
seperator = @find("option")
|
55
|
+
seperator = seperator.last()
|
56
|
+
|
57
|
+
new_option = $(document.createElement("option"))
|
58
|
+
new_option.attr "data-custom", true
|
59
|
+
|
60
|
+
if new_rule_str.substr(new_rule_str.length - 1) != "*"
|
61
|
+
new_rule_str+="*"
|
62
|
+
|
63
|
+
new_option.text new_rule_str
|
64
|
+
new_option.val new_rule_json
|
65
|
+
new_option.insertBefore seperator
|
66
|
+
|
67
|
+
methods: ->
|
68
|
+
methods
|
69
|
+
|
70
|
+
$.fn.recurring_select = (method) ->
|
71
|
+
if method of methods
|
72
|
+
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) );
|
73
|
+
else
|
74
|
+
$.error( "Method #{method} does not exist on jQuery.recurring_select" );
|
@@ -0,0 +1,300 @@
|
|
1
|
+
<%
|
2
|
+
template_path = File.expand_path("../templates/recurring_select/dialog_template.html", File.dirname(__FILE__))
|
3
|
+
File.open(template_path) do |file|
|
4
|
+
@template = file.read
|
5
|
+
end
|
6
|
+
%>
|
7
|
+
|
8
|
+
window.RecurringSelectDialog =
|
9
|
+
class RecurringSelectDialog
|
10
|
+
constructor: (@recurring_selector) ->
|
11
|
+
@current_rule = @recurring_selector.recurring_select('current_rule')
|
12
|
+
@initDialogBox()
|
13
|
+
if not @current_rule.hash? or not @current_rule.hash.rule_type?
|
14
|
+
@freqChanged()
|
15
|
+
else
|
16
|
+
setTimeout @positionDialogVert, 10 # allow initial render
|
17
|
+
|
18
|
+
initDialogBox: ->
|
19
|
+
$(".rs_dialog_holder").remove()
|
20
|
+
|
21
|
+
open_in = $("body")
|
22
|
+
open_in = $(".ui-page-active") if $(".ui-page-active").length
|
23
|
+
open_in.append "<%= ActionController::Base.helpers.escape_javascript @template %>"
|
24
|
+
@outer_holder = $(".rs_dialog_holder")
|
25
|
+
@inner_holder = @outer_holder.find ".rs_dialog"
|
26
|
+
@content = @outer_holder.find ".rs_dialog_content"
|
27
|
+
|
28
|
+
@positionDialogVert(true)
|
29
|
+
@mainEventInit()
|
30
|
+
@freqInit()
|
31
|
+
@summaryInit()
|
32
|
+
@freq_select.focus()
|
33
|
+
@outer_holder.trigger "recurring_select:dialog_opened"
|
34
|
+
|
35
|
+
positionDialogVert: (initial_positioning) =>
|
36
|
+
window_height = $(window).height()
|
37
|
+
window_width = $(window).width()
|
38
|
+
dialog_height = @content.outerHeight()
|
39
|
+
if dialog_height < 80
|
40
|
+
dialog_height = 80
|
41
|
+
margin_top = (window_height - dialog_height)/2 - 30
|
42
|
+
margin_top = 10 if margin_top < 10
|
43
|
+
# if dialog_height > window_height - 20
|
44
|
+
# dialog_height = window_height - 20
|
45
|
+
|
46
|
+
new_style_hash =
|
47
|
+
"margin-top" : margin_top+"px"
|
48
|
+
"height" : dialog_height+"px"
|
49
|
+
|
50
|
+
if initial_positioning?
|
51
|
+
@inner_holder.css new_style_hash
|
52
|
+
@inner_holder.trigger "recurring_select:dialog_positioned"
|
53
|
+
else
|
54
|
+
@content.css {"width": @content.width()+"px"}
|
55
|
+
@inner_holder.addClass "animated"
|
56
|
+
@inner_holder.animate new_style_hash, 200, =>
|
57
|
+
@inner_holder.removeClass "animated"
|
58
|
+
@content.css {"width": "auto"}
|
59
|
+
@inner_holder.trigger "recurring_select:dialog_positioned"
|
60
|
+
|
61
|
+
cancel: =>
|
62
|
+
@outer_holder.remove()
|
63
|
+
@recurring_selector.recurring_select('cancel')
|
64
|
+
|
65
|
+
outerCancel: (event) =>
|
66
|
+
if $(event.target).hasClass("rs_dialog_holder")
|
67
|
+
@cancel()
|
68
|
+
|
69
|
+
save: =>
|
70
|
+
return if !@current_rule.str?
|
71
|
+
@outer_holder.remove()
|
72
|
+
@recurring_selector.recurring_select('save', @current_rule)
|
73
|
+
|
74
|
+
# ========================= Init Methods ===============================
|
75
|
+
|
76
|
+
mainEventInit: ->
|
77
|
+
# Tap hooks are for jQueryMobile
|
78
|
+
@outer_holder.on 'click tap', @outerCancel
|
79
|
+
@content.on 'click tap', 'h1 a', @cancel
|
80
|
+
@save_button = @content.find('input.rs_save').on "click tap", @save
|
81
|
+
@content.find('input.rs_cancel').on "click tap", @cancel
|
82
|
+
|
83
|
+
freqInit: ->
|
84
|
+
@freq_select = @outer_holder.find ".rs_frequency"
|
85
|
+
if @current_rule.hash? && (rule_type = @current_rule.hash.rule_type)?
|
86
|
+
if rule_type.search(/Weekly/) != -1
|
87
|
+
@freq_select.prop('selectedIndex', 1)
|
88
|
+
@initWeeklyOptions()
|
89
|
+
else if rule_type.search(/Monthly/) != -1
|
90
|
+
@freq_select.prop('selectedIndex', 2)
|
91
|
+
@initMonthlyOptions()
|
92
|
+
else if rule_type.search(/Yearly/) != -1
|
93
|
+
@freq_select.prop('selectedIndex', 3)
|
94
|
+
@initYearlyOptions()
|
95
|
+
else
|
96
|
+
@initDailyOptions()
|
97
|
+
@freq_select.on "change", @freqChanged
|
98
|
+
|
99
|
+
initDailyOptions: ->
|
100
|
+
section = @content.find('.daily_options')
|
101
|
+
interval_input = section.find('.rs_daily_interval')
|
102
|
+
interval_input.val(@current_rule.hash.interval)
|
103
|
+
interval_input.on "change keyup", @intervalChanged
|
104
|
+
section.show()
|
105
|
+
|
106
|
+
initWeeklyOptions: ->
|
107
|
+
section = @content.find('.weekly_options')
|
108
|
+
|
109
|
+
# connect the interval field
|
110
|
+
interval_input = section.find('.rs_weekly_interval')
|
111
|
+
interval_input.val(@current_rule.hash.interval)
|
112
|
+
interval_input.on "change keyup", @intervalChanged
|
113
|
+
|
114
|
+
# connect the day fields
|
115
|
+
if @current_rule.hash.validations? && @current_rule.hash.validations.day?
|
116
|
+
$(@current_rule.hash.validations.day).each (index, val) ->
|
117
|
+
section.find(".day_holder a[data-value='"+val+"']").addClass("selected")
|
118
|
+
section.on "click", ".day_holder a", @daysChanged
|
119
|
+
|
120
|
+
section.show()
|
121
|
+
|
122
|
+
initMonthlyOptions: ->
|
123
|
+
section = @content.find('.monthly_options')
|
124
|
+
interval_input = section.find('.rs_monthly_interval')
|
125
|
+
interval_input.val(@current_rule.hash.interval)
|
126
|
+
interval_input.on "change keyup", @intervalChanged
|
127
|
+
|
128
|
+
@current_rule.hash.validations ||= {}
|
129
|
+
@current_rule.hash.validations.day_of_month ||= []
|
130
|
+
@current_rule.hash.validations.day_of_week ||= {}
|
131
|
+
@init_calendar_days(section)
|
132
|
+
@init_calendar_weeks(section)
|
133
|
+
|
134
|
+
in_week_mode = Object.keys(@current_rule.hash.validations.day_of_week).length > 0
|
135
|
+
section.find(".monthly_rule_type_week").prop("checked", in_week_mode)
|
136
|
+
section.find(".monthly_rule_type_day").prop("checked", !in_week_mode)
|
137
|
+
@toggle_month_view()
|
138
|
+
section.find("input[name=monthly_rule_type]").on "change", @toggle_month_view
|
139
|
+
section.show()
|
140
|
+
|
141
|
+
initYearlyOptions: ->
|
142
|
+
section = @content.find('.yearly_options')
|
143
|
+
interval_input = section.find('.rs_yearly_interval')
|
144
|
+
interval_input.val(@current_rule.hash.interval)
|
145
|
+
interval_input.on "change keyup", @intervalChanged
|
146
|
+
section.show()
|
147
|
+
|
148
|
+
|
149
|
+
summaryInit: ->
|
150
|
+
@summary = @outer_holder.find(".rs_summary")
|
151
|
+
@summaryUpdate()
|
152
|
+
|
153
|
+
# ========================= render methods ===============================
|
154
|
+
|
155
|
+
summaryUpdate: (new_string) =>
|
156
|
+
if @current_rule.hash? && @current_rule.str?
|
157
|
+
@summary.removeClass "fetching"
|
158
|
+
@save_button.removeClass("disabled")
|
159
|
+
rule_str = @current_rule.str.replace("*", "")
|
160
|
+
if rule_str.length < 20
|
161
|
+
rule_str = "Summary: "+rule_str
|
162
|
+
@summary.find("span").html rule_str
|
163
|
+
else
|
164
|
+
@summary.addClass "fetching"
|
165
|
+
@save_button.addClass("disabled")
|
166
|
+
@summary.find("span").html ""
|
167
|
+
@summaryFetch()
|
168
|
+
|
169
|
+
summaryFetch: ->
|
170
|
+
return if !(@current_rule.hash? && (rule_type = @current_rule.hash.rule_type)?)
|
171
|
+
@content.css {"width": @content.width()+"px"}
|
172
|
+
$.ajax
|
173
|
+
url: "/recurring_select/translate",
|
174
|
+
type: "POST",
|
175
|
+
data: @current_rule.hash
|
176
|
+
success: @summaryFetchSuccess
|
177
|
+
|
178
|
+
summaryFetchSuccess: (data) =>
|
179
|
+
@current_rule.str = data
|
180
|
+
@summaryUpdate()
|
181
|
+
@content.css {"width": "auto"}
|
182
|
+
|
183
|
+
init_calendar_days: (section) =>
|
184
|
+
monthly_calendar = section.find(".rs_calendar_day")
|
185
|
+
monthly_calendar.html ""
|
186
|
+
for num in [1..31]
|
187
|
+
monthly_calendar.append (day_link = $(document.createElement("a")).text(num))
|
188
|
+
if $.inArray(num, @current_rule.hash.validations.day_of_month) != -1
|
189
|
+
day_link.addClass("selected")
|
190
|
+
|
191
|
+
# add last day of month button
|
192
|
+
monthly_calendar.append (end_of_month_link = $(document.createElement("a")).text("Last Day"))
|
193
|
+
end_of_month_link.addClass("end_of_month")
|
194
|
+
if $.inArray(-1, @current_rule.hash.validations.day_of_month) != -1
|
195
|
+
end_of_month_link.addClass("selected")
|
196
|
+
|
197
|
+
monthly_calendar.find("a").on "click tap", @dateOfMonthChanged
|
198
|
+
|
199
|
+
init_calendar_weeks: (section) =>
|
200
|
+
monthly_calendar = section.find(".rs_calendar_week")
|
201
|
+
monthly_calendar.html ""
|
202
|
+
row_labels = ["1st", "2nd", "3rd", "4th"]
|
203
|
+
cell_str = ["S", "M", "T", "W", "T", "F", "S"]
|
204
|
+
|
205
|
+
for num in [1..4]
|
206
|
+
monthly_calendar.append "<span>#{row_labels[num - 1]}</span>"
|
207
|
+
for day_of_week in [0..6]
|
208
|
+
day_link = $("<a>", {text: cell_str[day_of_week]})
|
209
|
+
day_link.attr("day", day_of_week)
|
210
|
+
day_link.attr("instance", num)
|
211
|
+
monthly_calendar.append day_link
|
212
|
+
$.each @current_rule.hash.validations.day_of_week, (key, value) ->
|
213
|
+
$.each value, (index, instance) ->
|
214
|
+
section.find("a[day='#{key}'][instance='#{instance}']").addClass("selected")
|
215
|
+
monthly_calendar.find("a").on "click tap", @weekOfMonthChanged
|
216
|
+
|
217
|
+
toggle_month_view: =>
|
218
|
+
week_mode = @content.find(".monthly_rule_type_week").prop("checked")
|
219
|
+
@content.find(".rs_calendar_week").toggle(week_mode)
|
220
|
+
@content.find(".rs_calendar_day").toggle(!week_mode)
|
221
|
+
|
222
|
+
# ========================= Change callbacks ===============================
|
223
|
+
|
224
|
+
freqChanged: =>
|
225
|
+
@current_rule.hash = null unless $.isPlainObject(@current_rule.hash) # for custom values
|
226
|
+
|
227
|
+
@current_rule.hash ||= {}
|
228
|
+
@current_rule.hash.interval = 1
|
229
|
+
@current_rule.hash.until = null
|
230
|
+
@current_rule.hash.count = null
|
231
|
+
@current_rule.hash.validations = null
|
232
|
+
@content.find(".freq_option_section").hide();
|
233
|
+
@content.find("input[type=radio], input[type=checkbox]").prop("checked", false)
|
234
|
+
switch @freq_select.val()
|
235
|
+
when "Weekly"
|
236
|
+
@current_rule.hash.rule_type = "IceCube::WeeklyRule"
|
237
|
+
@current_rule.str = "Weekly"
|
238
|
+
@initWeeklyOptions()
|
239
|
+
when "Monthly"
|
240
|
+
@current_rule.hash.rule_type = "IceCube::MonthlyRule"
|
241
|
+
@current_rule.str = "Monthly"
|
242
|
+
@initMonthlyOptions()
|
243
|
+
when "Yearly"
|
244
|
+
@current_rule.hash.rule_type = "IceCube::YearlyRule"
|
245
|
+
@current_rule.str = "Yearly"
|
246
|
+
@initYearlyOptions()
|
247
|
+
else
|
248
|
+
@current_rule.hash.rule_type = "IceCube::DailyRule"
|
249
|
+
@current_rule.str = "Daily"
|
250
|
+
@initDailyOptions()
|
251
|
+
@summaryUpdate()
|
252
|
+
@positionDialogVert()
|
253
|
+
|
254
|
+
intervalChanged: (event) =>
|
255
|
+
@current_rule.str = null
|
256
|
+
@current_rule.hash ||= {}
|
257
|
+
@current_rule.hash.interval = parseInt($(event.currentTarget).val())
|
258
|
+
if @current_rule.hash.interval < 1 || isNaN(@current_rule.hash.interval)
|
259
|
+
@current_rule.hash.interval = 1
|
260
|
+
# $(event.currentTarget).val(@current_rule.hash.interval)
|
261
|
+
@summaryUpdate()
|
262
|
+
|
263
|
+
daysChanged: (event) =>
|
264
|
+
$(event.currentTarget).toggleClass("selected")
|
265
|
+
@current_rule.str = null
|
266
|
+
@current_rule.hash ||= {}
|
267
|
+
@current_rule.hash.validations = {}
|
268
|
+
raw_days = @content.find(".day_holder a.selected").map -> parseInt($(this).data("value"))
|
269
|
+
@current_rule.hash.validations.day = raw_days.get()
|
270
|
+
@summaryUpdate()
|
271
|
+
false # this prevents default and propogation
|
272
|
+
|
273
|
+
dateOfMonthChanged: (event) =>
|
274
|
+
$(event.currentTarget).toggleClass("selected")
|
275
|
+
@current_rule.str = null
|
276
|
+
@current_rule.hash ||= {}
|
277
|
+
@current_rule.hash.validations = {}
|
278
|
+
raw_days = @content.find(".monthly_options .rs_calendar_day a.selected").map ->
|
279
|
+
res = if $(this).text() == "Last Day" then -1 else parseInt($(this).text())
|
280
|
+
res
|
281
|
+
@current_rule.hash.validations.day_of_week = {}
|
282
|
+
@current_rule.hash.validations.day_of_month = raw_days.get()
|
283
|
+
@summaryUpdate()
|
284
|
+
false
|
285
|
+
|
286
|
+
weekOfMonthChanged: (event) =>
|
287
|
+
$(event.currentTarget).toggleClass("selected")
|
288
|
+
@current_rule.str = null
|
289
|
+
@current_rule.hash ||= {}
|
290
|
+
@current_rule.hash.validations = {}
|
291
|
+
@current_rule.hash.validations.day_of_month = []
|
292
|
+
@current_rule.hash.validations.day_of_week = {}
|
293
|
+
@content.find(".monthly_options .rs_calendar_week a.selected").each (index, elm) =>
|
294
|
+
day = parseInt($(elm).attr("day"))
|
295
|
+
instance = parseInt($(elm).attr("instance"))
|
296
|
+
@current_rule.hash.validations.day_of_week[day] ||= []
|
297
|
+
@current_rule.hash.validations.day_of_week[day].push instance
|
298
|
+
@summaryUpdate()
|
299
|
+
false
|
300
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
/*
|
2
|
+
*= require recurring_select
|
3
|
+
*= require_self
|
4
|
+
*/
|
5
|
+
|
6
|
+
.rs_dialog_holder {padding-left:0px; background-color:#333; background-color:rgba(30,30,30,0.3); font-size:1em; position:absolute;
|
7
|
+
.rs_dialog { position:absolute; left:10px; right:10px; margin:0px; display:block; z-index:51;
|
8
|
+
|
9
|
+
|
10
|
+
.rs_dialog_content {
|
11
|
+
|
12
|
+
label.ui-select { display:inline-block;}
|
13
|
+
div.ui-select {display:inline-block;}
|
14
|
+
|
15
|
+
input.rs_interval {display:inline-block;}
|
16
|
+
|
17
|
+
.freq_option_section {
|
18
|
+
.day_holder {height:36px;
|
19
|
+
a {padding:10px 12px;}
|
20
|
+
}
|
21
|
+
|
22
|
+
.rs_calendar { width:225px;
|
23
|
+
* { text-shadow: none; }
|
24
|
+
a {padding:10px 8px;}
|
25
|
+
a.end_of_month { width: 111px; }
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
.rs_summary span { font-size:90%; font-weight:normal; }
|
30
|
+
}
|
31
|
+
|
32
|
+
}
|
33
|
+
}
|
@@ -0,0 +1,101 @@
|
|
1
|
+
@import "utilities.scss";
|
2
|
+
|
3
|
+
/* -------- resets ---------------*/
|
4
|
+
|
5
|
+
.rs_dialog_holder { font-size:14px; color:black;
|
6
|
+
a {color:black;}
|
7
|
+
input[type=button] {
|
8
|
+
font: small/normal Arial,sans-serif;
|
9
|
+
background: #F5F5F5; color: #444; border: 1px solid #ccc;
|
10
|
+
font-size: 11px; font-weight: bold; height: 27px; line-height: 27px; outline: none; padding: 0 8px; text-align: center;
|
11
|
+
|
12
|
+
@include rounded_corners(2px);
|
13
|
+
@include gradiant(#f5f5f5, #f1f1f1);
|
14
|
+
filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#f5f5f5',EndColorStr='#f1f1f1');
|
15
|
+
|
16
|
+
&:hover {border-color:#aaa; color:#222; background-color:#f9f9f9; @include shadows(0px, 1px, 2px, rgba(0,0,0,0.2)); }
|
17
|
+
&:focus {border-color:#1E90FF;}
|
18
|
+
&:active {border-color:#1E90FF;}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
/*------- defaults ------------ */
|
23
|
+
.rs_dialog_holder { font-family:helvetica, arial, 'san-serif'; color:#222; font-size:12px;}
|
24
|
+
|
25
|
+
/*------- specifics ------------ */
|
26
|
+
|
27
|
+
select {
|
28
|
+
option.bold {font-weight:bold; color:red;}
|
29
|
+
}
|
30
|
+
|
31
|
+
.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;
|
32
|
+
.rs_dialog { background-color:#f6f6f6; border:1px solid #acacac; @include shadows(1px, 3px, 8px, rgba(0,0,0,0.25)); @include rounded_corners(7px);
|
33
|
+
display:inline-block; min-width:200px; margin-left:-125px; overflow:hidden; height:25px; position:relative;
|
34
|
+
.rs_dialog_content { padding:10px;
|
35
|
+
h1 { font-size:16px; padding:0px; margin:0 0 10px 0;
|
36
|
+
a {float:right; display:inline-block; height:16px; width:16px; background-image:url(<%=asset_path "recurring_select/cancel.png"%>); background-position:center; background-repeat:no-repeat;}
|
37
|
+
}
|
38
|
+
|
39
|
+
p { padding:5px 0; margin:0;
|
40
|
+
label {margin-right:10px;}
|
41
|
+
}
|
42
|
+
|
43
|
+
.freq_option_section { display:none;
|
44
|
+
label { font-weight: bold; }
|
45
|
+
.rs_interval {width:30px; text-align:center;}
|
46
|
+
|
47
|
+
.day_holder { border-left:1px solid #ccc; position:relative; margin-top:5px; height:26px;
|
48
|
+
a {display:block; padding:5px 7px; font-size:14px; border-style:solid; border-color:#ccc; border-width:1px 1px 1px 0px; float:left; text-decoration:none; font-weight:bold; @include inset_shadows(0px, 1px, 2px, rgba(0,0,0,0.1)); background-color:#fff;;
|
49
|
+
&.selected {background-color:#89a; color:#fff; @include inset_shadows(1px, 1px, 2px, rgba(0,0,0,0.2)); @include gradiant(#9ab, #789); }
|
50
|
+
&:hover { cursor:pointer; background-color:#dde;}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
.rs_calendar_day, .rs_calendar_week {
|
55
|
+
width:155px;
|
56
|
+
a {display:inline-block; text-align:center; width:15px; padding:5px 3px; font-size:12px; border-style:solid; border-color:#ccc; border-width:1px 1px 1px 1px; margin:-1px 0 0 -1px; line-height:10px; background-color:#fff; font-weight:bold;
|
57
|
+
&.selected {background-color:#89a; color:#fff; @include gradiant(#9ab, #789); @include inset_shadows(0px, 1px, 2px, rgba(0,0,0,0.2)); text-shadow:0 1px 1px #333;}
|
58
|
+
&:hover { cursor:pointer; background-color:#dde;}
|
59
|
+
}
|
60
|
+
a.end_of_month { width: 81px; }
|
61
|
+
}
|
62
|
+
.rs_calendar_week {
|
63
|
+
width: 183px;
|
64
|
+
span {
|
65
|
+
display: inline-block;
|
66
|
+
width: 27px;
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
.monthly_rule_type {
|
71
|
+
span {
|
72
|
+
margin-right: 15px;
|
73
|
+
}
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
|
78
|
+
.rs_summary { padding:0px; margin-top:15px; border-top:1px solid #ccc;
|
79
|
+
span {font-weight:bold; border-top:1px solid #fff; display:block; padding:10px 0 5px 0;}
|
80
|
+
&.fetching {color:#999;
|
81
|
+
span {background-image:url(<%=asset_path "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;}
|
82
|
+
}
|
83
|
+
label {font-weight:normal;}
|
84
|
+
}
|
85
|
+
|
86
|
+
.controls { padding:10px 0px 5px 0px; min-width:170px; text-align:center;
|
87
|
+
input[type=button] { margin:0px 5px; width:70px;
|
88
|
+
&.rs_save {color:#333; }
|
89
|
+
&.rs_cancel {color:#666;}
|
90
|
+
&.disabled {color:#aaa; }
|
91
|
+
}
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
&.animated {
|
96
|
+
.controls {position:absolute; bottom:10px; left:10px;}
|
97
|
+
.rs_summary, .freq_option_section {display:none;}
|
98
|
+
}
|
99
|
+
|
100
|
+
}
|
101
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
@mixin shadows($x_offset: 0px, $y_offset: 1px, $size: 8px, $color: rgba(0, 0, 0, 0.5)) {
|
2
|
+
-webkit-box-shadow: $x_offset $y_offset $size $color;
|
3
|
+
-moz-box-shadow: $x_offset $y_offset $size $color;
|
4
|
+
-ms-box-shadow: $x_offset $y_offset $size $color;
|
5
|
+
-o-box-shadow: $x_offset $y_offset $size $color;
|
6
|
+
box-shadow: $x_offset $y_offset $size $color;
|
7
|
+
}
|
8
|
+
|
9
|
+
@mixin inset_shadows($x_offset: 0px, $y_offset: 1px, $size: 8px, $color: rgba(0, 0, 0, 0.5)) {
|
10
|
+
-webkit-box-shadow: $x_offset $y_offset $size $color inset;
|
11
|
+
-moz-box-shadow: $x_offset $y_offset $size $color inset;
|
12
|
+
-ms-box-shadow: $x_offset $y_offset $size $color inset;
|
13
|
+
-o-box-shadow: $x_offset $y_offset $size $color inset;
|
14
|
+
box-shadow: $x_offset $y_offset $size $color inset;
|
15
|
+
}
|
16
|
+
|
17
|
+
@mixin rounded_corners($radius: 5px) { -moz-border-radius: $radius; -webkit-border-radius: $radius; border-radius: $radius;}
|
18
|
+
|
19
|
+
@mixin gradiant ($color_a:#aaa, $color_b:#bbb) {
|
20
|
+
position:relative;
|
21
|
+
background-image: -webkit-linear-gradient(top, $color_a, $color_b); /* Chrome 10+, Saf5.1+ */
|
22
|
+
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from($color_a), to($color_b)); /* Saf4+, Chrome */
|
23
|
+
background-image: -moz-linear-gradient(top,$color_a,$color_b); /* FF3.6 */
|
24
|
+
background-image: -ms-linear-gradient(top, $color_a, $color_b); /* IE10 */
|
25
|
+
background-image: -o-linear-gradient(top, $color_a, $color_b); /* Opera 11.10+ */
|
26
|
+
}
|
@@ -0,0 +1,77 @@
|
|
1
|
+
<div class='rs_dialog_holder'>
|
2
|
+
<div class='rs_dialog'>
|
3
|
+
<div class='rs_dialog_content'>
|
4
|
+
<h1>Repeat <a href='#' title='Cancel' Alt='Cancel'></a> </h1>
|
5
|
+
|
6
|
+
<p>
|
7
|
+
<label for='rs_frequency'>Frequency:</label>
|
8
|
+
<select id='rs_frequency' class='rs_frequency' name='rs_frequency'>
|
9
|
+
<option value="Daily">Daily</option>
|
10
|
+
<option value="Weekly">Weekly</option>
|
11
|
+
<option value="Monthly">Monthly</option>
|
12
|
+
<option value="Yearly">Yearly</option>
|
13
|
+
</select>
|
14
|
+
</p>
|
15
|
+
|
16
|
+
<div class="daily_options freq_option_section">
|
17
|
+
<p>
|
18
|
+
Every
|
19
|
+
<input type='text' name='rs_daily_interval' class='rs_daily_interval rs_interval' value='1' size='2' />
|
20
|
+
day(s)
|
21
|
+
</p>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<div class="weekly_options freq_option_section">
|
25
|
+
<p>
|
26
|
+
Every
|
27
|
+
<input type='text' name='rs_weekly_interval' class='rs_weekly_interval rs_interval' value='1' size='2' />
|
28
|
+
week(s) on :
|
29
|
+
</p>
|
30
|
+
<div class="day_holder">
|
31
|
+
<a href="#" data-value='0'>S</a>
|
32
|
+
<a href="#" data-value='1'>M</a>
|
33
|
+
<a href="#" data-value='2'>T</a>
|
34
|
+
<a href="#" data-value='3'>W</a>
|
35
|
+
<a href="#" data-value='4'>T</a>
|
36
|
+
<a href="#" data-value='5'>F</a>
|
37
|
+
<a href="#" data-value='6'>S</a>
|
38
|
+
</div>
|
39
|
+
|
40
|
+
<span style="clear:both; visibility:hidden; height:1px;">.</span>
|
41
|
+
</div>
|
42
|
+
|
43
|
+
<div class="monthly_options freq_option_section">
|
44
|
+
<p>
|
45
|
+
Every
|
46
|
+
<input type='text' name='rs_monthly_interval' class='rs_monthly_interval rs_interval' value='1' size='2' />
|
47
|
+
month(s):
|
48
|
+
</p>
|
49
|
+
<p class="monthly_rule_type">
|
50
|
+
<span>Day of Month <input type="radio" class="monthly_rule_type_day" name="monthly_rule_type" value="true" /></span>
|
51
|
+
<span>Day of Week <input type="radio" class="monthly_rule_type_week" name="monthly_rule_type" value="true" /></span>
|
52
|
+
</p>
|
53
|
+
<p class="rs_calendar_day"></p>
|
54
|
+
<p class="rs_calendar_week"></p>
|
55
|
+
</div>
|
56
|
+
|
57
|
+
<div class="yearly_options freq_option_section">
|
58
|
+
<p>
|
59
|
+
Every
|
60
|
+
<input type='text' name='rs_yearly_interval' class='rs_yearly_interval rs_interval' value='1' size='2' />
|
61
|
+
year(s)
|
62
|
+
</p>
|
63
|
+
</div>
|
64
|
+
|
65
|
+
|
66
|
+
<p class='rs_summary'>
|
67
|
+
<span></span>
|
68
|
+
</p>
|
69
|
+
|
70
|
+
<div class="controls">
|
71
|
+
<input type="button" class="rs_save" value='OK' />
|
72
|
+
<input type="button" class="rs_cancel" value='Cancel' />
|
73
|
+
</div>
|
74
|
+
|
75
|
+
</div>
|
76
|
+
</div>
|
77
|
+
</div>
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require "ice_cube"
|
2
|
+
|
3
|
+
module RecurringSelectHelper
|
4
|
+
module FormHelper
|
5
|
+
def select_recurring(object, method, default_schedules = nil, options = {}, html_options = {})
|
6
|
+
InstanceTag.new(object, method, self, options.delete(:object)).to_recurring_select_tag(default_schedules, options, html_options)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module FormOptionsHelper
|
11
|
+
def recurring_options_for_select(currently_selected_rule = nil, default_schedules = nil, options = {})
|
12
|
+
|
13
|
+
options_array = []
|
14
|
+
blank_option_label = options[:blank_label] || "- not recurring -"
|
15
|
+
blank_option = [blank_option_label, "null"]
|
16
|
+
seperator = ["or", {:disabled => true}]
|
17
|
+
|
18
|
+
if default_schedules.blank?
|
19
|
+
if currently_selected_rule
|
20
|
+
options_array << blank_option if options[:allow_blank]
|
21
|
+
options_array << ice_cube_rule_to_option(currently_selected_rule)
|
22
|
+
options_array << seperator
|
23
|
+
options_array << ["Change schedule...", "custom"]
|
24
|
+
else
|
25
|
+
options_array << blank_option
|
26
|
+
options_array << ["Set schedule...", "custom"]
|
27
|
+
end
|
28
|
+
else
|
29
|
+
options_array << blank_option if options[:allow_blank]
|
30
|
+
|
31
|
+
options_array += default_schedules.collect{|dc|
|
32
|
+
ice_cube_rule_to_option(dc)
|
33
|
+
}
|
34
|
+
|
35
|
+
if currently_selected_rule and not current_rule_in_defaults?(currently_selected_rule, default_schedules)
|
36
|
+
options_array << ice_cube_rule_to_option(currently_selected_rule, true)
|
37
|
+
custom_label = ["New custom schedule...", "custom"]
|
38
|
+
else
|
39
|
+
custom_label = ["Custom schedule...", "custom"]
|
40
|
+
end
|
41
|
+
|
42
|
+
options_array << seperator
|
43
|
+
options_array << custom_label
|
44
|
+
end
|
45
|
+
|
46
|
+
options_for_select(options_array, currently_selected_rule.to_json)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def ice_cube_rule_to_option(supplied_rule, custom = false)
|
52
|
+
return supplied_rule unless RecurringSelect.is_valid_rule?(supplied_rule)
|
53
|
+
|
54
|
+
rule = supplied_rule.is_a?(Hash) ? IceCube::Rule.from_hash(supplied_rule) : supplied_rule
|
55
|
+
ar = [rule.to_s, rule.to_hash.to_json]
|
56
|
+
|
57
|
+
if custom
|
58
|
+
ar[0] << "*"
|
59
|
+
ar << {"data-custom" => true}
|
60
|
+
end
|
61
|
+
|
62
|
+
ar
|
63
|
+
end
|
64
|
+
|
65
|
+
def current_rule_in_defaults?(currently_selected_rule, default_schedules)
|
66
|
+
default_schedules.any?{|option|
|
67
|
+
option == currently_selected_rule or
|
68
|
+
(option.is_a?(Array) and option[1] == currently_selected_rule)
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
class InstanceTag < ActionView::Helpers::InstanceTag
|
75
|
+
include FormOptionsHelper
|
76
|
+
|
77
|
+
def to_recurring_select_tag(default_schedules, options, html_options)
|
78
|
+
html_options = recurring_select_html_options(html_options)
|
79
|
+
add_default_name_and_id(html_options)
|
80
|
+
value = value(object)
|
81
|
+
content_tag("select",
|
82
|
+
add_options(
|
83
|
+
recurring_options_for_select(value, default_schedules, options),
|
84
|
+
options, value
|
85
|
+
), html_options
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def recurring_select_html_options(html_options)
|
92
|
+
html_options = html_options.stringify_keys
|
93
|
+
html_options["class"] = ((html_options["class"] || "").split() + ["recurring_select"]).join(" ")
|
94
|
+
html_options
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
module FormBuilder
|
99
|
+
def select_recurring(method, default_schedules = nil, options = {}, html_options = {})
|
100
|
+
if !@template.respond_to?(:select_recurring)
|
101
|
+
@template.class.send(:include, RecurringSelectHelper::FormHelper)
|
102
|
+
end
|
103
|
+
|
104
|
+
@template.select_recurring(@object_name, method, default_schedules, options.merge(:object => @object), html_options)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "ice_cube"
|
2
|
+
|
3
|
+
class RecurringSelectMiddleware
|
4
|
+
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
if env["PATH_INFO"] =~ /^\/recurring_select\/translate/
|
11
|
+
request = Rack::Request.new(env)
|
12
|
+
params = request.params
|
13
|
+
params.symbolize_keys!
|
14
|
+
|
15
|
+
if params and params[:rule_type]
|
16
|
+
rule = RecurringSelect.dirty_hash_to_rule(params)
|
17
|
+
[200, {"Content-Type" => "text/html"}, [rule.to_s]]
|
18
|
+
else
|
19
|
+
[200, {"Content-Type" => "text/html"}, [""]]
|
20
|
+
end
|
21
|
+
else
|
22
|
+
@app.call(env)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require "recurring_select/engine"
|
2
|
+
require "ice_cube"
|
3
|
+
|
4
|
+
#TODO: remove monkey patch when https://github.com/seejohnrun/ice_cube/issues/136 is fixed
|
5
|
+
IceCube::ValidatedRule.class_eval do
|
6
|
+
alias old_to_s to_s
|
7
|
+
def to_s
|
8
|
+
old_to_s.gsub("when it is", "and")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module RecurringSelect
|
13
|
+
|
14
|
+
def self.dirty_hash_to_rule(params)
|
15
|
+
if params.is_a? IceCube::Rule
|
16
|
+
params
|
17
|
+
else
|
18
|
+
if params.is_a?(String)
|
19
|
+
params = JSON.parse(params)
|
20
|
+
end
|
21
|
+
|
22
|
+
params.symbolize_keys!
|
23
|
+
rules_hash = filter_params(params)
|
24
|
+
|
25
|
+
IceCube::Rule.from_hash(rules_hash)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.is_valid_rule?(possible_rule)
|
30
|
+
return true if possible_rule.is_a?(IceCube::Rule)
|
31
|
+
return false if possible_rule.blank?
|
32
|
+
|
33
|
+
if possible_rule.is_a?(String)
|
34
|
+
begin
|
35
|
+
JSON.parse(possible_rule)
|
36
|
+
return true
|
37
|
+
rescue JSON::ParserError
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# TODO: this should really have an extra step where it tries to perform the final parsing
|
43
|
+
return true if possible_rule.is_a?(Hash)
|
44
|
+
|
45
|
+
false #only a hash or a string of a hash can be valid
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def self.filter_params(params)
|
51
|
+
params.reject!{|key, value| value.blank? || value=="null" }
|
52
|
+
|
53
|
+
params[:interval] = params[:interval].to_i if params[:interval]
|
54
|
+
|
55
|
+
params[:validations] ||= {}
|
56
|
+
params[:validations].symbolize_keys!
|
57
|
+
|
58
|
+
if params[:validations][:day]
|
59
|
+
params[:validations][:day] = params[:validations][:day].collect(&:to_i)
|
60
|
+
end
|
61
|
+
|
62
|
+
if params[:validations][:day_of_month]
|
63
|
+
params[:validations][:day_of_month] = params[:validations][:day_of_month].collect(&:to_i)
|
64
|
+
end
|
65
|
+
|
66
|
+
# this is soooooo ugly
|
67
|
+
if params[:validations][:day_of_week]
|
68
|
+
params[:validations][:day_of_week] ||= {}
|
69
|
+
if params[:validations][:day_of_week].length > 0 and not params[:validations][:day_of_week].keys.first =~ /\d/
|
70
|
+
params[:validations][:day_of_week].symbolize_keys!
|
71
|
+
else
|
72
|
+
originals = params[:validations][:day_of_week].dup
|
73
|
+
params[:validations][:day_of_week] = {}
|
74
|
+
originals.each{|key, value|
|
75
|
+
params[:validations][:day_of_week][key.to_i] = value
|
76
|
+
}
|
77
|
+
end
|
78
|
+
params[:validations][:day_of_week].each{|key, value|
|
79
|
+
params[:validations][:day_of_week][key] = value.collect(&:to_i)
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
if params[:validations][:day_of_year]
|
84
|
+
params[:validations][:day_of_year] = params[:validations][:day_of_year].collect(&:to_i)
|
85
|
+
end
|
86
|
+
|
87
|
+
params
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module RecurringSelect
|
2
|
+
class Engine < Rails::Engine
|
3
|
+
|
4
|
+
initializer "recurring_select.extending_form_builder" do |app|
|
5
|
+
# config.to_prepare do
|
6
|
+
ActionView::Helpers::FormHelper.send(:include, RecurringSelectHelper::FormHelper)
|
7
|
+
ActionView::Helpers::FormOptionsHelper.send(:include, RecurringSelectHelper::FormOptionsHelper)
|
8
|
+
ActionView::Helpers::FormBuilder.send(:include, RecurringSelectHelper::FormBuilder)
|
9
|
+
end
|
10
|
+
|
11
|
+
initializer "recurring_select.connecting_middleware" do |app|
|
12
|
+
app.middleware.use RecurringSelectMiddleware # insert_after ActionDispatch::ParamsParser,
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: recurring_select
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jobber
|
9
|
+
- Forrest Zeisler
|
10
|
+
- Nathan Youngman
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2013-04-16 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rails
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ! '>='
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '3.1'
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ! '>='
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '3.1'
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: jquery-rails
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: ice_cube
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0.8'
|
56
|
+
type: :runtime
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0.8'
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: sass-rails
|
66
|
+
requirement: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '3.1'
|
72
|
+
type: :runtime
|
73
|
+
prerelease: false
|
74
|
+
version_requirements: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '3.1'
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: coffee-rails
|
82
|
+
requirement: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '3.1'
|
88
|
+
type: :runtime
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '3.1'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: pry
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ! '>='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
description: This gem provides a useful interface for creating recurring rules for
|
113
|
+
the ice_cube gem.
|
114
|
+
email:
|
115
|
+
- forrest@getjobber.com
|
116
|
+
executables: []
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- app/assets/images/recurring_select/cancel.png
|
121
|
+
- app/assets/images/recurring_select/throbber_13x13.gif
|
122
|
+
- app/assets/javascripts/jquery-mobile-rs.js.coffee
|
123
|
+
- app/assets/javascripts/recurring_select.js.coffee
|
124
|
+
- app/assets/javascripts/recurring_select_dialog.js.coffee.erb
|
125
|
+
- app/assets/stylesheets/jquery-mobile-rs.css.scss.erb
|
126
|
+
- app/assets/stylesheets/recurring_select.css.scss.erb
|
127
|
+
- app/assets/stylesheets/utilities.scss
|
128
|
+
- app/assets/templates/recurring_select/dialog_template.html
|
129
|
+
- app/helpers/recurring_select_helper.rb
|
130
|
+
- app/middleware/recurring_select_middleware.rb
|
131
|
+
- config/routes.rb
|
132
|
+
- lib/recurring_select/engine.rb
|
133
|
+
- lib/recurring_select/version.rb
|
134
|
+
- lib/recurring_select.rb
|
135
|
+
- MIT-LICENSE
|
136
|
+
- Rakefile
|
137
|
+
- README.md
|
138
|
+
homepage: http://github.com/getjobber/recurring_select
|
139
|
+
licenses: []
|
140
|
+
post_install_message:
|
141
|
+
rdoc_options: []
|
142
|
+
require_paths:
|
143
|
+
- lib
|
144
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
none: false
|
152
|
+
requirements:
|
153
|
+
- - ! '>='
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0'
|
156
|
+
requirements: []
|
157
|
+
rubyforge_project:
|
158
|
+
rubygems_version: 1.8.24
|
159
|
+
signing_key:
|
160
|
+
specification_version: 3
|
161
|
+
summary: A select helper which gives you magical powers to generate ice_cube rules.
|
162
|
+
test_files: []
|
163
|
+
has_rdoc:
|