mirror of
https://github.com/OpenXE-org/OpenXE.git
synced 2024-11-16 04:57:15 +01:00
2869 lines
114 KiB
JavaScript
2869 lines
114 KiB
JavaScript
/*
|
|
* 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:
|
|
* <ul>
|
|
* <li>%start%</li>
|
|
* <li>%end%</li>
|
|
* <li>%date%</li>
|
|
* </ul>
|
|
* @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: '<br />',
|
|
/**
|
|
* 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 = $('<div style="width:50px;height:50px;overflow:auto"><div/></div>').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 = $('<div class=\"ui-widget wc-container\">').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 += '<div class=\"ui-widget-header wc-toolbar\">';
|
|
calendarNavHtml += '<div class=\"wc-display\"></div>';
|
|
calendarNavHtml += '<div class=\"wc-nav\">';
|
|
calendarNavHtml += '<button class=\"wc-prev\">' + options.buttonText.lastWeek + '</button>';
|
|
calendarNavHtml += '<button class=\"wc-today\">' + options.buttonText.today + '</button>';
|
|
calendarNavHtml += '<button class=\"wc-next\">' + options.buttonText.nextWeek + '</button>';
|
|
calendarNavHtml += '</div>';
|
|
calendarNavHtml += '<h1 class=\"wc-title\"></h1>';
|
|
calendarNavHtml += '</div>';
|
|
|
|
$(calendarNavHtml).appendTo($calendarContainer);
|
|
|
|
$calendarContainer.find('.wc-nav .wc-today')
|
|
.button({
|
|
icons: {primary: 'ui-icon-home'}})
|
|
.click(function() {
|
|
self.today();
|
|
return false;
|
|
});
|
|
|
|
$calendarContainer.find('.wc-nav .wc-prev')
|
|
.button({
|
|
text: false,
|
|
icons: {primary: 'ui-icon-seek-prev'}})
|
|
.click(function() {
|
|
self.element.weekCalendar('prev');
|
|
return false;
|
|
});
|
|
|
|
$calendarContainer.find('.wc-nav .wc-next')
|
|
.button({
|
|
text: false,
|
|
icons: {primary: 'ui-icon-seek-next'}})
|
|
.click(function() {
|
|
self.element.weekCalendar('next');
|
|
return false;
|
|
});
|
|
|
|
// now add buttons to switch display
|
|
if (this.options.switchDisplay && $.isPlainObject(this.options.switchDisplay)) {
|
|
var $container = $calendarContainer.find('.wc-display');
|
|
$.each(this.options.switchDisplay, function(label, option) {
|
|
var _id = 'wc-switch-display-' + option;
|
|
var _input = $('<input type="radio" id="' + _id + '" name="wc-switch-display" class="wc-switch-display"/>');
|
|
var _label = $('<label for="' + _id + '"></label>');
|
|
_label.html(label);
|
|
_input.val(option);
|
|
if (parseInt(self.options.daysToShow, 10) === parseInt(option, 10)) {
|
|
_input.attr('checked', 'checked');
|
|
}
|
|
$container
|
|
.append(_input)
|
|
.append(_label);
|
|
});
|
|
$container.find('input').change(function() {
|
|
self.setDaysToShow(parseInt($(this).val(), 10));
|
|
});
|
|
}
|
|
$calendarContainer.find('.wc-nav, .wc-display').buttonset();
|
|
var _height = $calendarContainer.find('.wc-nav').outerHeight();
|
|
$calendarContainer.find('.wc-title')
|
|
.height(_height)
|
|
.css('line-height', _height + 'px');
|
|
}else{
|
|
var calendarNavHtml = '';
|
|
calendarNavHtml += '<div class=\"ui-widget-header wc-toolbar\">';
|
|
calendarNavHtml += '<h1 class=\"wc-title\"></h1>';
|
|
calendarNavHtml += '</div>';
|
|
$(calendarNavHtml).appendTo($calendarContainer);
|
|
|
|
}
|
|
},
|
|
|
|
/**
|
|
* render the calendar header, including date and user header
|
|
*/
|
|
_renderCalendarHeader: function($calendarContainer) {
|
|
var self = this, options = this.options,
|
|
showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
|
|
rowspan = '', colspan = '', calendarHeaderHtml;
|
|
|
|
if (showAsSeparatedUser) {
|
|
rowspan = ' rowspan=\"2\"';
|
|
colspan = ' colspan=\"' + options.users.length + '\" ';
|
|
}
|
|
|
|
//first row
|
|
calendarHeaderHtml = '<div class=\"ui-widget-content wc-header\">';
|
|
calendarHeaderHtml += '<table><tbody><tr><td class=\"wc-time-column-header\"></td>';
|
|
for (var i = 1; i <= options.daysToShow; i++) {
|
|
calendarHeaderHtml += '<td class=\"wc-day-column-header wc-day-' + i + '\"' + colspan + '></td>';
|
|
}
|
|
calendarHeaderHtml += '<td class=\"wc-scrollbar-shim\"' + rowspan + '></td></tr>';
|
|
|
|
//users row
|
|
if (showAsSeparatedUser) {
|
|
calendarHeaderHtml += '<tr><td class=\"wc-time-column-header\"></td>';
|
|
var uLength = options.users.length,
|
|
_headerClass = '';
|
|
|
|
for (var i = 1; i <= options.daysToShow; i++) {
|
|
for (var j = 0; j < uLength; j++) {
|
|
_headerClass = [];
|
|
if (j == 0) {
|
|
_headerClass.push('wc-day-column-first');
|
|
}
|
|
if (j == uLength - 1) {
|
|
_headerClass.push('wc-day-column-last');
|
|
}
|
|
if (!_headerClass.length) {
|
|
_headerClass = 'wc-day-column-middle';
|
|
}
|
|
else {
|
|
_headerClass = _headerClass.join(' ');
|
|
}
|
|
calendarHeaderHtml += '<td class=\"' + _headerClass + ' wc-user-header wc-day-' + i + ' wc-user-' + self._getUserIdFromIndex(j) + '\">';
|
|
// calendarHeaderHtml+= "<div class=\"wc-user-header wc-day-" + i + " wc-user-" + self._getUserIdFromIndex(j) +"\" >";
|
|
calendarHeaderHtml += self._getUserName(j);
|
|
// calendarHeaderHtml+= "</div>";
|
|
calendarHeaderHtml += '</td>';
|
|
}
|
|
}
|
|
calendarHeaderHtml += '</tr>';
|
|
}
|
|
//close the header
|
|
calendarHeaderHtml += '</tbody></table></div>';
|
|
|
|
$(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 = '<div class=\"wc-scrollable-grid\">';
|
|
$calendarBody += '<table class=\"wc-time-slots\">';
|
|
$calendarBody += '<tbody>';
|
|
$calendarBody += '</tbody>';
|
|
$calendarBody += '</table>';
|
|
$calendarBody += '</div>';
|
|
$calendarBody = $($calendarBody);
|
|
$calendarTableTbody = $calendarBody.find('tbody');
|
|
|
|
self._renderCalendarBodyTimeSlots($calendarTableTbody);
|
|
self._renderCalendarBodyOddEven($calendarTableTbody);
|
|
self._renderCalendarBodyFreeBusy($calendarTableTbody);
|
|
self._renderCalendarBodyEvents($calendarTableTbody);
|
|
|
|
$calendarBody.appendTo($calendarContainer);
|
|
|
|
//set the column height
|
|
$calendarContainer.find('.wc-full-height-column').height(options.timeslotHeight * options.timeslotsPerDay);
|
|
//set the timeslot height
|
|
$calendarContainer.find('.wc-time-slot').height(options.timeslotHeight - 1); //account for border
|
|
//init the time row header height
|
|
/**
|
|
TODO if total height for an hour is less than 11px, there is a display problem.
|
|
Find a way to handle it
|
|
*/
|
|
$calendarContainer.find('.wc-time-header-cell').css({
|
|
height: (options.timeslotHeight * options.timeslotsPerHour) - 11,
|
|
padding: 5
|
|
});
|
|
//add the user data to every impacted column
|
|
if (showAsSeparatedUser) {
|
|
for (var i = 0, uLength = options.users.length; i < uLength; i++) {
|
|
$calendarContainer.find('.wc-user-' + self._getUserIdFromIndex(i))
|
|
.data('wcUser', options.users[i])
|
|
.data('wcUserIndex', i)
|
|
.data('wcUserId', self._getUserIdFromIndex(i));
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* render the timeslots separation
|
|
*/
|
|
_renderCalendarBodyTimeSlots: function($calendarTableTbody) {
|
|
var options = this.options,
|
|
renderRow, i, j,
|
|
showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
|
|
start = (options.businessHours.limitDisplay ? options.businessHours.start : 0),
|
|
end = (options.businessHours.limitDisplay ? options.businessHours.end : 24),
|
|
rowspan = 1;
|
|
|
|
//calculate the rowspan
|
|
if (options.displayOddEven) { rowspan += 1; }
|
|
if (options.displayFreeBusys) { rowspan += 1; }
|
|
if (rowspan > 1) {
|
|
rowspan = ' rowspan=\"' + rowspan + '\"';
|
|
}
|
|
else {
|
|
rowspan = '';
|
|
}
|
|
|
|
renderRow = '<tr class=\"wc-grid-row-timeslot\">';
|
|
renderRow += '<td class=\"wc-grid-timeslot-header\"' + rowspan + '></td>';
|
|
renderRow += '<td colspan=\"' + options.daysToShow * (showAsSeparatedUser ? options.users.length : 1) + '\">';
|
|
renderRow += '<div class=\"wc-no-height-wrapper wc-time-slot-wrapper\">';
|
|
renderRow += '<div class=\"wc-time-slots\">';
|
|
|
|
for (i = start; i < end; i++) {
|
|
for (j = 0; j < options.timeslotsPerHour - 1; j++) {
|
|
renderRow += '<div class=\"wc-time-slot\"></div>';
|
|
}
|
|
renderRow += '<div class=\"wc-time-slot wc-hour-end\"></div>';
|
|
}
|
|
|
|
renderRow += '</div>';
|
|
renderRow += '</div>';
|
|
renderRow += '</td>';
|
|
renderRow += '</tr>';
|
|
|
|
$(renderRow).appendTo($calendarTableTbody);
|
|
},
|
|
|
|
/**
|
|
* render the odd even columns
|
|
*/
|
|
_renderCalendarBodyOddEven: function($calendarTableTbody) {
|
|
if (this.options.displayOddEven) {
|
|
var options = this.options,
|
|
renderRow = '<tr class=\"wc-grid-row-oddeven\">',
|
|
showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
|
|
oddEven,
|
|
// let's take advantage of the jquery ui framework
|
|
oddEvenClasses = {'odd': 'wc-column-odd', 'even': 'ui-state-hover wc-column-even'};
|
|
|
|
//now let's display oddEven placeholders
|
|
for (var i = 1; i <= options.daysToShow; i++) {
|
|
if (!showAsSeparatedUser) {
|
|
oddEven = (oddEven == 'odd' ? 'even' : 'odd');
|
|
renderRow += '<td class=\"wc-day-column day-' + i + '\">';
|
|
renderRow += '<div class=\"wc-no-height-wrapper wc-oddeven-wrapper\">';
|
|
renderRow += '<div class=\"wc-full-height-column ' + oddEvenClasses[oddEven] + '\"></div>';
|
|
renderRow += '</div>';
|
|
renderRow += '</td>';
|
|
}
|
|
else {
|
|
var uLength = options.users.length;
|
|
for (var j = 0; j < uLength; j++) {
|
|
oddEven = (oddEven == 'odd' ? 'even' : 'odd');
|
|
renderRow += '<td class=\"wc-day-column day-' + i + '\">';
|
|
renderRow += '<div class=\"wc-no-height-wrapper wc-oddeven-wrapper\">';
|
|
renderRow += '<div class=\"wc-full-height-column ' + oddEvenClasses[oddEven] + '\" ></div>';
|
|
renderRow += '</div>';
|
|
renderRow += '</td>';
|
|
}
|
|
}
|
|
}
|
|
renderRow += '</tr>';
|
|
|
|
$(renderRow).appendTo($calendarTableTbody);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* render the freebusy placeholders
|
|
*/
|
|
_renderCalendarBodyFreeBusy: function($calendarTableTbody) {
|
|
if (this.options.displayFreeBusys) {
|
|
var self = this, options = this.options,
|
|
renderRow = '<tr class=\"wc-grid-row-freebusy\">',
|
|
showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;
|
|
renderRow += '</td>';
|
|
|
|
//now let's display freebusy placeholders
|
|
for (var i = 1; i <= options.daysToShow; i++) {
|
|
if (options.displayFreeBusys) {
|
|
if (!showAsSeparatedUser) {
|
|
renderRow += '<td class=\"wc-day-column day-' + i + '\">';
|
|
renderRow += '<div class=\"wc-no-height-wrapper wc-freebusy-wrapper\">';
|
|
renderRow += '<div class=\"wc-full-height-column wc-column-freebusy wc-day-' + i + '\"></div>';
|
|
renderRow += '</div>';
|
|
renderRow += '</td>';
|
|
}
|
|
else {
|
|
var uLength = options.users.length;
|
|
for (var j = 0; j < uLength; j++) {
|
|
renderRow += '<td class=\"wc-day-column day-' + i + '\">';
|
|
renderRow += '<div class=\"wc-no-height-wrapper wc-freebusy-wrapper\">';
|
|
renderRow += '<div class=\"wc-full-height-column wc-column-freebusy wc-day-' + i;
|
|
renderRow += ' wc-user-' + self._getUserIdFromIndex(j) + '\">';
|
|
renderRow += '</div>';
|
|
renderRow += '</div>';
|
|
renderRow += '</td>';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
renderRow += '</tr>';
|
|
|
|
$(renderRow).appendTo($calendarTableTbody);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* render the calendar body for event placeholders
|
|
*/
|
|
_renderCalendarBodyEvents: function($calendarTableTbody) {
|
|
var self = this, options = this.options,
|
|
renderRow,
|
|
showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
|
|
start = (options.businessHours.limitDisplay ? options.businessHours.start : 0),
|
|
end = (options.businessHours.limitDisplay ? options.businessHours.end : 24);
|
|
renderRow = '<tr class=\"wc-grid-row-events\">';
|
|
renderRow += '<td class=\"wc-grid-timeslot-header\">';
|
|
for (var i = start; i < end; i++) {
|
|
var bhClass = (options.businessHours.start <= i && options.businessHours.end > i) ? 'ui-state-active wc-business-hours' : 'ui-state-default';
|
|
renderRow += '<div class=\"wc-hour-header ' + bhClass + '\">';
|
|
if (options.use24Hour) {
|
|
renderRow += '<div class=\"wc-time-header-cell\">' + self._24HourForIndex(i) + '</div>';
|
|
}
|
|
else {
|
|
renderRow += '<div class=\"wc-time-header-cell\">' + self._hourForIndex(i) + '<span class=\"wc-am-pm\">' + self._amOrPm(i) + '</span></div>';
|
|
}
|
|
renderRow += '</div>';
|
|
}
|
|
renderRow += '</td>';
|
|
|
|
//now let's display events placeholders
|
|
var _columnBaseClass = 'ui-state-default wc-day-column';
|
|
for (var i = 1; i <= options.daysToShow; i++) {
|
|
if (!showAsSeparatedUser) {
|
|
renderRow += '<td class=\"' + _columnBaseClass + ' wc-day-column-first wc-day-column-last day-' + i + '\">';
|
|
renderRow += '<div class=\"wc-full-height-column wc-day-column-inner day-' + i + '\"></div>';
|
|
renderRow += '</td>';
|
|
}
|
|
else {
|
|
var uLength = options.users.length;
|
|
var columnclass;
|
|
for (var j = 0; j < uLength; j++) {
|
|
columnclass = [];
|
|
if (j == 0) {
|
|
columnclass.push('wc-day-column-first');
|
|
}
|
|
if (j == uLength - 1) {
|
|
columnclass.push('wc-day-column-last');
|
|
}
|
|
if (!columnclass.length) {
|
|
columnclass = 'wc-day-column-middle';
|
|
}
|
|
else {
|
|
columnclass = columnclass.join(' ');
|
|
}
|
|
renderRow += '<td class=\"' + _columnBaseClass + ' ' + columnclass + ' day-' + i + '\">';
|
|
renderRow += '<div class=\"wc-full-height-column wc-day-column-inner day-' + i;
|
|
renderRow += ' wc-user-' + self._getUserIdFromIndex(j) + '\">';
|
|
renderRow += '</div>';
|
|
renderRow += '</td>';
|
|
}
|
|
}
|
|
}
|
|
|
|
renderRow += '</tr>';
|
|
|
|
$(renderRow).appendTo($calendarTableTbody);
|
|
},
|
|
|
|
/*
|
|
* setup mouse events for capturing new events
|
|
*/
|
|
_setupEventCreationForWeekDay: function($weekDay) {
|
|
var self = this;
|
|
var options = this.options;
|
|
$weekDay.mousedown(function(event) {
|
|
var $target = $(event.target);
|
|
if ($target.hasClass('wc-day-column-inner')) {
|
|
|
|
var $newEvent = $('<div class=\"wc-cal-event wc-new-cal-event wc-new-cal-event-creating\"></div>');
|
|
|
|
$newEvent.css({lineHeight: (options.timeslotHeight - 2) + 'px', fontSize: (options.timeslotHeight / 2) + 'px'});
|
|
$target.append($newEvent);
|
|
|
|
var columnOffset = $target.offset().top;
|
|
var clickY = event.pageY - columnOffset;
|
|
var clickYRounded = (clickY - (clickY % options.timeslotHeight)) / options.timeslotHeight;
|
|
var topPosition = clickYRounded * options.timeslotHeight;
|
|
$newEvent.css({top: topPosition});
|
|
|
|
if (!options.preventDragOnEventCreation) {
|
|
$target.bind('mousemove.newevent', function(event) {
|
|
$newEvent.show();
|
|
$newEvent.addClass('ui-resizable-resizing');
|
|
var height = Math.round(event.pageY - columnOffset - topPosition);
|
|
var remainder = height % options.timeslotHeight;
|
|
//snap to closest timeslot
|
|
if (remainder < 0) {
|
|
var useHeight = height - remainder;
|
|
$newEvent.css('height', useHeight < options.timeslotHeight ? options.timeslotHeight : useHeight);
|
|
} else {
|
|
$newEvent.css('height', height + (options.timeslotHeight - remainder));
|
|
}
|
|
}).mouseup(function() {
|
|
$target.unbind('mousemove.newevent');
|
|
$newEvent.addClass('ui-corner-all');
|
|
});
|
|
}
|
|
}
|
|
|
|
}).mouseup(function(event) {
|
|
var $target = $(event.target);
|
|
|
|
var $weekDay = $target.closest('.wc-day-column-inner');
|
|
var $newEvent = $weekDay.find('.wc-new-cal-event-creating');
|
|
|
|
if ($newEvent.length) {
|
|
var createdFromSingleClick = !$newEvent.hasClass('ui-resizable-resizing');
|
|
|
|
//if even created from a single click only, default height
|
|
if (createdFromSingleClick) {
|
|
$newEvent.css({height: options.timeslotHeight * options.defaultEventLength}).show();
|
|
}
|
|
var top = parseInt($newEvent.css('top'));
|
|
var eventDuration = self._getEventDurationFromPositionedEventElement($weekDay, $newEvent, top);
|
|
|
|
$newEvent.remove();
|
|
var newCalEvent = {start: eventDuration.start, end: eventDuration.end, title: options.newEventText};
|
|
var showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;
|
|
|
|
if (showAsSeparatedUser) {
|
|
newCalEvent = self._setEventUserId(newCalEvent, $weekDay.data('wcUserId'));
|
|
}
|
|
else if (!options.showAsSeparateUsers && options.users && options.users.length == 1) {
|
|
newCalEvent = self._setEventUserId(newCalEvent, self._getUserIdFromIndex(0));
|
|
}
|
|
|
|
var freeBusyManager = self.getFreeBusyManagerForEvent(newCalEvent);
|
|
|
|
var $renderedCalEvent = self._renderEvent(newCalEvent, $weekDay);
|
|
|
|
if (!options.allowCalEventOverlap) {
|
|
self._adjustForEventCollisions($weekDay, $renderedCalEvent, newCalEvent, newCalEvent);
|
|
self._positionEvent($weekDay, $renderedCalEvent);
|
|
} else {
|
|
self._adjustOverlappingEvents($weekDay);
|
|
}
|
|
|
|
self._trigger('beforeEventNew', event, {
|
|
'calEvent': newCalEvent,
|
|
'createdFromSingleClick': createdFromSingleClick,
|
|
'calendar': self.element
|
|
});
|
|
options.eventNew(newCalEvent, $renderedCalEvent, freeBusyManager, self.element, event);
|
|
}
|
|
});
|
|
},
|
|
|
|
/*
|
|
* load calendar events for the week based on the date provided
|
|
*/
|
|
_loadCalEvents: function(dateWithinWeek) {
|
|
|
|
var date, weekStartDate, weekEndDate, $weekDayColumns;
|
|
var self = this;
|
|
var options = this.options;
|
|
date = this._fixMinMaxDate(dateWithinWeek || options.date);
|
|
// if date is not provided
|
|
// or was not set
|
|
// or is different than old one
|
|
if ((!date || !date.getTime) ||
|
|
(!options.date || !options.date.getTime) ||
|
|
date.getTime() != options.date.getTime()
|
|
) {
|
|
// trigger the changedate event
|
|
this._trigger('changedate', this.element, date);
|
|
}
|
|
this.options.date = date;
|
|
weekStartDate = self._dateFirstDayOfWeek(date);
|
|
weekEndDate = self._dateLastMilliOfWeek(date);
|
|
|
|
options.calendarBeforeLoad(self.element);
|
|
|
|
self.element.data('startDate', weekStartDate);
|
|
self.element.data('endDate', weekEndDate);
|
|
|
|
$weekDayColumns = self.element.find('.wc-day-column-inner');
|
|
|
|
self._updateDayColumnHeader($weekDayColumns);
|
|
|
|
//load events by chosen means
|
|
if (typeof options.data == 'string') {
|
|
if (options.loading) {
|
|
options.loading(true);
|
|
}
|
|
if (_currentAjaxCall) {
|
|
// first abort current request.
|
|
if (!_jQuery14OrLower) {
|
|
_currentAjaxCall.abort();
|
|
} else {
|
|
// due to the fact that jquery 1.4 does not detect a request was
|
|
// aborted, we need to replace the onreadystatechange and
|
|
// execute the "complete" callback.
|
|
_currentAjaxCall.onreadystatechange = null;
|
|
_currentAjaxCall.abort();
|
|
_currentAjaxCall = null;
|
|
if (options.loading) {
|
|
options.loading(false);
|
|
}
|
|
}
|
|
}
|
|
var jsonOptions = self._getJsonOptions();
|
|
jsonOptions[options.startParam || 'start'] = Math.round(weekStartDate.getTime() / 1000);
|
|
jsonOptions[options.endParam || 'end'] = Math.round(weekEndDate.getTime() / 1000);
|
|
_currentAjaxCall = $.ajax({
|
|
url: options.data,
|
|
data: jsonOptions,
|
|
dataType: 'json',
|
|
error: function(XMLHttpRequest, textStatus, errorThrown) {
|
|
// only prevent error with jQuery 1.5
|
|
// see issue #34. thanks to dapplebeforedawn
|
|
// (https://github.com/themouette/jquery-week-calendar/issues#issue/34)
|
|
// for 1.5+, aborted request mean errorThrown == 'abort'
|
|
// for prior version it means !errorThrown && !XMLHttpRequest.status
|
|
// fixes #55
|
|
if (errorThrown != 'abort' && XMLHttpRequest.status != 0) {
|
|
alert('unable to get data, error:' + textStatus);
|
|
}
|
|
},
|
|
success: function(data) {
|
|
self._renderEvents(data, $weekDayColumns);
|
|
},
|
|
complete: function() {
|
|
_currentAjaxCall = null;
|
|
if (options.loading) {
|
|
options.loading(false);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
else if ($.isFunction(options.data)) {
|
|
options.data(weekStartDate, weekEndDate,
|
|
function(data) {
|
|
self._renderEvents(data, $weekDayColumns);
|
|
});
|
|
}
|
|
else if (options.data) {
|
|
self._renderEvents(options.data, $weekDayColumns);
|
|
}
|
|
|
|
self._disableTextSelect($weekDayColumns);
|
|
|
|
|
|
},
|
|
|
|
/*
|
|
* update the display of each day column header based on the calendar week
|
|
*/
|
|
_updateDayColumnHeader: function($weekDayColumns) {
|
|
var self = this;
|
|
var options = this.options;
|
|
var currentDay = self._cloneDate(self.element.data('startDate'));
|
|
var showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;
|
|
var todayClass = 'ui-state-active wc-today';
|
|
|
|
self.element.find('.wc-header td.wc-day-column-header').each(function(i, val) {
|
|
$(this).html(self._getHeaderDate(currentDay));
|
|
if (self._isToday(currentDay)) {
|
|
$(this).addClass(todayClass);
|
|
} else {
|
|
$(this).removeClass(todayClass);
|
|
}
|
|
currentDay = self._addDays(currentDay, 1);
|
|
|
|
});
|
|
|
|
currentDay = self._cloneDate(self.element.data('startDate'));
|
|
if (showAsSeparatedUser)
|
|
{
|
|
self.element.find('.wc-header td.wc-user-header').each(function(i, val) {
|
|
if (self._isToday(currentDay)) {
|
|
$(this).addClass(todayClass);
|
|
} else {
|
|
$(this).removeClass(todayClass);
|
|
}
|
|
currentDay = ((i + 1) % options.users.length) ? currentDay : self._addDays(currentDay, 1);
|
|
});
|
|
}
|
|
|
|
currentDay = self._cloneDate(self.element.data('startDate'));
|
|
|
|
$weekDayColumns.each(function(i, val) {
|
|
|
|
$(this).data('startDate', self._cloneDate(currentDay));
|
|
$(this).data('endDate', new Date(currentDay.getTime() + (MILLIS_IN_DAY)));
|
|
if (self._isToday(currentDay)) {
|
|
$(this).parent()
|
|
.addClass(todayClass)
|
|
.removeClass('ui-state-default');
|
|
} else {
|
|
$(this).parent()
|
|
.removeClass(todayClass)
|
|
.addClass('ui-state-default');
|
|
}
|
|
|
|
if (!showAsSeparatedUser || !((i + 1) % options.users.length)) {
|
|
currentDay = self._addDays(currentDay, 1);
|
|
}
|
|
});
|
|
|
|
//now update the freeBusy placeholders
|
|
if (options.displayFreeBusys) {
|
|
currentDay = self._cloneDate(self.element.data('startDate'));
|
|
self.element.find('.wc-grid-row-freebusy .wc-column-freebusy').each(function(i, val) {
|
|
$(this).data('startDate', self._cloneDate(currentDay));
|
|
$(this).data('endDate', new Date(currentDay.getTime() + (MILLIS_IN_DAY)));
|
|
if (!showAsSeparatedUser || !((i + 1) % options.users.length)) {
|
|
currentDay = self._addDays(currentDay, 1);
|
|
}
|
|
});
|
|
}
|
|
|
|
//now update the calendar title
|
|
if (this.options.title && this.options.title.length) {
|
|
var _date = this.options.date,
|
|
_start = self._cloneDate(self.element.data('startDate')),
|
|
_end = self._dateLastDayOfWeek(new Date(this._cloneDate(self.element.data('endDate')).getTime() - (MILLIS_IN_DAY))),
|
|
_title = this._getCalendarTitle();
|
|
_title = _title.split('%start%').join(self._formatDate(_start, options.dateFormat));
|
|
_title = _title.split('%end%').join(self._formatDate(_end, options.dateFormat));
|
|
_title = _title.split('%date%').join(self._formatDate(_date, options.dateFormat));
|
|
$('.wc-toolbar .wc-title', self.element).html(_title);
|
|
}
|
|
//self._clearFreeBusys();
|
|
},
|
|
|
|
/*
|
|
* gets the calendar title options
|
|
*/
|
|
_getCalendarTitle: function() {
|
|
if ($.isFunction(this.options.title)) {
|
|
return this.options.title(this.options.daysToShow);
|
|
}
|
|
return this.options.title;
|
|
},
|
|
|
|
/*
|
|
* Render the events into the calendar
|
|
*/
|
|
_renderEvents: function(data, $weekDayColumns) {
|
|
var self = this;
|
|
var options = this.options;
|
|
var eventsToRender;
|
|
|
|
if (data.options) {
|
|
var updateLayout = false;
|
|
//update options
|
|
$.each(data.options, function(key, value) {
|
|
if (value !== options[key]) {
|
|
options[key] = value;
|
|
updateLayout = updateLayout || $.ui.weekCalendar.updateLayoutOptions[key];
|
|
}
|
|
});
|
|
|
|
self._computeOptions();
|
|
|
|
if (updateLayout) {
|
|
var hour = self._getCurrentScrollHour();
|
|
self.element.empty();
|
|
self._renderCalendar();
|
|
$weekDayColumns = self.element.find('.wc-time-slots .wc-day-column-inner');
|
|
self._updateDayColumnHeader($weekDayColumns);
|
|
self._resizeCalendar();
|
|
self._scrollToHour(hour, false);
|
|
}
|
|
}
|
|
this._clearCalendar();
|
|
|
|
if ($.isArray(data)) {
|
|
eventsToRender = self._cleanEvents(data);
|
|
} else if (data.events) {
|
|
eventsToRender = self._cleanEvents(data.events);
|
|
//render the freebusys
|
|
self._renderFreeBusys(data);
|
|
}
|
|
$.each(eventsToRender, function(i, calEvent) {
|
|
//render a multi day event as various event :
|
|
//thanks to http://github.com/fbeauchamp/jquery-week-calendar
|
|
var initialStart = new Date(calEvent.start);
|
|
var initialEnd = new Date(calEvent.end);
|
|
var maxHour = self.options.businessHours.limitDisplay ? self.options.businessHours.end : 24;
|
|
var minHour = self.options.businessHours.limitDisplay ? self.options.businessHours.start : 0;
|
|
var start = new Date(initialStart);
|
|
var startDate = self._formatDate(start, 'Ymd');
|
|
var endDate = self._formatDate(initialEnd, 'Ymd');
|
|
var $weekDay;
|
|
var isMultiday = false;
|
|
|
|
while (startDate < endDate) {
|
|
calEvent.start = start;
|
|
//end of this virual calEvent is set to the end of the day
|
|
calEvent.end.setFullYear(start.getFullYear());
|
|
calEvent.end.setDate(start.getDate());
|
|
calEvent.end.setMonth(start.getMonth());
|
|
calEvent.end.setHours(maxHour);
|
|
calEvent.end.setMinutes(0);
|
|
calEvent.end.setSeconds(0);
|
|
if (($weekDay = self._findWeekDayForEvent(calEvent, $weekDayColumns))) {
|
|
self._renderEvent(calEvent, $weekDay);
|
|
}
|
|
//start is set to the begin of the new day
|
|
start.setDate(start.getDate() + 1);
|
|
start.setHours(minHour);
|
|
start.setMinutes(0);
|
|
start.setSeconds(0);
|
|
startDate = self._formatDate(start, 'Ymd');
|
|
isMultiday = true;
|
|
}
|
|
if (start <= initialEnd) {
|
|
calEvent.start = start;
|
|
calEvent.end = initialEnd;
|
|
if (((isMultiday && calEvent.start.getTime() != calEvent.end.getTime()) || !isMultiday) && ($weekDay = self._findWeekDayForEvent(calEvent, $weekDayColumns))) {
|
|
self._renderEvent(calEvent, $weekDay);
|
|
}
|
|
}
|
|
|
|
//put back the initial start date
|
|
calEvent.start = initialStart;
|
|
});
|
|
|
|
$weekDayColumns.each(function() {
|
|
self._adjustOverlappingEvents($(this));
|
|
});
|
|
|
|
options.calendarAfterLoad(self.element);
|
|
|
|
if (!eventsToRender.length) {
|
|
options.noEvents();
|
|
}
|
|
|
|
},
|
|
|
|
/*
|
|
* Render a specific event into the day provided. Assumes correct
|
|
* day for calEvent date
|
|
*/
|
|
_renderEvent: function(calEvent, $weekDay) {
|
|
var self = this;
|
|
var options = this.options;
|
|
if (calEvent.start.getTime() > calEvent.end.getTime()) {
|
|
return; // can't render a negative height
|
|
}
|
|
|
|
|
|
var eventClass, eventTyp, eventHtml, $calEventList, $modifiedEvent;
|
|
|
|
eventClass = calEvent.id ? 'wc-cal-event' : 'wc-cal-event wc-new-cal-event';
|
|
eventHtml = '<div class=\"' + eventClass + ' ui-corner-all\">';
|
|
eventHtml += '<div class=\"wc-time ui-corner-top\"></div>';
|
|
eventHtml += '<div class=\"wc-title\"></div></div>';
|
|
|
|
$weekDay.each(function() {
|
|
var $calEvent = $(eventHtml);
|
|
$modifiedEvent = options.eventRender(calEvent, $calEvent);
|
|
$calEvent = $modifiedEvent ? $modifiedEvent.appendTo($(this)) : $calEvent.appendTo($(this));
|
|
$calEvent.css({lineHeight: (options.textSize + 2) + 'px', fontSize: options.textSize + 'px'});
|
|
|
|
self._refreshEventDetails(calEvent, $calEvent);
|
|
self._positionEvent($(this), $calEvent);
|
|
|
|
//add to event list
|
|
if ($calEventList) {
|
|
$calEventList = $calEventList.add($calEvent);
|
|
}
|
|
else {
|
|
$calEventList = $calEvent;
|
|
}
|
|
});
|
|
$calEventList.show();
|
|
|
|
if (!options.readonly && options.resizable(calEvent, $calEventList)) {
|
|
self._addResizableToCalEvent(calEvent, $calEventList, $weekDay);
|
|
}
|
|
if (!options.readonly && options.draggable(calEvent, $calEventList)) {
|
|
self._addDraggableToCalEvent(calEvent, $calEventList);
|
|
}
|
|
options.eventAfterRender(calEvent, $calEventList);
|
|
|
|
return $calEventList;
|
|
|
|
},
|
|
addEvent: function() {
|
|
return this._renderEvent.apply(this, arguments);
|
|
},
|
|
|
|
_adjustOverlappingEvents: function($weekDay) {
|
|
var self = this;
|
|
if (self.options.allowCalEventOverlap) {
|
|
var groupsList = self._groupOverlappingEventElements($weekDay);
|
|
$.each(groupsList, function() {
|
|
var curGroups = this;
|
|
$.each(curGroups, function(groupIndex) {
|
|
var curGroup = this;
|
|
|
|
// do we want events to be displayed as overlapping
|
|
if (self.options.overlapEventsSeparate) {
|
|
var newWidth = self.options.totalEventsWidthPercentInOneColumn / curGroups.length;
|
|
var newLeft = groupIndex * newWidth;
|
|
} else {
|
|
// TODO what happens when the group has more than 10 elements
|
|
var newWidth = self.options.totalEventsWidthPercentInOneColumn - ((curGroups.length - 1) * 10);
|
|
var newLeft = groupIndex * 10;
|
|
}
|
|
$.each(curGroup, function() {
|
|
// bring mouseovered event to the front
|
|
if (!self.options.overlapEventsSeparate) {
|
|
$(this).bind('mouseover.z-index', function() {
|
|
var $elem = $(this);
|
|
$.each(curGroup, function() {
|
|
$(this).css({'z-index': '1'});
|
|
});
|
|
$elem.css({'z-index': '3'});
|
|
});
|
|
}
|
|
$(this).css({width: newWidth + '%', left: newLeft + '%', right: 0});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
},
|
|
|
|
|
|
/*
|
|
* Find groups of overlapping events
|
|
*/
|
|
_groupOverlappingEventElements: function($weekDay) {
|
|
var $events = $weekDay.find('.wc-cal-event:visible');
|
|
var sortedEvents = $events.sort(function(a, b) {
|
|
return $(a).data('calEvent').start.getTime() - $(b).data('calEvent').start.getTime();
|
|
});
|
|
|
|
var lastEndTime = new Date(0, 0, 0);
|
|
var groups = [];
|
|
var curGroups = [];
|
|
var $curEvent;
|
|
$.each(sortedEvents, function() {
|
|
$curEvent = $(this);
|
|
//checks, if the current group list is not empty, if the overlapping is finished
|
|
if (curGroups.length > 0) {
|
|
if (lastEndTime.getTime() <= $curEvent.data('calEvent').start.getTime()) {
|
|
//finishes the current group list by adding it to the resulting list of groups and cleans it
|
|
|
|
groups.push(curGroups);
|
|
curGroups = [];
|
|
}
|
|
}
|
|
|
|
//finds the first group to fill with the event
|
|
for (var groupIndex = 0; groupIndex < curGroups.length; groupIndex++) {
|
|
if (curGroups[groupIndex].length > 0) {
|
|
//checks if the event starts after the end of the last event of the group
|
|
if (curGroups[groupIndex][curGroups[groupIndex].length - 1].data('calEvent').end.getTime() <= $curEvent.data('calEvent').start.getTime()) {
|
|
curGroups[groupIndex].push($curEvent);
|
|
if (lastEndTime.getTime() < $curEvent.data('calEvent').end.getTime()) {
|
|
lastEndTime = $curEvent.data('calEvent').end;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
//if not found, creates a new group
|
|
curGroups.push([$curEvent]);
|
|
if (lastEndTime.getTime() < $curEvent.data('calEvent').end.getTime()) {
|
|
lastEndTime = $curEvent.data('calEvent').end;
|
|
}
|
|
});
|
|
//adds the last groups in result
|
|
if (curGroups.length > 0) {
|
|
groups.push(curGroups);
|
|
}
|
|
return groups;
|
|
},
|
|
|
|
|
|
/*
|
|
* find the weekday in the current calendar that the calEvent falls within
|
|
*/
|
|
_findWeekDayForEvent: function(calEvent, $weekDayColumns) {
|
|
|
|
var $weekDay,
|
|
options = this.options,
|
|
showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
|
|
user_ids = this._getEventUserId(calEvent);
|
|
|
|
if (!$.isArray(user_ids)) {
|
|
user_ids = [user_ids];
|
|
}
|
|
|
|
$weekDayColumns.each(function(index, curDay) {
|
|
if ($(this).data('startDate').getTime() <= calEvent.start.getTime() &&
|
|
$(this).data('endDate').getTime() >= calEvent.end.getTime() &&
|
|
(!showAsSeparatedUser || $.inArray($(this).data('wcUserId'), user_ids) !== -1)
|
|
) {
|
|
if ($weekDay) {
|
|
$weekDay = $weekDay.add($(curDay));
|
|
}
|
|
else {
|
|
$weekDay = $(curDay);
|
|
}
|
|
}
|
|
});
|
|
|
|
return $weekDay;
|
|
},
|
|
|
|
/*
|
|
* update the events rendering in the calendar. Add if does not yet exist.
|
|
*/
|
|
_updateEventInCalendar: function(calEvent) {
|
|
var self = this;
|
|
self._cleanEvent(calEvent);
|
|
|
|
if (calEvent.id) {
|
|
self.element.find('.wc-cal-event').each(function() {
|
|
if ($(this).data('calEvent').id === calEvent.id || $(this).hasClass('wc-new-cal-event')) {
|
|
$(this).remove();
|
|
// return false;
|
|
}
|
|
});
|
|
}
|
|
|
|
var $weekDays = self._findWeekDayForEvent(calEvent, self.element.find('.wc-grid-row-events .wc-day-column-inner'));
|
|
if ($weekDays) {
|
|
$weekDays.each(function(index, weekDay) {
|
|
var $weekDay = $(weekDay);
|
|
var $calEvent = self._renderEvent(calEvent, $weekDay);
|
|
self._adjustForEventCollisions($weekDay, $calEvent, calEvent, calEvent);
|
|
self._refreshEventDetails(calEvent, $calEvent);
|
|
self._positionEvent($weekDay, $calEvent);
|
|
self._adjustOverlappingEvents($weekDay);
|
|
});
|
|
}
|
|
},
|
|
|
|
/*
|
|
* Position the event element within the weekday based on it's start / end dates.
|
|
*/
|
|
_positionEvent: function($weekDay, $calEvent) {
|
|
var options = this.options;
|
|
var calEvent = $calEvent.data('calEvent');
|
|
var pxPerMillis = $weekDay.height() / options.millisToDisplay;
|
|
var firstHourDisplayed = options.businessHours.limitDisplay ? options.businessHours.start : 0;
|
|
var startMillis = this._getDSTdayShift(calEvent.start).getTime() - this._getDSTdayShift(new Date(calEvent.start.getFullYear(), calEvent.start.getMonth(), calEvent.start.getDate(), firstHourDisplayed)).getTime();
|
|
var eventMillis = this._getDSTdayShift(calEvent.end).getTime() - this._getDSTdayShift(calEvent.start).getTime();
|
|
var pxTop = pxPerMillis * startMillis;
|
|
var pxHeight = pxPerMillis * eventMillis;
|
|
//var pxHeightFallback = pxPerMillis * (60 / options.timeslotsPerHour) * 60 * 1000;
|
|
$calEvent.css({top: pxTop, height: pxHeight || (pxPerMillis * 3600000 / options.timeslotsPerHour)});
|
|
},
|
|
|
|
/*
|
|
* Determine the actual start and end times of a calevent based on it's
|
|
* relative position within the weekday column and the starting hour of the
|
|
* displayed calendar.
|
|
*/
|
|
_getEventDurationFromPositionedEventElement: function($weekDay, $calEvent, top) {
|
|
var options = this.options;
|
|
var startOffsetMillis = options.businessHours.limitDisplay ? options.businessHours.start * 3600000 : 0;
|
|
var start = new Date($weekDay.data('startDate').getTime() + startOffsetMillis + Math.round(top / options.timeslotHeight) * options.millisPerTimeslot);
|
|
var end = new Date(start.getTime() + ($calEvent.height() / options.timeslotHeight) * options.millisPerTimeslot);
|
|
return {start: this._getDSTdayShift(start, -1), end: this._getDSTdayShift(end, -1)};
|
|
},
|
|
|
|
/*
|
|
* If the calendar does not allow event overlap, adjust the start or end date if necessary to
|
|
* avoid overlapping of events. Typically, shortens the resized / dropped event to it's max possible
|
|
* duration based on the overlap. If no satisfactory adjustment can be made, the event is reverted to
|
|
* it's original location.
|
|
*/
|
|
_adjustForEventCollisions: function($weekDay, $calEvent, newCalEvent, oldCalEvent, maintainEventDuration) {
|
|
var options = this.options;
|
|
|
|
if (options.allowCalEventOverlap) {
|
|
return;
|
|
}
|
|
var adjustedStart, adjustedEnd;
|
|
var self = this;
|
|
|
|
$weekDay.find('.wc-cal-event').not($calEvent).each(function() {
|
|
var currentCalEvent = $(this).data('calEvent');
|
|
|
|
//has been dropped onto existing event overlapping the end time
|
|
if (newCalEvent.start.getTime() < currentCalEvent.end.getTime() &&
|
|
newCalEvent.end.getTime() >= currentCalEvent.end.getTime()) {
|
|
|
|
adjustedStart = currentCalEvent.end;
|
|
}
|
|
|
|
|
|
//has been dropped onto existing event overlapping the start time
|
|
if (newCalEvent.end.getTime() > currentCalEvent.start.getTime() &&
|
|
newCalEvent.start.getTime() <= currentCalEvent.start.getTime()) {
|
|
|
|
adjustedEnd = currentCalEvent.start;
|
|
}
|
|
//has been dropped inside existing event with same or larger duration
|
|
if (oldCalEvent.resizable == false ||
|
|
(newCalEvent.end.getTime() <= currentCalEvent.end.getTime() &&
|
|
newCalEvent.start.getTime() >= currentCalEvent.start.getTime())) {
|
|
|
|
adjustedStart = oldCalEvent.start;
|
|
adjustedEnd = oldCalEvent.end;
|
|
return false;
|
|
}
|
|
|
|
});
|
|
|
|
|
|
newCalEvent.start = adjustedStart || newCalEvent.start;
|
|
|
|
if (adjustedStart && maintainEventDuration) {
|
|
newCalEvent.end = new Date(adjustedStart.getTime() + (oldCalEvent.end.getTime() - oldCalEvent.start.getTime()));
|
|
self._adjustForEventCollisions($weekDay, $calEvent, newCalEvent, oldCalEvent);
|
|
} else {
|
|
newCalEvent.end = adjustedEnd || newCalEvent.end;
|
|
}
|
|
|
|
|
|
//reset if new cal event has been forced to zero size
|
|
if (newCalEvent.start.getTime() >= newCalEvent.end.getTime()) {
|
|
newCalEvent.start = oldCalEvent.start;
|
|
newCalEvent.end = oldCalEvent.end;
|
|
}
|
|
|
|
$calEvent.data('calEvent', newCalEvent);
|
|
},
|
|
|
|
/*
|
|
* Add draggable capabilities to an event
|
|
*/
|
|
_addDraggableToCalEvent: function(calEvent, $calEvent) {
|
|
var options = this.options;
|
|
$calEvent.draggable({
|
|
handle: '.wc-time',
|
|
containment: 'div.wc-time-slots',
|
|
snap: '.wc-day-column-inner',
|
|
snapMode: 'inner',
|
|
snapTolerance: options.timeslotHeight - 1,
|
|
revert: 'invalid',
|
|
opacity: 0.5,
|
|
grid: [$calEvent.outerWidth() + 1, options.timeslotHeight],
|
|
start: function(event, ui) {
|
|
var $calEvent = ui.draggable;
|
|
options.eventDrag(calEvent, $calEvent);
|
|
}
|
|
});
|
|
},
|
|
|
|
/*
|
|
* Add droppable capabilites to weekdays to allow dropping of calEvents only
|
|
*/
|
|
_addDroppableToWeekDay: function($weekDay) {
|
|
var self = this;
|
|
var options = this.options;
|
|
$weekDay.droppable({
|
|
accept: '.wc-cal-event',
|
|
drop: function(event, ui) {
|
|
var $calEvent = ui.draggable;
|
|
var top = Math.round(parseInt(ui.position.top));
|
|
var eventDuration = self._getEventDurationFromPositionedEventElement($weekDay, $calEvent, top);
|
|
var calEvent = $calEvent.data('calEvent');
|
|
var newCalEvent = $.extend(true, {}, calEvent, {start: eventDuration.start, end: eventDuration.end});
|
|
var showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;
|
|
if (showAsSeparatedUser) {
|
|
// we may have dragged the event on column with a new user.
|
|
// nice way to handle that is:
|
|
// - get the newly dragged on user
|
|
// - check if user is part of the event
|
|
// - if yes, nothing changes, if not, find the old owner to remove it and add new one
|
|
var newUserId = $weekDay.data('wcUserId');
|
|
var userIdList = self._getEventUserId(calEvent);
|
|
var oldUserId = $(ui.draggable.parents('.wc-day-column-inner').get(0)).data('wcUserId');
|
|
if (!$.isArray(userIdList)) {
|
|
userIdList = [userIdList];
|
|
}
|
|
if ($.inArray(newUserId, userIdList) == -1) {
|
|
// remove old user
|
|
var _index = $.inArray(oldUserId, userIdList);
|
|
userIdList.splice(_index, 1);
|
|
// add new user ?
|
|
if ($.inArray(newUserId, userIdList) == -1) {
|
|
userIdList.push(newUserId);
|
|
}
|
|
}
|
|
newCalEvent = self._setEventUserId(newCalEvent, ((userIdList.length == 1) ? userIdList[0] : userIdList));
|
|
}
|
|
self._adjustForEventCollisions($weekDay, $calEvent, newCalEvent, calEvent, true);
|
|
var $weekDayColumns = self.element.find('.wc-day-column-inner');
|
|
|
|
//trigger drop callback
|
|
options.eventDrop(newCalEvent, calEvent, $calEvent);
|
|
|
|
var $newEvent = self._renderEvent(newCalEvent, self._findWeekDayForEvent(newCalEvent, $weekDayColumns));
|
|
$calEvent.hide();
|
|
|
|
$calEvent.data('preventClick', true);
|
|
|
|
var $weekDayOld = self._findWeekDayForEvent($calEvent.data('calEvent'), self.element.find('.wc-time-slots .wc-day-column-inner'));
|
|
|
|
if ($weekDayOld.data('startDate') != $weekDay.data('startDate')) {
|
|
self._adjustOverlappingEvents($weekDayOld);
|
|
}
|
|
self._adjustOverlappingEvents($weekDay);
|
|
|
|
setTimeout(function() {
|
|
$calEvent.remove();
|
|
}, 1000);
|
|
|
|
}
|
|
});
|
|
},
|
|
|
|
/*
|
|
* Add resizable capabilities to a calEvent
|
|
*/
|
|
_addResizableToCalEvent: function(calEvent, $calEvent, $weekDay) {
|
|
var self = this;
|
|
var options = this.options;
|
|
$calEvent.resizable({
|
|
grid: options.timeslotHeight,
|
|
containment: $weekDay,
|
|
handles: 's',
|
|
minHeight: options.timeslotHeight,
|
|
stop: function(event, ui) {
|
|
var $calEvent = ui.element;
|
|
var newEnd = new Date($calEvent.data('calEvent').start.getTime() + Math.max(1, Math.round(ui.size.height / options.timeslotHeight)) * options.millisPerTimeslot);
|
|
if (self._needDSTdayShift($calEvent.data('calEvent').start, newEnd))
|
|
newEnd = self._getDSTdayShift(newEnd, -1);
|
|
var newCalEvent = $.extend(true, {}, calEvent, {start: calEvent.start, end: newEnd});
|
|
self._adjustForEventCollisions($weekDay, $calEvent, newCalEvent, calEvent);
|
|
|
|
self._refreshEventDetails(newCalEvent, $calEvent);
|
|
self._positionEvent($weekDay, $calEvent);
|
|
self._adjustOverlappingEvents($weekDay);
|
|
//trigger resize callback
|
|
options.eventResize(newCalEvent, calEvent, $calEvent);
|
|
$calEvent.data('preventClick', true);
|
|
setTimeout(function() {
|
|
$calEvent.removeData('preventClick');
|
|
}, 500);
|
|
}
|
|
});
|
|
$('.ui-resizable-handle', $calEvent).text('=');
|
|
},
|
|
|
|
/*
|
|
* Refresh the displayed details of a calEvent in the calendar
|
|
*/
|
|
_refreshEventDetails: function(calEvent, $calEvent) {
|
|
$calEvent.find('.wc-time').html(this.options.eventHeader(calEvent, this.element));
|
|
$calEvent.find('.wc-title').html(this.options.eventBody(calEvent, this.element));
|
|
$calEvent.data('calEvent', calEvent);
|
|
this.options.eventRefresh(calEvent, $calEvent);
|
|
},
|
|
|
|
/*
|
|
* Clear all cal events from the calendar
|
|
*/
|
|
_clearCalendar: function() {
|
|
this.element.find('.wc-day-column-inner div').remove();
|
|
this._clearFreeBusys();
|
|
},
|
|
|
|
/*
|
|
* Scroll the calendar to a specific hour
|
|
*/
|
|
_scrollToHour: function(hour, animate) {
|
|
var self = this;
|
|
var options = this.options;
|
|
var $scrollable = this.element.find('.wc-scrollable-grid');
|
|
var slot = hour;
|
|
if (self.options.businessHours.limitDisplay) {
|
|
if (hour <= self.options.businessHours.start) {
|
|
slot = 0;
|
|
} else if (hour >= self.options.businessHours.end) {
|
|
slot = self.options.businessHours.end - self.options.businessHours.start - 1;
|
|
} else {
|
|
slot = hour - self.options.businessHours.start;
|
|
}
|
|
}
|
|
|
|
var $target = this.element.find('.wc-grid-timeslot-header .wc-hour-header:eq(' + slot + ')');
|
|
|
|
$scrollable.animate({scrollTop: 0}, 0, function() {
|
|
var targetOffset = $target.offset().top;
|
|
var scroll = targetOffset - $scrollable.offset().top - $target.outerHeight();
|
|
if (animate) {
|
|
$scrollable.animate({scrollTop: scroll}, options.scrollToHourMillis);
|
|
}
|
|
else {
|
|
$scrollable.animate({scrollTop: scroll}, 0);
|
|
}
|
|
});
|
|
},
|
|
|
|
/*
|
|
* find the hour (12 hour day) for a given hour index
|
|
*/
|
|
_hourForIndex: function(index) {
|
|
if (index === 0) { //midnight
|
|
return 12;
|
|
} else if (index < 13) { //am
|
|
return index;
|
|
} else { //pm
|
|
return index - 12;
|
|
}
|
|
},
|
|
|
|
_24HourForIndex: function(index) {
|
|
if (index === 0) { //midnight
|
|
return '00:00';
|
|
} else if (index < 10) {
|
|
return '0' + index + ':00';
|
|
} else {
|
|
return index + ':00';
|
|
}
|
|
},
|
|
|
|
_amOrPm: function(hourOfDay) {
|
|
return hourOfDay < 12 ? 'AM' : 'PM';
|
|
},
|
|
|
|
_isToday: function(date) {
|
|
var clonedDate = this._cloneDate(date);
|
|
this._clearTime(clonedDate);
|
|
var today = new Date();
|
|
this._clearTime(today);
|
|
return today.getTime() === clonedDate.getTime();
|
|
},
|
|
|
|
/*
|
|
* Clean events to ensure correct format
|
|
*/
|
|
_cleanEvents: function(events) {
|
|
var self = this;
|
|
$.each(events, function(i, event) {
|
|
self._cleanEvent(event);
|
|
});
|
|
return events;
|
|
},
|
|
|
|
/*
|
|
* Clean specific event
|
|
*/
|
|
_cleanEvent: function(event) {
|
|
if (event.date) {
|
|
event.start = event.date;
|
|
}
|
|
event.start = this._cleanDate(event.start);
|
|
event.end = this._cleanDate(event.end);
|
|
if (!event.end) {
|
|
event.end = this._addDays(this._cloneDate(event.start), 1);
|
|
}
|
|
},
|
|
|
|
/*
|
|
* Disable text selection of the elements in different browsers
|
|
*/
|
|
_disableTextSelect: function($elements) {
|
|
$elements.each(function() {
|
|
if ($.browser.mozilla) {//Firefox
|
|
$(this).css('MozUserSelect', 'none');
|
|
} else if ($.browser.msie) {//IE
|
|
$(this).bind('selectstart', function() {
|
|
return false;
|
|
});
|
|
} else {//Opera, etc.
|
|
$(this).mousedown(function() {
|
|
return false;
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
/*
|
|
* returns the date on the first millisecond of the week
|
|
*/
|
|
_dateFirstDayOfWeek: function(date) {
|
|
var self = this;
|
|
var midnightCurrentDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
var adjustedDate = new Date(midnightCurrentDate);
|
|
adjustedDate.setDate(adjustedDate.getDate() - self._getAdjustedDayIndex(midnightCurrentDate));
|
|
|
|
return adjustedDate;
|
|
},
|
|
|
|
/*
|
|
* returns the date on the first millisecond of the last day of the week
|
|
*/
|
|
_dateLastDayOfWeek: function(date) {
|
|
var self = this;
|
|
var midnightCurrentDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
var adjustedDate = new Date(midnightCurrentDate);
|
|
var daysToAdd = (self.options.daysToShow - 1 - self._getAdjustedDayIndex(midnightCurrentDate));
|
|
adjustedDate.setDate(adjustedDate.getDate() + daysToAdd);
|
|
|
|
return adjustedDate;
|
|
},
|
|
|
|
/**
|
|
* fix the date if it is not within given options
|
|
* minDate and maxDate
|
|
*/
|
|
_fixMinMaxDate: function(date) {
|
|
var minDate, maxDate;
|
|
date = this._cleanDate(date);
|
|
|
|
// not less than minDate
|
|
if (this.options.minDate) {
|
|
minDate = this._cleanDate(this.options.minDate);
|
|
// midnight on minDate
|
|
minDate = new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate());
|
|
if (date.getTime() < minDate.getTime()) {
|
|
this._trigger('reachedmindate', this.element, date);
|
|
}
|
|
date = this._cleanDate(Math.max(date.getTime(), minDate.getTime()));
|
|
}
|
|
|
|
// not more than maxDate
|
|
if (this.options.maxDate) {
|
|
maxDate = this._cleanDate(this.options.maxDate);
|
|
// apply correction for max date if not startOnFirstDayOfWeek
|
|
// to make sure no further date is displayed.
|
|
// otherwise, the complement will still be shown
|
|
if (!this._startOnFirstDayOfWeek()) {
|
|
var day = maxDate.getDate() - this.options.daysToShow + 1;
|
|
maxDate = new Date(maxDate.getFullYear(), maxDate.getMonth(), day);
|
|
}
|
|
// microsecond before midnight on maxDate
|
|
maxDate = new Date(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate(), 23, 59, 59, 999);
|
|
if (date.getTime() > maxDate.getTime()) {
|
|
this._trigger('reachedmaxdate', this.element, date);
|
|
}
|
|
date = this._cleanDate(Math.min(date.getTime(), maxDate.getTime()));
|
|
}
|
|
|
|
return date;
|
|
},
|
|
|
|
/*
|
|
* gets the index of the current day adjusted based on options
|
|
*/
|
|
_getAdjustedDayIndex: function(date) {
|
|
if (!this._startOnFirstDayOfWeek()) {
|
|
return 0;
|
|
}
|
|
|
|
var midnightCurrentDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
var currentDayOfStandardWeek = midnightCurrentDate.getDay();
|
|
var days = [0, 1, 2, 3, 4, 5, 6];
|
|
this._rotate(days, this._firstDayOfWeek());
|
|
return days[currentDayOfStandardWeek];
|
|
},
|
|
|
|
_firstDayOfWeek: function() {
|
|
if ($.isFunction(this.options.firstDayOfWeek)) {
|
|
return this.options.firstDayOfWeek(this.element);
|
|
}
|
|
return this.options.firstDayOfWeek;
|
|
},
|
|
|
|
/*
|
|
* returns the date on the last millisecond of the week
|
|
*/
|
|
_dateLastMilliOfWeek: function(date) {
|
|
var lastDayOfWeek = this._dateLastDayOfWeek(date);
|
|
lastDayOfWeek = this._cloneDate(lastDayOfWeek);
|
|
lastDayOfWeek.setDate(lastDayOfWeek.getDate() + 1);
|
|
return lastDayOfWeek;
|
|
|
|
},
|
|
|
|
/*
|
|
* Clear the time components of a date leaving the date
|
|
* of the first milli of day
|
|
*/
|
|
_clearTime: function(d) {
|
|
d.setHours(0);
|
|
d.setMinutes(0);
|
|
d.setSeconds(0);
|
|
d.setMilliseconds(0);
|
|
return d;
|
|
},
|
|
|
|
/*
|
|
* add specific number of days to date
|
|
*/
|
|
_addDays: function(d, n, keepTime) {
|
|
d.setDate(d.getDate() + n);
|
|
if (keepTime) {
|
|
return d;
|
|
}
|
|
return this._clearTime(d);
|
|
},
|
|
|
|
/*
|
|
* Rotate an array by specified number of places.
|
|
*/
|
|
_rotate: function(a /*array*/, p /* integer, positive integer rotate to the right, negative to the left... */) {
|
|
for (var l = a.length, p = (Math.abs(p) >= l && (p %= l), p < 0 && (p += l), p), i, x; p; p = (Math.ceil(l / p) - 1) * p - l + (l = p)) {
|
|
for (i = l; i > p; x = a[--i], a[i] = a[i - p], a[i - p] = x) {}
|
|
}
|
|
return a;
|
|
},
|
|
|
|
_cloneDate: function(d) {
|
|
return new Date(d.getTime());
|
|
},
|
|
|
|
/*
|
|
* return a date for different representations
|
|
*/
|
|
_cleanDate: function(d) {
|
|
if (typeof d == 'string') {
|
|
// if is numeric
|
|
if (!isNaN(parseFloat(d)) && isFinite()) {
|
|
return this._cleanDate(parseInt(d, 10));
|
|
}
|
|
// this is a human readable date
|
|
return Date.parse(d) || new Date(d);
|
|
}
|
|
if (typeof d == 'number') {
|
|
return new Date(d);
|
|
}
|
|
return d;
|
|
},
|
|
|
|
/*
|
|
* date formatting is adapted from
|
|
* http://jacwright.com/projects/javascript/date_format
|
|
*/
|
|
_formatDate: function(date, format) {
|
|
var returnStr = '';
|
|
for (var i = 0; i < format.length; i++) {
|
|
var curChar = format.charAt(i);
|
|
if (i != 0 && format.charAt(i - 1) == '\\') {
|
|
returnStr += curChar;
|
|
}
|
|
else if (this._replaceChars[curChar]) {
|
|
returnStr += this._replaceChars[curChar](date, this);
|
|
} else if (curChar != '\\') {
|
|
returnStr += curChar;
|
|
}
|
|
}
|
|
return returnStr;
|
|
},
|
|
|
|
_replaceChars: {
|
|
// Day
|
|
d: function(date) { return (date.getDate() < 10 ? '0' : '') + date.getDate(); },
|
|
D: function(date, calendar) { return calendar.options.shortDays[date.getDay()]; },
|
|
j: function(date) { return date.getDate(); },
|
|
l: function(date, calendar) { return calendar.options.longDays[date.getDay()]; },
|
|
N: function(date) { var _d = date.getDay(); return _d ? _d : 7; },
|
|
S: function(date) { return (date.getDate() % 10 == 1 && date.getDate() != 11 ? 'st' : (date.getDate() % 10 == 2 && date.getDate() != 12 ? 'nd' : (date.getDate() % 10 == 3 && date.getDate() != 13 ? 'rd' : 'th'))); },
|
|
w: function(date) { return date.getDay(); },
|
|
z: function(date) { var d = new Date(date.getFullYear(), 0, 1); return Math.ceil((date - d) / 86400000); }, // Fixed now
|
|
// Week
|
|
W: function(date) { var d = new Date(date.getFullYear(), 0, 1); return Math.ceil((((date - d) / 86400000) + d.getDay() + 1) / 7); }, // Fixed now
|
|
// Month
|
|
F: function(date, calendar) { return calendar.options.longMonths[date.getMonth()]; },
|
|
m: function(date) { return (date.getMonth() < 9 ? '0' : '') + (date.getMonth() + 1); },
|
|
M: function(date, calendar) { return calendar.options.shortMonths[date.getMonth()]; },
|
|
n: function(date) { return date.getMonth() + 1; },
|
|
t: function(date) { var d = date; return new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate() }, // Fixed now, gets #days of date
|
|
// Year
|
|
L: function(date) { var year = date.getFullYear(); return (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)); }, // Fixed now
|
|
o: function(date) { var d = new Date(date.valueOf()); d.setDate(d.getDate() - ((date.getDay() + 6) % 7) + 3); return d.getFullYear();}, //Fixed now
|
|
Y: function(date) { return date.getFullYear(); },
|
|
y: function(date) { return ('' + date.getFullYear()).substr(2); },
|
|
// Time
|
|
a: function(date) { return date.getHours() < 12 ? 'am' : 'pm'; },
|
|
A: function(date) { return date.getHours() < 12 ? 'AM' : 'PM'; },
|
|
B: function(date) { return Math.floor((((date.getUTCHours() + 1) % 24) + date.getUTCMinutes() / 60 + date.getUTCSeconds() / 3600) * 1000 / 24); }, // Fixed now
|
|
g: function(date) { return date.getHours() % 12 || 12; },
|
|
G: function(date) { return date.getHours(); },
|
|
h: function(date) { return ((date.getHours() % 12 || 12) < 10 ? '0' : '') + (date.getHours() % 12 || 12); },
|
|
H: function(date) { return (date.getHours() < 10 ? '0' : '') + date.getHours(); },
|
|
i: function(date) { return (date.getMinutes() < 10 ? '0' : '') + date.getMinutes(); },
|
|
s: function(date) { return (date.getSeconds() < 10 ? '0' : '') + date.getSeconds(); },
|
|
u: function(date) { var m = date.getMilliseconds(); return (m < 10 ? '00' : (m < 100 ? '0' : '')) + m; },
|
|
// Timezone
|
|
e: function(date) { return 'Not Yet Supported'; },
|
|
I: function(date) { return 'Not Yet Supported'; },
|
|
O: function(date) { return (-date.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(date.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(date.getTimezoneOffset() / 60)) + '00'; },
|
|
P: function(date) { return (-date.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(date.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(date.getTimezoneOffset() / 60)) + ':00'; }, // Fixed now
|
|
T: function(date) { var m = date.getMonth(); date.setMonth(0); var result = date.toTimeString().replace(/^.+ \(?([^\)]+)\)?$/, '$1'); date.setMonth(m); return result;},
|
|
Z: function(date) { return -date.getTimezoneOffset() * 60; },
|
|
// Full Date/Time
|
|
c: function(date, calendar) { return calendar._formatDate(date, 'Y-m-d\\TH:i:sP'); }, // Fixed now
|
|
r: function(date, calendar) { return calendar._formatDate(date, 'D, d M Y H:i:s O'); },
|
|
U: function(date) { return date.getTime() / 1000; }
|
|
},
|
|
|
|
/* USER MANAGEMENT FUNCTIONS */
|
|
|
|
getUserForId: function(id) {
|
|
return $.extend({}, this.options.users[this._getUserIndexFromId(id)]);
|
|
},
|
|
|
|
/**
|
|
* return the user name for header
|
|
*/
|
|
_getUserName: function(index) {
|
|
var self = this;
|
|
var options = this.options;
|
|
var user = options.users[index];
|
|
if ($.isFunction(options.getUserName)) {
|
|
return options.getUserName(user, index, self.element);
|
|
}
|
|
else {
|
|
return user;
|
|
}
|
|
},
|
|
/**
|
|
* return the user id for given index
|
|
*/
|
|
_getUserIdFromIndex: function(index) {
|
|
var self = this;
|
|
var options = this.options;
|
|
if ($.isFunction(options.getUserId)) {
|
|
return options.getUserId(options.users[index], index, self.element);
|
|
}
|
|
return index;
|
|
},
|
|
/**
|
|
* returns the associated user index for given ID
|
|
*/
|
|
_getUserIndexFromId: function(id) {
|
|
var self = this;
|
|
var options = this.options;
|
|
for (var i = 0; i < options.users.length; i++) {
|
|
if (self._getUserIdFromIndex(i) == id) {
|
|
return i;
|
|
}
|
|
}
|
|
return 0;
|
|
},
|
|
/**
|
|
* return the user ids for given calEvent.
|
|
* default is calEvent.userId field.
|
|
*/
|
|
_getEventUserId: function(calEvent) {
|
|
var self = this;
|
|
var options = this.options;
|
|
if (options.showAsSeparateUsers && options.users && options.users.length) {
|
|
if ($.isFunction(options.getEventUserId)) {
|
|
return options.getEventUserId(calEvent, self.element);
|
|
}
|
|
return calEvent.userId;
|
|
}
|
|
return [];
|
|
},
|
|
/**
|
|
* sets the event user id on given calEvent
|
|
* default is calEvent.userId field.
|
|
*/
|
|
_setEventUserId: function(calEvent, userId) {
|
|
var self = this;
|
|
var options = this.options;
|
|
if ($.isFunction(options.setEventUserId)) {
|
|
return options.setEventUserId(userId, calEvent, self.element);
|
|
}
|
|
calEvent.userId = userId;
|
|
return calEvent;
|
|
},
|
|
/**
|
|
* return the user ids for given freeBusy.
|
|
* default is freeBusy.userId field.
|
|
*/
|
|
_getFreeBusyUserId: function(freeBusy) {
|
|
var self = this;
|
|
var options = this.options;
|
|
if ($.isFunction(options.getFreeBusyUserId)) {
|
|
return options.getFreeBusyUserId(freeBusy.getOption(), self.element);
|
|
}
|
|
return freeBusy.getOption('userId');
|
|
},
|
|
|
|
/* FREEBUSY MANAGEMENT */
|
|
|
|
/**
|
|
* ckean the free busy managers and remove all the freeBusy
|
|
*/
|
|
_clearFreeBusys: function() {
|
|
if (this.options.displayFreeBusys) {
|
|
var self = this,
|
|
options = this.options,
|
|
$freeBusyPlaceholders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy');
|
|
$freeBusyPlaceholders.each(function() {
|
|
$(this).data('wcFreeBusyManager', new FreeBusyManager({
|
|
start: self._cloneDate($(this).data('startDate')),
|
|
end: self._cloneDate($(this).data('endDate')),
|
|
defaultFreeBusy: options.defaultFreeBusy || {}
|
|
}));
|
|
});
|
|
self.element.find('.wc-grid-row-freebusy .wc-freebusy').remove();
|
|
}
|
|
},
|
|
/**
|
|
* retrieve placeholders for given freebusy
|
|
*/
|
|
_findWeekDaysForFreeBusy: function(freeBusy, $weekDays) {
|
|
var $returnWeekDays,
|
|
options = this.options,
|
|
showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
|
|
self = this,
|
|
userList = self._getFreeBusyUserId(freeBusy);
|
|
if (!$.isArray(userList)) {
|
|
userList = userList != 'undefined' ? [userList] : [];
|
|
}
|
|
if (!$weekDays) {
|
|
$weekDays = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy');
|
|
}
|
|
$weekDays.each(function() {
|
|
var manager = $(this).data('wcFreeBusyManager'),
|
|
has_overlap = manager.isWithin(freeBusy.getStart()) ||
|
|
manager.isWithin(freeBusy.getEnd()) ||
|
|
freeBusy.isWithin(manager.getStart()) ||
|
|
freeBusy.isWithin(manager.getEnd()),
|
|
userId = $(this).data('wcUserId');
|
|
if (has_overlap && (!showAsSeparatedUser || ($.inArray(userId, userList) != -1))) {
|
|
$returnWeekDays = $returnWeekDays ? $returnWeekDays.add($(this)) : $(this);
|
|
}
|
|
});
|
|
return $returnWeekDays;
|
|
},
|
|
|
|
/**
|
|
* used to render all freeBusys
|
|
*/
|
|
_renderFreeBusys: function(freeBusys) {
|
|
if (this.options.displayFreeBusys) {
|
|
var self = this,
|
|
$freeBusyPlaceholders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy'),
|
|
freebusysToRender;
|
|
//insert freebusys to dedicated placeholders freebusy managers
|
|
if ($.isArray(freeBusys)) {
|
|
freebusysToRender = self._cleanFreeBusys(freeBusys);
|
|
} else if (freeBusys.freebusys) {
|
|
freebusysToRender = self._cleanFreeBusys(freeBusys.freebusys);
|
|
}
|
|
else {
|
|
freebusysToRender = [];
|
|
}
|
|
|
|
$.each(freebusysToRender, function(index, freebusy) {
|
|
var $placeholders = self._findWeekDaysForFreeBusy(freebusy, $freeBusyPlaceholders);
|
|
if ($placeholders) {
|
|
$placeholders.each(function() {
|
|
var manager = $(this).data('wcFreeBusyManager');
|
|
manager.insertFreeBusy(new FreeBusy(freebusy.getOption()));
|
|
$(this).data('wcFreeBusyManager', manager);
|
|
});
|
|
}
|
|
});
|
|
|
|
//now display freebusys on place holders
|
|
self._refreshFreeBusys($freeBusyPlaceholders);
|
|
}
|
|
},
|
|
/**
|
|
* refresh freebusys for given placeholders
|
|
*/
|
|
_refreshFreeBusys: function($freeBusyPlaceholders) {
|
|
if (this.options.displayFreeBusys && $freeBusyPlaceholders) {
|
|
var self = this,
|
|
options = this.options,
|
|
start = (options.businessHours.limitDisplay ? options.businessHours.start : 0),
|
|
end = (options.businessHours.limitDisplay ? options.businessHours.end : 24);
|
|
|
|
$freeBusyPlaceholders.each(function() {
|
|
var $placehoder = $(this);
|
|
var s = self._cloneDate($placehoder.data('startDate')),
|
|
e = self._cloneDate(s);
|
|
s.setHours(start);
|
|
e.setHours(end);
|
|
$placehoder.find('.wc-freebusy').remove();
|
|
$.each($placehoder.data('wcFreeBusyManager').getFreeBusys(s, e), function() {
|
|
self._renderFreeBusy(this, $placehoder);
|
|
});
|
|
});
|
|
}
|
|
},
|
|
/**
|
|
* render a freebusy item on dedicated placeholders
|
|
*/
|
|
_renderFreeBusy: function(freeBusy, $freeBusyPlaceholder) {
|
|
if (this.options.displayFreeBusys) {
|
|
var self = this,
|
|
options = this.options,
|
|
freeBusyHtml = '<div class="wc-freebusy"></div>';
|
|
|
|
var $fb = $(freeBusyHtml);
|
|
$fb.data('wcFreeBusy', new FreeBusy(freeBusy.getOption()));
|
|
this._positionFreeBusy($freeBusyPlaceholder, $fb);
|
|
$fb = options.freeBusyRender(freeBusy.getOption(), $fb, self.element);
|
|
if ($fb) {
|
|
$fb.appendTo($freeBusyPlaceholder);
|
|
}
|
|
}
|
|
},
|
|
/*
|
|
* Position the freebusy element within the weekday based on it's start / end dates.
|
|
*/
|
|
_positionFreeBusy: function($placeholder, $freeBusy) {
|
|
var options = this.options;
|
|
var freeBusy = $freeBusy.data('wcFreeBusy');
|
|
var pxPerMillis = $placeholder.height() / options.millisToDisplay;
|
|
var firstHourDisplayed = options.businessHours.limitDisplay ? options.businessHours.start : 0;
|
|
var startMillis = freeBusy.getStart().getTime() - new Date(freeBusy.getStart().getFullYear(), freeBusy.getStart().getMonth(), freeBusy.getStart().getDate(), firstHourDisplayed).getTime();
|
|
var eventMillis = freeBusy.getEnd().getTime() - freeBusy.getStart().getTime();
|
|
var pxTop = pxPerMillis * startMillis;
|
|
var pxHeight = pxPerMillis * eventMillis;
|
|
$freeBusy.css({top: pxTop, height: pxHeight});
|
|
},
|
|
/*
|
|
* Clean freebusys to ensure correct format
|
|
*/
|
|
_cleanFreeBusys: function(freebusys) {
|
|
var self = this,
|
|
freeBusyToReturn = [];
|
|
if (!$.isArray(freebusys)) {
|
|
var freebusys = [freebusys];
|
|
}
|
|
$.each(freebusys, function(i, freebusy) {
|
|
freeBusyToReturn.push(new FreeBusy(self._cleanFreeBusy(freebusy)));
|
|
});
|
|
return freeBusyToReturn;
|
|
},
|
|
|
|
/*
|
|
* Clean specific freebusy
|
|
*/
|
|
_cleanFreeBusy: function(freebusy) {
|
|
if (freebusy.date) {
|
|
freebusy.start = freebusy.date;
|
|
}
|
|
freebusy.start = this._cleanDate(freebusy.start);
|
|
freebusy.end = this._cleanDate(freebusy.end);
|
|
return freebusy;
|
|
},
|
|
|
|
/**
|
|
* retrives the first freebusy manager matching demand.
|
|
*/
|
|
getFreeBusyManagersFor: function(date, users) {
|
|
var calEvent = {
|
|
start: date,
|
|
end: date
|
|
};
|
|
this._setEventUserId(calEvent, users);
|
|
return this.getFreeBusyManagerForEvent(calEvent);
|
|
},
|
|
/**
|
|
* retrives the first freebusy manager for given event.
|
|
*/
|
|
getFreeBusyManagerForEvent: function(newCalEvent) {
|
|
var self = this,
|
|
options = this.options,
|
|
freeBusyManager;
|
|
if (options.displayFreeBusys) {
|
|
var $freeBusyPlaceHoders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy'),
|
|
freeBusy = new FreeBusy({start: newCalEvent.start, end: newCalEvent.end}),
|
|
showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
|
|
userId = showAsSeparatedUser ? self._getEventUserId(newCalEvent) : null;
|
|
if (!$.isArray(userId)) {
|
|
userId = [userId];
|
|
}
|
|
$freeBusyPlaceHoders.each(function() {
|
|
var manager = $(this).data('wcFreeBusyManager'),
|
|
has_overlap = manager.isWithin(freeBusy.getEnd()) ||
|
|
manager.isWithin(freeBusy.getEnd()) ||
|
|
freeBusy.isWithin(manager.getStart()) ||
|
|
freeBusy.isWithin(manager.getEnd());
|
|
if (has_overlap && (!showAsSeparatedUser || $.inArray($(this).data('wcUserId'), userId) != -1)) {
|
|
freeBusyManager = $(this).data('wcFreeBusyManager');
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
return freeBusyManager;
|
|
},
|
|
/**
|
|
* appends the freebusys to replace the old ones.
|
|
* @param {array|object} freeBusys freebusy(s) to apply.
|
|
*/
|
|
updateFreeBusy: function(freeBusys) {
|
|
var self = this,
|
|
options = this.options;
|
|
if (options.displayFreeBusys) {
|
|
var $toRender,
|
|
$freeBusyPlaceHoders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy'),
|
|
_freeBusys = self._cleanFreeBusys(freeBusys);
|
|
|
|
$.each(_freeBusys, function(index, _freeBusy) {
|
|
|
|
var $weekdays = self._findWeekDaysForFreeBusy(_freeBusy, $freeBusyPlaceHoders);
|
|
//if freebusy has a placeholder
|
|
if ($weekdays.length) {
|
|
$weekdays.each(function(index, day) {
|
|
var manager = $(day).data('wcFreeBusyManager');
|
|
manager.insertFreeBusy(_freeBusy);
|
|
$(day).data('wcFreeBusyManager', manager);
|
|
});
|
|
$toRender = $toRender ? $toRender.add($weekdays) : $weekdays;
|
|
}
|
|
});
|
|
self._refreshFreeBusys($toRender);
|
|
}
|
|
},
|
|
|
|
/* NEW OPTIONS MANAGEMENT */
|
|
|
|
/**
|
|
* checks wether or not the calendar should be displayed starting on first day of week
|
|
*/
|
|
_startOnFirstDayOfWeek: function() {
|
|
return jQuery.isFunction(this.options.startOnFirstDayOfWeek) ? this.options.startOnFirstDayOfWeek(this.element) : this.options.startOnFirstDayOfWeek;
|
|
},
|
|
|
|
/**
|
|
* finds out the current scroll to apply it when changing the view
|
|
*/
|
|
_getCurrentScrollHour: function() {
|
|
var self = this;
|
|
var options = this.options;
|
|
var $scrollable = this.element.find('.wc-scrollable-grid');
|
|
var scroll = $scrollable.scrollTop();
|
|
if (self.options.businessHours.limitDisplay) {
|
|
scroll = scroll + options.businessHours.start * options.timeslotHeight * options.timeslotsPerHour;
|
|
}
|
|
return Math.round(scroll / (options.timeslotHeight * options.timeslotsPerHour)) + 1;
|
|
},
|
|
_getJsonOptions: function() {
|
|
if ($.isFunction(this.options.jsonOptions)) {
|
|
return $.extend({}, this.options.jsonOptions(this.element));
|
|
}
|
|
if ($.isPlainObject(this.options.jsonOptions)) {
|
|
return $.extend({}, this.options.jsonOptions);
|
|
}
|
|
return {};
|
|
},
|
|
_getHeaderDate: function(date) {
|
|
var options = this.options;
|
|
if (options.getHeaderDate && $.isFunction(options.getHeaderDate))
|
|
{
|
|
return options.getHeaderDate(date, this.element);
|
|
}
|
|
var dayName = options.useShortDayNames ? options.shortDays[date.getDay()] : options.longDays[date.getDay()];
|
|
return dayName + (options.headerSeparator) + this._formatDate(date, options.dateFormat);
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
* returns corrected date related to DST problem
|
|
*/
|
|
_getDSTdayShift: function(date, shift) {
|
|
var start = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0);
|
|
var offset1 = start.getTimezoneOffset();
|
|
var offset2 = date.getTimezoneOffset();
|
|
if (offset1 == offset2)
|
|
return date;
|
|
shift = shift ? shift : 1;
|
|
return new Date(date.getTime() - shift * (offset1 > offset2 ? -1 : 1) * (Math.max(offset1, offset2) - Math.min(offset1, offset2)) * 60000);
|
|
},
|
|
_needDSTdayShift: function(date1, date2) {
|
|
return date1.getTimezoneOffset() != date2.getTimezoneOffset();
|
|
}
|
|
|
|
|
|
|
|
}; // end of widget function return
|
|
})() //end of widget function closure execution
|
|
); // end of $.widget("ui.weekCalendar"...
|
|
|
|
$.extend($.ui.weekCalendar, {
|
|
version: '2.0-dev',
|
|
updateLayoutOptions: {
|
|
startOnFirstDayOfWeek: true,
|
|
firstDayOfWeek: true,
|
|
daysToShow: true,
|
|
displayOddEven: true,
|
|
timeFormat: true,
|
|
dateFormat: true,
|
|
use24Hour: true,
|
|
useShortDayNames: true,
|
|
businessHours: true,
|
|
timeslotHeight: true,
|
|
timeslotsPerHour: true,
|
|
buttonText: true,
|
|
height: true,
|
|
shortMonths: true,
|
|
longMonths: true,
|
|
shortDays: true,
|
|
longDays: true,
|
|
textSize: true,
|
|
users: true,
|
|
showAsSeparateUsers: true,
|
|
displayFreeBusys: true
|
|
}
|
|
});
|
|
|
|
var MILLIS_IN_DAY = 86400000;
|
|
var MILLIS_IN_WEEK = MILLIS_IN_DAY * 7;
|
|
|
|
/* FREE BUSY MANAGERS */
|
|
var FreeBusyProto = {
|
|
getStart: function() {return this.getOption('start')},
|
|
getEnd: function() {return this.getOption('end')},
|
|
getOption: function() {
|
|
if (!arguments.length) { return this.options }
|
|
if (typeof(this.options[arguments[0]]) !== 'undefined') {
|
|
return this.options[arguments[0]];
|
|
}
|
|
else if (typeof(arguments[1]) !== 'undefined') {
|
|
return arguments[1];
|
|
}
|
|
return null;
|
|
},
|
|
setOption: function(key, value) {
|
|
if (arguments.length == 1) {
|
|
$.extend(this.options, arguments[0]);
|
|
return this;
|
|
}
|
|
this.options[key] = value;
|
|
return this;
|
|
},
|
|
isWithin: function(dateTime) {return Math.floor(dateTime.getTime() / 1000) >= Math.floor(this.getStart().getTime() / 1000) && Math.floor(dateTime.getTime() / 1000) <= Math.floor(this.getEnd().getTime() / 1000)},
|
|
isValid: function() {return this.getStart().getTime() < this.getEnd().getTime()}
|
|
};
|
|
|
|
/**
|
|
* @constructor
|
|
* single user freebusy manager.
|
|
*/
|
|
var FreeBusy = function(options) {
|
|
this.options = $.extend({}, options || {});
|
|
};
|
|
$.extend(FreeBusy.prototype, FreeBusyProto);
|
|
|
|
var FreeBusyManager = function(options) {
|
|
this.options = $.extend({
|
|
defaultFreeBusy: {}
|
|
}, options || {});
|
|
this.freeBusys = [];
|
|
this.freeBusys.push(new FreeBusy($.extend({
|
|
start: this.getStart(),
|
|
end: this.getEnd()
|
|
}, this.options.defaultFreeBusy)));
|
|
};
|
|
$.extend(FreeBusyManager.prototype, FreeBusyProto, {
|
|
/**
|
|
* return matching freeBusys.
|
|
* if you do not pass any argument, returns all freebusys.
|
|
* if you only pass a start date, only matchinf freebusy will be returned.
|
|
* if you pass 2 arguments, then all freebusys available within the time period will be returned
|
|
* @param {Date} start [optionnal] if you do not pass end date, will return the freeBusy within which this date falls.
|
|
* @param {Date} end [optionnal] the date where to stop the search.
|
|
* @return {Array} an array of FreeBusy matching arguments.
|
|
*/
|
|
getFreeBusys: function() {
|
|
switch (arguments.length) {
|
|
case 0:
|
|
return this.freeBusys;
|
|
case 1:
|
|
var freeBusy = [];
|
|
var start = arguments[0];
|
|
if (!this.isWithin(start)) {
|
|
return freeBusy;
|
|
}
|
|
$.each(this.freeBusys, function() {
|
|
if (this.isWithin(start)) {
|
|
freeBusy.push(this);
|
|
}
|
|
if (Math.floor(this.getEnd().getTime() / 1000) > Math.floor(start.getTime() / 1000)) {
|
|
return false;
|
|
}
|
|
});
|
|
return freeBusy;
|
|
default:
|
|
//we assume only 2 first args are revealants
|
|
var freeBusy = [];
|
|
var start = arguments[0], end = arguments[1];
|
|
var tmpFreeBusy = new FreeBusy({start: start, end: end});
|
|
if (end.getTime() < start.getTime() || this.getStart().getTime() > end.getTime() || this.getEnd().getTime() < start.getTime()) {
|
|
return freeBusy;
|
|
}
|
|
$.each(this.freeBusys, function() {
|
|
if (this.getStart().getTime() >= end.getTime()) {
|
|
return false;
|
|
}
|
|
if (tmpFreeBusy.isWithin(this.getStart()) && tmpFreeBusy.isWithin(this.getEnd())) {
|
|
freeBusy.push(this);
|
|
}
|
|
else if (this.isWithin(tmpFreeBusy.getStart()) && this.isWithin(tmpFreeBusy.getEnd())) {
|
|
var _f = new FreeBusy(this.getOption());
|
|
_f.setOption('end', tmpFreeBusy.getEnd());
|
|
_f.setOption('start', tmpFreeBusy.getStart());
|
|
freeBusy.push(_f);
|
|
}
|
|
else if (this.isWithin(tmpFreeBusy.getStart()) && this.getStart().getTime() < start.getTime()) {
|
|
var _f = new FreeBusy(this.getOption());
|
|
_f.setOption('start', tmpFreeBusy.getStart());
|
|
freeBusy.push(_f);
|
|
}
|
|
else if (this.isWithin(tmpFreeBusy.getEnd()) && this.getEnd().getTime() > end.getTime()) {
|
|
var _f = new FreeBusy(this.getOption());
|
|
_f.setOption('end', tmpFreeBusy.getEnd());
|
|
freeBusy.push(_f);
|
|
}
|
|
});
|
|
return freeBusy;
|
|
}
|
|
},
|
|
insertFreeBusy: function(freeBusy) {
|
|
var freeBusy = new FreeBusy(freeBusy.getOption());
|
|
//first, if inserted freebusy is bigger than manager
|
|
if (freeBusy.getStart().getTime() < this.getStart().getTime()) {
|
|
freeBusy.setOption('start', this.getStart());
|
|
}
|
|
if (freeBusy.getEnd().getTime() > this.getEnd().getTime()) {
|
|
freeBusy.setOption('end', this.getEnd());
|
|
}
|
|
var start = freeBusy.getStart(), end = freeBusy.getEnd(),
|
|
startIndex = 0, endIndex = this.freeBusys.length - 1,
|
|
newFreeBusys = [];
|
|
var pushNewFreeBusy = function(_f) {if (_f.isValid()) newFreeBusys.push(_f);};
|
|
|
|
$.each(this.freeBusys, function(index) {
|
|
//within the loop, we have following vars:
|
|
// curFreeBusyItem: the current iteration freeBusy, part of manager freeBusys list
|
|
// start: the insterted freeBusy start
|
|
// end: the inserted freebusy end
|
|
var curFreeBusyItem = this;
|
|
if (curFreeBusyItem.isWithin(start) && curFreeBusyItem.isWithin(end)) {
|
|
/*
|
|
we are in case where inserted freebusy fits in curFreeBusyItem:
|
|
curFreeBusyItem: *-----------------------------*
|
|
freeBusy: *-------------*
|
|
obviously, start and end indexes are this item.
|
|
*/
|
|
startIndex = index;
|
|
endIndex = index;
|
|
if (start.getTime() == curFreeBusyItem.getStart().getTime() && end.getTime() == curFreeBusyItem.getEnd().getTime()) {
|
|
/*
|
|
in this case, inserted freebusy is exactly curFreeBusyItem:
|
|
curFreeBusyItem: *-----------------------------*
|
|
freeBusy: *-----------------------------*
|
|
|
|
just replace curFreeBusyItem with freeBusy.
|
|
*/
|
|
var _f1 = new FreeBusy(freeBusy.getOption());
|
|
pushNewFreeBusy(_f1);
|
|
}
|
|
else if (start.getTime() == curFreeBusyItem.getStart().getTime()) {
|
|
/*
|
|
in this case inserted freebusy starts with curFreeBusyItem:
|
|
curFreeBusyItem: *-----------------------------*
|
|
freeBusy: *--------------*
|
|
|
|
just replace curFreeBusyItem with freeBusy AND the rest.
|
|
*/
|
|
var _f1 = new FreeBusy(freeBusy.getOption());
|
|
var _f2 = new FreeBusy(curFreeBusyItem.getOption());
|
|
_f2.setOption('start', end);
|
|
pushNewFreeBusy(_f1);
|
|
pushNewFreeBusy(_f2);
|
|
}
|
|
else if (end.getTime() == curFreeBusyItem.getEnd().getTime()) {
|
|
/*
|
|
in this case inserted freebusy ends with curFreeBusyItem:
|
|
curFreeBusyItem: *-----------------------------*
|
|
freeBusy: *--------------*
|
|
|
|
just replace curFreeBusyItem with before part AND freeBusy.
|
|
*/
|
|
var _f1 = new FreeBusy(curFreeBusyItem.getOption());
|
|
_f1.setOption('end', start);
|
|
var _f2 = new FreeBusy(freeBusy.getOption());
|
|
pushNewFreeBusy(_f1);
|
|
pushNewFreeBusy(_f2);
|
|
}
|
|
else {
|
|
/*
|
|
in this case inserted freebusy is within curFreeBusyItem:
|
|
curFreeBusyItem: *-----------------------------*
|
|
freeBusy: *--------------*
|
|
|
|
just replace curFreeBusyItem with before part AND freeBusy AND the rest.
|
|
*/
|
|
var _f1 = new FreeBusy(curFreeBusyItem.getOption());
|
|
var _f2 = new FreeBusy(freeBusy.getOption());
|
|
var _f3 = new FreeBusy(curFreeBusyItem.getOption());
|
|
_f1.setOption('end', start);
|
|
_f3.setOption('start', end);
|
|
pushNewFreeBusy(_f1);
|
|
pushNewFreeBusy(_f2);
|
|
pushNewFreeBusy(_f3);
|
|
}
|
|
/*
|
|
as work is done, no need to go further.
|
|
return false
|
|
*/
|
|
return false;
|
|
}
|
|
else if (curFreeBusyItem.isWithin(start) && curFreeBusyItem.getEnd().getTime() != start.getTime()) {
|
|
/*
|
|
in this case, inserted freebusy starts within curFreeBusyItem:
|
|
curFreeBusyItem: *----------*
|
|
freeBusy: *-------------------*
|
|
|
|
set start index AND insert before part, we'll insert freebusy later
|
|
*/
|
|
if (curFreeBusyItem.getStart().getTime() != start.getTime()) {
|
|
var _f1 = new FreeBusy(curFreeBusyItem.getOption());
|
|
_f1.setOption('end', start);
|
|
pushNewFreeBusy(_f1);
|
|
}
|
|
startIndex = index;
|
|
}
|
|
else if (curFreeBusyItem.isWithin(end) && curFreeBusyItem.getStart().getTime() != end.getTime()) {
|
|
/*
|
|
in this case, inserted freebusy starts within curFreeBusyItem:
|
|
curFreeBusyItem: *----------*
|
|
freeBusy: *-------------------*
|
|
|
|
set end index AND insert freebusy AND insert after part if needed
|
|
*/
|
|
pushNewFreeBusy(new FreeBusy(freeBusy.getOption()));
|
|
if (end.getTime() < curFreeBusyItem.getEnd().getTime()) {
|
|
var _f1 = new FreeBusy(curFreeBusyItem.getOption());
|
|
_f1.setOption('start', end);
|
|
pushNewFreeBusy(_f1);
|
|
}
|
|
endIndex = index;
|
|
return false;
|
|
}
|
|
});
|
|
//now compute arguments
|
|
var tmpFB = this.freeBusys;
|
|
this.freeBusys = [];
|
|
|
|
if (startIndex) {
|
|
this.freeBusys = this.freeBusys.concat(tmpFB.slice(0, startIndex));
|
|
}
|
|
this.freeBusys = this.freeBusys.concat(newFreeBusys);
|
|
if (endIndex < tmpFB.length) {
|
|
this.freeBusys = this.freeBusys.concat(tmpFB.slice(endIndex + 1));
|
|
}
|
|
/* if(start.getDate() == 1){
|
|
console.info('insert from '+freeBusy.getStart() +' to '+freeBusy.getEnd());
|
|
console.log('index from '+ startIndex + ' to ' + endIndex);
|
|
var str = [];
|
|
$.each(tmpFB, function(i){str.push(i + ": " + this.getStart().getHours() + ' > ' + this.getEnd().getHours() + ' ' + (this.getOption('free') ? 'free' : 'busy'))});
|
|
console.log(str.join('\n'));
|
|
|
|
console.log('insert');
|
|
var str = [];
|
|
$.each(newFreeBusys, function(i){str.push(this.getStart().getHours() + ' > ' + this.getEnd().getHours())});
|
|
console.log(str.join(', '));
|
|
|
|
console.log('results');
|
|
var str = [];
|
|
$.each(this.freeBusys, function(i){str.push(i + ": " + this.getStart().getHours() + ' > ' + this.getEnd().getHours() + ' ' + (this.getOption('free') ? 'free' :'busy'))});
|
|
console.log(str.join('\n'));
|
|
}*/
|
|
return this;
|
|
}
|
|
});
|
|
|
|
})(jQuery);
|