Skip to content

Instantly share code, notes, and snippets.

Created June 29, 2010 17:51
Show Gist options
  • Save anonymous/457546 to your computer and use it in GitHub Desktop.
Save anonymous/457546 to your computer and use it in GitHub Desktop.
// CalendarDateSelect version 1.15 - a prototype based date picker
// Questions, comments, bugs? - see the project page: http://code.google.com/p/calendardateselect
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 %>).");
}
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");
}
Element.addMethods({
purgeElement: function(el){
el = $(el);
if (!el) { return; }
var garbageBin = $('IELeakGarbageBin');
if (!garbageBin) {
garbageBin = new Element('DIV', {
id: 'IELeakGarbageBin'
}).hide();
document.body.appendChild(garbageBin);
}
el.stopObserving();
el.descendants().invoke('stopObserving');
garbageBin.insert(el);
garbageBin.update('');
}
});
var nil = null;
Date.one_day = 24 * 60 * 60 * 1000;
Date.weekdays = $w("S M T W T F S");
Date.first_day_of_week = 0;
Date.months = $w("January February March April May June July August September October November December");
Date.padded2 = function(hour){
var padded2 = parseInt(hour, 10);
if (hour < 10) {
padded2 = "0" + padded2;
}
return padded2;
};
Date.prototype.getPaddedMinutes = function(){ return Date.padded2(this.getMinutes()); };
Date.prototype.getAMPMHour = function(){
var hour = this.getHours();
return (hour === 0) ? 12 : (hour > 12 ? hour - 12 : hour);
};
Date.prototype.getAMPM = function(){ return (this.getHours() < 12) ? "AM" : "PM"; };
Date.prototype.stripTime = function(){ return new Date(this.getFullYear(), this.getMonth(), this.getDate()); };
Date.prototype.daysDistance = function(compare_date){ return Math.round((compare_date - this) / Date.one_day); };
Date.prototype.toFormattedString = function(include_time){
var hour, str;
str = Date.months[this.getMonth()] + " " + this.getDate() + ", " + this.getFullYear();
if (include_time) {
hour = this.getHours();
str += " " + this.getAMPMHour() + ":" + this.getPaddedMinutes() + " " + this.getAMPM();
}
return str;
};
Date.parseFormattedString = function(string){ return new Date(string); };
Math.floor_to_interval = function(n, i){ return Math.floor(n / i) * i; };
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); };
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); };
_translations = {
"OK": "OK",
"Now": "Now",
"Today": "Today",
"Clear": "Clear"
};
SelectBox = Class.create();
SelectBox.prototype = {
initialize: function(parent_element, values, html_options, style_options, onchange){
this.element = new Element('select', html_options).setStyle(style_options).observe('change', onchange);
parent_element.insert(this.element);
this.populate(values);
},
populate: function(values){
this.element.update('');
var pair;
for (var i = 0, l = values.length; i < l; ++i) {
pair = values[i];
if (typeof(pair) != "object") {
pair = [pair, pair];
}
this.element.insert(new Element("option", {
value: pair[1]
}).update(pair[0]));
}
},
setValue: function(value){
var matched = false;
var e = this.element;
for (var i = 0, l = e.options.length; i < l; ++i) {
if (e.options[i].value == value.toString()) {
e.selectedIndex = i;
matched = true;
}
}
return matched;
},
getValue: function(){ return $F(this.element); }
};
CalendarDateSelect = Class.create();
CalendarDateSelect.prototype = {
initialize: function(target_element, options){
this.target_element = $(target_element); // make sure it's an element, not a string
if (!this.target_element) {
alert("Target element " + target_element + " not found!");
return false;
}
if (this.target_element.tagName != "INPUT") {
this.target_element = this.target_element.down("INPUT");
}
this.target_element.calendar_date_select = this;
this.last_click_at = 0;
// initialize the date control
this.options = $H({
embedded: false,
popup: nil,
time: false,
buttons: true,
clear_button: true,
year_range: 10,
close_on_click: nil,
minute_interval: 5,
offsetLeft: 0,
offsetTop: 0,
IEoffsetLeft: 0,
IEoffsetTop: 0,
popup_by: this.target_element,
month_year: "dropdowns",
onchange: this.target_element.onchange,
parentElement: document.body,
valid_date_check: nil
}).merge(options ||
{});
this.use_time = this.options.get("time");
this.parseDate();
this.callback("before_show");
this.initCalendarDiv();
if (!this.options.get("embedded")) {
this.positionCalendarDiv();
// set the click handler to check if a user has clicked away from the document
Event.observe(document, "mousedown", this.closeIfClickedOut_handler = this.closeIfClickedOut.bindAsEventListener(this));
Event.observe(document, "keypress", this.keyPress_handler = this.keyPress.bindAsEventListener(this));
}
this.callback("after_show");
},
positionCalendarDiv: function(){
var above = false;
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;
var w_top = window.f_scrollTop(), w_height = window.f_height();
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;
var parentEl = $(this.options.get("parentElement"));
var parent_pos = parentEl.cumulativeOffset();
e_top -= parent_pos[1];
e_left -= parent_pos[0];
e_bottom = e_top + e_height;
var tmpInt;
if (Prototype.Browser.IE) {
e_top += this.options.get("IEoffsetTop");
e_left += this.options.get("IEoffsetLeft");
}
else {
// Firefox we need to adjust position by borders
tmpInt = parseInt(parentEl.getStyle('borderLeftWidth'), 10);
if (!isNaN(tmpInt)) {
e_left += tmpInt;
}
tmpInt = parseInt(parentEl.getStyle('borderTopWidth'), 10);
if (!isNaN(tmpInt)) {
e_top += tmpInt;
}
tmpInt = parseInt(this.calendar_div.getStyle('borderLeftWidth'), 10);
if (!isNaN(tmpInt)) {
e_left += tmpInt;
}
tmpInt = parseInt(this.calendar_div.getStyle('borderTopWidth'), 10);
if (!isNaN(tmpInt)) {
e_top += tmpInt;
}
// offsets
e_top += this.options.get("offsetTop");
e_left += this.options.get("offsetLeft");
}
if (((e_bottom + c_height) > (w_top + w_height)) && (e_bottom - c_height > w_top)) {
above = true;
}
var left_px = e_left.toString() + "px", top_px = (above ? (e_top - c_height) : (e_top + e_height)).toString() + "px";
this.calendar_div.style.left = left_px;
this.calendar_div.style.top = top_px;
this.calendar_div.setStyle({
visibility: ""
});
// draw an iframe behind the calendar -- ugly hack to make IE 6 happy
if (Prototype.Browser.IE) {
this.iframe = new Element("iframe", {
src: "javascript:false",
className: "ie6_blocker"
}).setStyle({
left: left_px,
top: top_px,
height: c_height.toString() + "px",
width: c_width.toString() + "px",
border: "0px"
});
$(this.options.get('parentElement')).insert(this.iframe);
}
},
initCalendarDiv: function(){
var parent, style;
if (this.options.get("embedded")) {
parent = this.target_element.parentNode;
style = {};
}
else {
parent = this.options.get('parentElement');
style = {
position: "absolute",
visibility: "hidden",
left: 0,
top: 0
};
}
this.calendar_div = new Element('div', {
className: "calendar_date_select"
}).setStyle(style);
$(parent).insert(this.calendar_div);
// create the divs
$w("top header body buttons footer bottom").each((function(name){
this.calendar_div.insert((this[name + "_div"] = new Element('div', {
className: 'cds_' + name
}).setStyle({
clear: 'left'
})));
}).bind(this));
this.initHeaderDiv();
this.initButtonsDiv();
this.initCalendarGrid();
this.updateFooter("&#160;");
this.refresh();
this.setUseTime(this.use_time);
},
initHeaderDiv: function(){
var header_div = this.header_div;
header_div.insert((this.close_button = new Element('a', {
href: "#",
className: "close"
}).update("x").observe('click', this.close.bindAsEventListener(this))));
header_div.insert((this.next_month_button = new Element('a', {
href: "#",
className: "next"
}).update("&gt;").observe('click', (function(ev){
ev.stop();
this.navMonth(this.date.getMonth() + 1);
}).bindAsEventListener(this))));
header_div.insert((this.prev_month_button = new Element('a', {
href: "#",
className: "prev"
}).update("&lt;").observe('click', (function(ev){
ev.stop();
this.navMonth(this.date.getMonth() - 1);
}).bindAsEventListener(this))));
if (this.options.get("month_year") == "dropdowns") {
this.month_select = new SelectBox(header_div, $R(0, 11).map(function(m){
return [Date.months[m], m];
}), {
className: "month"
}, null, (function(){
this.navMonth(this.month_select.getValue());
}).bindAsEventListener(this));
this.year_select = new SelectBox(header_div, [], {
className: "year"
}, null, (function(){
this.navYear(this.year_select.getValue());
}).bindAsEventListener(this));
this.populateYearRange();
}
else {
header_div.insert((this.month_year_label = new Element('span')));
}
},
initCalendarGrid: function(){
var body_div = this.body_div;
this.calendar_day_grid = [];
var days_table = new Element("table", {
cellPadding: "0px",
cellSpacing: "0px",
width: "100%"
});
// make the weekdays!
var weekdays_thead = new Element('thead');
var weekdays_row = new Element('tr');
weekdays_thead.insert(weekdays_row);
Date.weekdays.each(function(weekday){
weekdays_row.insert(new Element("th").update(weekday));
});
days_table.insert(weekdays_thead);
var days_tbody = new Element("tbody");
days_table.insert(days_tbody);
// Make the days!
this.fillDays(days_tbody);
days_table.observe('mouseover', (function(ev){
var el = ev.findElement('td');
if (el && el.match('.calendarDateCell')) {
this.dayHover(ev.findElement('td'));
}
}).bindAsEventListener(this)).observe('mouseout', (function(ev){
var el = ev.findElement('td');
if (el && el.match('.calendarDateCell')) {
this.dayHoverOut(ev.findElement('td'));
}
}).bindAsEventListener(this)).observe('click', (function(ev){
ev.stop();
var el = ev.findElement('td');
if (el && el.match('.calendarDateCell')) {
this.updateSelectedDate(ev.findElement('td'), true);
}
}).bindAsEventListener(this));
body_div.insert(days_table);
},
fillDays: function(days_tbody){
var row_number = 0, weekday, days_row;
for (var cell_index = 0; cell_index < 42; cell_index++) {
weekday = (cell_index + Date.first_day_of_week) % 7;
if (cell_index % 7 === 0) {
days_row = new Element("tr", {
className: 'row_' + row_number++
});
days_tbody.insert(days_row);
}
this.calendar_day_grid[cell_index] = new Element("td", {
className: (weekday === 0) || (weekday == 6) ? "weekend calendarDateCell" : "calendarDateCell" //clear the class
}).setStyle({
cursor: "pointer"
}).insert(new Element("div"));
days_row.insert(this.calendar_day_grid[cell_index]);
}
},
initButtonsDiv: function(){
var buttons_div = this.buttons_div;
if (this.options.get("time")) {
var blank_time = $A(this.options.get("time") == "mixed" ? [[" - ", ""]] : []);
buttons_div.insert(new Element("span", {
className: "at_sign"
}).update("@"));
var t = new Date();
this.hour_select = new SelectBox(buttons_div, blank_time.concat($R(0, 23).map(function(x){
t.setHours(x);
return $A([t.getAMPMHour() + " " + t.getAMPM(), x]);
})), {
className: "hour"
}, null, (function(ev){
this.updateSelectedDate({
hour: $F(ev.element())
});
}).bindAsEventListener(this));
buttons_div.insert(new Element("span", {
className: "seperator"
}).update(":"));
this.minute_select = new SelectBox(buttons_div, blank_time.concat($R(0, 59).select((function(x){
return (x % this.options.get('minute_interval') === 0);
}).bind(this)).map(function(x){
return $A([Date.padded2(x), x]);
})), {
className: "minute"
}, null, (function(ev){
this.updateSelectedDate({
minute: $F(ev.element())
});
}).bindAsEventListener(this));
}
else if (!this.options.get("buttons")) {
buttons_div.purgeElement();
}
if (this.options.get("buttons")) {
buttons_div.insert(new Element('span').update("&#160;"));
if (this.options.get("time") == "mixed" || !this.options.get("time")) {
buttons_div.insert(new Element("a", {
href: "#"
}).update(_translations.Today).observe('click', (function(ev){
ev.stop();
this.today(false);
}).bindAsEventListener(this)));
}
if (this.options.get("time") == "mixed") {
buttons_div.insert(new Element('span', {
className: "button_seperator"
}).update("&#160;|&#160;"));
}
if (this.options.get("time")) {
buttons_div.insert(new Element("a", {
href: "#"
}).update(_translations.Now).observe('click', (function(ev){
ev.stop();
this.today(true);
}).bindAsEventListener(this)));
}
if (!this.options.get("embedded") && !this.closeOnClick()) {
buttons_div.insert(new Element('span', {
className: "button_seperator"
}).update("&#160;|&#160;"));
buttons_div.insert(new Element("a", {
href: "#"
}).update(_translations.OK).observe('click', (function(ev){
ev.stop();
this.close();
}).bindAsEventListener(this)));
}
if (this.options.get('clear_button')) {
buttons_div.insert(new Element('span', {
className: "button_seperator"
}).update("&#160;|&#160;"));
buttons_div.insert(new Element("a", {
href: "#"
}).update(_translations.Clear).observe('click', (function(ev){
ev.stop();
this.clearDate();
if (!this.options.get("embedded")) {
this.close();
}
}).bindAsEventListener(this)));
}
}
},
refresh: function(){
this.refreshMonthYear();
this.refreshCalendarGrid();
this.setSelectedClass();
this.updateFooter();
},
refreshCalendarGrid: function(){
this.beginning_date = new Date(this.date).stripTime();
this.beginning_date.setDate(1);
this.beginning_date.setHours(12); // Prevent daylight savings time boundaries from showing a duplicate day
var pre_days = this.beginning_date.getDay(); // draw some days before the fact
if (pre_days < 3) {
pre_days += 7;
}
this.beginning_date.setDate(1 - pre_days + Date.first_day_of_week);
var iterator = new Date(this.beginning_date);
var today = new Date().stripTime();
var this_month = this.date.getMonth();
vdc = this.options.get("valid_date_check");
for (var cell_index = 0; cell_index < 42; cell_index++) {
day = iterator.getDate();
month = iterator.getMonth();
cell = this.calendar_day_grid[cell_index];
Element.purgeElement(cell.childNodes[0]);
cell.insert((div = new Element("div").update(day)));
if (month != this_month) {
div.className = "other";
}
cell.day = day;
cell.month = month;
cell.year = iterator.getFullYear();
if (vdc) {
if (vdc(iterator.stripTime())) {
cell.removeClassName("disabled");
}
else {
cell.addClassName("disabled");
}
}
iterator.setDate(day + 1);
}
if (this.today_cell) {
this.today_cell.removeClassName("today");
}
if ($R(0, 41).include(days_until = this.beginning_date.stripTime().daysDistance(today))) {
this.today_cell = this.calendar_day_grid[days_until];
this.today_cell.addClassName("today");
}
},
refreshMonthYear: function(){
var m = this.date.getMonth();
var y = this.date.getFullYear();
// set the month
if (this.options.get("month_year") == "dropdowns") {
this.month_select.setValue(m, false);
var e = this.year_select.element;
if (this.flexibleYearRange() && (!(this.year_select.setValue(y, false)) || e.selectedIndex <= 1 || e.selectedIndex >= e.options.length - 2)) {
this.populateYearRange();
}
this.year_select.setValue(y);
}
else {
this.month_year_label.update(Date.months[m] + " " + y.toString());
}
},
populateYearRange: function(){
this.year_select.populate(this.yearRange().toArray());
},
yearRange: function(){
if (!this.flexibleYearRange()) { return $R(this.options.get("year_range")[0], this.options.get("year_range")[1]); }
var y = this.date.getFullYear();
return $R(y - this.options.get("year_range"), y + this.options.get("year_range"));
},
flexibleYearRange: function(){ return (typeof(this.options.get("year_range")) == "number"); },
validYear: function(year){
if (this.flexibleYearRange()) { return true; }
else { return this.yearRange().include(year); }
},
dayHover: function(element){
var hover_date = new Date(this.selected_date);
hover_date.setYear(element.year);
hover_date.setMonth(element.month);
hover_date.setDate(element.day);
this.updateFooter(hover_date.toFormattedString(this.use_time));
},
dayHoverOut: function(element){
this.updateFooter();
},
clearSelectedClass: function(){
if (this.selected_cell) {
this.selected_cell.removeClassName("selected");
}
},
setSelectedClass: function(){
if (!this.selection_made) { return; }
this.clearSelectedClass();
if ($R(0, 42).include(days_until = this.beginning_date.stripTime().daysDistance(this.selected_date.stripTime()))) {
this.selected_cell = this.calendar_day_grid[days_until];
this.selected_cell.addClassName("selected");
}
},
reparse: function(){
this.parseDate();
this.refresh();
},
dateString: function(){ return (this.selection_made) ? this.selected_date.toFormattedString(this.use_time) : "&#160;"; },
parseDate: function(){
var value = $F(this.target_element).strip();
this.selection_made = (value !== "");
this.date = value === "" ? NaN : Date.parseFormattedString(this.options.get("date") || value);
if (isNaN(this.date)) {
this.date = new Date();
}
if (!this.validYear(this.date.getFullYear())) {
this.date.setYear((this.date.getFullYear() < this.yearRange().start) ? this.yearRange().start : this.yearRange().end);
}
this.selected_date = new Date(this.date);
this.use_time = /[0-9]:[0-9]{2}/.exec(value) ? true : false;
this.date.setDate(1);
},
updateFooter: function(text){
if (!text) {
text = this.dateString();
}
this.footer_div.update('');
this.footer_div.insert(new Element("span").update(text));
},
clearDate: function(){
if ((this.target_element.disabled || this.target_element.readOnly) && this.options.get("popup") != "force") { return false; }
var last_value = this.target_element.value;
this.target_element.value = "";
this.clearSelectedClass();
this.updateFooter('&#160;');
if (last_value != this.target_element.value) {
this.callback("onchange");
}
},
updateSelectedDate: function(partsOrElement, via_click){
var parts = $H(partsOrElement);
if ((this.target_element.disabled || this.target_element.readOnly) && this.options.get("popup") != "force") { return false; }
if (parts.get("day")) {
var t_selected_date = this.selected_date, vdc = this.options.get("valid_date_check");
for (var x = 0; x <= 3; x++) {
t_selected_date.setDate(parts.get("day"));
}
t_selected_date.setYear(parts.get("year"));
t_selected_date.setMonth(parts.get("month"));
if (vdc && !vdc(t_selected_date.stripTime())) { return false; }
this.selected_date = t_selected_date;
this.selection_made = true;
}
if (!isNaN(parts.get("hour"))) {
this.selected_date.setHours(parts.get("hour"));
}
if (!isNaN(parts.get("minute"))) {
this.selected_date.setMinutes(Math.floor_to_interval(parts.get("minute"), this.options.get("minute_interval")));
}
if (parts.get("hour") === "" || parts.get("minute") === "") {
this.setUseTime(false);
}
else if (!isNaN(parts.get("hour")) || !isNaN(parts.get("minute"))) {
this.setUseTime(true);
}
this.updateFooter();
this.setSelectedClass();
if (this.selection_made) {
this.updateValue();
}
if (this.closeOnClick()) {
this.close();
}
if (via_click && !this.options.get("embedded")) {
if ((new Date() - this.last_click_at) < 333) {
this.close();
}
this.last_click_at = new Date();
}
},
closeOnClick: function(){
if (this.options.get("embedded")) { return false; }
if (this.options.get("close_on_click") === nil) { return (this.options.get("time")) ? false : true; }
else { return (this.options.get("close_on_click")); }
},
navMonth: function(month){
(target_date = new Date(this.date)).setMonth(month);
return (this.navTo(target_date));
},
navYear: function(year){
(target_date = new Date(this.date)).setYear(year);
return (this.navTo(target_date));
},
navTo: function(date){
if (!this.validYear(date.getFullYear())) { return false; }
this.date = date;
this.date.setDate(1);
this.refresh();
this.callback("after_navigate", this.date);
return true;
},
setUseTime: function(turn_on){
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"
if (this.use_time && this.selected_date) { // only set hour/minute if a date is already selected
var minute = Math.floor_to_interval(this.selected_date.getMinutes(), this.options.get("minute_interval"));
var hour = this.selected_date.getHours();
this.hour_select.setValue(hour);
this.minute_select.setValue(minute);
}
else if (this.options.get("time") == "mixed") {
this.hour_select.setValue("");
this.minute_select.setValue("");
}
},
updateValue: function(){
var last_value = this.target_element.value;
this.target_element.value = this.dateString();
if (last_value != this.target_element.value) {
this.callback("onchange");
}
},
today: function(now){
var d = new Date();
this.date = new Date();
var o = $H({
day: d.getDate(),
month: d.getMonth(),
year: d.getFullYear(),
hour: d.getHours(),
minute: d.getMinutes()
});
if (!now) {
o = o.merge({
hour: "",
minute: ""
});
}
this.updateSelectedDate(o, true);
this.refresh();
},
close: function(ev){
if (this.closed) { return false; }
if (ev && ev.stop) {
ev.stop();
}
this.callback("before_close");
this.target_element.calendar_date_select = nil;
Event.stopObserving(document, "mousedown", this.closeIfClickedOut_handler);
Event.stopObserving(document, "keypress", this.keyPress_handler);
this.calendar_div.purgeElement();
this.closed = true;
if (this.iframe) {
this.iframe.purgeElement();
}
if (this.target_element.type != "hidden" && !this.target_element.disabled) {
this.target_element.focus();
}
this.callback("after_close");
},
closeIfClickedOut: function(e){
if (!$(Event.element(e)).descendantOf(this.calendar_div)) {
this.close();
}
},
keyPress: function(e){
if (e.keyCode == Event.KEY_ESC) {
this.close();
}
},
callback: function(name, param){
if (this.options.get(name)) {
this.options.get(name).bind(this.target_element)(param);
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment