recurring_select_fix_v1_2_0 1.2.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +140 -0
- data/Rakefile +24 -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 +98 -0
- data/app/assets/javascripts/recurring_select/fr.js.coffee +23 -0
- data/app/assets/javascripts/recurring_select_dialog.js.coffee.erb +371 -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/helpers/recurring_select_helper.rb +27 -0
- data/app/helpers/utilities/form_options_ext.rb +64 -0
- data/app/helpers/utilities/tag_ext.rb +53 -0
- data/app/middleware/recurring_select_middleware.rb +26 -0
- data/config/locales/en.yml +8 -0
- data/config/locales/fr.yml +8 -0
- data/config/routes.rb +2 -0
- data/lib/recurring_select.rb +82 -0
- data/lib/recurring_select/engine.rb +16 -0
- data/lib/recurring_select/version.rb +3 -0
- metadata +167 -0
@@ -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; 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: 191px;
|
64
|
+
span {
|
65
|
+
display: inline-block;
|
66
|
+
width: 35px;
|
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,27 @@
|
|
1
|
+
require "ice_cube"
|
2
|
+
require_relative "utilities/form_options_ext"
|
3
|
+
require_relative "utilities/tag_ext"
|
4
|
+
|
5
|
+
module RecurringSelectHelper
|
6
|
+
module FormHelper
|
7
|
+
def select_recurring(object, method, default_schedules = nil, options = {}, html_options = {})
|
8
|
+
if defined?(RecurringSelectHelper::RecurringSelectTag)
|
9
|
+
# === Rails 4
|
10
|
+
RecurringSelectTag.new(object, method, self, default_schedules, options, html_options).render
|
11
|
+
elsif defined?(RecurringSelectHelper::InstanceTag)
|
12
|
+
# === Rails 3
|
13
|
+
InstanceTag.new(object, method, self, options.delete(:object)).to_recurring_select_tag(default_schedules, options, html_options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module FormBuilder
|
19
|
+
def select_recurring(method, default_schedules = nil, options = {}, html_options = {})
|
20
|
+
if !@template.respond_to?(:select_recurring)
|
21
|
+
@template.class.send(:include, RecurringSelectHelper::FormHelper)
|
22
|
+
end
|
23
|
+
|
24
|
+
@template.select_recurring(@object_name, method, default_schedules, options.merge(:object => @object), html_options)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module RecurringSelectHelper
|
2
|
+
module FormOptionsHelper
|
3
|
+
def recurring_options_for_select(currently_selected_rule = nil, default_schedules = nil, options = {})
|
4
|
+
|
5
|
+
options_array = []
|
6
|
+
blank_option_label = options[:blank_label] || I18n.t("recurring_select.not_recurring")
|
7
|
+
blank_option = [blank_option_label, "null"]
|
8
|
+
separator = [I18n.t("recurring_select.or"), {:disabled => true}]
|
9
|
+
|
10
|
+
if default_schedules.blank?
|
11
|
+
if currently_selected_rule
|
12
|
+
options_array << blank_option if options[:allow_blank]
|
13
|
+
options_array << ice_cube_rule_to_option(currently_selected_rule)
|
14
|
+
options_array << separator
|
15
|
+
options_array << [I18n.t("recurring_select.change_schedule"), "custom"]
|
16
|
+
else
|
17
|
+
options_array << blank_option
|
18
|
+
options_array << [I18n.t("recurring_select.set_schedule"), "custom"]
|
19
|
+
end
|
20
|
+
else
|
21
|
+
options_array << blank_option if options[:allow_blank]
|
22
|
+
|
23
|
+
options_array += default_schedules.collect{|dc|
|
24
|
+
ice_cube_rule_to_option(dc)
|
25
|
+
}
|
26
|
+
|
27
|
+
if currently_selected_rule and not current_rule_in_defaults?(currently_selected_rule, default_schedules)
|
28
|
+
options_array << ice_cube_rule_to_option(currently_selected_rule, true)
|
29
|
+
custom_label = [I18n.t("recurring_select.new_custom_schedule"), "custom"]
|
30
|
+
else
|
31
|
+
custom_label = [I18n.t("recurring_select.custom_schedule"), "custom"]
|
32
|
+
end
|
33
|
+
|
34
|
+
options_array << separator
|
35
|
+
options_array << custom_label
|
36
|
+
end
|
37
|
+
|
38
|
+
options_for_select(options_array, currently_selected_rule.to_json)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def ice_cube_rule_to_option(supplied_rule, custom = false)
|
44
|
+
return supplied_rule unless RecurringSelect.is_valid_rule?(supplied_rule)
|
45
|
+
|
46
|
+
rule = RecurringSelect.dirty_hash_to_rule(supplied_rule)
|
47
|
+
ar = [rule.to_s, rule.to_hash.to_json]
|
48
|
+
|
49
|
+
if custom
|
50
|
+
ar[0] << "*"
|
51
|
+
ar << {"data-custom" => true}
|
52
|
+
end
|
53
|
+
|
54
|
+
ar
|
55
|
+
end
|
56
|
+
|
57
|
+
def current_rule_in_defaults?(currently_selected_rule, default_schedules)
|
58
|
+
default_schedules.any?{|option|
|
59
|
+
option == currently_selected_rule or
|
60
|
+
(option.is_a?(Array) and option[1] == currently_selected_rule)
|
61
|
+
}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module RecurringSelectHelper
|
2
|
+
module SelectHTMLOptions
|
3
|
+
private
|
4
|
+
|
5
|
+
def recurring_select_html_options(html_options)
|
6
|
+
html_options = html_options.stringify_keys
|
7
|
+
html_options["class"] = (html_options["class"].to_s.split + ["recurring_select"]).join(" ")
|
8
|
+
html_options
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
if defined?(ActionView::Helpers::Tags::Base)
|
13
|
+
# === Rails 4
|
14
|
+
class RecurringSelectTag < ActionView::Helpers::Tags::Base
|
15
|
+
include RecurringSelectHelper::FormOptionsHelper
|
16
|
+
include SelectHTMLOptions
|
17
|
+
|
18
|
+
def initialize(object, method, template_object, default_schedules = nil, options = {}, html_options = {})
|
19
|
+
@default_schedules = default_schedules
|
20
|
+
@choices = @choices.to_a if @choices.is_a?(Range)
|
21
|
+
@method_name = method.to_s
|
22
|
+
@object_name = object.to_s
|
23
|
+
@html_options = recurring_select_html_options(html_options)
|
24
|
+
add_default_name_and_id(@html_options)
|
25
|
+
|
26
|
+
super(object, method, template_object, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def render
|
30
|
+
option_tags = add_options(recurring_options_for_select(value(object), @default_schedules, @options), @options, value(object))
|
31
|
+
select_content_tag(option_tags, @options, @html_options)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
elsif defined?(ActionView::Helpers::InstanceTag)
|
36
|
+
# === Rails 3
|
37
|
+
class InstanceTag < ActionView::Helpers::InstanceTag
|
38
|
+
include RecurringSelectHelper::FormOptionsHelper
|
39
|
+
include SelectHTMLOptions
|
40
|
+
|
41
|
+
def to_recurring_select_tag(default_schedules, options, html_options)
|
42
|
+
html_options = recurring_select_html_options(html_options)
|
43
|
+
add_default_name_and_id(html_options)
|
44
|
+
value = value(object)
|
45
|
+
options = add_options(
|
46
|
+
recurring_options_for_select(value, default_schedules, options),
|
47
|
+
options, value
|
48
|
+
)
|
49
|
+
content_tag("select", options, html_options)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
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,82 @@
|
|
1
|
+
require "recurring_select/engine"
|
2
|
+
require "ice_cube"
|
3
|
+
|
4
|
+
module RecurringSelect
|
5
|
+
|
6
|
+
def self.dirty_hash_to_rule(params)
|
7
|
+
if params.is_a? IceCube::Rule
|
8
|
+
params
|
9
|
+
else
|
10
|
+
if params.is_a?(String)
|
11
|
+
params = JSON.parse(params)
|
12
|
+
end
|
13
|
+
|
14
|
+
params.symbolize_keys!
|
15
|
+
rules_hash = filter_params(params)
|
16
|
+
|
17
|
+
IceCube::Rule.from_hash(rules_hash)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.is_valid_rule?(possible_rule)
|
22
|
+
return true if possible_rule.is_a?(IceCube::Rule)
|
23
|
+
return false if possible_rule.blank?
|
24
|
+
|
25
|
+
if possible_rule.is_a?(String)
|
26
|
+
begin
|
27
|
+
JSON.parse(possible_rule)
|
28
|
+
return true
|
29
|
+
rescue JSON::ParserError
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# TODO: this should really have an extra step where it tries to perform the final parsing
|
35
|
+
return true if possible_rule.is_a?(Hash)
|
36
|
+
|
37
|
+
false #only a hash or a string of a hash can be valid
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def self.filter_params(params)
|
43
|
+
params.reject!{|key, value| value.blank? || value=="null" }
|
44
|
+
|
45
|
+
params[:interval] = params[:interval].to_i if params[:interval]
|
46
|
+
params[:week_start] = params[:week_start].to_i if params[:week_start]
|
47
|
+
|
48
|
+
params[:validations] ||= {}
|
49
|
+
params[:validations].symbolize_keys!
|
50
|
+
|
51
|
+
if params[:validations][:day]
|
52
|
+
params[:validations][:day] = params[:validations][:day].collect(&:to_i)
|
53
|
+
end
|
54
|
+
|
55
|
+
if params[:validations][:day_of_month]
|
56
|
+
params[:validations][:day_of_month] = params[:validations][:day_of_month].collect(&:to_i)
|
57
|
+
end
|
58
|
+
|
59
|
+
# this is soooooo ugly
|
60
|
+
if params[:validations][:day_of_week]
|
61
|
+
params[:validations][:day_of_week] ||= {}
|
62
|
+
if params[:validations][:day_of_week].length > 0 and not params[:validations][:day_of_week].keys.first =~ /\d/
|
63
|
+
params[:validations][:day_of_week].symbolize_keys!
|
64
|
+
else
|
65
|
+
originals = params[:validations][:day_of_week].dup
|
66
|
+
params[:validations][:day_of_week] = {}
|
67
|
+
originals.each{|key, value|
|
68
|
+
params[:validations][:day_of_week][key.to_i] = value
|
69
|
+
}
|
70
|
+
end
|
71
|
+
params[:validations][:day_of_week].each{|key, value|
|
72
|
+
params[:validations][:day_of_week][key] = value.collect(&:to_i)
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
if params[:validations][:day_of_year]
|
77
|
+
params[:validations][:day_of_year] = params[:validations][:day_of_year].collect(&:to_i)
|
78
|
+
end
|
79
|
+
|
80
|
+
params
|
81
|
+
end
|
82
|
+
end
|