gbdev-calendar_date_select 1.11.20080824

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