By Karl Agius
Introduction
This article describes the development and usage of the attached calendar script. The calendar generated by the script can be used as a date picker, an event navigator, or simply as a dynamically generated static display. While this script has been tested with Internet Explorer 6.0 and Firefox 0.8 and 1.0, on Windows XP, I have not tested it against other browsers and operating systems. There is a known issue with Internet Explorer on Windows XP SP2; points on working around this are presented below.
Background
When I started this project, it was mainly to create a lightweight and more neatly customizable version of the ASP.NET calendar control. Although I use C# nearly exclusively these days, I come from a Java/JSP background, and still make occasional use of PHP. With this in mind, I wanted something which I could use independently of the server side scripting language being used, without compromising functionality.
There were a few objectives which I wanted to meet with this calendar:
- It had to run with little or no maintenance.
- It had to be simple enough for a person with little scripting experience to use.
- It had to have a simple way of customizing it for multicultural/multilanguage contexts.
- It had to provide a transparent fallback mechanism for non-JavaScript browsers.
Although I think I missed the "lightweight" target, I feel that the current implementation achieves all of the above objectives.
Using the code
The calendar script should be referenced like any other external JavaScript file. In order to avoid falling foul of the SP2 script blocking, make sure that you deploy the script file to the same domain as the page calling it.
To set up the calendar, you should use the constructor as shown below:
cal1 = new Calendar ("cal1", "tester", new Date());
The first argument,
cname
in the script, represents the name by which the script refers to the calendar instance. This should be the same as the name of the variable. (Note: I am still trying to avoid this requirement, as it is rather inelegant. Any suggestion would be appreciated.) The second argument represents the ID of the calendar; this is used by the fallback mechanism to determine which field/s the calendar should control. The last argument is the date to which the calendar will default.Binding
In order to provide data for a form action, the calendar has to be bound to an
input
element in a form. This is a two step process; first, you have to give the input
an ID appropriate to the calendar ID, and second, you must specify the fallback mode for it.If you want to bind the calendar to a single text box, define an
input
element with an ID equal to that given to the calendar as an argument. In other words, the instance cal1
we created above would bind to an element of IDtester
. Then, you should specify that the fallback mode is a single field, as described below:cal1.fallback = cal1.fallback_single;
Creating a calendar feeding three separate fields for day, month and year requires a little more work, but not much. The fields have to be given IDs with the suffixes "_day", "_month" and "_year" respectively; our example would bind to "
tester_day
", "tester_month
" and "tester_year
". This done, set the fallback mode to multi:cal1.fallback = cal1.fallback_multi;
Display
If you test the page at this point, nothing should appear. In order to render the calendar, specify a
div
element where you wish to place the calendar, and give it an id of "cal_[calendar id]_display
", where [calendar id]
is the ID of your calendar. In our example, the div
ID will be "cal_tester_display
".To render the calendar, call the
renderCalendar
method, like so:renderCalendar (cal1);
This will cause the calendar to be displayed in the
div
. In order to avoid the SP2 block, it might be a good idea to call this explicitly from a user event such as a button or other onclick event, rather than from a script body.Events
To highlight event dates in your calendar, insert an array of event dates into the calendar's
eventDates
property, like this:cal2.eventDates = new Array(
new Array("2005/2/10", 1)
, new Array("2005/2/12", 2)
, new Array("2005/2/14", 3)
);
The first argument in each array represents the event date; the second represents an event ID.
To add on-click functionality to the event dates, you can specify an
onclick
event for them by adding a function to the selectEvent
stub:cal2.selectEvent = function(eventId) {alert("cal2 Event: " + eventId);};
Overall, I think this is the shakiest area when it comes to configuration. I hope to revisit it soon and polish it up using JavaScript RPCs.
Code :
Inside Head Tag:
CSS :
<style>
.cal_calendar {font-size:10pt;font-family:verdana;padding:0px;margin:0px;border:none; border-collapse:collapse;}
.cal_header {background-color:#CCCCCC;padding:0px;margin:0px;border:none; border-collapse:collapse;}
.cal_cell {padding:2px;margin:1px;border:2px groove;text-align:center;width:3ex}
.cal_labelcell {padding:2px;margin:1px;border:2px groove;text-align:center;}
.cal_oddweek {background-color:#AAAACC;padding:0px;margin:0px;border:none; border-collapse:collapse;}
.cal_evenweek {background-color:#CCCCEE;padding:0px;margin:0px;border:none; border-collapse:collapse;}
.cal_day {width:3ex;text-align:center;padding:0px;margin:0px;border:none; border-collapse:collapse;cursor:hand;}
.cal_today {color:black;font-weight:bold;width:3ex;padding:0px;margin:0px;border:none; border-collapse:collapse;}
.cal_disabled {color:#999999;width:3ex;padding:0px;margin:0px;border:none; border-collapse:collapse;}
.cal_common {color:black;width:3ex;padding:0px;margin:0px;border:none; border-collapse:collapse;}
.cal_holiday {color:red;width:3ex;padding:0px;margin:0px;border:none; border-collapse:collapse;}
.cal_event {background-color:yellow;color:red;width:3ex;padding:0px;margin:0px;border:none; border-collapse:collapse;}
</style>
Javascript:
function Calendar (cname, id, date)
{
// Used to notify the calendar that it is attached to a single html field.
this.fallback_single = 0;
// Used to notify the claendar that it is attached to 3 html fields.
this.fallback_multi = 1;
// Used to notify the calendar that it is attached to both field sets.
this.fallback_both = 2;
// Read-only calendar
this.viewOnly = false;
// Allows the user to select weekends
this.allowWeekends = true;
// Allows the user to select weekdays
this.allowWeekdays = true;
// The minimum date that the user can select (inclusive)
this.minDate = "--";
// The maximum date that the user can select (exclusive)
this.maxDate = "--";
// Allow the user to scroll dates
this.scrolling = true;
// The id of this calendar
this.name = cname;
// The first day of the week in the calendar (0-Sunday, 6-Saturday)
this.firstDayOfWeek = 0;
// Fallback method
this.fallback = this.fallback_both;
// Sets the date and strips out time information
this.calendarDate = date;
this.calendarDate.setUTCHours(0);
this.calendarDate.setUTCMinutes(0);
this.calendarDate.setUTCSeconds(0);
this.calendarDate.setUTCMilliseconds(0);
// The field id that the calendar is attached to.
// For single input, this is used "as is". for the
// Multi-input, it is given a suffix for _day, _month
// and _year inputs.
this.attachedId = id;
// The left and right month control icons
this.controlLeft = "«";
this.controlRight = "»";
// The left and right month control icons (when disabled)
this.controlLeftDisabled = "";
this.controlRightDisabled = "";
// The css classes for the calendar and header
this.calendarStyle = "cal_calendar";
this.headerStyle = "cal_header";
this.headerCellStyle = "cal_cell";
this.headerCellStyleLabel = "cal_labelcell";
// The css classes for the rows
this.weekStyle = "cal_week";
this.evenWeekStyle = "cal_evenweek";
this.oddWeekStyle = "cal_oddweek";
// The css classes for the day elements
this.dayStyle = "cal_day";
this.disabledDayStyle = "cal_disabled";
this.commonDayStyle = "cal_common";
this.holidayDayStyle = "cal_holiday";
this.eventDayStyle = "cal_event";
this.todayDayStyle = "cal_today";
// specifies the labels for this calendar
this.dayLabels = new Array("Sun", "Mon", "Thu", "Wed", "Tue", "Fri", "Sat");
this.monthLabels = new Array(
"January", "February", "March", "April"
, "May", "June", "July", "August"
, "September", "October", "November", "December");
// Specifies the dates of any event. The events are to be defined as arrays,
// with element 0 being the date and element 1 being an id.
this.eventDates = new Array();
// Attach event handlers to any fallback fields.
if (this.viewOnly == false) {
setFieldValue(this.attachedId, this.calendarDate);
if ((this.fallback = this.fallback_both) || (this.fallback = this.fallback_single)) {
eval("document.getElementById(\"" + this.attachedId + "\").onchange = function () {updateFromSingle("+this.name+", this);}");
}
if ((this.fallback = this.fallback_both) || (this.fallback = this.fallback_multi)) {
eval("document.getElementById(\"" + this.attachedId + "_day\").onchange = function () {updateFromMultiDay("+this.name+", this);}");
eval("document.getElementById(\"" + this.attachedId + "_month\").onchange = function () {updateFromMultiMonth("+this.name+", this);}");
eval("document.getElementById(\"" + this.attachedId + "_year\").onchange = function () {updateFromMultiYear("+this.name+", this);}");
}
}
selectEvent = new Function();
}
function updateFromSingle (sender, helper) {
newDate = new Date (helper.value);
newDate.setUTCDate(newDate.getUTCDate()+1);
sender.calendarDate = newDate;
renderCalendar (sender);
setFieldValue(sender.attachedId, sender.calendarDate);
}
function updateFromMultiDay (sender, helper) {
if (isNaN(helper.value)) {
helper.value = sender.calendarDate.getUTCDate();
return false;
}
sender.calendarDate.setUTCDate(helper.value);
renderCalendar (sender);
setFieldValue(sender.attachedId, sender.calendarDate);
}
function updateFromMultiMonth (sender, helper) {
if (isNaN(helper.value)) {
helper.value = sender.calendarDate.getUTCMonths() -1;
return false;
}
sender.calendarDate.setUTCMonth(helper.value-1);
renderCalendar (sender);
setFieldValue(sender.attachedId, sender.calendarDate);
}
function updateFromMultiYear (sender, helper) {
if (isNaN(helper.value)) {
helper.value = sender.calendarDate.getUTCFullYear();
return false;
}
sender.calendarDate.setUTCFullYear(helper.value);
renderCalendar (sender);
setFieldValue(sender.attachedId, sender.calendarDate);
}
function getFirstCalendarDate (calendar)
{
return new Date (
calendar.calendarDate.getUTCFullYear()
, calendar.calendarDate.getUTCMonth()
, 1
);
}
function renderCalendar (calendar)
{
calHtml1 = ("<table id=\"cal_" + calendar.attachedId + "\" class=\"" + calendar.calendarStyle +"\">");
calHtml1 += ((calendar.scrolling)?buildHeader(calendar):buildStaticHeader(calendar));
calHtml1 += buildCalendarTable (calendar);
calHtml1 += ("</table>");
document.getElementById("cal_" + calendar.attachedId + "_display").innerHTML = calHtml1;
}
function scrollMonthBack (calendar)
{
calendar.calendarDate.setUTCMonth(calendar.calendarDate.getUTCMonth() - 1);
setFieldValue(calendar.attachedId, calendar.calendarDate);
renderCalendar (calendar);
}
function selectDate (calendar, day)
{
if (!calendar.viewOnly) {
calendar.calendarDate.setUTCDate(day);
setFieldValue(calendar.attachedId, calendar.calendarDate);
renderCalendar (calendar);
}
}
function scrollMonthForward (calendar)
{
calendar.calendarDate.setUTCMonth(calendar.calendarDate.getUTCMonth() + 1);
setFieldValue(calendar.attachedId, calendar.calendarDate);
renderCalendar (calendar);
}
function setFieldValue(fieldId, date) {
document.getElementById(fieldId).value = date.getUTCFullYear() + "/" + (date.getUTCMonth()+1) + "/" + date.getUTCDate();
document.getElementById(fieldId + "_year").value = date.getUTCFullYear();
document.getElementById(fieldId + "_month").selectedIndex = date.getUTCMonth();
document.getElementById(fieldId + "_day").value = date.getUTCDate();
}
function buildHeader (calendar)
{
enableLeft = true;
enableRight = true;
if (calendar.minDate != "--")
{
if (calendar.calendarDate.getUTCFullYear() <= calendar.minDate.getUTCFullYear())
{
if (calendar.calendarDate.getUTCMonth() <= calendar.minDate.getUTCMonth())
{
enableLeft = false;
}
}
}
if (calendar.maxDate != "--")
{
if (calendar.calendarDate.getUTCFullYear() >= calendar.maxDate.getUTCFullYear())
{
if (calendar.calendarDate.getUTCMonth() >= calendar.maxDate.getUTCMonth())
{
enableRight = false;
}
}
}
calHtml2 = "";
calHtml2 += (
"<tr class=\""
+ calendar.headerStyle
+ "\">");
calHtml2 += (
"<td class=\""
+ calendar.headerCellStyle
+ ((enableLeft)?("\" onclick=\"scrollMonthBack(" + calendar.name + ")"):"")
+ "\">"
+ ((enableLeft)?calendar.controlLeft:calendar.controlLeftDisabled)
+ "</td>");
calHtml2 += (
"<td colspan=\"5\" class=\""
+ calendar.headerCellStyleLabel
+ "\">"
+ calendar.monthLabels[calendar.calendarDate.getUTCMonth()]
+ ", " + calendar.calendarDate.getUTCFullYear()
+ "</td>");
calHtml2 += (
"<td class=\""
+ calendar.headerCellStyle
+ ((enableRight)?("\" onclick=\"scrollMonthForward(" + calendar.name + ")"):"")
+ "\">"
+ ((enableRight)?calendar.controlRight:calendar.controlRightDisabled)
+ "</td>");
calHtml2 += ("</tr>");
calHtml2 += (
"<tr class=\""
+ calendar.headerStyle
+ "\">")
for (i = 0; i < 7; i++) {
showDay = i + calendar.firstDayOfWeek;
if (showDay > 6) showDay = showDay - 7;
calHtml2 += (
"<td class=\""
+ calendar.headerCellStyle
+ "\">"
+ calendar.dayLabels[showDay]
+ "</td>");
}
calHtml2 += ("</tr>");
return calHtml2
}
function buildStaticHeader (calendar)
{
calHtml2 = "";
calHtml2 += (
"<tr class=\""
+ calendar.headerStyle
+ "\">");
calHtml2 += (
"<td colspan=\"7\" class=\""
+ calendar.headerCellStyleLabel
+ "\">"
+ calendar.monthLabels[calendar.calendarDate.getUTCMonth()]
+ ", " + calendar.calendarDate.getUTCFullYear()
+ "</td>");
calHtml2 += ("</tr>");
calHtml2 += (
"<tr class=\""
+ calendar.headerStyle
+ "\">")
for (i = 0; i < 7; i++) {
showDay = i + calendar.firstDayOfWeek;
if (showDay > 6) showDay = showDay - 7;
calHtml2 += (
"<td class=\""
+ calendar.headerCellStyle
+ "\">"
+ calendar.dayLabels[showDay]
+ "</td>");
}
calHtml2 += ("</tr>");
return calHtml2
}
function RenderDayDisabled (calendar, currentDate)
{
calHtml += ('<td class="day">');
calHtml += ("<span class=\"" + calendar.disabledDayStyle + "\">");
calHtml += (currentDate.getUTCDate());
calHtml += ("</span>");
calHtml += ("</td>");
}
function RenderDayEnabled (calendar, currentDate, dayStyle)
{
currentDayStyle = dayStyle;
calHtml += ('<td class="day">');
calHtml += ("<span class=\"" + dayStyle + "\" onclick=\"selectDate(" + calendar.name + ", " + currentDate.getUTCDate() + ")\">");
calHtml += (currentDate.getUTCDate());
calHtml += ("</span>");
calHtml += ("</td>");
}
function RenderDayEvent (calendar, currentDate, dayStyle, eventId)
{
currentDayStyle = dayStyle;
calHtml += ('<td class="day">');
calHtml += ("<span class=\"" + dayStyle + "\" onclick=\"selectDate(" + calendar.name + ", " + currentDate.getUTCDate() + "); " + calendar.name + ".selectEvent('" + eventId + "')\">");
calHtml += (currentDate.getUTCDate());
calHtml += ("</span>");
calHtml += ("</td>");
}
function buildCalendarTable (calendar)
{
currentDate = getFirstCalendarDate(calendar);
odd = 0;
while (currentDate.getUTCDay() != calendar.firstDayOfWeek)
{
currentDate.setUTCDate(currentDate.getUTCDate() - 1);
}
calHtml = "";
do
{
odd += 1;
calHtml += (
"<tr class=\"" + (((odd%2)==0) ? calendar.evenWeekStyle : calendar.oddWeekStyle) + "\">")
for (i = 0;i < 7;i++)
{
currentDayStyle = calendar.dayStyle;
currentEventStyle = calendar.commonDayStyle;
currentDateString = currentDate.getUTCFullYear() + "/" + (currentDate.getUTCMonth()+1) + "/" + currentDate.getUTCDate();
if (currentDate < calendar.minDate)
{
RenderDayDisabled (calendar, currentDate);
}
else if (currentDate > calendar.maxDate)
{
RenderDayDisabled (calendar, currentDate);
}
else if (currentDate.getUTCMonth() != calendar.calendarDate.getUTCMonth())
{
RenderDayDisabled (calendar, currentDate);
}
else if (currentDate.getUTCDate() == calendar.calendarDate.getUTCDate())
{
if ((currentDate.getUTCDay() == 0) || (currentDate.getUTCDay() == 6))
{
if (calendar.allowWeekends == true)
{
RenderDayEnabled (calendar, currentDate, calendar.todayDayStyle);
}
else
{
RenderDayDisabled (calendar, currentDate);
month = calendar.calendarDate.getUTCMonth();
calendar.calendarDate.setUTCDate(calendar.calendarDate.getUTCDate()+1);
if (month != calendar.calendarDate.getUTCMonth())
{
renderCalendar(calendar);
}
setFieldValue(calendar.attachedId, calendar.calendarDate);
}
} else {
if (calendar.allowWeekdays == true)
{
RenderDayEnabled (calendar, currentDate, calendar.todayDayStyle);
}
else
{
RenderDayDisabled (calendar, currentDate);
month = calendar.calendarDate.getUTCMonth();
calendar.calendarDate.setUTCDate(calendar.calendarDate.getUTCDate()+1);
if (month != calendar.calendarDate.getUTCMonth())
{
renderCalendar(calendar);
}
setFieldValue(calendar.attachedId, calendar.calendarDate);
}
}
}
else if ((currentDate.getUTCDay() == 0) || (currentDate.getUTCDay() == 6))
{
if (calendar.allowWeekends == true)
{
style = calendar.holidayDayStyle
for (j=0; j < calendar.eventDates.length; j++)
{
if (calendar.eventDates[j][0] == currentDateString)
{
style = calendar.eventDayStyle;
RenderDayEvent (calendar, currentDate, style, calendar.eventDates[j][0]);
}
}
if (style == calendar.holidayDayStyle)
{
RenderDayEnabled (calendar, currentDate, style);
}
}
else
{
RenderDayDisabled (calendar, currentDate);
}
} else {
if (calendar.allowWeekdays == true)
{
style = calendar.commonDayStyle
for (j=0; j < calendar.eventDates.length; j++)
{
if (calendar.eventDates[j][0] == currentDateString)
{
style = calendar.eventDayStyle;
RenderDayEvent (calendar, currentDate, style, calendar.eventDates[j][0]);
}
}
if (style == calendar.commonDayStyle)
{
RenderDayEnabled (calendar, currentDate, style);
}
}
else
{
RenderDayDisabled (calendar, currentDate);
}
}
currentDate.setUTCDate(currentDate.getUTCDate() + 1);
}
calHtml += ("</tr>");
} while (currentDate.getUTCMonth() == calendar.calendarDate.getUTCMonth());
return calHtml;
}
Inside Body Tag :
<span style="vertical-align:top;">
<fieldset style="width:220px;padding:5px;">
<legend>Calendar</legend>
<input type="text" name="tester" id="tester" value=""/>
<p/>
<input type="text" name="tester_day" id="tester_day" value="" size="3" maxLength="2" />
<select name="tester_month" id="tester_month">
<option value="1">January</option>
<option value="2">February</option>
<option value="3">March</option>
<option value="4">April</option>
<option value="5">May</option>
<option value="6">June</option>
<option value="7">July</option>
<option value="8">August</option>
<option value="9">September</option>
<option value="10">October</option>
<option value="11">November</option>
<option value="12">December</option>
</select>
<input type="text" name="tester_year" id="tester_year" value="" size="5" maxLength="4"/>
<p/>
<div id="cal_tester_display"></div>
<script type="text/javascript">
cal1 = new Calendar ("cal1", "tester", new Date());
renderCalendar (cal1);
</script>
</fieldset>
</span>
<span style="vertical-align:top;">
<fieldset style="width:220px;padding:5px;">
<legend>Calendar with Events</legend>
<input type="text" name="tester2" id="tester2" value=""/>
<p/>
<input type="text" name="tester2_day" id="tester2_day" value="" size="3" maxLength="2" />
<select name="tester2_month" id="tester2_month">
<option value="1">January</option>
<option value="2">February</option>
<option value="3">March</option>
<option value="4">April</option>
<option value="5">May</option>
<option value="6">June</option>
<option value="7">July</option>
<option value="8">August</option>
<option value="9">September</option>
<option value="10">October</option>
<option value="11">November</option>
<option value="12">December</option>
</select>
<input type="text" name="tester2_year" id="tester2_year" value="" size="5" maxLength="4"/>
<p/>
<div id="cal_tester2_display"></div>
<script type="text/javascript">
cal2 = new Calendar ("cal2", "tester2", new Date());
cal2.eventDates = new Array(
new Array("2005/2/10", 1)
, new Array("2005/2/12", 2)
, new Array("2005/2/14", 3)
);
cal2.scrolling = false;
cal2.selectEvent = function(eventId) {alert("cal2 Event: " + eventId);};
renderCalendar (cal2);
</script>
</fieldset>
No comments:
Post a Comment