calendar_date_select 1.11.1

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.
Files changed (40) hide show
  1. data/History.txt +216 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Manifest.txt +39 -0
  4. data/Rakefile +31 -0
  5. data/Readme.txt +16 -0
  6. data/init.rb +1 -0
  7. data/js_test/functional/cds_test.html +334 -0
  8. data/js_test/prototype.js +4184 -0
  9. data/js_test/test.css +40 -0
  10. data/js_test/unit/cds_helper_methods.html +46 -0
  11. data/js_test/unittest.js +564 -0
  12. data/lib/calendar_date_select.rb +19 -0
  13. data/lib/calendar_date_select/calendar_date_select.rb +223 -0
  14. data/lib/calendar_date_select/includes_helper.rb +38 -0
  15. data/public/blank_iframe.html +2 -0
  16. data/public/images/calendar_date_select/calendar.gif +0 -0
  17. data/public/javascripts/calendar_date_select/calendar_date_select.js +442 -0
  18. data/public/javascripts/calendar_date_select/format_american.js +34 -0
  19. data/public/javascripts/calendar_date_select/format_db.js +27 -0
  20. data/public/javascripts/calendar_date_select/format_euro_24hr.js +7 -0
  21. data/public/javascripts/calendar_date_select/format_euro_24hr_ymd.js +7 -0
  22. data/public/javascripts/calendar_date_select/format_finnish.js +32 -0
  23. data/public/javascripts/calendar_date_select/format_hyphen_ampm.js +36 -0
  24. data/public/javascripts/calendar_date_select/format_iso_date.js +46 -0
  25. data/public/javascripts/calendar_date_select/format_italian.js +24 -0
  26. data/public/javascripts/calendar_date_select/locale/de.js +10 -0
  27. data/public/javascripts/calendar_date_select/locale/fi.js +10 -0
  28. data/public/javascripts/calendar_date_select/locale/fr.js +10 -0
  29. data/public/javascripts/calendar_date_select/locale/pl.js +10 -0
  30. data/public/javascripts/calendar_date_select/locale/pt.js +11 -0
  31. data/public/javascripts/calendar_date_select/locale/ru.js +10 -0
  32. data/public/stylesheets/calendar_date_select/blue.css +130 -0
  33. data/public/stylesheets/calendar_date_select/default.css +135 -0
  34. data/public/stylesheets/calendar_date_select/plain.css +128 -0
  35. data/public/stylesheets/calendar_date_select/red.css +135 -0
  36. data/public/stylesheets/calendar_date_select/silver.css +133 -0
  37. data/test/functional/calendar_date_select_test.rb +157 -0
  38. data/test/functional/helper_methods_test.rb +15 -0
  39. data/test/test_helper.rb +19 -0
  40. metadata +104 -0
@@ -0,0 +1,19 @@
1
+ require "calendar_date_select/calendar_date_select.rb"
2
+ require "calendar_date_select/includes_helper.rb"
3
+
4
+ if Object.const_defined?(:Rails) && File.directory?(Rails.root + "/public")
5
+ ActionView::Helpers::FormHelper.send(:include, CalendarDateSelect::FormHelper)
6
+ ActionView::Base.send(:include, CalendarDateSelect::FormHelper)
7
+ ActionView::Base.send(:include, CalendarDateSelect::IncludesHelper)
8
+
9
+ # install files
10
+ unless File.exists?(RAILS_ROOT + '/public/javascripts/calendar_date_select/calendar_date_select.js')
11
+ ['/public', '/public/javascripts/calendar_date_select', '/public/stylesheets/calendar_date_select', '/public/images/calendar_date_select', '/public/javascripts/calendar_date_select/locale'].each do |dir|
12
+ source = File.dirname(__FILE__) + "/../#{dir}"
13
+ dest = RAILS_ROOT + dir
14
+ FileUtils.mkdir_p(dest)
15
+ FileUtils.cp(Dir.glob(source+'/*.*'), dest)
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,223 @@
1
+ class CalendarDateSelect
2
+ VERSION = '1.11.1'
3
+ FORMATS = {
4
+ :natural => {
5
+ :date => "%B %d, %Y",
6
+ :time => " %I:%M %p"
7
+ },
8
+ :hyphen_ampm => {
9
+ :date => "%Y-%m-%d",
10
+ :time => " %I:%M %p",
11
+ :javascript_include => "format_hyphen_ampm"
12
+ },
13
+ :iso_date => {
14
+ :date => "%Y-%m-%d",
15
+ :time => " %H:%M",
16
+ :javascript_include => "format_iso_date"
17
+ },
18
+ :finnish => {
19
+ :date => "%d.%m.%Y",
20
+ :time => " %H:%M",
21
+ :javascript_include => "format_finnish"
22
+ },
23
+ :american => {
24
+ :date => "%m/%d/%Y",
25
+ :time => " %I:%M %p",
26
+ :javascript_include => "format_american"
27
+ },
28
+ :euro_24hr => {
29
+ :date => "%d %B %Y",
30
+ :time => " %H:%M",
31
+ :javascript_include => "format_euro_24hr"
32
+ },
33
+ :euro_24hr_ymd => {
34
+ :date => "%Y.%m.%d",
35
+ :time => " %H:%M",
36
+ :javascript_include => "format_euro_24hr_ymd"
37
+ },
38
+ :italian => {
39
+ :date => "%d/%m/%Y",
40
+ :time => " %H:%M",
41
+ :javascript_include => "format_italian"
42
+ },
43
+ :db => {
44
+ :date => "%Y-%m-%d",
45
+ :time => "%H:%M",
46
+ :javascript_include => "format_db"
47
+ }
48
+ }
49
+
50
+ cattr_accessor :image
51
+ @@image = "calendar_date_select/calendar.gif"
52
+
53
+ cattr_reader :format
54
+ @@format = FORMATS[:natural]
55
+
56
+ class << self
57
+ def format=(format)
58
+ raise "CalendarDateSelect: Unrecognized format specification: #{format}" unless FORMATS.has_key?(format)
59
+ @@format = FORMATS[format]
60
+ end
61
+
62
+ def javascript_format_include
63
+ @@format[:javascript_include] && "calendar_date_select/#{@@format[:javascript_include]}"
64
+ end
65
+
66
+ def date_format_string(time=false)
67
+ @@format[:date] + ( time ? @@format[:time] : "" )
68
+ end
69
+
70
+ def format_date(date)
71
+ if Date===date
72
+ date.strftime(date_format_string(false))
73
+ else
74
+ date.strftime(date_format_string(true))
75
+ end
76
+ end
77
+
78
+ def has_time?(value)
79
+ /[0-9]:[0-9]{2}/.match(value.to_s)
80
+ end
81
+ end
82
+
83
+ module FormHelper
84
+ def calendar_date_select_tag( name, value = nil, options = {})
85
+ calendar_options = calendar_date_select_process_options(options)
86
+ value = format_time(value, calendar_options)
87
+
88
+ calendar_options.delete(:format)
89
+
90
+ options[:id] ||= name
91
+ tag = calendar_options[:hidden] || calendar_options[:embedded] ?
92
+ hidden_field_tag(name, value, options) :
93
+ text_field_tag(name, value, options)
94
+
95
+ calendar_date_select_output(tag, calendar_options)
96
+ end
97
+
98
+ def format_time(value, options = {})
99
+ if value.respond_to?("strftime")
100
+ if options[:format]
101
+ value = value.strftime(options[:format])
102
+ else
103
+ if options.has_key? :time
104
+ value = value.strftime(CalendarDateSelect.date_format_string(options[:time]))
105
+ else
106
+ value = CalendarDateSelect.format_date(value)
107
+ end
108
+ end
109
+ end
110
+ value
111
+ end
112
+
113
+ # extracts any options passed into calendar date select, appropriating them to either the Javascript call or the html tag.
114
+ def calendar_date_select_process_options(options)
115
+ calendar_options = {}
116
+ callbacks = [:before_show, :before_close, :after_show, :after_close, :after_navigate]
117
+ for key in [:time, :valid_date_check, :embedded, :buttons, :clear_button, :format, :year_range, :month_year, :popup, :hidden, :minute_interval] + callbacks
118
+ calendar_options[key] = options.delete(key) if options.has_key?(key)
119
+ end
120
+
121
+ # if passing in mixed, pad it with single quotes
122
+ calendar_options[:time] = "'mixed'" if calendar_options[:time].to_s=="mixed"
123
+ calendar_options[:month_year] = "'#{calendar_options[:month_year]}'" if calendar_options[:month_year]
124
+
125
+ # if we are forcing the popup, automatically set the readonly property on the input control.
126
+ if calendar_options[:popup].to_s == "force"
127
+ calendar_options[:popup] = "'force'"
128
+ options[:readonly] = true
129
+ end
130
+
131
+ if (vdc=calendar_options.delete(:valid_date_check))
132
+ if vdc.include?(";") || vdc.include?("function")
133
+ throw ":valid_date_check function is missing a 'return' statement. Try something like: :valid_date_check => 'if (date > new(Date)) return true; else return false;'" unless vdc.include?("return");
134
+ end
135
+
136
+ vdc = "return(#{vdc})" unless vdc.include?("return")
137
+ vdc = "function(date) { #{vdc} }" unless vdc.include?("function")
138
+ calendar_options[:valid_date_check] = vdc
139
+ end
140
+
141
+ calendar_options[:popup_by] ||= "this" if calendar_options[:hidden]
142
+
143
+ # surround any callbacks with a function, if not already done so
144
+ for key in callbacks
145
+ calendar_options[key] = "function(param) { #{calendar_options[key]} }" unless calendar_options[key].include?("function") if calendar_options[key]
146
+ end
147
+
148
+ calendar_options[:year_range] = format_year_range(calendar_options[:year_range] || 10)
149
+ calendar_options
150
+ end
151
+
152
+ def calendar_date_select(object, method, options={})
153
+ obj = options[:object] || instance_variable_get("@#{object}")
154
+
155
+ if !options.include?(:time) && obj.class.respond_to?("columns_hash")
156
+ column_type = (obj.class.columns_hash[method.to_s].type rescue nil)
157
+ options[:time] = true if column_type == :datetime
158
+ end
159
+
160
+ use_time = options[:time]
161
+
162
+ if options[:time].to_s=="mixed"
163
+ use_time = false if Date===(obj.respond_to?(method) && obj.send(method))
164
+ end
165
+
166
+ calendar_options = calendar_date_select_process_options(options)
167
+
168
+ options[:value] ||=
169
+ if(obj.respond_to?(method) && obj.send(method).respond_to?(:strftime))
170
+ obj.send(method).strftime(CalendarDateSelect.date_format_string(use_time))
171
+ elsif obj.respond_to?("#{method}_before_type_cast")
172
+ obj.send("#{method}_before_type_cast")
173
+ elsif obj.respond_to?(method)
174
+ obj.send(method).to_s
175
+ else
176
+ nil
177
+ end
178
+
179
+ tag = ActionView::Helpers::InstanceTag.new(object, method, self, nil, options.delete(:object))
180
+ calendar_date_select_output(
181
+ tag.to_input_field_tag( (calendar_options[:hidden] || calendar_options[:embedded]) ? "hidden" : "text", options),
182
+ calendar_options
183
+ )
184
+ end
185
+
186
+ def calendar_date_select_output(input, calendar_options = {})
187
+ out = input
188
+ if calendar_options[:embedded]
189
+ uniq_id = "cds_placeholder_#{(rand*100000).to_i}"
190
+ # we need to be able to locate the target input element, so lets stick an invisible span tag here we can easily locate
191
+ out << content_tag(:span, nil, :style => "display: none; position: absolute;", :id => uniq_id)
192
+
193
+ out << javascript_tag("new CalendarDateSelect( $('#{uniq_id}').previous(), #{options_for_javascript(calendar_options)} ); ")
194
+ else
195
+ out << " "
196
+
197
+ out << image_tag(CalendarDateSelect.image,
198
+ :onclick => "new CalendarDateSelect( $(this).previous(), #{options_for_javascript(calendar_options)} );",
199
+ :style => 'border:0px; cursor:pointer;')
200
+ end
201
+
202
+ out
203
+ end
204
+
205
+ private
206
+ def format_year_range(year) # nodoc
207
+ return year unless year.respond_to?(:first)
208
+ return "[#{year.first}, #{year.last}]" unless year.first.respond_to?(:strftime)
209
+ return "[#{year.first.year}, #{year.last.year}]"
210
+ end
211
+ end
212
+ end
213
+
214
+
215
+ module ActionView
216
+ module Helpers
217
+ class FormBuilder
218
+ def calendar_date_select(method, options = {})
219
+ @template.calendar_date_select(@object_name, method, options.merge(:object => @object))
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,38 @@
1
+ class CalendarDateSelect
2
+ module IncludesHelper
3
+ def calendar_date_select_stylesheets(options = {})
4
+ options.assert_valid_keys(:style, :format, :locale)
5
+
6
+ style = options[:style]
7
+ cds_css_file = style ? "calendar_date_select/#{style}" : "calendar_date_select/default"
8
+ return cds_css_file
9
+ end
10
+
11
+ def calendar_date_select_javascripts(options = {})
12
+ options.assert_valid_keys(:style, :format, :locale)
13
+
14
+ style = options[:style]
15
+ locale = options[:locale]
16
+ cds_css_file = style ? "calendar_date_select/#{style}" : "calendar_date_select/default"
17
+
18
+ output = []
19
+ output << "calendar_date_select/calendar_date_select"
20
+ output << "calendar_date_select/locale/#{locale}" if locale
21
+ output << CalendarDateSelect.javascript_format_include if CalendarDateSelect.javascript_format_include
22
+ return output
23
+ end
24
+
25
+ def calendar_date_select_includes(*args)
26
+ return "" if @cds_already_included
27
+ @cds_already_included=true
28
+
29
+ options = (Hash === args.last) ? args.pop : {}
30
+ options.assert_valid_keys(:style, :format, :locale)
31
+ options[:style] ||= args.shift
32
+
33
+ js = javascript_include_tag(*calendar_date_select_javascripts(options))
34
+ css = stylesheet_link_tag(*calendar_date_select_stylesheets(options))
35
+ "#{js}\n#{css}\n"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,2 @@
1
+ <!-- Nothing here; part of the calendar_date_select plugin -->
2
+ <html><head></head><body></body></html>
@@ -0,0 +1,442 @@
1
+ // CalendarDateSelect version 1.11.1 - a prototype based date picker
2
+ // Questions, comments, bugs? - see the project page: http://code.google.com/p/calendardateselect
3
+ if (typeof Prototype == 'undefined') alert("CalendarDateSelect Error: Prototype could not be found. Please make sure that your application's layout includes prototype.js (.g. <%= javascript_include_tag :defaults %>) *before* it includes calendar_date_select.js (.g. <%= calendar_date_select_includes %>).");
4
+ if (Prototype.Version < "1.6") alert("Prototype 1.6.0 is required. If using earlier version of prototype, please use calendar_date_select version 1.8.3");
5
+
6
+ Element.addMethods({
7
+ purgeChildren: function(element) { $A(element.childNodes).each(function(e){$(e).remove();}); },
8
+ build: function(element, type, options, style) {
9
+ var newElement = Element.buildAndAppend(type, options, style);
10
+ element.appendChild(newElement);
11
+ return newElement;
12
+ }
13
+ });
14
+
15
+ Element.buildAndAppend = function(type, options, style)
16
+ {
17
+ var e = $(document.createElement(type));
18
+ $H(options).each(function(pair) { eval("e." + pair.key + " = pair.value" ); });
19
+ if (style)
20
+ $H(style).each(function(pair) { eval("e.style." + pair.key + " = pair.value" ); });
21
+ return e;
22
+ };
23
+ nil = null;
24
+
25
+ Date.one_day = 24*60*60*1000;
26
+ Date.weekdays = $w("S M T W T F S");
27
+ Date.first_day_of_week = 0;
28
+ Date.months = $w("January February March April May June July August September October November December" );
29
+ Date.padded2 = function(hour) { var padded2 = parseInt(hour, 10); if (hour < 10) padded2 = "0" + padded2; return padded2; }
30
+ Date.prototype.getPaddedMinutes = function() { return Date.padded2(this.getMinutes()); }
31
+ Date.prototype.getAMPMHour = function() { var hour = this.getHours(); return (hour == 0) ? 12 : (hour > 12 ? hour - 12 : hour ) }
32
+ Date.prototype.getAMPM = function() { return (this.getHours() < 12) ? "AM" : "PM"; }
33
+ Date.prototype.stripTime = function() { return new Date(this.getFullYear(), this.getMonth(), this.getDate());};
34
+ Date.prototype.daysDistance = function(compare_date) { return Math.round((compare_date - this) / Date.one_day); };
35
+ Date.prototype.toFormattedString = function(include_time){
36
+ var hour, str;
37
+ str = Date.months[this.getMonth()] + " " + this.getDate() + ", " + this.getFullYear();
38
+
39
+ if (include_time) { hour = this.getHours(); str += " " + this.getAMPMHour() + ":" + this.getPaddedMinutes() + " " + this.getAMPM() }
40
+ return str;
41
+ }
42
+ Date.parseFormattedString = function(string) { return new Date(string);}
43
+ Math.floor_to_interval = function(n, i) { return Math.floor(n/i) * i;}
44
+ window.f_height = function() { return( [window.innerHeight ? window.innerHeight : null, document.documentElement ? document.documentElement.clientHeight : null, document.body ? document.body.clientHeight : null].select(function(x){return x>0}).first()||0); }
45
+ window.f_scrollTop = function() { return ([window.pageYOffset ? window.pageYOffset : null, document.documentElement ? document.documentElement.scrollTop : null, document.body ? document.body.scrollTop : null].select(function(x){return x>0}).first()||0 ); }
46
+
47
+ _translations = {
48
+ "OK": "OK",
49
+ "Now": "Now",
50
+ "Today": "Today",
51
+ "Clear": "Clear"
52
+ }
53
+ SelectBox = Class.create();
54
+ SelectBox.prototype = {
55
+ initialize: function(parent_element, values, html_options, style_options) {
56
+ this.element = $(parent_element).build("select", html_options, style_options);
57
+ this.populate(values);
58
+ },
59
+ populate: function(values) {
60
+ this.element.purgeChildren();
61
+ var that = this; $A(values).each(function(pair) { if (typeof(pair)!="object") {pair = [pair, pair]}; that.element.build("option", { value: pair[1], innerHTML: pair[0]}) });
62
+ },
63
+ setValue: function(value) {
64
+ var e = this.element;
65
+ var matched = false;
66
+ $R(0, e.options.length - 1 ).each(function(i) { if(e.options[i].value==value.toString()) {e.selectedIndex = i; matched = true;}; } );
67
+ return matched;
68
+ },
69
+ getValue: function() { return $F(this.element)}
70
+ }
71
+ CalendarDateSelect = Class.create();
72
+ CalendarDateSelect.prototype = {
73
+ initialize: function(target_element, options) {
74
+ this.target_element = $(target_element); // make sure it's an element, not a string
75
+ if (!this.target_element) { alert("Target element " + target_element + " not found!"); return false;}
76
+ if (this.target_element.tagName != "INPUT") this.target_element = this.target_element.down("INPUT")
77
+
78
+ this.target_element.calendar_date_select = this;
79
+ this.last_click_at = 0;
80
+ // initialize the date control
81
+ this.options = $H({
82
+ embedded: false,
83
+ popup: nil,
84
+ time: false,
85
+ buttons: true,
86
+ clear_button: true,
87
+ year_range: 10,
88
+ close_on_click: nil,
89
+ minute_interval: 5,
90
+ popup_by: this.target_element,
91
+ month_year: "dropdowns",
92
+ onchange: this.target_element.onchange,
93
+ valid_date_check: nil
94
+ }).merge(options || {});
95
+ this.use_time = this.options.get("time");
96
+ this.parseDate();
97
+ this.callback("before_show")
98
+ this.initCalendarDiv();
99
+ if(!this.options.get("embedded")) {
100
+ this.positionCalendarDiv()
101
+ // set the click handler to check if a user has clicked away from the document
102
+ Event.observe(document, "mousedown", this.closeIfClickedOut_handler = this.closeIfClickedOut.bindAsEventListener(this));
103
+ Event.observe(document, "keypress", this.keyPress_handler = this.keyPress.bindAsEventListener(this));
104
+ }
105
+ this.callback("after_show")
106
+ },
107
+ positionCalendarDiv: function() {
108
+ var above = false;
109
+ var c_pos = this.calendar_div.cumulativeOffset(), c_left = c_pos[0], c_top = c_pos[1], c_dim = this.calendar_div.getDimensions(), c_height = c_dim.height, c_width = c_dim.width;
110
+ var w_top = window.f_scrollTop(), w_height = window.f_height();
111
+ var e_dim = $(this.options.get("popup_by")).cumulativeOffset(), e_top = e_dim[1], e_left = e_dim[0], e_height = $(this.options.get("popup_by")).getDimensions().height, e_bottom = e_top + e_height;
112
+
113
+ if ( (( e_bottom + c_height ) > (w_top + w_height)) && ( e_bottom - c_height > w_top )) above = true;
114
+ var left_px = e_left.toString() + "px", top_px = (above ? (e_top - c_height ) : ( e_top + e_height )).toString() + "px";
115
+
116
+ this.calendar_div.style.left = left_px; this.calendar_div.style.top = top_px;
117
+
118
+ this.calendar_div.setStyle({visibility:""});
119
+
120
+ // draw an iframe behind the calendar -- ugly hack to make IE 6 happy
121
+ if(navigator.appName=="Microsoft Internet Explorer") this.iframe = $(document.body).build("iframe", {src: "javascript:false", className: "ie6_blocker"}, { left: left_px, top: top_px, height: c_height.toString()+"px", width: c_width.toString()+"px", border: "0px"})
122
+ },
123
+ initCalendarDiv: function() {
124
+ if (this.options.get("embedded")) {
125
+ var parent = this.target_element.parentNode;
126
+ var style = {}
127
+ } else {
128
+ var parent = document.body
129
+ var style = { position:"absolute", visibility: "hidden", left:0, top:0 }
130
+ }
131
+ this.calendar_div = $(parent).build('div', {className: "calendar_date_select"}, style);
132
+
133
+ var that = this;
134
+ // create the divs
135
+ $w("top header body buttons footer bottom").each(function(name) {
136
+ eval("var " + name + "_div = that." + name + "_div = that.calendar_div.build('div', { className: 'cds_"+name+"' }, { clear: 'left'} ); ");
137
+ });
138
+
139
+ this.initHeaderDiv();
140
+ this.initButtonsDiv();
141
+ this.initCalendarGrid();
142
+ this.updateFooter("&#160;");
143
+
144
+ this.refresh();
145
+ this.setUseTime(this.use_time);
146
+ },
147
+ initHeaderDiv: function() {
148
+ var header_div = this.header_div;
149
+ this.close_button = header_div.build("a", { innerHTML: "x", href:"#", onclick:function () { this.close(); return false; }.bindAsEventListener(this), className: "close" });
150
+ this.next_month_button = header_div.build("a", { innerHTML: "&gt;", href:"#", onclick:function () { this.navMonth(this.date.getMonth() + 1 ); return false; }.bindAsEventListener(this), className: "next" });
151
+ this.prev_month_button = header_div.build("a", { innerHTML: "&lt;", href:"#", onclick:function () { this.navMonth(this.date.getMonth() - 1 ); return false; }.bindAsEventListener(this), className: "prev" });
152
+
153
+ if (this.options.get("month_year")=="dropdowns") {
154
+ this.month_select = new SelectBox(header_div, $R(0,11).map(function(m){return [Date.months[m], m]}), {className: "month", onchange: function () { this.navMonth(this.month_select.getValue()) }.bindAsEventListener(this)});
155
+ this.year_select = new SelectBox(header_div, [], {className: "year", onchange: function () { this.navYear(this.year_select.getValue()) }.bindAsEventListener(this)});
156
+ this.populateYearRange();
157
+ } else {
158
+ this.month_year_label = header_div.build("span")
159
+ }
160
+ },
161
+ initCalendarGrid: function() {
162
+ var body_div = this.body_div;
163
+ this.calendar_day_grid = [];
164
+ var days_table = body_div.build("table", { cellPadding: "0px", cellSpacing: "0px", width: "100%" })
165
+ // make the weekdays!
166
+ var weekdays_row = days_table.build("thead").build("tr");
167
+ Date.weekdays.each( function(weekday) {
168
+ weekdays_row.build("th", {innerHTML: weekday});
169
+ });
170
+
171
+ var days_tbody = days_table.build("tbody")
172
+ // Make the days!
173
+ var row_number = 0, weekday;
174
+ for(var cell_index = 0; cell_index<42; cell_index++)
175
+ {
176
+ weekday = (cell_index+Date.first_day_of_week ) % 7;
177
+ if ( cell_index % 7==0 ) days_row = days_tbody.build("tr", {className: 'row_'+row_number++});
178
+ (this.calendar_day_grid[cell_index] = days_row.build("td", {
179
+ calendar_date_select: this,
180
+ onmouseover: function () { this.calendar_date_select.dayHover(this); },
181
+ onmouseout: function () { this.calendar_date_select.dayHoverOut(this) },
182
+ onclick: function() { this.calendar_date_select.updateSelectedDate(this, true); },
183
+ className: (weekday==0) || (weekday==6) ? " weekend" : "" //clear the class
184
+ },
185
+ { cursor: "pointer" }
186
+ )).build("div");
187
+ this.calendar_day_grid[cell_index];
188
+ }
189
+ },
190
+ initButtonsDiv: function()
191
+ {
192
+ var buttons_div = this.buttons_div;
193
+ if (this.options.get("time"))
194
+ {
195
+ var blank_time = $A(this.options.get("time")=="mixed" ? [[" - ", ""]] : []);
196
+ buttons_div.build("span", {innerHTML:"@", className: "at_sign"});
197
+
198
+ var t = new Date();
199
+ this.hour_select = new SelectBox(buttons_div,
200
+ blank_time.concat($R(0,23).map(function(x) {t.setHours(x); return $A([t.getAMPMHour()+ " " + t.getAMPM(),x])} )),
201
+ {
202
+ calendar_date_select: this,
203
+ onchange: function() { this.calendar_date_select.updateSelectedDate( { hour: this.value });},
204
+ className: "hour"
205
+ }
206
+ );
207
+ buttons_div.build("span", {innerHTML:":", className: "seperator"});
208
+ var that = this;
209
+ this.minute_select = new SelectBox(buttons_div,
210
+ blank_time.concat($R(0,59).select(function(x){return (x % that.options.get('minute_interval')==0)}).map(function(x){ return $A([ Date.padded2(x), x]); } ) ),
211
+ {
212
+ calendar_date_select: this,
213
+ onchange: function() { this.calendar_date_select.updateSelectedDate( {minute: this.value }) },
214
+ className: "minute"
215
+ }
216
+ );
217
+
218
+ } else if (! this.options.get("buttons")) buttons_div.remove();
219
+
220
+ if (this.options.get("buttons")) {
221
+ buttons_div.build("span", {innerHTML: "&#160;"});
222
+ if (this.options.get("time")=="mixed" || !this.options.get("time")) b = buttons_div.build("a", {
223
+ innerHTML: _translations["Today"],
224
+ href: "#",
225
+ onclick: function() {this.today(false); return false;}.bindAsEventListener(this)
226
+ });
227
+
228
+ if (this.options.get("time")=="mixed") buttons_div.build("span", {innerHTML: "&#160;|&#160;", className:"button_seperator"})
229
+
230
+ if (this.options.get("time")) b = buttons_div.build("a", {
231
+ innerHTML: _translations["Now"],
232
+ href: "#",
233
+ onclick: function() {this.today(true); return false}.bindAsEventListener(this)
234
+ });
235
+
236
+ if (!this.options.get("embedded") && !this.closeOnClick())
237
+ {
238
+ buttons_div.build("span", {innerHTML: "&#160;|&#160;", className:"button_seperator"})
239
+ buttons_div.build("a", { innerHTML: _translations["OK"], href: "#", onclick: function() {this.close(); return false;}.bindAsEventListener(this) });
240
+ }
241
+ if (this.options.get('clear_button')) {
242
+ buttons_div.build("span", {innerHTML: "&#160;|&#160;", className:"button_seperator"})
243
+ buttons_div.build("a", { innerHTML: _translations["Clear"], href: "#", onclick: function() {this.clearDate(); if (!this.options.get("embedded")) this.close(); return false;}.bindAsEventListener(this) });
244
+ }
245
+ }
246
+ },
247
+ refresh: function ()
248
+ {
249
+ this.refreshMonthYear();
250
+ this.refreshCalendarGrid();
251
+
252
+ this.setSelectedClass();
253
+ this.updateFooter();
254
+ },
255
+ refreshCalendarGrid: function () {
256
+ this.beginning_date = new Date(this.date).stripTime();
257
+ this.beginning_date.setDate(1);
258
+ this.beginning_date.setHours(12); // Prevent daylight savings time boundaries from showing a duplicate day
259
+ var pre_days = this.beginning_date.getDay() // draw some days before the fact
260
+ if (pre_days < 3) pre_days += 7;
261
+ this.beginning_date.setDate(1 - pre_days + Date.first_day_of_week);
262
+
263
+ var iterator = new Date(this.beginning_date);
264
+
265
+ var today = new Date().stripTime();
266
+ var this_month = this.date.getMonth();
267
+ vdc = this.options.get("valid_date_check");
268
+ for (var cell_index = 0;cell_index<42; cell_index++)
269
+ {
270
+ day = iterator.getDate(); month = iterator.getMonth();
271
+ cell = this.calendar_day_grid[cell_index];
272
+ Element.remove(cell.childNodes[0]); div = cell.build("div", {innerHTML:day});
273
+ if (month!=this_month) div.className = "other";
274
+ cell.day = day; cell.month = month; cell.year = iterator.getFullYear();
275
+ if (vdc) { if (vdc(iterator.stripTime())) cell.removeClassName("disabled"); else cell.addClassName("disabled") };
276
+ iterator.setDate( day + 1);
277
+ }
278
+
279
+ if (this.today_cell) this.today_cell.removeClassName("today");
280
+
281
+ if ( $R( 0, 41 ).include(days_until = this.beginning_date.stripTime().daysDistance(today)) ) {
282
+ this.today_cell = this.calendar_day_grid[days_until];
283
+ this.today_cell.addClassName("today");
284
+ }
285
+ },
286
+ refreshMonthYear: function() {
287
+ var m = this.date.getMonth();
288
+ var y = this.date.getFullYear();
289
+ // set the month
290
+ if (this.options.get("month_year") == "dropdowns")
291
+ {
292
+ this.month_select.setValue(m, false);
293
+
294
+ var e = this.year_select.element;
295
+ if (this.flexibleYearRange() && (!(this.year_select.setValue(y, false)) || e.selectedIndex <= 1 || e.selectedIndex >= e.options.length - 2 )) this.populateYearRange();
296
+
297
+ this.year_select.setValue(y);
298
+
299
+ } else {
300
+ this.month_year_label.update( Date.months[m] + " " + y.toString() );
301
+ }
302
+ },
303
+ populateYearRange: function() {
304
+ this.year_select.populate(this.yearRange().toArray());
305
+ },
306
+ yearRange: function() {
307
+ if (!this.flexibleYearRange())
308
+ return $R(this.options.get("year_range")[0], this.options.get("year_range")[1]);
309
+
310
+ var y = this.date.getFullYear();
311
+ return $R(y - this.options.get("year_range"), y + this.options.get("year_range"));
312
+ },
313
+ flexibleYearRange: function() { return (typeof(this.options.get("year_range")) == "number"); },
314
+ validYear: function(year) { if (this.flexibleYearRange()) { return true;} else { return this.yearRange().include(year);} },
315
+ dayHover: function(element) {
316
+ var hover_date = new Date(this.selected_date);
317
+ hover_date.setYear(element.year); hover_date.setMonth(element.month); hover_date.setDate(element.day);
318
+ this.updateFooter(hover_date.toFormattedString(this.use_time));
319
+ },
320
+ dayHoverOut: function(element) { this.updateFooter(); },
321
+ clearSelectedClass: function() {if (this.selected_cell) this.selected_cell.removeClassName("selected");},
322
+ setSelectedClass: function() {
323
+ if (!this.selection_made) return;
324
+ this.clearSelectedClass()
325
+ if ($R(0,42).include( days_until = this.beginning_date.stripTime().daysDistance(this.selected_date.stripTime()) )) {
326
+ this.selected_cell = this.calendar_day_grid[days_until];
327
+ this.selected_cell.addClassName("selected");
328
+ }
329
+ },
330
+ reparse: function() { this.parseDate(); this.refresh(); },
331
+ dateString: function() {
332
+ return (this.selection_made) ? this.selected_date.toFormattedString(this.use_time) : "&#160;";
333
+ },
334
+ parseDate: function()
335
+ {
336
+ var value = $F(this.target_element).strip()
337
+ this.selection_made = (value != "");
338
+ this.date = value=="" ? NaN : Date.parseFormattedString(this.options.get("date") || value);
339
+ if (isNaN(this.date)) this.date = new Date();
340
+ if (!this.validYear(this.date.getFullYear())) this.date.setYear( (this.date.getFullYear() < this.yearRange().start) ? this.yearRange().start : this.yearRange().end);
341
+ this.selected_date = new Date(this.date);
342
+ this.use_time = /[0-9]:[0-9]{2}/.exec(value) ? true : false;
343
+ this.date.setDate(1);
344
+ },
345
+ updateFooter:function(text) { if (!text) text = this.dateString(); this.footer_div.purgeChildren(); this.footer_div.build("span", {innerHTML: text }); },
346
+ clearDate:function() {
347
+ if ((this.target_element.disabled || this.target_element.readOnly) && this.options.get("popup") != "force") return false;
348
+ this.target_element.value = "";
349
+ this.clearSelectedClass();
350
+ this.updateFooter('&#160;');
351
+ },
352
+ updateSelectedDate:function(partsOrElement, via_click) {
353
+ var parts = $H(partsOrElement);
354
+ if ((this.target_element.disabled || this.target_element.readOnly) && this.options.get("popup") != "force") return false;
355
+ if (parts.get("day")) {
356
+ var t_selected_date = this.selected_date, vdc = this.options.get("valid_date_check");
357
+ for (var x = 0; x<=3; x++) t_selected_date.setDate(parts.get("day"));
358
+ t_selected_date.setYear(parts.get("year"));
359
+ t_selected_date.setMonth(parts.get("month"));
360
+
361
+ if (vdc && ! vdc(t_selected_date.stripTime())) { return false; }
362
+ this.selected_date = t_selected_date;
363
+ this.selection_made = true;
364
+ }
365
+
366
+ if (!isNaN(parts.get("hour"))) this.selected_date.setHours(parts.get("hour"));
367
+ if (!isNaN(parts.get("minute"))) this.selected_date.setMinutes( Math.floor_to_interval(parts.get("minute"), this.options.get("minute_interval")) );
368
+ if (parts.get("hour") === "" || parts.get("minute") === "")
369
+ this.setUseTime(false);
370
+ else if (!isNaN(parts.get("hour")) || !isNaN(parts.get("minute")))
371
+ this.setUseTime(true);
372
+
373
+ this.updateFooter();
374
+ this.setSelectedClass();
375
+
376
+ if (this.selection_made) this.updateValue();
377
+ if (this.closeOnClick()) { this.close(); }
378
+ if (via_click && !this.options.get("embedded")) {
379
+ if ((new Date() - this.last_click_at) < 333) this.close();
380
+ this.last_click_at = new Date();
381
+ }
382
+ },
383
+ closeOnClick: function() {
384
+ if (this.options.get("embedded")) return false;
385
+ if (this.options.get("close_on_click")===nil )
386
+ return (this.options.get("time")) ? false : true
387
+ else
388
+ return (this.options.get("close_on_click"))
389
+ },
390
+ navMonth: function(month) { (target_date = new Date(this.date)).setMonth(month); return (this.navTo(target_date)); },
391
+ navYear: function(year) { (target_date = new Date(this.date)).setYear(year); return (this.navTo(target_date)); },
392
+ navTo: function(date) {
393
+ if (!this.validYear(date.getFullYear())) return false;
394
+ this.date = date;
395
+ this.date.setDate(1);
396
+ this.refresh();
397
+ this.callback("after_navigate", this.date);
398
+ return true;
399
+ },
400
+ setUseTime: function(turn_on) {
401
+ this.use_time = this.options.get("time") && (this.options.get("time")=="mixed" ? turn_on : true) // force use_time to true if time==true && time!="mixed"
402
+ if (this.use_time && this.selected_date) { // only set hour/minute if a date is already selected
403
+ var minute = Math.floor_to_interval(this.selected_date.getMinutes(), this.options.get("minute_interval"));
404
+ var hour = this.selected_date.getHours();
405
+
406
+ this.hour_select.setValue(hour);
407
+ this.minute_select.setValue(minute)
408
+ } else if (this.options.get("time")=="mixed") {
409
+ this.hour_select.setValue(""); this.minute_select.setValue("");
410
+ }
411
+ },
412
+ updateValue: function() {
413
+ var last_value = this.target_element.value;
414
+ this.target_element.value = this.dateString();
415
+ if (last_value!=this.target_element.value) this.callback("onchange");
416
+ },
417
+ today: function(now) {
418
+ var d = new Date(); this.date = new Date();
419
+ var o = $H({ day: d.getDate(), month: d.getMonth(), year: d.getFullYear(), hour: d.getHours(), minute: d.getMinutes()});
420
+ if ( ! now ) o = o.merge({hour: "", minute:""});
421
+ this.updateSelectedDate(o, true);
422
+ this.refresh();
423
+ },
424
+ close: function() {
425
+ if (this.closed) return false;
426
+ this.callback("before_close");
427
+ this.target_element.calendar_date_select = nil;
428
+ Event.stopObserving(document, "mousedown", this.closeIfClickedOut_handler);
429
+ Event.stopObserving(document, "keypress", this.keyPress_handler);
430
+ this.calendar_div.remove(); this.closed = true;
431
+ if (this.iframe) this.iframe.remove();
432
+ if (this.target_element.type!="hidden") this.target_element.focus();
433
+ this.callback("after_close");
434
+ },
435
+ closeIfClickedOut: function(e) {
436
+ if (! $(Event.element(e)).descendantOf(this.calendar_div) ) this.close();
437
+ },
438
+ keyPress: function(e) {
439
+ if (e.keyCode==Event.KEY_ESC) this.close();
440
+ },
441
+ callback: function(name, param) { if (this.options.get(name)) { this.options.get(name).bind(this.target_element)(param); } }
442
+ }