atd-calendar_date_select 1.11.20090108

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 (37) hide show
  1. data/History.txt +231 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Manifest.txt +42 -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 +33 -0
  13. data/lib/calendar_date_select/calendar_date_select.rb +116 -0
  14. data/lib/calendar_date_select/includes_helper.rb +29 -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 +443 -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 +11 -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. metadata +103 -0
@@ -0,0 +1,33 @@
1
+ require "calendar_date_select/calendar_date_select.rb"
2
+ require "calendar_date_select/form_helpers.rb"
3
+ require "calendar_date_select/includes_helper.rb"
4
+
5
+ if Object.const_defined?(:Rails) && File.directory?(Rails.root + "/public")
6
+ ActionView::Helpers::FormHelper.send(:include, CalendarDateSelect::FormHelpers)
7
+ ActionView::Base.send(:include, CalendarDateSelect::FormHelpers)
8
+ ActionView::Base.send(:include, CalendarDateSelect::IncludesHelper)
9
+
10
+ # Filthy backwards compatibility hooks... grumble
11
+ if ([Rails::VERSION::MAJOR, Rails::VERSION::MINOR] <=> [2, 2]) == -1
12
+ ActionView::Helpers::InstanceTag.class_eval do
13
+ def self.new_with_backwards_compatibility(object_name, method_name, template_object, object = nil)
14
+ new(object_name, method_name, template_object, nil, object)
15
+ end
16
+ end
17
+
18
+ else
19
+ ActionView::Helpers::InstanceTag.class_eval do
20
+ class << self; alias new_with_backwards_compatibility new; end
21
+ end
22
+ end
23
+
24
+ # install files
25
+ unless File.exists?(RAILS_ROOT + '/public/javascripts/calendar_date_select/calendar_date_select.js')
26
+ ['/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|
27
+ source = File.dirname(__FILE__) + "/../#{dir}"
28
+ dest = RAILS_ROOT + dir
29
+ FileUtils.mkdir_p(dest)
30
+ FileUtils.cp(Dir.glob(source+'/*.*'), dest)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,116 @@
1
+ module CalendarDateSelect
2
+ VERSION = '1.13'
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
+ # Returns the default_options hash. These options are by default provided to every calendar_date_select control, unless otherwise overrided.
51
+ #
52
+ # Example:
53
+ # # At the bottom of config/environment.rb:
54
+ # CalendarDateSelect.default_options.update(
55
+ # :popup => "force",
56
+ # :month_year => "label",
57
+ # :image => "custom_calendar_picker.png"
58
+ # )
59
+ def self.default_options
60
+ @default_options ||= { :image => "calendar_date_select/calendar.gif" }
61
+ end
62
+
63
+ # Set the picker image. Provide the image url the same way you would provide it to image_tag
64
+ def self.image=(value)
65
+ default_options[:image] = value
66
+ end
67
+
68
+ # Returns the options for the given format
69
+ #
70
+ # Example:
71
+ # CalendarDateSelect.format = :italian
72
+ # puts CalendarDateSelect.format[:date]
73
+ # => "%d/%m/%Y"
74
+ def self.format
75
+ @format ||= FORMATS[:natural]
76
+ end
77
+
78
+ # Set the format. To see a list of available formats, CalendarDateSelect::FORMATS.keys, or open lib/calendar_date_select/calendar_date_select.rb
79
+ #
80
+ # (e.g. CalendarDateSelect.format = :italian)
81
+ def self.format=(format)
82
+ raise "CalendarDateSelect: Unrecognized format specification: #{format}" unless FORMATS.has_key?(format)
83
+ @format = FORMATS[format]
84
+ end
85
+
86
+ def self.date_format_string(time = false)
87
+ format[:date] + (time ? format[:time] : "")
88
+ end
89
+
90
+ def self.format_date(date)
91
+ if date.is_a?(Date)
92
+ date.strftime(date_format_string(false))
93
+ else
94
+ date.strftime(date_format_string(true))
95
+ end
96
+ end
97
+
98
+ def self.format_time(value, options = {})
99
+ return value unless value.respond_to?("strftime")
100
+ if options[:time]
101
+ format_date(value)
102
+ else
103
+ format_date(value.to_date)
104
+ end
105
+ end
106
+
107
+ # Detects the presence of time in a date, string
108
+ def self.has_time?(value)
109
+ case value
110
+ when DateTime, Time then true
111
+ when Date then false
112
+ else
113
+ /[0-9]:[0-9]{2}/.match(value.to_s) ? true : false
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,29 @@
1
+ module CalendarDateSelect::IncludesHelper
2
+ # returns the selected calendar_date_select stylesheet (not an array)
3
+ def calendar_date_select_stylesheets(options = {})
4
+ options.assert_valid_keys(:style)
5
+ "calendar_date_select/#{options[:style] || "default"}"
6
+ end
7
+
8
+ # returns an array of javascripts needed for the selected locale, date_format, and calendar control itself.
9
+ def calendar_date_select_javascripts(options = {})
10
+ options.assert_valid_keys(:format, :locale)
11
+ files = ["calendar_date_select/calendar_date_select"]
12
+ files << "calendar_date_select/locale/#{options[:locale]}" if options[:locale]
13
+ files << "calendar_date_select/#{CalendarDateSelect.format[:javascript_include]}" if CalendarDateSelect.format[:javascript_include]
14
+ files
15
+ end
16
+
17
+ # returns html necessary to load javascript and css to make calendar_date_select work
18
+ def calendar_date_select_includes(*args)
19
+ return "" if @cds_already_included
20
+ @cds_already_included=true
21
+
22
+ options = (Hash === args.last) ? args.pop : {}
23
+ options.assert_valid_keys(:style, :format, :locale)
24
+ options[:style] ||= args.shift
25
+
26
+ javascript_include_tag(*calendar_date_select_javascripts(:locale => options[:locale], :format => options[:format])) + "\n" +
27
+ stylesheet_link_tag(*calendar_date_select_stylesheets(:style => options[:style])) + "\n"
28
+ end
29
+ 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,443 @@
1
+ // CalendarDateSelect version 1.13 - 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) { e[pair.key] = pair.value });
19
+ if (style) e.setStyle(style);
20
+ return e;
21
+ };
22
+ nil = null;
23
+
24
+ Date.one_day = 24*60*60*1000;
25
+ Date.weekdays = $w("S M T W T F S");
26
+ Date.first_day_of_week = 0;
27
+ Date.months = $w("January February March April May June July August September October November December" );
28
+ Date.padded2 = function(hour) { var padded2 = parseInt(hour, 10); if (hour < 10) padded2 = "0" + padded2; return padded2; }
29
+ Date.prototype.getPaddedMinutes = function() { return Date.padded2(this.getMinutes()); }
30
+ Date.prototype.getAMPMHour = function() { var hour = this.getHours(); return (hour == 0) ? 12 : (hour > 12 ? hour - 12 : hour ) }
31
+ Date.prototype.getAMPM = function() { return (this.getHours() < 12) ? "AM" : "PM"; }
32
+ Date.prototype.stripTime = function() { return new Date(this.getFullYear(), this.getMonth(), this.getDate());};
33
+ Date.prototype.daysDistance = function(compare_date) { return Math.round((compare_date - this) / Date.one_day); };
34
+ Date.prototype.toFormattedString = function(include_time){
35
+ var hour, str;
36
+ str = Date.months[this.getMonth()] + " " + this.getDate() + ", " + this.getFullYear();
37
+
38
+ if (include_time) { hour = this.getHours(); str += " " + this.getAMPMHour() + ":" + this.getPaddedMinutes() + " " + this.getAMPM() }
39
+ return str;
40
+ }
41
+ Date.parseFormattedString = function(string) { return new Date(string);}
42
+ Math.floor_to_interval = function(n, i) { return Math.floor(n/i) * i;}
43
+ 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); }
44
+ 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 ); }
45
+
46
+ _translations = {
47
+ "OK": "OK",
48
+ "Now": "Now",
49
+ "Today": "Today",
50
+ "Clear": "Clear"
51
+ }
52
+ SelectBox = Class.create();
53
+ SelectBox.prototype = {
54
+ initialize: function(parent_element, values, html_options, style_options) {
55
+ this.element = $(parent_element).build("select", html_options, style_options);
56
+ this.populate(values);
57
+ },
58
+ populate: function(values) {
59
+ this.element.purgeChildren();
60
+ 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]}) });
61
+ },
62
+ setValue: function(value) {
63
+ var e = this.element;
64
+ var matched = false;
65
+ $R(0, e.options.length - 1 ).each(function(i) { if(e.options[i].value==value.toString()) {e.selectedIndex = i; matched = true;}; } );
66
+ return matched;
67
+ },
68
+ getValue: function() { return $F(this.element)}
69
+ }
70
+ CalendarDateSelect = Class.create();
71
+ CalendarDateSelect.prototype = {
72
+ initialize: function(target_element, options) {
73
+ this.target_element = $(target_element); // make sure it's an element, not a string
74
+ if (!this.target_element) { alert("Target element " + target_element + " not found!"); return false;}
75
+ if (this.target_element.tagName != "INPUT") this.target_element = this.target_element.down("INPUT")
76
+
77
+ this.target_element.calendar_date_select = this;
78
+ this.last_click_at = 0;
79
+ // initialize the date control
80
+ this.options = $H({
81
+ embedded: false,
82
+ popup: nil,
83
+ time: false,
84
+ buttons: true,
85
+ clear_button: true,
86
+ year_range: 10,
87
+ close_on_click: nil,
88
+ minute_interval: 5,
89
+ popup_by: this.target_element,
90
+ month_year: "dropdowns",
91
+ onchange: this.target_element.onchange,
92
+ valid_date_check: nil
93
+ }).merge(options || {});
94
+ this.use_time = this.options.get("time");
95
+ this.parseDate();
96
+ this.callback("before_show")
97
+ this.initCalendarDiv();
98
+ if(!this.options.get("embedded")) {
99
+ this.positionCalendarDiv()
100
+ // set the click handler to check if a user has clicked away from the document
101
+ Event.observe(document, "mousedown", this.closeIfClickedOut_handler = this.closeIfClickedOut.bindAsEventListener(this));
102
+ Event.observe(document, "keypress", this.keyPress_handler = this.keyPress.bindAsEventListener(this));
103
+ }
104
+ this.callback("after_show")
105
+ },
106
+ positionCalendarDiv: function() {
107
+ var above = false;
108
+ 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;
109
+ var w_top = window.f_scrollTop(), w_height = window.f_height();
110
+ 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;
111
+
112
+ if ( (( e_bottom + c_height ) > (w_top + w_height)) && ( e_bottom - c_height > w_top )) above = true;
113
+ var left_px = e_left.toString() + "px", top_px = (above ? (e_top - c_height ) : ( e_top + e_height )).toString() + "px";
114
+
115
+ this.calendar_div.style.left = left_px; this.calendar_div.style.top = top_px;
116
+
117
+ this.calendar_div.setStyle({visibility:""});
118
+
119
+ // draw an iframe behind the calendar -- ugly hack to make IE 6 happy
120
+ 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"})
121
+ },
122
+ initCalendarDiv: function() {
123
+ if (this.options.get("embedded")) {
124
+ var parent = this.target_element.parentNode;
125
+ var style = {}
126
+ } else {
127
+ var parent = document.body
128
+ var style = { position:"absolute", visibility: "hidden", left:0, top:0 }
129
+ }
130
+ this.calendar_div = $(parent).build('div', {className: "calendar_date_select"}, style);
131
+
132
+ var that = this;
133
+ // create the divs
134
+ $w("top header body buttons footer bottom").each(function(name) {
135
+ eval("var " + name + "_div = that." + name + "_div = that.calendar_div.build('div', { className: 'cds_"+name+"' }, { clear: 'left'} ); ");
136
+ });
137
+
138
+ this.initHeaderDiv();
139
+ this.initButtonsDiv();
140
+ this.initCalendarGrid();
141
+ this.updateFooter("&#160;");
142
+
143
+ this.refresh();
144
+ this.setUseTime(this.use_time);
145
+ },
146
+ initHeaderDiv: function() {
147
+ var header_div = this.header_div;
148
+ this.close_button = header_div.build("a", { innerHTML: "x", href:"#", onclick:function () { this.close(); return false; }.bindAsEventListener(this), className: "close" });
149
+ 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" });
150
+ 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" });
151
+
152
+ if (this.options.get("month_year")=="dropdowns") {
153
+ 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)});
154
+ this.year_select = new SelectBox(header_div, [], {className: "year", onchange: function () { this.navYear(this.year_select.getValue()) }.bindAsEventListener(this)});
155
+ this.populateYearRange();
156
+ } else {
157
+ this.month_year_label = header_div.build("span")
158
+ }
159
+ },
160
+ initCalendarGrid: function() {
161
+ var body_div = this.body_div;
162
+ this.calendar_day_grid = [];
163
+ var days_table = body_div.build("table", { cellPadding: "0px", cellSpacing: "0px", width: "100%" })
164
+ // make the weekdays!
165
+ var weekdays_row = days_table.build("thead").build("tr");
166
+ Date.weekdays.each( function(weekday) {
167
+ weekdays_row.build("th", {innerHTML: weekday});
168
+ });
169
+
170
+ var days_tbody = days_table.build("tbody")
171
+ // Make the days!
172
+ var row_number = 0, weekday;
173
+ for(var cell_index = 0; cell_index<42; cell_index++)
174
+ {
175
+ weekday = (cell_index+Date.first_day_of_week ) % 7;
176
+ if ( cell_index % 7==0 ) days_row = days_tbody.build("tr", {className: 'row_'+row_number++});
177
+ (this.calendar_day_grid[cell_index] = days_row.build("td", {
178
+ calendar_date_select: this,
179
+ onmouseover: function () { this.calendar_date_select.dayHover(this); },
180
+ onmouseout: function () { this.calendar_date_select.dayHoverOut(this) },
181
+ onclick: function() { this.calendar_date_select.updateSelectedDate(this, true); },
182
+ className: (weekday==0) || (weekday==6) ? " weekend" : "" //clear the class
183
+ },
184
+ { cursor: "pointer" }
185
+ )).build("div");
186
+ this.calendar_day_grid[cell_index];
187
+ }
188
+ },
189
+ initButtonsDiv: function()
190
+ {
191
+ var buttons_div = this.buttons_div;
192
+ if (this.options.get("time"))
193
+ {
194
+ var blank_time = $A(this.options.get("time")=="mixed" ? [[" - ", ""]] : []);
195
+ buttons_div.build("span", {innerHTML:"@", className: "at_sign"});
196
+
197
+ var t = new Date();
198
+ this.hour_select = new SelectBox(buttons_div,
199
+ blank_time.concat($R(0,23).map(function(x) {t.setHours(x); return $A([t.getAMPMHour()+ " " + t.getAMPM(),x])} )),
200
+ {
201
+ calendar_date_select: this,
202
+ onchange: function() { this.calendar_date_select.updateSelectedDate( { hour: this.value });},
203
+ className: "hour"
204
+ }
205
+ );
206
+ buttons_div.build("span", {innerHTML:":", className: "seperator"});
207
+ var that = this;
208
+ this.minute_select = new SelectBox(buttons_div,
209
+ 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]); } ) ),
210
+ {
211
+ calendar_date_select: this,
212
+ onchange: function() { this.calendar_date_select.updateSelectedDate( {minute: this.value }) },
213
+ className: "minute"
214
+ }
215
+ );
216
+
217
+ } else if (! this.options.get("buttons")) buttons_div.remove();
218
+
219
+ if (this.options.get("buttons")) {
220
+ buttons_div.build("span", {innerHTML: "&#160;"});
221
+ if (this.options.get("time")=="mixed" || !this.options.get("time")) b = buttons_div.build("a", {
222
+ innerHTML: _translations["Today"],
223
+ href: "#",
224
+ onclick: function() {this.today(false); return false;}.bindAsEventListener(this)
225
+ });
226
+
227
+ if (this.options.get("time")=="mixed") buttons_div.build("span", {innerHTML: "&#160;|&#160;", className:"button_seperator"})
228
+
229
+ if (this.options.get("time")) b = buttons_div.build("a", {
230
+ innerHTML: _translations["Now"],
231
+ href: "#",
232
+ onclick: function() {this.today(true); return false}.bindAsEventListener(this)
233
+ });
234
+
235
+ if (!this.options.get("embedded") && !this.closeOnClick())
236
+ {
237
+ buttons_div.build("span", {innerHTML: "&#160;|&#160;", className:"button_seperator"})
238
+ buttons_div.build("a", { innerHTML: _translations["OK"], href: "#", onclick: function() {this.close(); return false;}.bindAsEventListener(this) });
239
+ }
240
+ if (this.options.get('clear_button')) {
241
+ buttons_div.build("span", {innerHTML: "&#160;|&#160;", className:"button_seperator"})
242
+ buttons_div.build("a", { innerHTML: _translations["Clear"], href: "#", onclick: function() {this.clearDate(); if (!this.options.get("embedded")) this.close(); return false;}.bindAsEventListener(this) });
243
+ }
244
+ }
245
+ },
246
+ refresh: function ()
247
+ {
248
+ this.refreshMonthYear();
249
+ this.refreshCalendarGrid();
250
+
251
+ this.setSelectedClass();
252
+ this.updateFooter();
253
+ },
254
+ refreshCalendarGrid: function () {
255
+ this.beginning_date = new Date(this.date).stripTime();
256
+ this.beginning_date.setDate(1);
257
+ this.beginning_date.setHours(12); // Prevent daylight savings time boundaries from showing a duplicate day
258
+ var pre_days = this.beginning_date.getDay() // draw some days before the fact
259
+ if (pre_days < 3) pre_days += 7;
260
+ this.beginning_date.setDate(1 - pre_days + Date.first_day_of_week);
261
+
262
+ var iterator = new Date(this.beginning_date);
263
+
264
+ var today = new Date().stripTime();
265
+ var this_month = this.date.getMonth();
266
+ vdc = this.options.get("valid_date_check");
267
+ for (var cell_index = 0;cell_index<42; cell_index++)
268
+ {
269
+ day = iterator.getDate(); month = iterator.getMonth();
270
+ cell = this.calendar_day_grid[cell_index];
271
+ Element.remove(cell.childNodes[0]); div = cell.build("div", {innerHTML:day});
272
+ if (month!=this_month) div.className = "other";
273
+ cell.day = day; cell.month = month; cell.year = iterator.getFullYear();
274
+ if (vdc) { if (vdc(iterator.stripTime())) cell.removeClassName("disabled"); else cell.addClassName("disabled") };
275
+ iterator.setDate( day + 1);
276
+ }
277
+
278
+ if (this.today_cell) this.today_cell.removeClassName("today");
279
+
280
+ if ( $R( 0, 41 ).include(days_until = this.beginning_date.stripTime().daysDistance(today)) ) {
281
+ this.today_cell = this.calendar_day_grid[days_until];
282
+ this.today_cell.addClassName("today");
283
+ }
284
+ },
285
+ refreshMonthYear: function() {
286
+ var m = this.date.getMonth();
287
+ var y = this.date.getFullYear();
288
+ // set the month
289
+ if (this.options.get("month_year") == "dropdowns")
290
+ {
291
+ this.month_select.setValue(m, false);
292
+
293
+ var e = this.year_select.element;
294
+ if (this.flexibleYearRange() && (!(this.year_select.setValue(y, false)) || e.selectedIndex <= 1 || e.selectedIndex >= e.options.length - 2 )) this.populateYearRange();
295
+
296
+ this.year_select.setValue(y);
297
+
298
+ } else {
299
+ this.month_year_label.update( Date.months[m] + " " + y.toString() );
300
+ }
301
+ },
302
+ populateYearRange: function() {
303
+ this.year_select.populate(this.yearRange().toArray());
304
+ },
305
+ yearRange: function() {
306
+ if (!this.flexibleYearRange())
307
+ return $R(this.options.get("year_range")[0], this.options.get("year_range")[1]);
308
+
309
+ var y = this.date.getFullYear();
310
+ return $R(y - this.options.get("year_range"), y + this.options.get("year_range"));
311
+ },
312
+ flexibleYearRange: function() { return (typeof(this.options.get("year_range")) == "number"); },
313
+ validYear: function(year) { if (this.flexibleYearRange()) { return true;} else { return this.yearRange().include(year);} },
314
+ dayHover: function(element) {
315
+ var hover_date = new Date(this.selected_date);
316
+ hover_date.setYear(element.year); hover_date.setMonth(element.month); hover_date.setDate(element.day);
317
+ this.updateFooter(hover_date.toFormattedString(this.use_time));
318
+ },
319
+ dayHoverOut: function(element) { this.updateFooter(); },
320
+ clearSelectedClass: function() {if (this.selected_cell) this.selected_cell.removeClassName("selected");},
321
+ setSelectedClass: function() {
322
+ if (!this.selection_made) return;
323
+ this.clearSelectedClass()
324
+ if ($R(0,42).include( days_until = this.beginning_date.stripTime().daysDistance(this.selected_date.stripTime()) )) {
325
+ this.selected_cell = this.calendar_day_grid[days_until];
326
+ this.selected_cell.addClassName("selected");
327
+ }
328
+ },
329
+ reparse: function() { this.parseDate(); this.refresh(); },
330
+ dateString: function() {
331
+ return (this.selection_made) ? this.selected_date.toFormattedString(this.use_time) : "&#160;";
332
+ },
333
+ parseDate: function()
334
+ {
335
+ var value = $F(this.target_element).strip()
336
+ this.selection_made = (value != "");
337
+ this.date = value=="" ? NaN : Date.parseFormattedString(this.options.get("date") || value);
338
+ if (isNaN(this.date)) this.date = new Date();
339
+ if (!this.validYear(this.date.getFullYear())) this.date.setYear( (this.date.getFullYear() < this.yearRange().start) ? this.yearRange().start : this.yearRange().end);
340
+ this.selected_date = new Date(this.date);
341
+ this.use_time = /[0-9]:[0-9]{2}/.exec(value) ? true : false;
342
+ this.date.setDate(1);
343
+ },
344
+ updateFooter:function(text) { if (!text) text = this.dateString(); this.footer_div.purgeChildren(); this.footer_div.build("span", {innerHTML: text }); },
345
+ clearDate:function() {
346
+ if ((this.target_element.disabled || this.target_element.readOnly) && this.options.get("popup") != "force") return false;
347
+ var last_value = this.target_element.value;
348
+ this.target_element.value = "";
349
+ this.clearSelectedClass();
350
+ this.updateFooter('&#160;');
351
+ if (last_value!=this.target_element.value) this.callback("onchange");
352
+ },
353
+ updateSelectedDate:function(partsOrElement, via_click) {
354
+ var parts = $H(partsOrElement);
355
+ if ((this.target_element.disabled || this.target_element.readOnly) && this.options.get("popup") != "force") return false;
356
+ if (parts.get("day")) {
357
+ var t_selected_date = this.selected_date, vdc = this.options.get("valid_date_check");
358
+ for (var x = 0; x<=3; x++) t_selected_date.setDate(parts.get("day"));
359
+ t_selected_date.setYear(parts.get("year"));
360
+ t_selected_date.setMonth(parts.get("month"));
361
+
362
+ if (vdc && ! vdc(t_selected_date.stripTime())) { return false; }
363
+ this.selected_date = t_selected_date;
364
+ this.selection_made = true;
365
+ }
366
+
367
+ if (!isNaN(parts.get("hour"))) this.selected_date.setHours(parts.get("hour"));
368
+ if (!isNaN(parts.get("minute"))) this.selected_date.setMinutes( Math.floor_to_interval(parts.get("minute"), this.options.get("minute_interval")) );
369
+ if (parts.get("hour") === "" || parts.get("minute") === "")
370
+ this.setUseTime(false);
371
+ else if (!isNaN(parts.get("hour")) || !isNaN(parts.get("minute")))
372
+ this.setUseTime(true);
373
+
374
+ this.updateFooter();
375
+ this.setSelectedClass();
376
+
377
+ if (this.selection_made) this.updateValue();
378
+ if (this.closeOnClick()) { this.close(); }
379
+ if (via_click && !this.options.get("embedded")) {
380
+ if ((new Date() - this.last_click_at) < 333) this.close();
381
+ this.last_click_at = new Date();
382
+ }
383
+ },
384
+ closeOnClick: function() {
385
+ if (this.options.get("embedded")) return false;
386
+ if (this.options.get("close_on_click")===nil )
387
+ return (this.options.get("time")) ? false : true
388
+ else
389
+ return (this.options.get("close_on_click"))
390
+ },
391
+ navMonth: function(month) { (target_date = new Date(this.date)).setMonth(month); return (this.navTo(target_date)); },
392
+ navYear: function(year) { (target_date = new Date(this.date)).setYear(year); return (this.navTo(target_date)); },
393
+ navTo: function(date) {
394
+ if (!this.validYear(date.getFullYear())) return false;
395
+ this.date = date;
396
+ this.date.setDate(1);
397
+ this.refresh();
398
+ this.callback("after_navigate", this.date);
399
+ return true;
400
+ },
401
+ setUseTime: function(turn_on) {
402
+ 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"
403
+ if (this.use_time && this.selected_date) { // only set hour/minute if a date is already selected
404
+ var minute = Math.floor_to_interval(this.selected_date.getMinutes(), this.options.get("minute_interval"));
405
+ var hour = this.selected_date.getHours();
406
+
407
+ this.hour_select.setValue(hour);
408
+ this.minute_select.setValue(minute)
409
+ } else if (this.options.get("time")=="mixed") {
410
+ this.hour_select.setValue(""); this.minute_select.setValue("");
411
+ }
412
+ },
413
+ updateValue: function() {
414
+ var last_value = this.target_element.value;
415
+ this.target_element.value = this.dateString();
416
+ if (last_value!=this.target_element.value) this.callback("onchange");
417
+ },
418
+ today: function(now) {
419
+ var d = new Date(); this.date = new Date();
420
+ var o = $H({ day: d.getDate(), month: d.getMonth(), year: d.getFullYear(), hour: d.getHours(), minute: d.getMinutes()});
421
+ if ( ! now ) o = o.merge({hour: "", minute:""});
422
+ this.updateSelectedDate(o, true);
423
+ this.refresh();
424
+ },
425
+ close: function() {
426
+ if (this.closed) return false;
427
+ this.callback("before_close");
428
+ this.target_element.calendar_date_select = nil;
429
+ Event.stopObserving(document, "mousedown", this.closeIfClickedOut_handler);
430
+ Event.stopObserving(document, "keypress", this.keyPress_handler);
431
+ this.calendar_div.remove(); this.closed = true;
432
+ if (this.iframe) this.iframe.remove();
433
+ if (this.target_element.type != "hidden" && ! this.target_element.disabled) this.target_element.focus();
434
+ this.callback("after_close");
435
+ },
436
+ closeIfClickedOut: function(e) {
437
+ if (! $(Event.element(e)).descendantOf(this.calendar_div) ) this.close();
438
+ },
439
+ keyPress: function(e) {
440
+ if (e.keyCode==Event.KEY_ESC) this.close();
441
+ },
442
+ callback: function(name, param) { if (this.options.get(name)) { this.options.get(name).bind(this.target_element)(param); } }
443
+ }