/*
* jQuery.weekCalendar v2.0-dev
*
* for support join us at the google group:
* - http://groups.google.com/group/jquery-week-calendar
* have a look to the wiki for documentation:
* - http://wiki.github.com/themouette/jquery-week-calendar/
* something went bad ? report an issue:
* - http://github.com/themouette/jquery-week-calendar/issues
* get the last version on github:
* - http://github.com/themouette/jquery-week-calendar
*
* Copyright (c) 2009 Rob Monie
* Copyright (c) 2010 Julien MUETTON
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* If you're after a monthly calendar plugin, check out this one :
* http://arshaw.com/fullcalendar/
*/
(function($) {
// check the jquery version
var _v = $.fn.jquery.split('.'),
_jQuery14OrLower = (10 * _v[0] + _v[1]) < 15;
$.widget('ui.weekCalendar', (function() {
var _currentAjaxCall;
return {
options: {
date: new Date(),
timeFormat: null,
dateFormat: 'M d, Y',
alwaysDisplayTimeMinutes: true,
use24Hour: true,
daysToShow: 5,
minBodyHeight: 100,
firstDayOfWeek: function(calendar) {
if ($(calendar).weekCalendar('option', 'daysToShow') != 5) {
return 0;
} else {
//workweek
return 1;
}
}, // 0 = Sunday, 1 = Monday, 2 = Tuesday, ... , 6 = Saturday
useShortDayNames: true,
timeSeparator: ' bis ',
startParam: 'start',
endParam: 'end',
businessHours: {start: 8, end: 18, limitDisplay: true},
newEventText: 'Neuen Termin',
timeslotHeight: 7,
defaultEventLength: 1,
timeslotsPerHour: 4,
minDate: null,
maxDate: null,
buttons: true,
buttonText: {
today: 'Heute',
lastWeek: 'previous',
nextWeek: 'next'
},
switchDisplay: {},
scrollToHourMillis: 0,
allowCalEventOverlap: true,
overlapEventsSeparate: true,
totalEventsWidthPercentInOneColumn : 100,
readonly: false,
allowEventCreation: true,
draggable: function(calEvent, element) {
return true;
},
resizable: function(calEvent, element) {
return true;
},
eventClick: function(calEvent, element, dayFreeBusyManager,
calendar, clickEvent) {
},
eventRender: function(calEvent, element) {
return element;
},
eventAfterRender: function(calEvent, element) {
return element;
},
eventRefresh: function(calEvent, element) {
return element;
},
eventDrag: function(calEvent, element) {
},
eventDrop: function(calEvent, element) {
},
eventResize: function(calEvent, element) {
},
eventNew: function(calEvent, element, dayFreeBusyManager,
calendar, mouseupEvent) {
},
eventMouseover: function(calEvent, $event) {
},
eventMouseout: function(calEvent, $event) {
},
calendarBeforeLoad: function(calendar) {
},
calendarAfterLoad: function(calendar) {
},
noEvents: function() {
},
eventHeader: function(calEvent, calendar) {
var options = calendar.weekCalendar('option');
var one_hour = 3600000;
var displayTitleWithTime = calEvent.end.getTime() - calEvent.start.getTime() <= (one_hour / options.timeslotsPerHour);
if (displayTitleWithTime) {
return calendar.weekCalendar(
'formatTime', calEvent.start) +
': ' + calEvent.title;
} else {
return calendar.weekCalendar(
'formatTime', calEvent.start) +
options.timeSeparator +
calendar.weekCalendar(
'formatTime', calEvent.end);
}
},
eventBody: function(calEvent, calendar) {
return calEvent.title; // NICHT
},
shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
longMonths: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
longDays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
/* multi-users options */
/**
* the available users for calendar.
* if you want to display users separately, enable the
* showAsSeparateUsers option.
* if you provide a list of user and do not enable showAsSeparateUsers
* option, then only the events that belongs to one or several of
* given users will be displayed
* @type {array}
*/
users: [],
/**
* should the calendar be displayed with separate column for each
* users.
* note that this option does nothing if you do not provide at least
* one user.
* @type {boolean}
*/
showAsSeparateUsers: true,
/**
* callback used to read user id from a user object.
* @param {Object} user the user to retrieve the id from.
* @param {number} index the user index from user list.
* @param {jQuery} calendar the calendar object.
* @return {int|String} the user id.
*/
getUserId: function(user, index, calendar) {
return index;
},
/**
* callback used to read user name from a user object.
* @param {Object} user the user to retrieve the name from.
* @param {number} index the user index from user list.
* @param {jQuery} calendar the calendar object.
* @return {String} the user name.
*/
getUserName: function(user, index, calendar) {
return user;
},
/**
* reads the id(s) of user(s) for who the event should be displayed.
* @param {Object} calEvent the calEvent to read informations from.
* @param {jQuery} calendar the calendar object.
* @return {number|String|Array} the user id(s) to appened events for.
*/
getEventUserId: function(calEvent, calendar) {
return calEvent.userId;
},
/**
* sets user id(s) to the calEvent
* @param {Object} calEvent the calEvent to set informations to.
* @param {jQuery} calendar the calendar object.
* @return {Object} the calEvent with modified user id.
*/
setEventUserId: function(userId, calEvent, calendar) {
calEvent.userId = userId;
return calEvent;
},
/* freeBusy options */
/**
* should the calendar display freebusys ?
* @type {boolean}
*/
displayFreeBusys: false,
/**
* read the id(s) for who the freebusy is available
* @param {Object} calEvent the calEvent to read informations from.
* @param {jQuery} calendar the calendar object.
* @return {number|String|Array} the user id(s) to appened events for.
*/
getFreeBusyUserId: function(calFreeBusy, calendar) {
return calFreeBusy.userId;
},
/**
* the default freeBusy object, used to manage default state
* @type {Object}
*/
defaultFreeBusy: {free: false},
/**
* function used to display the freeBusy element
* @type {Function}
* @param {Object} freeBusy the freeBusy timeslot to render.
* @param {jQuery} $freeBusy the freeBusy HTML element.
* @param {jQuery} calendar the calendar element.
*/
freeBusyRender: function(freeBusy, $freeBusy, calendar) {
if (!freeBusy.free) {
$freeBusy.addClass('free-busy-busy');
}
else {
$freeBusy.addClass('free-busy-free');
}
return $freeBusy;
},
/* other options */
/**
* true means start on first day of week, false means starts on
* startDate.
* @param {jQuery} calendar the calendar object.
* @type {Function|bool}
*/
startOnFirstDayOfWeek: function(calendar) {
return $(calendar).weekCalendar('option', 'daysToShow') >= 5;
},
/**
* should the columns be rendered alternatively using odd/even
* class
* @type {boolean}
*/
displayOddEven: false,
textSize: 13,
/**
* the title attribute for the calendar. possible placeholders are:
*
*
%start%
*
%end%
*
%date%
*
* @type {Function|string}
* @param {number} option daysToShow.
* @return {String} the title attribute for the calendar.
*/
title: '%start% - %end%',
/**
* default options to pass to callback
* you can pass a function returning an object or a litteral object
* @type {object|function(#calendar)}
*/
jsonOptions: {},
headerSeparator: ' ',
/**
* returns formatted header for day display
* @type {function(date,calendar)}
*/
getHeaderDate: null,
preventDragOnEventCreation: false,
/**
* the event on which to bind calendar resize
* @type {string}
*/
resizeEvent: 'resize.weekcalendar'
},
/***********************
* Initialise calendar *
***********************/
_create: function() {
var self = this;
self._computeOptions();
self._setupEventDelegation();
self._renderCalendar();
self._loadCalEvents();
self._resizeCalendar();
self._scrollToHour(self.options.date.getHours(), true);
if (this.options.resizeEvent) {
$(window).unbind(this.options.resizeEvent);
$(window).bind(this.options.resizeEvent, function() {
self._resizeCalendar();
});
}
},
/********************
* public functions *
********************/
/*
* Refresh the events for the currently displayed week.
*/
refresh: function() {
//reload with existing week
this._loadCalEvents(this.element.data('startDate'));
},
/*
* Clear all events currently loaded into the calendar
*/
clear: function() {
this._clearCalendar();
},
/*
* Go to this week
*/
today: function() {
this._clearCalendar();
this._loadCalEvents(new Date());
},
/*
* Go to the previous week relative to the currently displayed week
*/
prevWeek: function() {
//minus more than 1 day to be sure we're in previous week - account for daylight savings or other anomolies
var newDate = new Date(this.element.data('startDate').getTime() - (MILLIS_IN_WEEK / 6));
this._clearCalendar();
this._loadCalEvents(newDate);
},
/*
* Go to the next week relative to the currently displayed week
*/
nextWeek: function() {
//add 8 days to be sure of being in prev week - allows for daylight savings or other anomolies
var newDate = new Date(this.element.data('startDate').getTime() + MILLIS_IN_WEEK + MILLIS_IN_DAY);
this._clearCalendar();
this._loadCalEvents(newDate);
},
/*
* Reload the calendar to whatever week the date passed in falls on.
*/
gotoWeek: function(date) {
this._clearCalendar();
this._loadCalEvents(date);
},
/*
* Reload the calendar to whatever week the date passed in falls on.
*/
gotoDate: function(date) {
this._clearCalendar();
this._loadCalEvents(date);
},
/**
* change the number of days to show
*/
setDaysToShow: function(daysToShow) {
var self = this;
var hour = self._getCurrentScrollHour();
self.options.daysToShow = daysToShow;
$(self.element).html('');
self._renderCalendar();
self._loadCalEvents();
self._resizeCalendar();
self._scrollToHour(hour, false);
if (this.options.resizeEvent) {
$(window).unbind(this.options.resizeEvent);
$(window).bind(this.options.resizeEvent, function() {
self._resizeCalendar();
});
}
},
/*
* Remove an event based on it's id
*/
removeEvent: function(eventId) {
$.ajax({
type: "POST",
url: "index.php?module=kalender&action=delete",
data: "id=" + eventId ,
dataType: "html",
success: function(msg)
{
$("#message").html(msg);
},
error: function()
{
alert("Can't save event");
}
});
var self = this;
self.element.find('.wc-cal-event').each(function() {
if ($(this).data('calEvent').id === eventId) {
$(this).remove();
return false;
}
});
//this could be more efficient rather than running on all days regardless...
self.element.find('.wc-day-column-inner').each(function() {
self._adjustOverlappingEvents($(this));
});
},
/*
* Removes any events that have been added but not yet saved (have no id).
* This is useful to call after adding a freshly saved new event.
*/
removeUnsavedEvents: function() {
var self = this;
self.element.find('.wc-new-cal-event').each(function() {
$(this).remove();
});
//this could be more efficient rather than running on all days regardless...
self.element.find('.wc-day-column-inner').each(function() {
self._adjustOverlappingEvents($(this));
});
},
/*
* update an event in the calendar. If the event exists it refreshes
* it's rendering. If it's a new event that does not exist in the calendar
* it will be added.
*/
updateEvent: function(calEvent) {
$.ajax({
type: "POST",
url: "index.php?module=kalender&action=update",
data: "id=" + calEvent.id + "&title=" + calEvent.title + "&text=" + calEvent.body + "&start=" + calEvent.start.getTime() + "&end=" + calEvent.end.getTime() + "&public=" + calEvent.publicEvent,
dataType: "html",
success: function(msg){$("#message").html(msg);},
error: function(){ alert("Can't save event");}
});
this._updateEventInCalendar(calEvent);
},
/*
* Returns an array of timeslot start and end times based on
* the configured grid of the calendar. Returns in both date and
* formatted time based on the 'timeFormat' config option.
*/
getTimeslotTimes: function(date) {
var options = this.options;
var firstHourDisplayed = options.businessHours.limitDisplay ? options.businessHours.start : 0;
var startDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), firstHourDisplayed);
var times = [],
startMillis = startDate.getTime();
for (var i = 0; i < options.timeslotsPerDay; i++) {
var endMillis = startMillis + options.millisPerTimeslot;
times[i] = {
start: new Date(startMillis),
startFormatted: this.formatTime(new Date(startMillis), options.timeFormat),
end: new Date(endMillis),
endFormatted: this.formatTime(new Date(endMillis), options.timeFormat)
};
startMillis = endMillis;
}
return times;
},
formatDate: function(date, format) {
if (format) {
return this._formatDate(date, format);
} else {
return this._formatDate(date, this.options.dateFormat);
}
},
formatTime: function(date, format) {
if (format) {
return this._formatDate(date, format);
} else if (this.options.timeFormat) {
return this._formatDate(date, this.options.timeFormat);
} else if (this.options.use24Hour) {
return this._formatDate(date, 'H:i');
} else {
return this._formatDate(date, 'h:i a');
}
},
serializeEvents: function() {
var self = this;
var calEvents = [];
self.element.find('.wc-cal-event').each(function() {
calEvents.push($(this).data('calEvent'));
});
return calEvents;
},
next: function() {
if (this._startOnFirstDayOfWeek()) {
return this.nextWeek();
}
var newDate = new Date(this.element.data('startDate').getTime());
newDate.setDate(newDate.getDate() + this.options.daysToShow);
this._clearCalendar();
this._loadCalEvents(newDate);
},
prev: function() {
if (this._startOnFirstDayOfWeek()) {
return this.prevWeek();
}
var newDate = new Date(this.element.data('startDate').getTime());
newDate.setDate(newDate.getDate() - this.options.daysToShow);
this._clearCalendar();
this._loadCalEvents(newDate);
},
getCurrentFirstDay: function() {
return this._dateFirstDayOfWeek(this.options.date || new Date());
},
getCurrentLastDay: function() {
return this._addDays(this.getCurrentFirstDay(), this.options.daysToShow - 1);
},
/*********************
* private functions *
*********************/
_setOption: function(key, value) {
var self = this;
if (self.options[key] != value) {
// event callback change, no need to re-render the events
if (key == 'beforeEventNew') {
self.options[key] = value;
return;
}
// this could be made more efficient at some stage by caching the
// events array locally in a store but this should be done in conjunction
// with a proper binding model.
var currentEvents = $.map(self.element.find('.wc-cal-event'), function() {
return $(this).data('calEvent');
});
var newOptions = {};
newOptions[key] = value;
self._renderEvents({events: currentEvents, options: newOptions}, self.element.find('.wc-day-column-inner'));
}
},
// compute dynamic options based on other config values
_computeOptions: function() {
var options = this.options;
if (options.businessHours.limitDisplay) {
options.timeslotsPerDay = options.timeslotsPerHour * (options.businessHours.end - options.businessHours.start);
options.millisToDisplay = (options.businessHours.end - options.businessHours.start) * 3600000; // 60 * 60 * 1000
options.millisPerTimeslot = options.millisToDisplay / options.timeslotsPerDay;
} else {
options.timeslotsPerDay = options.timeslotsPerHour * 24;
options.millisToDisplay = MILLIS_IN_DAY;
options.millisPerTimeslot = MILLIS_IN_DAY / options.timeslotsPerDay;
}
},
/*
* Resize the calendar scrollable height based on the provided function in options.
*/
_resizeCalendar: function() {
var options = this.options;
if (options && $.isFunction(options.height)) {
var calendarHeight = options.height(this.element);
var headerHeight = this.element.find('.wc-header').outerHeight();
var navHeight = this.element.find('.wc-toolbar').outerHeight();
var scrollContainerHeight = Math.max(calendarHeight - navHeight - headerHeight, options.minBodyHeight);
var timeslotHeight = this.element.find('.wc-time-slots').outerHeight();
this.element.find('.wc-scrollable-grid').height(scrollContainerHeight);
if (timeslotHeight <= scrollContainerHeight) {
this.element.find('.wc-scrollbar-shim').width(0);
}
else {
this.element.find('.wc-scrollbar-shim').width(this._findScrollBarWidth());
}
this._trigger('resize', this.element);
}
},
_findScrollBarWidth: function() {
var parent = $('
').appendTo('body');
var child = parent.children();
var width = child.innerWidth() - child.height(99).innerWidth();
parent.remove();
return width || /* default to 16 that is the average */ 16;
},
/*
* configure calendar interaction events that are able to use event
* delegation for greater efficiency
*/
_setupEventDelegation: function() {
var self = this;
var options = this.options;
this.element.click(function(event) {
var $target = $(event.target),
freeBusyManager;
if ($target.data('preventClick')) {
return;
}
var $calEvent = $target.hasClass('wc-cal-event') ? $target : $target.parents('.wc-cal-event');
if ($calEvent.length) {
freeBusyManager = self.getFreeBusyManagerForEvent($calEvent.data('calEvent'));
options.eventClick($calEvent.data('calEvent'), $calEvent, freeBusyManager, self.element, event);
}
}).mouseover(function(event) {
var $target = $(event.target);
if (self._isDraggingOrResizing($target)) {
return;
}
if ($target.hasClass('wc-cal-event')) {
options.eventMouseover($target.data('calEvent'), $target, event);
}
}).mouseout(function(event) {
var $target = $(event.target);
if (self._isDraggingOrResizing($target)) {
return;
}
if ($target.hasClass('wc-cal-event')) {
if ($target.data('sizing')) { return;}
options.eventMouseout($target.data('calEvent'), $target, event);
}
});
},
/*
* check if a ui draggable or resizable is currently being dragged or resized
*/
_isDraggingOrResizing: function($target) {
return $target.hasClass('ui-draggable-dragging') || $target.hasClass('ui-resizable-resizing');
},
/*
* Render the main calendar layout
*/
_renderCalendar: function() {
var $calendarContainer, $weekDayColumns;
var self = this;
var options = this.options;
$calendarContainer = $('
').appendTo(self.element);
//render the different parts
// nav links
self._renderCalendarButtons($calendarContainer);
// header
self._renderCalendarHeader($calendarContainer);
// body
self._renderCalendarBody($calendarContainer);
$weekDayColumns = $calendarContainer.find('.wc-day-column-inner');
$weekDayColumns.each(function(i, val) {
if (!options.readonly) {
self._addDroppableToWeekDay($(this));
if (options.allowEventCreation) {
self._setupEventCreationForWeekDay($(this));
}
}
});
},
/**
* render the nav buttons on top of the calendar
*/
_renderCalendarButtons: function($calendarContainer) {
var self = this, options = this.options;
if (options.buttons) {
var calendarNavHtml = '';
calendarNavHtml += '
';
$(calendarHeaderHtml).appendTo($calendarContainer);
},
/**
* render the calendar body.
* Calendar body is composed of several distinct parts.
* Each part is displayed in a separated row to ease rendering.
* for further explanations, see each part rendering function.
*/
_renderCalendarBody: function($calendarContainer) {
var self = this, options = this.options,
showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
$calendarBody, $calendarTableTbody;
// create the structure
$calendarBody = '