Sunday, July 10, 2005

Code Dump: Calendar.js

This is something a friend and I whipped up in our abundance of spare time. :)

The intent is to use it at work (after much more tweaking of course) for our own "1-click" calendar "control".

This requires the Prototype Library. You can use the calendar like so:


<html>
<head>
<title>Calendar Test</title>
<script type="text/javascript" language="javascript" src="prototype.js"></script>
<script type="text/javascript" language="javascript" src="calendar.js"></script>
</head>
<body>
<p>A calendar control should render here:
<span id="placeholder1" style="background-color: blue;"></span>
</p>
</body>
</html>

<script type="text/javascript" language="javascript">
new Calendar("placeholder1", "calendar1");
</script>


And here's the code:


var _calendars = new Array();

var Calendar = Class.create();
Calendar.prototype = {
initialize: function(placeholderId, inputId) {
this.id = inputId;
this.timeout = 0;
this.isIE = (document.all)? true: false;

_calendars[this.id] = this;

this._createInputbox(placeholderId);
this._createPlaceholder(placeholderId);

this.years = new Array(2003, 2004, 2005);
this.months = new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
},

_getNumberOfDays: function(year, month) {
var oneHour = 1000 * 60 * 60;
var oneDay = oneHour * 24;
var thisMonth = new Date(year, month, 1);
var nextMonth = new Date(year, month + 1, 1);
return Math.ceil( (nextMonth.getTime() - thisMonth.getTime() - oneHour) / oneDay);
},

_setInnerText: function(element, text) {
if(this.isIE) {
element.innerText = text;
} else {
element.innerHTML = text;
}
},

_getInnerText: function(element) {
if(this.isIE) {
return element.innerText;
} else {
return element.innerHTML;
}
},

_removeChild: function(id) {
for(var i = 0; i < this.placeholder.childNodes.length; i++) {
var child = this.placeholder.childNodes[i];

if(child.id == id) {
this.placeholder.removeChild(child);
}
}
},

_displayItems: function(immediate) {
window.clearTimeout(this.timeout);

if(immediate) {
if(this.yearbox) {
this.yearbox.style.display = this.yearbox.attributes["over"].value == "true"? "block": "none";
}

if(this.monthbox) {
this.monthbox.style.display = this.monthbox.attributes["over"].value == "true"? "block": "none";
}

if(this.daybox) {
this.daybox.style.display = this.daybox.attributes["over"].value == "true"? "block": "none";
}
} else {
this.timeout = window.setTimeout("_calendars[\"" + this.id + "\"]._displayItems(true);", 500, "javascript");
}
},

hideItems: function(immediate) {
if(this.yearbox) {
this.yearbox.setAttribute("over", false);
}

if(this.monthbox) {
this.monthbox.setAttribute("over", false);
}

if(this.daybox) {
this.daybox.setAttribute("over", false);
}

if(immediate) {
this._displayItems(true);
} else {
this._displayItems();
}
},

show: function(element) {
var items = new Array(this.yearbox, this.monthbox, this.daybox);
var showLevel = true;

for(i = 0; i < items.length; i++) {
var item = items[i];

if(!item) break;

item.setAttribute("over", showLevel);

if(element.id == item.id) {
showLevel = false;
}
}

this._displayItems(true);
},

showYears: function() {
var element = document.createElement("div");
element.style.display = "none";
element.style.margin = "4px 0px";
element.id = this.id + "_yearbox";
element.setAttribute("calendarId", this.id);

element.onmouseout = function() {
_calendars[this.attributes["calendarId"].value].hideItems();
}

element.onmouseover = function() {
window.clearTimeout(_calendars[this.attributes["calendarId"].value].timeout);
}

this._removeChild(element.id);
this.placeholder.appendChild(element);
this.yearbox = element;

for(i = 0; i < this.years.length; i++) {
this._addYear(this.years[i]);
}

this.show(element);
},

showMonths: function(year) {
this.selectedYear = this._getInnerText(year);

var element = document.createElement("div");
element.style.display = "none";
element.style.margin = "4px 0px";
//element.style.width = "20em";
element.style.backgroundColor = "lime";
element.id = this.id + "_monthbox";
element.setAttribute("calendarId", this.id);

element.onmouseout = function() {
_calendars[this.attributes["calendarId"].value].hideItems();
}

element.onmouseover = function() {
window.clearTimeout(_calendars[this.attributes["calendarId"].value].timeout);
}

this._removeChild(element.id);
this.placeholder.appendChild(element);
this.monthbox = element;

for(i = 0; i < this.months.length; i++) {
this._addMonth(this.months[i], i);
}

this.show(element);
},

showDays: function(month) {
this.selectedMonth = parseInt(month.attributes["number"].value);

var element = document.createElement("span");
element.style.display = "none";
element.style.backgroundColor = "red";
element.id = this.id + "_daybox";
element.setAttribute("calendarId", this.id);

element.onmouseout = function() {
_calendars[this.attributes["calendarId"].value].hideItems();
}

element.onmouseover = function() {
window.clearTimeout(_calendars[this.attributes["calendarId"].value].timeout);
}

this._removeChild(element.id);
this.placeholder.appendChild(element);
this.daybox = element;

var table = this._buildCalendar(this.selectedYear, this.selectedMonth);

// element.style.width = table.offsetWidth;

this.show(element);
},

_createPlaceholder: function(id) {
var element = document.createElement("div");
element.style.position = "absolute";
element.style.left = $(id).offsetLeft;
element.style.top = $(id).offsetTop + $(id).offsetHeight;

element.id = this.id + "_placeHolder";
element.setAttribute("calendarId", this.id);

$(id).appendChild(element);
this.placeholder = element;

return element;
},

_createInputbox: function(id) {
var element = document.createElement("input");
element.type = "text";
element.style.border = "solid 1px black";
//element.style.clear = "right";
element.id = this.id;
element.setAttribute("calendarId", this.id);

element.onmouseover = function() {
_calendars[this.attributes["calendarId"].value].showYears();
}

element.onmouseout = function() {
_calendars[this.attributes["calendarId"].value].hideItems();
}

$(id).appendChild(element);
this.inputbox = element;

return element;
},

_addYear: function(year) {
var span = document.createElement("span");
span.style.border = "solid 1px black";
span.style.margin = "0px 2px";
span.style.padding = "1px 2px";

span.onmouseover = function() {
_calendars[this.parentNode.attributes["calendarId"].value].showMonths(this);
}

this._setInnerText(span, year);
this.yearbox.appendChild(span);
},

_addMonth: function(month, number) {
var span = document.createElement("span");
span.style.border = "solid 1px black";
span.style.margin = "0px 2px";
span.style.padding = "1px 2px";
span.setAttribute("number", number);

span.onmouseover = function() {
this.style.backgroundColor = "blue";
var calendar = _calendars[this.parentNode.attributes["calendarId"].value];
calendar.showDays(this);
}

span.onmouseout = function() {
this.style.backgroundColor = "transparent";
}

this._setInnerText(span, month);
this.monthbox.appendChild(span);
},

_buildCalendar: function(year, month) {

var table = document.createElement("table");

var daysRow = document.createElement("tr");
var daysOfWeek = new Array("S", "M", "T", "W", "T", "F", "S");

for(i = 0; i < daysOfWeek.length; i++) {
var cell = document.createElement("td");
cell.style.textAlign = "center";
this._setInnerText(cell, daysOfWeek[i]);
daysRow.appendChild(cell);
}

table.appendChild(daysRow);

var firstDay = new Date(year, month, 1);
var dayOfWeek = firstDay.getDay();

var previousMonth = month == 0? new Array(year - 1, 11): new Array(year, month - 1);

var previousNumberOfDays = this._getNumberOfDays(previousMonth[0], previousMonth[1]);
var currentNumberOfDays = this._getNumberOfDays(year, month);

var counter = previousNumberOfDays - dayOfWeek + 1;
do {
var row = document.createElement("tr");
table.appendChild(row);

for(x = 0; x < 7; x++) {
var cell = document.createElement("td");
cell.style.textAlign = "right";
cell.onclick = function() {
var calendarId = this.parentNode.parentNode.parentNode.attributes["calendarId"].value;
var calendar = _calendars[calendarId];

var selectedDay = parseInt(calendar._getInnerText(this));
var selectedDate = new Date(calendar.selectedYear, calendar.selectedMonth, selectedDay);

calendar.inputbox.value = selectedDate.getMonth() + 1 + "/"
+ selectedDate.getDate() + "/"
+ selectedDate.getFullYear();

calendar.hideItems(true);
};

row.appendChild(cell);

if(counter > previousNumberOfDays + currentNumberOfDays) {
// trailing month...
this._setInnerText(cell, counter - (previousNumberOfDays + currentNumberOfDays) );
} else if(counter > previousNumberOfDays) {
// current month...
this._setInnerText(cell, counter - previousNumberOfDays);
} else {
// leading month...
this._setInnerText(cell, counter);
}

counter++;
}
} while(counter <= previousNumberOfDays + currentNumberOfDays);

this.daybox.appendChild(table);

return table;
}
}


A bit sloppy sure, but should be easy enough to follow hopefully. Of course it'll need some styling tweaks, localization, etc, and I didn't check the latest version in IE (though you'll notice a isIE member to accomodate if it doesn't work).

Still, should get you pretty close to a working javascript calendar control.
Comments: Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?