/*!
* FullCalendar v1.2
* http://arshaw.com/fullcalendar/
*
* use fullcalendar.css for basic styling
* requires jQuery UI core and draggables ONLY if you plan to do drag & drop
*
* Copyright (c) 2009 Adam Shaw
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Date: 2009-05-31 15:56:02 -0500 (Sun, 31 May 2009)
* Revision: 23
*/
(function($) {
$.fn.fullCalendar = function(options) {
//
// Calls methods on a pre-existing instance
//
if (typeof options == 'string') {
var args = Array.prototype.slice.call(arguments, 1);
var res;
this.each(function() {
var r = $.data(this, 'fullCalendar')[options].apply(this, args);
if (typeof res == 'undefined') res = r;
});
if (typeof res != 'undefined') {
return res;
}
return this;
}
//
// Process options
//
options = options || {};
var r2l = options.rightToLeft;
var dis, dit; // day index sign / translate
if (r2l) {
dis = -1;
dit = 6;
this.addClass('r2l');
}else{
dis = 1;
dit = 0;
}
var showTime = typeof options.showTime == 'undefined' ? 'guess' : options.showTime;
var bo = typeof options.buttons == 'undefined' ? true : options.buttons;
var weekStart = (options.weekStart || 0) % 7;
var timeFormat = options.timeFormat || 'gx';
var titleFormat = options.titleFormat || (r2l ? 'Y F' : 'F Y');
//
// Rendering bug detection variables
//
var tdTopBug, trTopBug, tbodyTopBug, sniffBugs = true;
this.each(function() {
//
// Instance variables
//
var date = options.year ? // holds the year & month of current month
new Date(options.year, options.month || 0, 1) :
new Date();
var start, end; // first & last VISIBLE dates
var today;
var numWeeks;
var ignoreResizes = false;
var events = [];
var eventSources = options.eventSources || [];
if (options.events) eventSources.push(options.events);
//
// Month navigation functions
//
function refreshMonth() {
clearEventElements();
render();
}
function prevMonth() {
addMonths(date, -1);
refreshMonth();
}
function nextMonth() {
addMonths(date, 1);
refreshMonth();
}
function gotoToday() {
date = new Date();
refreshMonth();
}
function gotoMonth(year, month) {
date = new Date(year, month, 1);
refreshMonth();
}
function prevYear() {
addYears(date, -1);
refreshMonth();
}
function nextYear() {
addYears(date, 1);
refreshMonth();
}
//
// Publicly accessible methods
//
$.data(this, 'fullCalendar', {
refresh: refreshMonth,
prevMonth: prevMonth,
nextMonth: nextMonth,
today: gotoToday,
gotoMonth: gotoMonth,
prevYear: prevYear,
nextYear: nextYear,
//
// Event CRUD
//
addEvent: function(event) {
events.push(normalizeEvent(event));
clearEventElements();
renderEvents();
},
updateEvent: function(event) {
event.start = $.fullCalendar.parseDate(event.start);
event.end = $.fullCalendar.parseDate(event.end);
var startDelta = event.start - event._start;
var msLength = event.end - event.start;
event._start = cloneDate(event.start);
for (var i=0; i").appendTo(this);
if (bo) { // "button options"
var buttons = $("
").appendTo(header);
if (bo == true || bo.today !== false) {
todayButton = $("")
.append($("").html(
typeof bo.today == 'string' ?
bo.today : "today"))
.click(gotoToday);
buttons.append(todayButton);
}
if (bo.prevYear) {
var b = $("")
.append($("")
.html(typeof bo.prevYear == 'string' ?
bo.prevYear : "«"))
.click(prevYear);
if (r2l) buttons.prepend(b);
else buttons.append(b);
}
if (bo == true || bo.prevMonth !== false) {
var b = $("")
.append($("")
.html(typeof bo.prevMonth == 'string' ?
bo.prevMonth : (r2l ? ">" : "<")))
.click(prevMonth);
if (r2l) buttons.prepend(b);
else buttons.append(b);
}
if (bo == true || bo.nextMonth !== false) {
var b = $("")
.append($("").html(typeof bo.nextMonth == 'string' ?
bo.nextMonth : (r2l ? "<" : ">")))
.click(nextMonth);
if (r2l) buttons.prepend(b);
else buttons.append(b);
}
if (bo.nextYear) {
var b = $("")
.append($("").html(typeof bo.nextYear == 'string'
? bo.nextYear : "»"))
.click(nextYear);
if (r2l) buttons.prepend(b);
else buttons.append(b);
}
}
if (options.title !== false) {
titleElement = $("").appendTo(header);
}
monthElement = $("")
.appendTo($("").appendTo(this));
//
// Build the TABLE cells for the current month. (calls event fetching & rendering code)
//
var thead, tbody, glass;
function render() {
ignoreResizes = true;
date.setDate(1);
clearTime(date);
var year = date.getFullYear();
var month = date.getMonth();
var monthTitle = $.fullCalendar.formatDate(date, titleFormat);
if (titleElement) titleElement.text(monthTitle);
clearTime(date);
start = cloneDate(date);
addDays(start, -((start.getDay() - weekStart + 7) % 7));
end = cloneDate(date);
addMonths(end, 1);
addDays(end, (7 - end.getDay() + weekStart) % 7);
numWeeks = Math.round((end.getTime() - start.getTime()) / 604800000);
if (options.fixedWeeks != false) {
addDays(end, (6 - numWeeks) * 7);
numWeeks = 6;
}
today = clearTime(new Date());
if (todayButton) {
if (today.getFullYear() == year && today.getMonth() == month) {
todayButton.css('visibility', 'hidden');
}else{
todayButton.css('visibility', 'visible');
}
}
var dayNames = $.fullCalendar.dayNames;
var dayAbbrevs = $.fullCalendar.dayAbbrevs;
if (!tbody) {
// first time, build all cells from scratch
var table = $("").appendTo(monthElement);
thead = "";
for (var i=0; i<7; i++) {
var j = (i * dis + dit + weekStart) % 7;
thead +=
"" +
(options.abbrevDayHeadings!=false ? dayAbbrevs[j] : dayNames[j]) +
" | ";
}
thead = $(thead + "
").appendTo(table);
tbody = "";
var d = cloneDate(start);
for (var i=0; i";
var tds = "";
for (var j=0; j<7; j++) {
var s =
"" + d.getDate() + " " +
" | ";
if (r2l) tds = s + tds;
else tds += s;
addDays(d, 1);
}
tbody += tds + "";
}
tbody = $(tbody + "").appendTo(table);
// a protective coating over the TABLE
// intercepts mouse clicks and prevents text-selection
glass = $("")
.appendTo(monthElement)
.click(function(ev, ui) {
if (options.dayClick) {
buildDayGrid();
var td = dayTD(ev.pageX, ev.pageY);
if (td) return options.dayClick.call(td, dayDate(td));
}
});
}else{
// NOT first time, reuse as many cells as possible
var diff = numWeeks - tbody.find('tr').length;
if (diff < 0) {
// remove extra rows
tbody.find('tr:gt(' + (numWeeks-1) + ')').remove();
}
else if (diff > 0) {
var trs = "";
for (var i=0; i";
for (var j=0; j<7; j++) {
trs +=
"" +
"" +
"" +
" | ";
}
trs += "";
}
if (trs) tbody.append(trs);
}
// re-label and re-class existing cells
var d = cloneDate(start);
tbody.find('tr').each(function() {
for (var i=0; i<7; i++) {
var td = this.childNodes[i * dis + dit];
if (d.getMonth() == month) {
$(td).removeClass('other-month');
}else{
$(td).addClass('other-month');
}
if (d.getTime() == today.getTime()) {
$(td).addClass('today');
}else{
$(td).removeClass('today');
}
$(td.childNodes[0]).text(d.getDate());
addDays(d, 1);
}
});
}
setCellSizes();
if (sniffBugs) {
// nasty bugs in opera 9.25
// position() returning relative to direct parent
var tr = tbody.find('tr');
var td = tr.find('td');
var trTop = tr.position().top;
var tdTop = td.position().top;
tdTopBug = tdTop < 0;
trTopBug = trTop != tdTop;
tbodyTopBug = tbody.position().top != trTop;
sniffBugs = false;
}
fetchEvents(renderEvents);
ignoreResizes = false;
if (options.monthDisplay)
options.monthDisplay(date.getFullYear(), date.getMonth(), monthTitle);
}
//
// Adjust dimensions of the cells, based on container's width
//
function setCellSizes() {
var tbodyw = tbody.width();
var cellw = Math.floor(tbodyw / 7);
var cellh = Math.round(cellw * .85);
thead.find('th')
.filter(':lt(6)').width(cellw).end()
.filter(':eq(6)').width(tbodyw - cellw*6);
tbody.find('td').height(cellh);
glass.height(monthElement.height());
monthElementWidth = monthElement.width();
}
/*******************************************************************/
//
// Event Rendering
//
/*******************************************************************/
//
// Render the 'events' array. First, break up into segments
//
var eventMatrix = [];
function renderEvents() {
eventMatrix = [];
var i = 0;
var ws = cloneDate(start);
var we = addDays(cloneDate(ws), 7);
while (ws.getTime() < end.getTime()) {
var segs = [];
$.each(events, function(j, event) {
if (event.end.getTime() > ws.getTime() && event.start.getTime() < we.getTime()) {
var ss, se, isStart, isEnd;
if (event.start.getTime() < ws.getTime()) {
ss = cloneDate(ws);
isStart = false;
}else{
ss = cloneDate(event.start);
isStart = true;
}
if (event.end.getTime() > we.getTime()) {
se = cloneDate(we);
isEnd = false;
}else{
se = cloneDate(event.end);
isEnd = true;
}
ss = clearTime(ss);
se = clearTime((se.getHours()==0 && se.getMinutes()==0) ? se : addDays(se, 1));
segs.push({
event: event, start: ss, end: se,
isStart: isStart, isEnd: isEnd, msLength: se - ss
});
}
});
segs.sort(segCmp);
var levels = [];
$.each(segs, function(j, seg) {
var l = 0; // level index
while (true) {
var collide = false;
if (levels[l]) {
for (var k=0; k levels[l][k].start.getTime() &&
seg.start.getTime() < levels[l][k].end.getTime()) {
collide = true;
break;
}
}
}
if (collide) {
l++;
continue;
}else{
break;
}
}
if (levels[l]) levels[l].push(seg);
else levels[l] = [seg];
});
eventMatrix[i] = levels;
addDays(ws, 7);
addDays(we, 7);
i++;
}
_renderEvents();
}
//
// Do the REAL rendering of the segments
//
var eventElements = []; // [[event, element], ...]
function _renderEvents() {
for (var i=0; i")
.append("" +
(roundW ? " | " : '') +
" | " +
(roundE ? " | " : '') + "
")
.append("" +
(roundW ? " | " : '') +
" | " +
(roundE ? " | " : '') + "
")
.append("" +
(roundW ? " | " : '') +
" | " +
(roundE ? " | " : '') + "
");
buildEventText(event, element.find('td.c'));
if (options.eventRender) {
var res = options.eventRender(event, element);
if (typeof res != 'undefined') {
if (res === false) continue;
if (res !== true) element = $(res);
}
}
element
.css({
position: 'absolute',
top: top,
left: left1,
width: left2 - left1,
'z-index': 3
})
.appendTo(monthElement);
initEventElement(event, element);
var h = element.outerHeight({margin:true});
if (h > maxh) maxh = h;
}
height += maxh;
top += maxh;
}
innerDiv.height(height);
}
}
//
// create the text-contents of an event segment
//
function buildEventText(event, element) {
$("")
.text(event.title)
.appendTo(element);
var st = typeof event.showTime == 'undefined' ? showTime : event.showTime;
if (st != false) {
if (st == true || st == 'guess' &&
(event.start.getHours() || event.start.getMinutes() ||
event.end.getHours() || event.end.getMinutes())) {
var timeStr = $.fullCalendar.formatDate(event.start, timeFormat);
var timeElement = $("");
if (r2l) element.append(timeElement.text(' ' + timeStr));
else element.prepend(timeElement.text(timeStr + ' '));
}
}
}
//
// Attach event handlers to an event segment
//
function initEventElement(event, element) {
element.click(function(ev) {
if (!element.hasClass('ui-draggable-dragging')) {
if (options.eventClick) {
var res = options.eventClick.call(this, event, ev);
if (res === false) return false;
}
if (event.url) window.location.href = event.url;
}
});
if (options.eventMouseover)
element.mouseover(function(ev) {
options.eventMouseover.call(this, event, ev);
});
if (options.eventMouseout)
element.mouseout(function(ev) {
options.eventMouseout.call(this, event, ev);
});
if (typeof event.draggable != 'undefined') {
if (event.draggable)
draggableEvent(event, element);
}
else if (options.draggable) {
draggableEvent(event, element);
}
eventElements.push([event, element]);
}
//
// Remove all event segments from DOM
//
function clearEventElements() {
for (var i=0; i")
.appendTo(monthElement);
buildDayGrid();
dragTD = dragStartTD = null;
eventDrag(this, ev, ui);
if (options.eventDragStart)
options.eventDragStart.call(this, event, ev, ui);
},
drag: function(ev, ui) {
eventDrag(this, ev, ui);
},
stop: function(ev, ui) {
if (!dragTD || dragTD == dragStartTD) {
// show all events
for (var i=0; i dayY0 + dayY[r+1]) r++;
while (c < cmax && x > dayX0 + dayX[c+1]) c++;
if (r < 0 || r >= rmax || c < 0 || c >= cmax)
return currTD = null;
else if (!currTD || r != currR || c != currC) {
currR = r;
currC = c;
currTD = tbody.find('tr:eq('+r+') td:eq('+c+')').get(0);
currTDX = dayX[c];
currTDY = dayY[r];
currTDW = dayX[c+1] - currTDX;
currTDH = dayY[r+1] - currTDY;
return currTD;
}
return currTD;
}
//
// Get a TD's date
//
function dayDate(td) {
var i, trs = tbody.get(0).getElementsByTagName('tr');
for (i=0; i 0) {
var queued = eventSources.length;
var sourceDone = function() {
if (--queued == 0) {
if (options.loading) {
options.loading(false);
}
if (callback) {
callback(events);
}
}
};
if (options.loading) {
options.loading(true);
}
for (var i=0; i