JQuery Calendar Code

Of all the code floating around the web calendar drawing code is usually messy, hard to maintain, and inflexible. A while back I got permission to open-source calendar drawing code that I wrote. It’s client-side JavaScript code as a JQuery plug-in but I plan to port it to both PHP and C# (the first version used Prototype’s psuedo object oriented framework so porting to classical OO languages will be a cinch).


 
/*global jQuery: false */
/*jslint undef: true, nomen: true, bitwise: true, regexp: true, strict: true, newcap: true */
"use strict";
(function ($) {
 
//
// jqCal
//
$.fn.jqCal = function(options) {
	// build main options before element iteration
	var opts = $.extend({}, $.fn.jqCal.defaults, options);
 
	function optDateEqual(d1, d2) {
		return d1[0] == d2[0] &&
				d1[1] == d2[1] &&
				d1[2] == d2[2];
	}
 
	function dateHasAppts(date) {
		for (var i=0; i<opts.events.length; i++) {
			if (opts.events[i].date == null) continue;
			if (optDateEqual(opts.events[i].date, date)) {
				return true;
			}
		}
		return false;
	}
 
	function getDateAppts(date, baseClasses) {
		switch (opts.mode) {
		case $.fn.jqCal.modes.Big :
			var apptsHtml = "";
			for (var i=0; i<opts.events.length; i++) {
				if (opts.events[i].date == null) continue;
				if (optDateEqual(opts.events[i].date, date)) {
					apptsHtml += "<a href='" + opts.events[i].url + "' class='AppointmentLink' target='_new" + opts.events[i].key + "'>" +
						"<div class='Appointment'>" +
						opts.events[i].desc + "</div></a>";
				}
			}
			return apptsHtml;
		case $.fn.jqCal.modes.Print :
			var apptsHtml = "<table width='100%'>";
			for (var i=0; i<opts.events.length; i++) {
				if (opts.events[i].date == null) continue;
				if (optDateEqual(opts.events[i].date, date)) {
					apptsHtml += "<tr><td><div class='" + this.CSS_ApptPrint + "'>" + opts.events[i].desc + "</div></td></tr>";
				}
			}
			apptsHtml += "</table>";
			return apptsHtml;
		case $.fn.jqCal.modes.Tiny :
			var appts = 0;
			var toolTipText = "";
			var cssClass = baseClasses;
			for (var i=0; i<opts.events.length; i++) {
				if (opts.events[i].date == null) continue;
				if (optDateEqual(opts.events[i].date, date)) {
					appts++;
					if (appts > 1) toolTipText += " | ";
					toolTipText += opts.events[i].desc.replace(/<[^>]*>/g, " ").replace(/'/g, "&apos;");
					cssClass += " TinyAppointment";
				}
			}
			if (appts > 1) cssClass += this.CSS_MultiA;
			return " class='" + cssClass + "' title='" + toolTipText + "' ";
		case $.fn.jqCal.modes.Collective :
			var apptsHtml = "<table width='100%'>";
			for (var i=0; i<opts.events.length; i++) {
				if (opts.events[i].date == null) continue;
				if (optDateEqual(opts.events[i].date, date)) {
					apptsHtml += "<tr><td class='" + this.CSS_Appointment + "'>" + opts.events[i].desc + "</td></tr>";
				}
			}
			apptsHtml += "</table>";
			return apptsHtml;
		}
	}
 
 
	function dateIsHoliday(date) {
		for (var i=0; i<opts.holidays.length; i++) {
			if (opts.holidays[i].date == null) continue;
			if (optDateEqual(opts.holidays[i].date, date)) {
				return true;
			}
		}
		return false;
	}
 
	// iterate each matched element
	return this.each(function() {
		var $this = $(this);
 
		// build element specific options
		//var o = $.meta ? $.extend({}, opts, $this.data()) : opts;
		var o = opts;
 
		var date = o.date ? new Date(o.date.valueOf()) : new Date((new Date()).getFullYear(), (new Date()).getMonth(), 1);
		date.setDate(1);
 
		var currentDate = new Date();
		var currentDate = [currentDate.getFullYear(), currentDate.getMonth() + 1, currentDate.getDate()];
 
		var calHtml = ["<table class='" + o.cssClasses.CalClass + 
			((o.mode === $.fn.jqCal.modes.Tiny) ? " " + o.cssClasses.CalClass + "Tiny" : "") + "'>" +
			((o.monthYear) ? "<tr><td class='" + o.cssClasses.MonthYear + 
				((o.mode === $.fn.jqCal.modes.Tiny) ? " " + o.cssClasses.MonthYear + "Tiny" : "") + "' colspan=7>" + 
			o.months[date.getMonth()] + ", " + date.getFullYear() + "</td></tr><tr>" : "")];
		for (var i=0; i<7; i++) {
			calHtml[calHtml.length] = "<td class='" + o.cssClasses.Weekday + "' align='center'>" + 
				((o.mode !== $.fn.jqCal.modes.Tiny) ? o.days[i] : o.daysTiny[i]) + "</td>";
		}
		calHtml[calHtml.length] = "</tr>";
		calHtml[calHtml.length] = "<tr>";
		var dayClass, numClass, isHoliday, hasAppts;
		$.fn.jqCal.calDays(date, function(iter) {
			dayClass = iter.during ? o.cssClasses.DayReg : o.cssClasses.DayEmpty;
			dayClass += ((o.mode === $.fn.jqCal.modes.Tiny) ? " " + o.cssClasses.Day + "Tiny" : "");
			dayClass += optDateEqual(currentDate, iter.optDate) ? " today" : "";
			numClass = iter.during ? o.cssClasses.DayRegNum : o.cssClasses.DayEmptyNum;
			hasAppts = dateHasAppts(iter.optDate);
			isHoliday = dateIsHoliday(iter.optDate);
			if (iter.newRow) {
				calHtml[calHtml.length] = "<tr>";
			}
			if (o.mode != $.fn.jqCal.modes.Tiny) {
				calHtml[calHtml.length] = 
					"<td class='" + o.cssClasses.Day + " " + dayClass + "' id='" + iter.idStr + "' width='14%'>" +
//					((isHoliday) ? "<table cellpadding='0' cellspacing='0' width='100%' height='100%'>" +
//					"<tr><td valign='top'>" : "") +
					iter.dayNum +
//					((isHoliday) ? "</td></tr><tr><td valign='bottom'>" + this.getHolidays(iter.curDate) + 
//					"</td></tr></table>" : "") +
					"" + ((hasAppts) ? getDateAppts(iter.optDate) : "&nbsp;") + 
					"</td>";
			} else {
				calHtml[calHtml.length] = 
					"<td" + getDateAppts(iter.optDate, o.cssClasses.Day + " " + dayClass) + " width='14%'>" + iter.dayNum + "</td>";
			}
			if (iter.endRow === 6) {
				calHtml[calHtml.length] = "</tr>";
			}
		});
		calHtml[calHtml.length] = "</tr></table>";
 
		$this.html(calHtml.join(""));
	});
};
 
$.fn.jqCal.modes = {
Tiny : 0,
Big : 1,
Print : 2,
Collective : 3
};
 
//
// plugin defaults
//
$.fn.jqCal.defaults = {
mode : $.fn.jqCal.modes.Tiny,
events : [],
holidays : [],
monthYear : true,
months : ["January", "February", "March", "April", "May", "June", "July", "August",
	"September", "October", "November", "December"],
monthsTiny : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
	"Sep", "Oct", "Nov", "Dec"],
days : ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"],
daysTiny : ["S", "M", "T", "W", "T", "F", "S"],
cssClasses : {
	CalClass : "CalClass",
	MonthYear : "MonthYear",
	Weekday : "Weekday",
	Day : "CalDay",
	DayEmpty : "DayEmpty",
	DayEmptyNum : "DayEmptyNum",
	Appointment : "Appointment",
	ApptDay : "ApptDay",
	ApptDesc : "ApptDesc",
	MultiA : "MultiAppt",
	DayReg : "DayReg",
	DayRegNum : "DayRegNum",
	Holiday : "Holiday",
	ApptPrint : "ApptPrint",
	Hover : "CalDateHover"
		}
};
 
$.fn.jqCal.calDays = function(date, callback) {
	var calendarDate = new Date(date.valueOf());
	var calendarDateDay = calendarDate.getDay();
	var curDate = new Date(date.valueOf());
	curDate.setDate(curDate.getDate() - (curDate.getDay() + 1));
 
	var calendarMonth = calendarDate.getMonth();
	var calendarYear = calendarDate.getFullYear();
 
	var beforeMonth = curDate.getMonth() !== calendarMonth;
	var duringMonth = curDate.getMonth() === calendarMonth;
	var afterMonth = false;
 
	var idStrBase = "cal-" + calendarMonth + "-" + calendarYear + "-";
 
	var dayMs = 1000 * 60 * 60 * 24;
 
	var lastDayMs = new Date(calendarYear, calendarMonth + 1, 1).valueOf() - dayMs;
	var numDays = (new Date(lastDayMs)).getDate();
 
	var before=0, day=0, after=0;
 
	function dayNum() {
		if (duringMonth) {
			return day;
		} else {
			return curDate.getDate();
		}
	}
 
	function month() {
		var ret;
		if (duringMonth) {
			ret = calendarMonth;
		} else if (beforeMonth) {
			ret = calendarMonth - 1;
			if (ret === -1) {
				ret = 11;
			}
		} else {
			ret = calendarMonth + 1;
			if (ret === 12) {
				ret = 0;
			}
		}
		return ret;
	}
 
	function year() {
		if (duringMonth) {
			return calendarYear;
		} else {
			return curDate.getFullYear();
		}
	}
 
	function doDay() {
		var showDay = dayNum();
		var weekDay = curDate.getDay();
		callback({
			day : curDate, 
			during : duringMonth,
			before : beforeMonth, 
			after : afterMonth,
			dayNum : showDay,
			idStr : idStrBase + month() + "-" + showDay + "-" + year(),
			newRow : weekDay === 0,
			endRow : weekDay === 6,
			optDate : [year(), month(), showDay]
				});
	}
 
	while (true) {
		if (beforeMonth) {
			if (before < calendarDateDay) {
				curDate.setDate(curDate.getDate() + 1);
				before++;
				doDay();
				continue;
			} else {
				beforeMonth = false;
				duringMonth = true;
			}
		}
		if (duringMonth) {
			if (day < numDays) {
				curDate.setDate(curDate.getDate() + 1);
				day++;
				doDay();
				continue;
			}
			after = curDate.getDay();
			if (after === 6) {
				return;
			} else {
				duringMonth = false;
				afterMonth = true;
			}
		}
		if (afterMonth) {
			if (after < 6) {
				curDate.setDate(curDate.getDate() + 1);
				after++;
				doDay();
				continue;
			} else {
				return;
			}
		}
	}
 
};
 
 
})(jQuery);

The PHP and C# versions will be up here soon.


Leave a Reply