InfCloud/lib/fullcalendar.js

7197 lines
192 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @preserve
* FullCalendar v1.5.4
* http://arshaw.com/fullcalendar/
*
* Use fullcalendar.css for basic styling.
* For event drag & drop, requires jQuery UI draggable.
* For event resizing, requires jQuery UI resizable.
*
* Copyright (c) 2011 Adam Shaw
* Dual licensed under the MIT and GPL licenses, located in
* MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
*
* Date: Tue Sep 4 23:38:33 2012 -0700
*
*/
(function($, undefined) {
var defaults = {
// display
defaultView: 'month',
aspectRatio: 1.35,
header: {
left: 'title',
center: '',
right: 'today prev,next'
},
weekends: true,
currentTimeIndicator: false,
// editing
//editable: false,
//disableDragging: false,
//disableResizing: false,
allDayDefault: true,
ignoreTimezone: true,
// event ajax
lazyFetching: true,
startParam: 'start',
endParam: 'end',
// time formats
titleFormat: {
month: 'MMMM yyyy',
multiWeek: "MMM d[ yyyy]{ ''[ MMM] d yyyy}",
week: "MMM d[ yyyy]{ ''[ MMM] d yyyy}",
day: 'dddd, MMM d, yyyy',
list: 'MMM d, yyyy',
table: "MMM d[ yyyy]{ ''[ MMM] d yyyy}",
todo: "MMM d[ yyyy]{ ''[ MMM] d yyyy}",
},
columnFormat: {
month: 'ddd',
multiWeek: 'ddd',
week: 'ddd M/d',
day: 'dddd M/d',
list: 'dddd, MMM d, yyyy',
table: 'MMM d, yyyy',
todo: 'MMM d, yyyy',
},
timeFormat: { // for event elements
'': 'h(:mm)t', // default
agenda: 'h:mm{ h:mm}', //agenda views
list: 'hh:mm{ hh:mm}', //list and table views
listFull: 'hh:mm M d yyyy{ hh:mm M d yyyy}', //list and table views for events that span multiple days
listFullAllDay: 'M d yyyy{ M d yyyy}', //list and table views for allday events that span multiple days
},
// locale
isRTL: false,
firstDay: 0,
weekendDays: [0, 6],
monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
buttonText: {
prev: '  ',
next: '  ',
prevYear: ' << ',
nextYear: ' >> ',
today: 'today',
month: 'month',
multiWeek: 'mweek',
week: 'week',
day: 'day',
list: 'list',
table: 'table',
todo: 'todo',
prevMonth: 'Load previous month',
nextMonth: 'Load next month',
filtersHeader: 'Filters',
filtersFooter: '* completed at or after %date%',
filterAction: 'Needs action',
filterProgress: 'In progress',
filterCompleted: 'Completed',
filterCanceled: 'Canceled'
},
listTexts: {
until: 'until',
past: 'Past events',
today: 'Today',
tomorrow: 'Tomorrow',
thisWeek: 'This week',
nextWeek: 'Next week',
thisMonth: 'This month',
nextMonth: 'Next month',
future: 'Future events',
week: 'W'
},
// list/table options
listSections: 'smart', // false|'day'|'week'|'month'|'smart'
listRange: 30, // number of days to be displayed
listPage: 7, // number of days to jump when paging
tableCols: ['handle', 'date', 'time', 'title'],
todoCols: ['handle', 'check', 'priority', 'time', 'title', 'location', 'status', 'percent'],
todoColThresholds: [],
todoOptionalCols: [],
//defaultFilters: ['filterAction', 'filterProgress', 'filterCompleted', 'filterCanceled'],
defaultFilters: [],
// jquery-ui theming
theme: false,
buttonIcons: {
prev: 'circle-triangle-w',
next: 'circle-triangle-e'
},
//selectable: false,
unselectAuto: true,
dropAccept: '*',
headerContainer: false,
bindingMode: 'single',
dayEventSizeStrict: false,
startOfBusiness: 0,
endOfBusiness: 0,
showWeekNumbers: true,
multiWeekSize: 3,
showDatepicker: false,
eventMode: true,
showUnstartedEvents: false,
simpleFilters: false,
};
// right-to-left defaults
var rtlDefaults = {
header: {
left: 'next,prev today',
center: '',
right: 'title'
},
headerContainer: '',
buttonText: {
prev: ' ► ',
next: ' ◄ ',
prevYear: ' >> ',
nextYear: ' << '
},
buttonIcons: {
prev: 'circle-triangle-e',
next: 'circle-triangle-w'
}
};
var fc = $.fullCalendar = { version: "1.5.4" };
var fcViews = fc.views = {};
$.fn.fullCalendar = function(options) {
// method calling
if (typeof options == 'string') {
var args = Array.prototype.slice.call(arguments, 1);
var res;
this.each(function() {
var calendar = $.data(this, 'fullCalendar');
if (calendar && $.isFunction(calendar[options])) {
var r = calendar[options].apply(calendar, args);
if (res === undefined) {
res = r;
}
if (options == 'destroy') {
$.removeData(this, 'fullCalendar');
}
}
});
if (res !== undefined) {
return res;
}
return this;
}
// would like to have this logic in EventManager, but needs to happen before options are recursively extended
var eventSources = options.eventSources || [];
delete options.eventSources;
if (options.events) {
eventSources.push(options.events);
delete options.events;
}
options = $.extend(true, {},
defaults,
(options.isRTL || options.isRTL===undefined && defaults.isRTL) ? rtlDefaults : {},
options
);
this.each(function(i, _element) {
var element = $(_element);
var calendar = new Calendar(element, options, eventSources);
element.data('fullCalendar', calendar); // TODO: look into memory leak implications
calendar.render();
});
return this;
};
// function for adding/overriding defaults
function setDefaults(d) {
$.extend(true, defaults, d);
}
function Calendar(element, options, eventSources) {
var t = this;
// exports
t.options = options;
t.render = render;
t.destroy = destroy;
t.refetchEvents = refetchEvents;
t.reportEvents = reportEvents;
t.reportEventChange = reportEventChange;
t.rerenderEvents = rerenderEvents;
t.changeView = changeView;
t.select = select;
t.unselect = unselect;
t.prev = prev;
t.next = next;
t.prevYear = prevYear;
t.nextYear = nextYear;
t.today = today;
t.findToday = findToday;
t.gotoDate = gotoDate;
t.incrementDate = incrementDate;
t.formatDate = function(format, date) { return formatDate(format, date, options) };
t.formatDates = function(format, date1, date2) { return formatDates(format, date1, date2, options) };
t.getDate = getDate;
t.getView = getView;
t.option = option;
t.trigger = trigger;
t.selectEvent = selectEvent;
t.allowSelectEvent = allowSelectEvent;
t.updateToday = updateToday;
t.updateGrid = updateGrid;
t.renderViews = renderViews;
t.setOptions = setOptions;
t.getOption = getOption;
t.viewInstances = {};
// imports
EventManager.call(t, options, eventSources);
var isFetchNeeded = t.isFetchNeeded;
var fetchEvents = t.fetchEvents;
// locals
var _element = element[0];
var header;
var headerElement;
var content;
var tm; // for making theme classes
var currentView;
var elementOuterWidth;
var suggestedViewHeight;
var absoluteViewElement;
var resizeUID = 0;
var ignoreWindowResize = 0;
var date = new Date();
var events = [];
var _dragElement;
/* Main Rendering
-----------------------------------------------------------------------------*/
setYMD(date, options.year, options.month, options.date);
function render(inc) {
if (!content) {
initialRender();
}else{
calcSize();
markSizesDirty();
markEventsDirty();
renderView(inc);
}
}
function initialRender() {
tm = options.theme ? 'ui' : 'fc';
element.addClass('fc');
if (options.isRTL) {
element.addClass('fc-rtl');
}
if (options.theme) {
element.addClass('ui-widget');
}
content = $("<div class='fc-content' style='position:relative'/>")
.prependTo(element);
header = new Header(t, options);
headerElement = header.render();
if (headerElement) {
options.headerContainer ? options.headerContainer.prepend(headerElement) : element.prepend(headerElement);
}
changeView(options.defaultView);
$(window).resize(windowResize);
// needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a windowResize
if (!bodyVisible()) {
lateRender();
}
}
// called when we know the calendar couldn't be rendered when it was initialized,
// but we think it's ready now
function lateRender() {
setTimeout(function() { // IE7 needs this so dimensions are calculated correctly
if (!currentView.start && bodyVisible()) { // !currentView.start makes sure this never happens more than once
renderView();
}
},0);
}
function updateToday()
{
for(var view in t.viewInstances)
t.viewInstances[view].updateToday();
}
function updateGrid()
{
for(var view in t.viewInstances)
t.viewInstances[view].updateGrid();
}
function renderViews()
{
//Force rerender of all views
for(var view in t.viewInstances)
t.viewInstances[view].start=null;
renderView();
}
function setOptions(newOptions)
{
var rerender=false;
$.each(newOptions, function(key,value){
if($.isPlainObject(value))
$.extend(options[key],value);
else
options[key]=value;
if(key=='firstDay' || key=='timeFormat')
rerender=true;
else
{
for(var view in t.viewInstances)
t.viewInstances[view]['set'+key.charAt(0).toUpperCase()+key.slice(1)]();
}
});
if(rerender)
renderViews();
}
function getOption(option)
{
return options[option];
}
function destroy() {
$(window).unbind('resize', windowResize);
header.destroy();
content.remove();
element.removeClass('fc fc-rtl ui-widget');
}
function elementVisible() {
return _element.offsetWidth !== 0;
}
function bodyVisible() {
return $('body')[0].offsetWidth !== 0;
}
/* View Rendering
-----------------------------------------------------------------------------*/
// TODO: improve view switching (still weird transition in IE, and FF has whiteout problem)
function changeView(newViewName) {
if (!currentView || newViewName != currentView.name) {
ignoreWindowResize++; // because setMinHeight might change the height before render (and subsequently setSize) is reached
unselect();
var oldView = currentView;
var newViewElement;
if (oldView) {
(oldView.beforeHide || noop)(); // called before changing min-height. if called after, scroll state is reset (in Opera)
//setMinHeight(content, content.height()); why is this necessary?
oldView.element.hide();
if(oldView.addedView) {
oldView.addedView.element.hide();
}
}else{
setMinHeight(content, 1); // needs to be 1 (not 0) for IE7, or else view dimensions miscalculated
}
content.css('overflow', 'hidden');
currentView = t.viewInstances[newViewName];
if (currentView) {
currentView.element.show();
}else{
currentView = t.viewInstances[newViewName] = new fcViews[newViewName](
newViewElement = absoluteViewElement =
$("<div class='fc-view fc-view-" + newViewName + "' style='position:absolute'/>")
.appendTo(content),
t // the calendar object
);
}
if(newViewName == 'agendaDay') {
addedView = t.viewInstances['table'];
if (addedView) {
addedView.element.show();
}else{
addedView = t.viewInstances['table'] = new fcViews['table'](
addedNewViewElement = addedAbsoluteViewElement =
$("<div class='fc-view fc-view-" + 'table' + "' style='position:absolute'/>")
.appendTo(content),
t // the calendar object
);
currentView.addedView = addedView;
}
}
if (oldView) {
header.deactivateButton(oldView.name);
}
header.activateButton(newViewName);
renderView(); // after height has been set, will make absoluteViewElement's position=relative, then set to null
content.css('overflow', '');
if (oldView) {
setMinHeight(content, 1);
}
if (!newViewElement) {
(currentView.afterShow || noop)(); // called after setting min-height/overflow, so in final scroll state (for Opera)
}
ignoreWindowResize--;
currentView.trigger('viewChanged', _element);
}
}
function renderView(inc) {
if (elementVisible()) {
currentView.trigger('beforeViewDisplay', _element);
ignoreWindowResize++; // because renderEvents might temporarily change the height before setSize is reached
unselect();
if (suggestedViewHeight === undefined) {
calcSize();
}
if(currentView.addedView && currentView.start && cloneDate(date, true).getTime() == currentView.start.getTime()) {
currentView.addedView.scrollToDate(date);
}
var forceEventRender = false;
if (!currentView.start || inc || date < currentView.start || date >= currentView.end) {
// view must render an entire new date range (and refetch/render events)
currentView.render(date, inc || 0); // responsible for clearing events
setSize(true);
forceEventRender = true;
}
else if (currentView.sizeDirty) {
// view must resize (and rerender events)
currentView.clearEvents();
setSize();
forceEventRender = true;
}
else if (currentView.eventsDirty) {
currentView.clearEvents();
forceEventRender = true;
}
currentView.sizeDirty = false;
currentView.eventsDirty = false;
updateEvents(forceEventRender);
elementOuterWidth = element.outerWidth();
header.updateTitle(currentView.title);
var today = new Date();
if (today >= currentView.start && today < currentView.end) {
//header.disableButton('today');
header.setTodayScroll(element);
findToday();
}else{
//header.enableButton('today');
header.setTodayDefault();
}
ignoreWindowResize--;
currentView.trigger('viewDisplay', _element);
}
}
/* Resizing
-----------------------------------------------------------------------------*/
function updateSize() {
markSizesDirty();
if (elementVisible()) {
calcSize();
setSize();
if(currentView.name!='todo')
{
unselect();
currentView.clearEvents();
currentView.renderEvents(events);
}
currentView.sizeDirty = false;
}
}
function markSizesDirty() {
$.each(t.viewInstances, function(i, inst) {
inst.sizeDirty = true;
});
}
function calcSize() {
if (options.contentHeight) {
suggestedViewHeight = options.contentHeight;
}
else if (options.height) {
suggestedViewHeight = options.height - (headerElement ? headerElement.height() : 0) - vsides(content);
}
else {
suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));
}
}
function setSize(dateChanged) { // todo: dateChanged?
ignoreWindowResize++;
currentView.setWidth(content.width(), dateChanged);
currentView.setHeight(suggestedViewHeight, dateChanged);
if (absoluteViewElement) {
absoluteViewElement.css('position', 'relative');
absoluteViewElement = null;
}
/*if(currentView.addedView) {
currentView.addedView.setWidth(content.width(), dateChanged);
var tmpContentWidth = Math.floor(content.width() / 2);
currentView.element.width(tmpContentWidth);
currentView.addedView.element.css({'left' : tmpContentWidth,
'width' : tmpContentWidth - 2});
}*/
ignoreWindowResize--;
}
function windowResize() {
if (!ignoreWindowResize) {
if (currentView.start) { // view has already been rendered
var uid = ++resizeUID;
//setTimeout(function() { // add a delay
if (uid == resizeUID && !ignoreWindowResize && elementVisible()) {
if (elementOuterWidth != (elementOuterWidth = element.outerWidth())) {
ignoreWindowResize++; // in case the windowResize callback changes the height
updateSize();
currentView.trigger('windowResize', _element);
ignoreWindowResize--;
}
}
//}, 200);
}else{
// calendar must have been initialized in a 0x0 iframe that has just been resized
lateRender();
}
}
}
/* Event Fetching/Rendering
-----------------------------------------------------------------------------*/
// fetches events if necessary, rerenders events if necessary (or if forced)
function updateEvents(forceRender) {
if (!options.lazyFetching || isFetchNeeded(currentView.visStart, currentView.visEnd)) {
refetchEvents();
}
else if (forceRender) {
rerenderEvents();
}
}
function refetchEvents() {
fetchEvents(currentView.visStart, currentView.visEnd); // will call reportEvents
}
// called when event data arrives
function reportEvents(_events) {
events = _events;
rerenderEvents();
}
// called when a single event's data has been changed
function reportEventChange(eventID) {
rerenderEvents(eventID);
}
// attempts to rerenderEvents
function rerenderEvents(modifiedEventID) {
markEventsDirty();
if (elementVisible()) {
currentView.clearEvents();
currentView.renderEvents(events, modifiedEventID);
currentView.eventsDirty = false;
}
}
function markEventsDirty() {
$.each(t.viewInstances, function(i, inst) {
inst.eventsDirty = true;
});
}
/* Selection
-----------------------------------------------------------------------------*/
function select(start, end, allDay) {
currentView.select(start, end, allDay===undefined ? true : allDay);
}
function unselect() { // safe to be called before renderView
if(currentView)
currentView.unselect();
}
/* Date
-----------------------------------------------------------------------------*/
function prev() {
renderView(-1);
trigger('prevClick');
}
function next() {
renderView(1);
trigger('nextClick');
}
function prevYear() {
addYears(date, -1);
renderView();
}
function nextYear() {
addYears(date, 1);
renderView();
}
function today() {
date = new Date();
renderView();
findToday();
trigger('todayClick');
}
function findToday() {
if(currentView.addedView) {
if(currentView.addedView.getDaySegmentContainer().find('.fc-today').length>0) {
if(new Date().getDate()==1) {
currentView.addedView.getDaySegmentContainer().parent().scrollTop(0);
}
else {
offset = currentView.addedView.getDaySegmentContainer().find('.fc-today').position().top;
var top = currentView.addedView.getDaySegmentContainer().parent().scrollTop();
currentView.addedView.getDaySegmentContainer().parent().scrollTop(top + offset);
}
}
}
else if(currentView.name == 'todo') {
if(currentView.getDaySegmentContainer().find('.fc-today').length>0) {
offset = currentView.getDaySegmentContainer().find('.fc-today').position().top;
var top = currentView.getDaySegmentContainer().parent().scrollTop();
currentView.getDaySegmentContainer().parent().scrollTop(top + offset);
}
}
else {
var todayElem = currentView.element.find('.fc-today');
if(todayElem.length>0) {
var offset = 0;
if(!todayElem.parent().hasClass('fc-week0')) {
offset = todayElem.position().top;
}
element.parent().scrollTop(offset);
}
}
}
function gotoDate(year, month, dateOfMonth) {
if (year instanceof Date)
date = cloneDate(year); // provided 1 argument, a Date
else
setYMD(date, year, month, dateOfMonth);
renderView();
}
function incrementDate(years, months, days) {
if(years !== undefined)
addYears(date, years);
if(months !== undefined)
addMonths(date, months);
if(days !== undefined)
addDays(date, days);
renderView();
}
function getDate() {
return cloneDate(date);
}
/* Misc
-----------------------------------------------------------------------------*/
function getView() {
return currentView;
}
function option(name, value) {
if (value === undefined) {
return options[name];
}
if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {
options[name] = value;
updateSize();
} else if (name.indexOf('list') == 0 || name == 'tableCols') {
options[name] = value;
currentView.start = null; // force re-render
}
}
function trigger(name, thisObj) {
if (options[name]) {
return options[name].apply(
thisObj || _element,
Array.prototype.slice.call(arguments, 2)
);
}
}
function selectEvent(eventElement, noClick) {
currentView.selectEvent(eventElement, noClick);
}
function allowSelectEvent(value) {
currentView.allowSelectEvent(value);
}
/* External Dragging
------------------------------------------------------------------------*/
if (options.droppable) {
$(document)
.bind('dragstart', function(ev, ui) {
var _e = ev.target;
var e = $(_e);
if (!e.parents('.fc').length) { // not already inside a calendar
var accept = options.dropAccept;
if ($.isFunction(accept) ? accept.call(_e, e) : e.is(accept)) {
_dragElement = _e;
currentView.dragStart(_dragElement, ev, ui);
}
}
})
.bind('dragstop', function(ev, ui) {
if (_dragElement) {
currentView.dragStop(_dragElement, ev, ui);
_dragElement = null;
}
});
}
}
function Header(calendar, options) {
var t = this;
// exports
t.render = render;
t.destroy = destroy;
t.updateTitle = updateTitle;
t.activateButton = activateButton;
t.deactivateButton = deactivateButton;
t.disableButton = disableButton;
t.enableButton = enableButton;
t.setTodayDefault = setTodayDefault;
t.setTodayScroll = setTodayScroll;
// locals
var element = $([]);
var tm;
function render() {
tm = options.theme ? 'ui' : 'fc';
var sections = options.header;
if (sections) {
element = $("<table class='fc-header' style='width:100%'/>")
.append(
$("<tr/>")
.append(renderSection('left'))
.append(renderSection('center'))
.append(renderSection('right'))
);
return element;
}
}
function destroy() {
element.remove();
}
function renderSection(position) {
var e = $("<td class='fc-header-" + position + "'/>");
var buttonStr = options.header[position];
if (buttonStr) {
$.each(buttonStr.split(' '), function(i) {
if (i > 0) {
e.append("<span class='fc-header-space'/>");
}
var prevButton;
$.each(this.split(','), function(j, buttonName) {
if (buttonName == 'title') {
e.append("<span class='fc-header-title'><h2>&nbsp;</h2></span>");
if (prevButton) {
prevButton.addClass(tm + '-corner-right');
}
prevButton = null;
}else{
var buttonClick;
if (calendar[buttonName]) {
buttonClick = calendar[buttonName]; // calendar method
}
else if (fcViews[buttonName]) {
buttonClick = function() {
button.removeClass(tm + '-state-hover'); // forget why
calendar.changeView(buttonName);
};
}
if (buttonClick) {
// var icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null; // why are we using smartProperty here?
var icon = (buttonName=='prev' || buttonName=='next') ? buttonName : null;
var text = smartProperty(options.buttonText, buttonName); // why are we using smartProperty here?
var button = $(
"<span class='fc-button fc-button-" + buttonName + " " + tm + "-state-default'>" +
"<span class='fc-button-inner'>" +
"<span class='fc-button-content'>" +
(icon ?
"<img src='images/arrow_" + icon + ".svg'/>" :
text
) +
"</span>" +
"<span class='fc-button-effect'><span></span></span>" +
"</span>" +
"</span>"
);
if (button) {
button
.click(function() {
if (!button.hasClass(tm + '-state-disabled')) {
buttonClick();
}
})
.mousedown(function() {
button
.not('.' + tm + '-state-active')
.not('.' + tm + '-state-disabled')
.addClass(tm + '-state-down');
})
.mouseup(function() {
button.removeClass(tm + '-state-down');
})
.hover(
function() {
button
.not('.' + tm + '-state-active')
.not('.' + tm + '-state-disabled')
.addClass(tm + '-state-hover');
},
function() {
button
.removeClass(tm + '-state-hover')
.removeClass(tm + '-state-down');
}
)
.appendTo(e);
if (!prevButton) {
button.addClass(tm + '-corner-left');
}
prevButton = button;
}
}
}
});
if (prevButton) {
prevButton.addClass(tm + '-corner-right');
}
});
}
return e;
}
function updateTitle(html) {
element.find('h2')
.html(html)
.attr('title', $("<div/>").html(html).text());
}
function activateButton(buttonName) {
element.find('span.fc-button-' + buttonName)
.addClass(tm + '-state-active');
}
function deactivateButton(buttonName) {
element.find('span.fc-button-' + buttonName)
.removeClass(tm + '-state-active');
}
function disableButton(buttonName) {
element.find('span.fc-button-' + buttonName)
.addClass(tm + '-state-disabled');
}
function enableButton(buttonName) {
element.find('span.fc-button-' + buttonName)
.removeClass(tm + '-state-disabled');
}
function setTodayDefault() {
var todayBt = element.find('span.fc-button-' + 'today');
var todayBtClc = calendar['today'];
todayBt.unbind('click');
todayBt.click(function(){
if(!todayBt.hasClass(tm + '-state-disabled')) {
todayBtClc();
}
});
}
function setTodayScroll(body) {
var todayBt = element.find('span.fc-button-' + 'today');
var todayBtClc = calendar['findToday'];
todayBt.unbind('click');
todayBt.click(function(){
if(!todayBt.hasClass(tm + '-state-disabled'))
todayBtClc();
});
}
}
fc.sourceNormalizers = [];
fc.sourceFetchers = [];
var ajaxDefaults = {
dataType: 'json',
cache: false
};
var eventGUID = 1;
function EventManager(options, _sources) {
var t = this;
// exports
t.isFetchNeeded = isFetchNeeded;
t.fetchEvents = fetchEvents;
t.addEventSource = addEventSource;
t.removeEventSource = removeEventSource;
t.removeEventSources = removeEventSources;
t.updateEvent = updateEvent;
t.renderEvent = renderEvent;
t.removeEvents = removeEvents;
t.clientEvents = clientEvents;
t.normalizeEvent = normalizeEvent;
// imports
var trigger = t.trigger;
var getView = t.getView;
var reportEvents = t.reportEvents;
// locals
var stickySource = { events: [] };
var sources = [ stickySource ];
var rangeStart, rangeEnd;
var currentFetchID = 0;
var pendingSourceCnt = 0;
var loadingLevel = 0;
var cache = [];
for (var i=0; i<_sources.length; i++) {
_addEventSource(_sources[i]);
}
/* Fetching
-----------------------------------------------------------------------------*/
function isFetchNeeded(start, end) {
return !rangeStart || start < rangeStart || end > rangeEnd;
}
function fetchEvents(start, end) {
rangeStart = start;
rangeEnd = end;
cache = [];
var fetchID = ++currentFetchID;
var len = sources.length;
pendingSourceCnt = len;
for (var i=0; i<len; i++) {
fetchEventSource(sources[i], fetchID);
}
}
function fetchEventSource(source, fetchID) {
_fetchEventSource(source, function(events) {
if (fetchID == currentFetchID) {
if (events) {
for (var i=0; i<events.length; i++) {
events[i].source = source;
normalizeEvent(events[i]);
}
cache = cache.concat(events);
}
pendingSourceCnt--;
if (!pendingSourceCnt) {
reportEvents(cache);
}
}
});
}
function _fetchEventSource(source, callback) {
var i;
var fetchers = fc.sourceFetchers;
var res;
for (i=0; i<fetchers.length; i++) {
res = fetchers[i](source, rangeStart, rangeEnd, callback);
if (res === true) {
// the fetcher is in charge. made its own async request
return;
}
else if (typeof res == 'object') {
// the fetcher returned a new source. process it
_fetchEventSource(res, callback);
return;
}
}
var events = source.events;
if (events) {
if ($.isFunction(events)) {
pushLoading();
events(cloneDate(rangeStart), cloneDate(rangeEnd), function(events) {
callback(events);
popLoading();
});
}
else if ($.isArray(events)) {
callback(events);
}
else {
callback();
}
}else{
var url = source.url;
if (url) {
var success = source.success;
var error = source.error;
var complete = source.complete;
var data = $.extend({}, source.data || {});
var startParam = firstDefined(source.startParam, options.startParam);
var endParam = firstDefined(source.endParam, options.endParam);
if (startParam) {
data[startParam] = Math.round(+rangeStart / 1000);
}
if (endParam) {
data[endParam] = Math.round(+rangeEnd / 1000);
}
pushLoading();
$.ajax($.extend({}, ajaxDefaults, source, {
data: data,
success: function(events) {
events = events || [];
var res = applyAll(success, this, arguments);
if ($.isArray(res)) {
events = res;
}
callback(events);
},
error: function() {
applyAll(error, this, arguments);
callback();
},
complete: function() {
applyAll(complete, this, arguments);
popLoading();
}
}));
}else{
callback();
}
}
}
/* Sources
-----------------------------------------------------------------------------*/
function addEventSource(source) {
source = _addEventSource(source);
if (source) {
pendingSourceCnt++;
fetchEventSource(source, currentFetchID); // will eventually call reportEvents
}
return source;
}
function _addEventSource(source) {
if ($.isFunction(source) || $.isArray(source)) {
source = { events: source };
}
else if (typeof source == 'string') {
source = { url: source };
}
if (typeof source == 'object') {
normalizeSource(source);
sources.push(source);
return source;
}
}
function removeEventSource(source) {
sources = $.grep(sources, function(src) {
return !isSourcesEqual(src, source);
});
// remove all client events from that source
cache = $.grep(cache, function(e) {
return !isSourcesEqual(e.source, source);
});
reportEvents(cache);
}
function removeEventSources() {
while(source = sources.shift()) {
// remove all client events from that source
cache = $.grep(cache, function(e) {
return !isSourcesEqual(e.source, source);
});
reportEvents(cache);
}
}
/* Manipulation
-----------------------------------------------------------------------------*/
function updateEvent(event) { // update an existing event
var i, len = cache.length, e,
defaultEventEnd = getView().defaultEventEnd, // getView???
startDelta = event.start - event._start,
endDelta = event.end ?
(event.end - (event._end || defaultEventEnd(event))) // event._end would be null if event.end
: 0; // was null and event was just resized
for (i=0; i<len; i++) {
e = cache[i];
if (e._id == event._id && e != event) {
e.start = new Date(+e.start + startDelta);
if (event.end) {
if (e.end) {
e.end = new Date(+e.end + endDelta);
}else{
e.end = new Date(+defaultEventEnd(e) + endDelta);
}
}else{
e.end = null;
}
e.title = event.title;
e.url = event.url;
e.allDay = event.allDay;
e.className = event.className;
e.editable = event.editable;
e.color = event.color;
e.backgroudColor = event.backgroudColor;
e.borderColor = event.borderColor;
e.textColor = event.textColor;
normalizeEvent(e);
}
}
normalizeEvent(event);
reportEvents(cache);
}
function renderEvent(event, stick) {
normalizeEvent(event);
if (!event.source) {
if (stick) {
stickySource.events.push(event);
event.source = stickySource;
}
}
// always push event to cache (issue #1112:)
cache.push(event);
reportEvents(cache);
}
function removeEvents(filter) {
var oldCache = cache;
if (!filter) { // remove all
cache = [];
// clear all array sources
/*for (var i=0; i<sources.length; i++) {
if ($.isArray(sources[i].events)) {
sources[i].events = [];
}
}*/
}else{
if (!$.isFunction(filter)) { // an event ID
var id = filter + '';
filter = function(e) {
return e._id == id;
};
}
cache = $.grep(cache, filter, true);
// remove events from array sources
/*for (var i=0; i<sources.length; i++) {
if ($.isArray(sources[i].events)) {
sources[i].events = $.grep(sources[i].events, filter, true);
}
}*/
}
if(oldCache.length != cache.length)
reportEvents(cache);
}
function clientEvents(filter) {
if ($.isFunction(filter)) {
return $.grep(cache, filter);
}
else if (filter) { // an event ID
filter += '';
return $.grep(cache, function(e) {
return e._id == filter;
});
}
return cache; // else, return all
}
/* Loading State
-----------------------------------------------------------------------------*/
function pushLoading() {
if (!loadingLevel++) {
trigger('loading', null, true);
}
}
function popLoading() {
if (!--loadingLevel) {
trigger('loading', null, false);
}
}
/* Event Normalization
-----------------------------------------------------------------------------*/
function normalizeEvent(event) {
var source = event.source || {};
var ignoreTimezone = firstDefined(source.ignoreTimezone, options.ignoreTimezone);
event._id = event._id || (event.id === undefined ? '_fc' + eventGUID++ : event.id + '');
if (event.date) {
if (!event.start) {
event.start = event.date;
}
delete event.date;
}
event._start = cloneDate(event.start = parseDate(event.start, ignoreTimezone));
event.end = parseDate(event.end, ignoreTimezone);
if (event.end && ((options.eventMode && event.end <= event.start) || (!options.eventMode && event.end < event.start))) {
event.end = null;
}
event._end = event.end ? cloneDate(event.end) : null;
if (event.allDay === undefined) {
event.allDay = firstDefined(source.allDayDefault, options.allDayDefault);
}
if (event.className) {
if (typeof event.className == 'string') {
event.className = event.className.split(/\s+/);
}
}else{
event.className = [];
}
// TODO: if there is no start date, return false to indicate an invalid event
}
/* Utils
------------------------------------------------------------------------------*/
function normalizeSource(source) {
if (source.className) {
// TODO: repeat code, same code for event classNames
if (typeof source.className == 'string') {
source.className = source.className.split(/\s+/);
}
}else{
source.className = [];
}
var normalizers = fc.sourceNormalizers;
for (var i=0; i<normalizers.length; i++) {
normalizers[i](source);
}
}
function isSourcesEqual(source1, source2) {
return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
}
function getSourcePrimitive(source) {
return ((typeof source == 'object') ? (source.events || source.url) : '') || source;
}
}
fc.addDays = addDays;
fc.cloneDate = cloneDate;
fc.parseDate = parseDate;
fc.parseISO8601 = parseISO8601;
fc.parseTime = parseTime;
fc.formatDate = formatDate;
fc.formatDates = formatDates;
/* Date Math
-----------------------------------------------------------------------------*/
var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
DAY_MS = 86400000,
HOUR_MS = 3600000,
MINUTE_MS = 60000;
function addYears(d, n, keepTime) {
d.setFullYear(d.getFullYear() + n);
if (!keepTime) {
clearTime(d);
}
return d;
}
function addMonths(d, n, keepTime) { // prevents day overflow/underflow
if (+d) { // prevent infinite looping on invalid dates
var m = d.getMonth() + n,
check = cloneDate(d);
check.setDate(1);
check.setMonth(m);
d.setMonth(m);
if (!keepTime) {
clearTime(d);
}
while (d.getMonth() != check.getMonth()) {
d.setDate(d.getDate() + (d < check ? 1 : -1));
}
}
return d;
}
function addDays(d, n, keepTime) { // deals with daylight savings
if (+d) {
var dd = d.getDate() + n,
check = cloneDate(d);
check.setHours(9); // set to middle of day
check.setDate(dd);
d.setDate(dd);
if (!keepTime) {
clearTime(d);
}
fixDate(d, check);
}
return d;
}
function fixDate(d, check) { // force d to be on check's YMD, for daylight savings purposes
if (+d) { // prevent infinite looping on invalid dates
while (d.getDate() != check.getDate()) {
d.setTime(+d + (d < check ? 1 : -1) * HOUR_MS);
}
}
}
function addMinutes(d, n) {
d.setMinutes(d.getMinutes() + n);
return d;
}
function clearTime(d) {
d.setHours(0);
d.setMinutes(0);
d.setSeconds(0);
d.setMilliseconds(0);
return d;
}
function cloneDate(d, dontKeepTime) {
if(d==null) {
return null;
}
else if (dontKeepTime) {
return clearTime(new Date(+d));
}
return new Date(+d);
}
function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
var i=0, d;
do {
d = new Date(1970, i++, 1);
} while (d.getHours()); // != 0
return d;
}
function skipWeekend(date, inc, excl) {
inc = inc || 1;
while (!date.getDay() || (excl && date.getDay()==1 || !excl && date.getDay()==6)) {
addDays(date, inc);
}
return date;
}
function dayDiff(d1, d2) { // d1 - d2
return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_MS);
}
function minDiff(d1, d2) { // d1 - d2
return Math.round((cloneDate(d1, false) - cloneDate(d2, false)) / MINUTE_MS);
}
function setYMD(date, y, m, d) {
if (y !== undefined && y != date.getFullYear()) {
date.setDate(1);
date.setMonth(0);
date.setFullYear(y);
}
if (m !== undefined && m != date.getMonth()) {
date.setDate(1);
date.setMonth(m);
}
if (d !== undefined) {
date.setDate(d);
}
}
/* Date Parsing
-----------------------------------------------------------------------------*/
function parseDate(s, ignoreTimezone) { // ignoreTimezone defaults to true
if (typeof s == 'object') { // already a Date object
return s;
}
if (typeof s == 'number') { // a UNIX timestamp
return new Date(s * 1000);
}
if (typeof s == 'string') {
if (s.match(/^\d+(\.\d+)?$/)) { // a UNIX timestamp
return new Date(parseFloat(s) * 1000);
}
if (ignoreTimezone === undefined) {
ignoreTimezone = true;
}
return parseISO8601(s, ignoreTimezone) || (s ? new Date(s) : null);
}
// TODO: never return invalid dates (like from new Date(<string>)), return null instead
return null;
}
function parseISO8601(s, ignoreTimezone) { // ignoreTimezone defaults to false
// derived from http://delete.me.uk/2005/03/iso8601.html
// TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
var m = s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?$/);
if (!m) {
return null;
}
var date = new Date(m[1], 0, 1);
if (ignoreTimezone || !m[13]) {
var check = new Date(m[1], 0, 1, 12, 0);
fixDate(date, check);
if (m[3]) {
date.setMonth(m[3] - 1);
check.setMonth(m[3] - 1);
}
if (m[5]) {
date.setDate(m[5]);
check.setDate(m[5]);
}
fixDate(date, check);
if (m[7]) {
date.setHours(m[7]);
}
if (m[8]) {
date.setMinutes(m[8]);
}
if (m[10]) {
date.setSeconds(m[10]);
}
if (m[12]) {
date.setMilliseconds(Number("0." + m[12]) * 1000);
}
fixDate(date, check);
}else{
date.setUTCFullYear(
m[1],
m[3] ? m[3] - 1 : 0,
m[5] || 1
);
date.setUTCHours(
m[7] || 0,
m[8] || 0,
m[10] || 0,
m[12] ? Number("0." + m[12]) * 1000 : 0
);
if (m[14]) {
var offset = Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0);
offset *= m[15] == '-' ? 1 : -1;
date = new Date(+date + (offset * 60 * 1000));
}
}
return date;
}
function parseTime(s) { // returns minutes since start of day
if (typeof s == 'number') { // an hour
return s * 60;
}
if (typeof s == 'object') { // a Date object
return s.getHours() * 60 + s.getMinutes();
}
var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
if (m) {
var h = parseInt(m[1], 10);
if (m[3]) {
h %= 12;
if (m[3].toLowerCase().charAt(0) == 'p') {
h += 12;
}
}
return h * 60 + (m[2] ? parseInt(m[2], 10) : 0);
}
}
/* Date Formatting
-----------------------------------------------------------------------------*/
// TODO: use same function formatDate(date, [date2], format, [options])
function formatDate(date, format, options) {
return formatDates(date, null, format, options);
}
function formatDates(date1, date2, format, options) {
options = options || defaults;
var date = date1,
otherDate = date2,
i, len = format.length, c,
i2, formatter,
res = '';
for (i=0; i<len; i++) {
c = format.charAt(i);
if (c == "'") {
for (i2=i+1; i2<len; i2++) {
if (format.charAt(i2) == "'") {
if (date) {
if (i2 == i+1) {
res += "'";
}else{
res += format.substring(i+1, i2);
}
i = i2;
}
break;
}
}
}
else if (c == '(') {
for (i2=i+1; i2<len; i2++) {
if (format.charAt(i2) == ')') {
var subres = formatDate(date, format.substring(i+1, i2), options);
if (parseInt(subres.replace(/\D/, ''), 10)) {
res += subres;
}
i = i2;
break;
}
}
}
else if (c == '[') {
for (i2=i+1; i2<len; i2++) {
if (format.charAt(i2) == ']') {
var subformat = format.substring(i+1, i2);
var subres = formatDate(date, subformat, options);
if (subres != formatDate(otherDate, subformat, options)) {
res += subres;
}
i = i2;
break;
}
}
}
else if (c == '{') {
date = date2;
otherDate = date1;
}
else if (c == '}') {
date = date1;
otherDate = date2;
}
else {
for (i2=len; i2>i; i2--) {
if (formatter = dateFormatters[format.substring(i, i2)]) {
if (date) {
res += formatter(date, options);
}
i = i2 - 1;
break;
}
}
if (i2 == i) {
if (date) {
res += c;
}
}
}
}
return res;
};
var dateFormatters = {
s : function(d) {return d.getSeconds() },
ss : function(d) {return zeroPad(d.getSeconds())},
m : function(d) {return d.getMinutes()},
mm : function(d) {return zeroPad(d.getMinutes())},
h : function(d) {return d.getHours() % 12 || 12},
hh : function(d) {return zeroPad(d.getHours() % 12 || 12)},
H : function(d) {return d.getHours()},
HH : function(d) {return zeroPad(d.getHours())},
d : function(d) {return d.getDate()},
dd : function(d) {return zeroPad(d.getDate())},
ddd : function(d,o) {return o.dayNamesShort[d.getDay()]},
dddd: function(d,o) {return o.dayNames[d.getDay()]},
W : function(d) {return getWeekNumber(d)},
M : function(d) {return d.getMonth() + 1},
MM : function(d) {return zeroPad(d.getMonth() + 1)},
MMM : function(d,o) {return o.monthNamesShort[d.getMonth()]},
MMMM: function(d,o) {return o.monthNames[d.getMonth()]},
yy : function(d) {return (d.getFullYear()+'').substring(2)},
yyyy: function(d) {return d.getFullYear()},
t : function(d) {return d.getHours() < 12 ? 'a' : 'p'},
tt : function(d) {return d.getHours() < 12 ? 'am' : 'pm'},
T : function(d) {return d.getHours() < 12 ? 'A' : 'P'},
TT : function(d) {return d.getHours() < 12 ? 'AM' : 'PM'},
u : function(d) {return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'")},
S : function(d) {
var date = d.getDate();
if (date > 10 && date < 20) {
return 'th';
}
return ['st', 'nd', 'rd'][date%10-1] || 'th';
}
};
fc.applyAll = applyAll;
/* Event Date Math
-----------------------------------------------------------------------------*/
function exclEndDay(event) {
if (event.end) {
return _exclEndDay(event.end, event.allDay);
}else{
return addDays(cloneDate(event.start), 1);
}
}
function _exclEndDay(end, allDay) {
end = cloneDate(end);
return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : clearTime(end);
}
function segCmp(a, b) {
return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
}
function segsCollide(seg1, seg2) {
return seg1.end > seg2.start && seg1.start < seg2.end;
}
/* Event Sorting
-----------------------------------------------------------------------------*/
// event rendering utilities
function sliceSegs(events, visEventEnds, start, end) {
var segs = [],
i, len=events.length, event,
eventStart, eventEnd,
segStart, segEnd,
isStart, isEnd;
for (i=0; i<len; i++) {
event = events[i];
eventStart = event.start;
eventEnd = visEventEnds[i];
if (eventEnd > start && eventStart < end) {
if (eventStart < start) {
segStart = cloneDate(start);
isStart = false;
}else{
segStart = eventStart;
isStart = true;
}
if (eventEnd > end) {
segEnd = cloneDate(end);
isEnd = false;
}else{
segEnd = eventEnd;
isEnd = true;
}
segs.push({
event: event,
start: segStart,
end: segEnd,
isStart: isStart,
isEnd: isEnd,
msLength: segEnd - segStart
});
}
}
return segs.sort(segCmp);
}
// event rendering calculation utilities
function stackSegs(segs) {
var levels = [],
i, len = segs.length, seg,
j, collide, k;
for (i=0; i<len; i++) {
seg = segs[i];
j = 0; // the level index where seg should belong
while (true) {
collide = false;
if (levels[j]) {
for (k=0; k<levels[j].length; k++) {
if (segsCollide(levels[j][k], seg)) {
collide = true;
break;
}
}
}
if (collide) {
j++;
}else{
break;
}
}
if (levels[j]) {
levels[j].push(seg);
}else{
levels[j] = [seg];
}
}
return levels;
}
/* Event Element Binding
-----------------------------------------------------------------------------*/
function lazySegBind(container, segs, bindHandlers) {
container.unbind('mouseover').mouseover(function(ev) {
var parent=ev.target, e,
i, seg;
while (parent != this) {
e = parent;
parent = parent.parentNode;
}
if ((i = e._fci) !== undefined) {
e._fci = undefined;
seg = segs[i];
bindHandlers(seg.event, seg.element, seg);
$(ev.target).trigger(ev);
}
ev.stopPropagation();
});
}
/* Element Dimensions
-----------------------------------------------------------------------------*/
function setOuterWidth(element, width, includeMargins) {
for (var i=0, e; i<element.length; i++) {
e = $(element[i]);
e.width(Math.max(0, width - hsides(e, includeMargins)));
}
}
function setOuterHeight(element, height, includeMargins) {
for (var i=0, e; i<element.length; i++) {
e = $(element[i]);
e.height(Math.max(0, height - vsides(e, includeMargins)));
}
}
function hsides(element, includeMargins) {
return hpadding(element) + hborders(element) + (includeMargins ? hmargins(element) : 0);
}
function hpadding(element) {
return (parseFloat($.css(element[0], 'paddingLeft', true)) || 0) +
(parseFloat($.css(element[0], 'paddingRight', true)) || 0);
}
function hmargins(element) {
return (parseFloat($.css(element[0], 'marginLeft', true)) || 0) +
(parseFloat($.css(element[0], 'marginRight', true)) || 0);
}
function hborders(element) {
return (parseFloat($.css(element[0], 'borderLeftWidth', true)) || 0) +
(parseFloat($.css(element[0], 'borderRightWidth', true)) || 0);
}
function vsides(element, includeMargins) {
return vpadding(element) + vborders(element) + (includeMargins ? vmargins(element) : 0);
}
function vpadding(element) {
return (parseFloat($.css(element[0], 'paddingTop', true)) || 0) +
(parseFloat($.css(element[0], 'paddingBottom', true)) || 0);
}
function vmargins(element) {
return (parseFloat($.css(element[0], 'marginTop', true)) || 0) +
(parseFloat($.css(element[0], 'marginBottom', true)) || 0);
}
function vborders(element) {
return (parseFloat($.css(element[0], 'borderTopWidth', true)) || 0) +
(parseFloat($.css(element[0], 'borderBottomWidth', true)) || 0);
}
function setMinHeight(element, height) {
height = (typeof height == 'number' ? height + 'px' : height);
element.each(function(i, _element) {
_element.style.cssText += ';min-height:' + height + ';_height:' + height;
// why can't we just use .css() ? i forget
});
}
/* Misc Utils
-----------------------------------------------------------------------------*/
//TODO: arraySlice
//TODO: isFunction, grep ?
function noop() { }
function cmp(a, b) {
return a - b;
}
function arrayMax(a) {
return Math.max.apply(Math, a);
}
function zeroPad(n) {
return (n < 10 ? '0' : '') + n;
}
function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object
if (obj[name] !== undefined) {
return obj[name];
}
var parts = name.split(/(?=[A-Z])/),
i=parts.length-1, res;
for (; i>=0; i--) {
res = obj[parts[i].toLowerCase()];
if (res !== undefined) {
return res;
}
}
return obj[''];
}
function htmlEscape(s) {
return s.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/'/g, '&#039;')
.replace(/"/g, '&quot;')
.replace(/\n/g, '<br />');
}
function cssKey(_element) {
return _element.id + '/' + _element.className + '/' + _element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, '');
}
function disableTextSelection(element) {
element
.attr('unselectable', 'on')
.css('MozUserSelect', 'none')
.bind('selectstart.ui', function() { return false; });
}
/*
function enableTextSelection(element) {
element
.attr('unselectable', 'off')
.css('MozUserSelect', '')
.unbind('selectstart.ui');
}
*/
function markFirstLast(e) {
e.children()
.removeClass('fc-first fc-last')
.filter(':first-child')
.addClass('fc-first')
.end()
.filter(':last-child')
.addClass('fc-last');
}
function setDayID(cell, date, opt) {
cell.each(function(i, _cell) {
_cell.className = _cell.className.replace(/^fc-\w*( fc-weekend-day)?/, 'fc-' + dayIDs[date.getDay()] + (opt('weekendDays').length>0 && opt('weekendDays').indexOf(date.getDay())!=-1 ? ' fc-weekend-day' : ''));
// TODO: make a way that doesn't rely on order of classes
});
}
function getSkinCss(event, opt) {
var source = event.source || {};
var eventColor = event.color;
var sourceColor = source.color;
var optionColor = opt('eventColor');
var backgroundColor =
event.backgroundColor ||
eventColor ||
source.backgroundColor ||
sourceColor ||
opt('eventBackgroundColor') ||
optionColor;
var borderColor =
event.borderColor ||
eventColor ||
source.borderColor ||
sourceColor ||
opt('eventBorderColor') ||
optionColor;
var textColor =
event.textColor ||
source.textColor ||
opt('eventTextColor');
var statements = [];
if (backgroundColor) {
statements.push('background-color:' + backgroundColor);
}
if (borderColor) {
statements.push('border-color:' + borderColor);
}
if (textColor) {
statements.push('color:' + textColor);
}
return statements.join(';');
}
function applyAll(functions, thisObj, args) {
if ($.isFunction(functions)) {
functions = [ functions ];
}
if (functions) {
var i;
var ret;
for (i=0; i<functions.length; i++) {
ret = functions[i].apply(thisObj, args) || ret;
}
return ret;
}
}
function firstDefined() {
for (var i=0; i<arguments.length; i++) {
if (arguments[i] !== undefined) {
return arguments[i];
}
}
}
fcViews.month = MonthView;
function MonthView(element, calendar) {
var t = this;
// exports
t.render = render;
// imports
BasicView.call(t, element, calendar, 'month');
var opt = t.opt;
var renderBasic = t.renderBasic;
var formatDate = calendar.formatDate;
function render(date, delta) {
if (delta) {
addMonths(date, delta);
date.setDate(1);
}
var start = cloneDate(date, true);
start.setDate(1);
var end = addMonths(cloneDate(start), 1);
var visStart = cloneDate(start);
var visEnd = cloneDate(end);
var firstDay = opt('firstDay');
var nwe = opt('weekends') ? 0 : 1;
if (nwe) {
skipWeekend(visStart);
skipWeekend(visEnd, -1, true);
}
addDays(visStart, -((visStart.getDay() - Math.max(firstDay, nwe) + 7) % 7));
addDays(visEnd, (7 - visEnd.getDay() + Math.max(firstDay, nwe)) % 7);
var rowCnt = Math.round((visEnd - visStart) / (DAY_MS * 7));
if (opt('weekMode') == 'fixed') {
addDays(visEnd, (6 - rowCnt) * 7);
rowCnt = 6;
}
t.title = formatDate(start, opt('titleFormat'));
t.start = start;
t.end = end;
t.visStart = visStart;
t.visEnd = visEnd;
renderBasic(6, rowCnt, nwe ? 5 : 7, true);
}
}
fcViews.multiWeek = MultiWeekView;
function MultiWeekView(element, calendar) {
var t = this;
// exports
t.render = render;
// imports
BasicView.call(t, element, calendar, 'multiWeek');
var opt = t.opt;
var renderBasic = t.renderBasic;
var formatDates = calendar.formatDates;
function render(date, delta) {
if (delta) {
addDays(date, delta * opt('multiWeekSize') * 7);
}
//Adjust displayed date-range, to make sure today will always stay in the top row
var currentDate = cloneDate(new Date(), true);
var dateWeekStart = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
var currentWeekStart = addDays(cloneDate(currentDate), -((currentDate.getDay() - opt('firstDay') + 7) % 7));
if(opt('multiWeekSize')>0)
addDays(date, -(( - (Math.abs(Math.ceil(dayDiff(dateWeekStart, currentWeekStart) / 7)) % opt('multiWeekSize'))) % opt('multiWeekSize')) * 7);
//var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
var start = cloneDate(date);
//var end = addDays(cloneDate(start), opt('multiWeekSize') * 7);
//var visStart = cloneDate(start);
var visStart = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
var end = addDays(cloneDate(visStart), opt('multiWeekSize') * 7);
var visEnd = cloneDate(end);
var firstDay = opt('firstDay');
var nwe = opt('weekends') ? 0 : 1;
if (nwe) {
skipWeekend(visStart);
skipWeekend(visEnd, -1, true);
}
addDays(visStart, -((visStart.getDay() - Math.max(firstDay, nwe) + 7) % 7));
addDays(visEnd, (7 - visEnd.getDay() + Math.max(firstDay, nwe)) % 7);
t.title = formatDates(
visStart,
addDays(cloneDate(visEnd), -1),
opt('titleFormat')
);
t.start = start;
t.end = end;
t.visStart = visStart;
t.visEnd = visEnd;
renderBasic(opt('multiWeekSize'), opt('multiWeekSize'), nwe ? 5 : 7, true);
}
}
fcViews.basicWeek = BasicWeekView;
function BasicWeekView(element, calendar) {
var t = this;
// exports
t.render = render;
// imports
BasicView.call(t, element, calendar, 'basicWeek');
var opt = t.opt;
var renderBasic = t.renderBasic;
var formatDates = calendar.formatDates;
function render(date, delta) {
if (delta) {
addDays(date, delta * 7);
}
var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
var end = addDays(cloneDate(start), 7);
var visStart = cloneDate(start);
var visEnd = cloneDate(end);
var weekends = opt('weekends');
if (!weekends) {
skipWeekend(visStart);
skipWeekend(visEnd, -1, true);
}
t.title = formatDates(
visStart,
addDays(cloneDate(visEnd), -1),
opt('titleFormat')
);
t.start = start;
t.end = end;
t.visStart = visStart;
t.visEnd = visEnd;
renderBasic(1, 1, weekends ? 7 : 5, false);
}
}
fcViews.basicDay = BasicDayView;
//TODO: when calendar's date starts out on a weekend, shouldn't happen
function BasicDayView(element, calendar) {
var t = this;
// exports
t.render = render;
// imports
BasicView.call(t, element, calendar, 'basicDay');
var opt = t.opt;
var renderBasic = t.renderBasic;
var formatDate = calendar.formatDate;
function render(date, delta) {
if (delta) {
addDays(date, delta);
if (!opt('weekends')) {
skipWeekend(date, delta < 0 ? -1 : 1);
}
}
t.title = formatDate(date, opt('titleFormat'));
t.start = t.visStart = cloneDate(date, true);
t.end = t.visEnd = addDays(cloneDate(t.start), 1);
renderBasic(1, 1, 1, false);
}
}
setDefaults({
weekMode: 'fixed'
});
function BasicView(element, calendar, viewName) {
var t = this;
// exports
t.renderBasic = renderBasic;
t.setHeight = setHeight;
t.setWidth = setWidth;
t.renderDayOverlay = renderDayOverlay;
t.defaultSelectionEnd = defaultSelectionEnd;
t.renderSelection = renderSelection;
t.clearSelection = clearSelection;
t.reportDayClick = reportDayClick; // for selection (kinda hacky)
t.dragStart = dragStart;
t.dragStop = dragStop;
t.defaultEventEnd = defaultEventEnd;
t.getHoverListener = function() { return hoverListener };
t.colContentLeft = colContentLeft;
t.colContentRight = colContentRight;
t.dayOfWeekCol = dayOfWeekCol;
t.dateCell = dateCell;
t.cellDate = cellDate;
t.cellIsAllDay = function() { return true };
t.allDayRow = allDayRow;
t.allDayBounds = allDayBounds;
t.getRowCnt = function() { return rowCnt };
t.getColCnt = function() { return colCnt };
t.getColWidth = function() { return colWidth };
t.getDaySegmentContainer = function() { return daySegmentContainer };
t.updateGrid = updateGrid;
t.updateToday = updateToday;
t.setAxisFormat = setAxisFormat;
t.setStartOfBusiness = setStartOfBusiness;
t.setEndOfBusiness = setEndOfBusiness;
t.setWeekendDays = setWeekendDays;
t.setBindingMode = setBindingMode;
t.setSelectable = setSelectable;
// imports
View.call(t, element, calendar, viewName);
OverlayManager.call(t);
SelectionManager.call(t);
BasicEventRenderer.call(t);
var opt = t.opt;
var trigger = t.trigger;
var clearEvents = t.clearEvents;
var renderOverlay = t.renderOverlay;
var clearOverlays = t.clearOverlays;
var daySelectionMousedown = t.daySelectionMousedown;
var formatDate = calendar.formatDate;
// locals
var head;
var headCells;
var body;
var bodyRows;
var bodyCells;
var bodyFirstCells;
var bodyCellTopInners;
var daySegmentContainer;
var viewWidth;
var viewHeight;
var colWidth;
var rowCnt, colCnt;
var coordinateGrid;
var hoverListener;
var colContentPositions;
var rtl, dis, dit;
var firstDay;
var nwe;
var tm;
var colFormat;
/* Rendering
------------------------------------------------------------*/
disableTextSelection(element.addClass('fc-grid'));
function renderBasic(maxr, r, c, showNumbers) {
rowCnt = r;
colCnt = c;
updateOptions();
var firstTime = !body;
if (firstTime) {
buildSkeleton(maxr, showNumbers);
}else{
clearEvents();
}
updateCells(true);
}
function updateOptions() {
rtl = opt('isRTL');
if (rtl) {
dis = -1;
dit = colCnt - 1;
}else{
dis = 1;
dit = 0;
}
firstDay = opt('firstDay');
nwe = opt('weekends') ? 0 : 1;
tm = opt('theme') ? 'ui' : 'fc';
colFormat = opt('columnFormat');
}
function buildSkeleton(maxRowCnt, showNumbers) {
var s;
var headerClass = tm + "-widget-header";
var contentClass = tm + "-widget-content";
var i, j;
var table;
s =
"<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
"<thead>" +
"<tr>";
for (i=0; i<colCnt; i++) {
s +=
"<th class='fc- " + headerClass + "'/>"; // need fc- for setDayID
}
s +=
"</tr>" +
"</thead>" +
"<tbody>";
for (i=0; i<maxRowCnt; i++) {
s +=
"<tr class='fc-week" + i + "'>";
for (j=0; j<colCnt; j++) {
s +=
"<td class='fc- " + contentClass + " fc-day" + (i*colCnt+j) + "'>" + // need fc- for setDayID
"<div>" +
(showNumbers ?
"<div class='fc-day-header'><div class='fc-week-number'/><div class='fc-day-text'/><div class='fc-day-number'/></div>" :
''
) +
"<div class='fc-day-content'>" +
"<div style='position:relative'>&nbsp;</div>" +
"</div>" +
"</div>" +
"</td>";
}
s +=
"</tr>";
}
s +=
"</tbody>" +
"</table>";
table = $(s).appendTo(element);
head = table.find('thead');
headCells = head.find('th');
body = table.find('tbody');
bodyRows = body.find('tr');
bodyCells = body.find('td');
bodyFirstCells = bodyCells.filter(':first-child');
bodyCellTopInners = bodyRows.eq(0).find('div.fc-day-content div');
markFirstLast(head.add(head.find('tr'))); // marks first+last tr/th's
markFirstLast(bodyRows); // marks first+last td's
bodyRows.eq(0).addClass('fc-first'); // fc-last is done in updateCells
dayBind(bodyCells);
daySegmentContainer =
$("<div style='position:absolute;z-index:8;top:0;left:0'/>")
.appendTo(element);
}
function updateCells(firstTime) {
var dowDirty = firstTime || rowCnt == 1; // could the cells' day-of-weeks need updating?
var month = t.start.getMonth();
var today = clearTime(new Date());
var cell;
var date;
var row;
if (dowDirty) {
headCells.each(function(i, _cell) {
cell = $(_cell);
date = indexDate(i);
cell.html(formatDate(date, colFormat));
setDayID(cell, date, opt);
});
}
bodyCells.each(function(i, _cell) {
cell = $(_cell);
date = indexDate(i);
if (date.getMonth() == month) {
cell.removeClass('fc-other-month');
}else{
cell.addClass('fc-other-month');
}
if(opt('showWeekNumbers') && (i % 7 == 0)) {
removeWeekNumber(cell, date);
addWeekNumber(cell, date);
}
if (+date == +today) {
cell.addClass(tm + '-state-highlight fc-today');
removeTodayText(cell, opt('buttonText', 'today'));
addTodayText(cell, opt('buttonText', 'today'));
}else{
cell.removeClass(tm + '-state-highlight fc-today');
removeTodayText(cell, opt('buttonText', 'today'));
}
cell.find('div.fc-day-number').text(date.getDate());
if (dowDirty) {
setDayID(cell, date, opt);
}
});
bodyRows.each(function(i, _row) {
row = $(_row);
if (i < rowCnt) {
row.show();
if (i == rowCnt-1) {
row.addClass('fc-last');
}else{
row.removeClass('fc-last');
}
}else{
row.hide();
}
});
}
function updateGrid()
{
updateToday();
setAxisFormat();
setStartOfBusiness();
setEndOfBusiness();
setWeekendDays();
setBindingMode();
setSelectable();
}
function updateToday()
{
var today = clearTime(new Date());
var cell;
var date;
bodyCells.each(function(i, _cell) {
cell = $(_cell);
date = indexDate(i);
if (+date == +today) {
cell.addClass(tm + '-state-highlight fc-today');
removeTodayText(cell, opt('buttonText', 'today'));
addTodayText(cell, opt('buttonText', 'today'));
}else{
cell.removeClass(tm + '-state-highlight fc-today');
removeTodayText(cell, opt('buttonText', 'today'));
}
});
}
function setAxisFormat()
{
// dummy
}
function setStartOfBusiness()
{
// dummy
}
function setEndOfBusiness()
{
// dummy
}
function setWeekendDays()
{
headCells.each(function(i, _cell) {
setDayID($(_cell), indexDate(i), opt);
});
bodyCells.each(function(i, _cell) {
setDayID($(_cell), indexDate(i), opt);
});
}
function setBindingMode()
{
dayBind(bodyCells);
}
function setSelectable()
{
dayBind(bodyCells);
}
function setHeight(height) {
viewHeight = height;
var bodyHeight = viewHeight - head.height();
var rowHeight;
var rowHeightLast;
var cell;
if (opt('weekMode') == 'variable') {
rowHeight = rowHeightLast = Math.floor(bodyHeight / (rowCnt==1 ? 2 : 6));
}else{
rowHeight = Math.floor(bodyHeight / rowCnt);
rowHeightLast = bodyHeight - rowHeight * (rowCnt-1);
}
bodyFirstCells.each(function(i, _cell) {
if (i < rowCnt) {
cell = $(_cell);
setMinHeight(
cell.find('> div'),
(i==rowCnt-1 ? rowHeightLast : rowHeight) - vsides(cell)
);
}
});
}
function setWidth(width) {
viewWidth = width;
colContentPositions.clear();
colWidth = Math.floor(viewWidth / colCnt);
setOuterWidth(headCells.slice(0, -1), colWidth);
}
/* Day clicking and binding
-----------------------------------------------------------*/
function dayBind(days) {
days.unbind('click dblclick');
if(opt('bindingMode') == 'double')
days.dblclick(dayClick).mousedown(daySelectionMousedown);
else
days.click(dayClick).mousedown(daySelectionMousedown);
}
function dayClick(ev) {
//if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
var index = parseInt(this.className.match(/fc\-day(\d+)/)[1]); // TODO: maybe use .data
var date = indexDate(index);
trigger('dayClick', this, date, true, ev);
//}
}
/* Semi-transparent Overlay Helpers
------------------------------------------------------*/
function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive
if (refreshCoordinateGrid) {
coordinateGrid.build();
}
var rowStart = cloneDate(t.visStart);
var rowEnd = addDays(cloneDate(rowStart), colCnt);
for (var i=0; i<rowCnt; i++) {
var stretchStart = new Date(Math.max(rowStart, overlayStart));
var stretchEnd = new Date(Math.min(rowEnd, overlayEnd));
if (stretchStart < stretchEnd) {
var colStart, colEnd;
if (rtl) {
colStart = dayDiff(stretchEnd, rowStart)*dis+dit+1;
colEnd = dayDiff(stretchStart, rowStart)*dis+dit+1;
}else{
colStart = dayDiff(stretchStart, rowStart);
colEnd = dayDiff(stretchEnd, rowStart);
}
dayBind(
renderCellOverlay(i, colStart, i, colEnd-1)
);
}
addDays(rowStart, 7);
addDays(rowEnd, 7);
}
}
function renderCellOverlay(row0, col0, row1, col1) { // row1,col1 is inclusive
var rect = coordinateGrid.rect(row0, col0, row1, col1, element);
return renderOverlay(rect, element);
}
/* Selection
-----------------------------------------------------------------------*/
function defaultSelectionEnd(startDate, allDay) {
return cloneDate(startDate);
}
function renderSelection(startDate, endDate, allDay) {
renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true); // rebuild every time???
}
function clearSelection() {
clearOverlays();
}
function reportDayClick(date, allDay, ev) {
var cell = dateCell(date);
var _element = bodyCells[cell.row*colCnt + cell.col];
trigger('dayClick', _element, date, allDay, ev);
}
/* External Dragging
-----------------------------------------------------------------------*/
function dragStart(_dragElement, ev, ui) {
hoverListener.start(function(cell) {
clearOverlays();
if (cell) {
renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
}
}, ev);
}
function dragStop(_dragElement, ev, ui) {
var cell = hoverListener.stop();
clearOverlays();
if (cell) {
var d = cellDate(cell);
trigger('drop', _dragElement, d, true, ev, ui);
}
}
/* Utilities
--------------------------------------------------------*/
function defaultEventEnd(event) {
return cloneDate(event.start);
}
coordinateGrid = new CoordinateGrid(function(rows, cols) {
var e, n, p;
headCells.each(function(i, _e) {
e = $(_e);
n = e.offset().left;
if (i) {
p[1] = n;
}
p = [n];
cols[i] = p;
});
p[1] = n + e.outerWidth();
bodyRows.each(function(i, _e) {
if (i < rowCnt) {
e = $(_e);
n = e.offset().top;
if (i) {
p[1] = n;
}
p = [n];
rows[i] = p;
}
});
p[1] = n + e.outerHeight();
});
hoverListener = new HoverListener(coordinateGrid);
colContentPositions = new HorizontalPositionCache(function(col) {
return bodyCellTopInners.eq(col);
});
function colContentLeft(col) {
return colContentPositions.left(col);
}
function colContentRight(col) {
return colContentPositions.right(col);
}
function dateCell(date) {
return {
row: Math.floor(dayDiff(date, t.visStart) / 7),
col: dayOfWeekCol(date.getDay())
};
}
function cellDate(cell) {
return _cellDate(cell.row, cell.col);
}
function _cellDate(row, col) {
return addDays(cloneDate(t.visStart), row*7 + col*dis+dit);
// what about weekends in middle of week?
}
function indexDate(index) {
return _cellDate(Math.floor(index/colCnt), index%colCnt);
}
function dayOfWeekCol(dayOfWeek) {
return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt) * dis + dit;
}
function allDayRow(i) {
return bodyRows.eq(i);
}
function allDayBounds(i) {
return {
left: 0,
right: viewWidth
};
}
}
function BasicEventRenderer() {
var t = this;
// exports
t.renderEvents = renderEvents;
t.compileDaySegs = compileSegs; // for DayEventRenderer
t.clearEvents = clearEvents;
t.bindDaySeg = bindDaySeg;
// imports
DayEventRenderer.call(t);
var opt = t.opt;
var trigger = t.trigger;
//var setOverflowHidden = t.setOverflowHidden;
var isEventDraggable = t.isEventDraggable;
var isEventResizable = t.isEventResizable;
var reportEvents = t.reportEvents;
var reportEventClear = t.reportEventClear;
var eventElementHandlers = t.eventElementHandlers;
var showEvents = t.showEvents;
var hideEvents = t.hideEvents;
var eventDrop = t.eventDrop;
var getDaySegmentContainer = t.getDaySegmentContainer;
var getHoverListener = t.getHoverListener;
var renderDayOverlay = t.renderDayOverlay;
var clearOverlays = t.clearOverlays;
var getRowCnt = t.getRowCnt;
var getColCnt = t.getColCnt;
var renderDaySegs = t.renderDaySegs;
var resizableDayEvent = t.resizableDayEvent;
/* Rendering
--------------------------------------------------------------------*/
function renderEvents(events, modifiedEventId) {
reportEvents(events);
renderDaySegs(compileSegs(events), modifiedEventId, false);
}
function clearEvents() {
reportEventClear();
getDaySegmentContainer().empty();
}
function compileSegs(events) {
var rowCnt = getRowCnt(),
colCnt = getColCnt(),
d1 = cloneDate(t.visStart),
d2 = addDays(cloneDate(d1), colCnt),
visEventsEnds = $.map(events, exclEndDay),
i, row,
j, level,
k, seg,
segs=[];
for (i=0; i<rowCnt; i++) {
row = stackSegs(sliceSegs(events, visEventsEnds, d1, d2));
for (j=0; j<row.length; j++) {
level = row[j];
for (k=0; k<level.length; k++) {
seg = level[k];
seg.row = i;
seg.level = j; // not needed anymore
segs.push(seg);
}
}
addDays(d1, 7);
addDays(d2, 7);
}
return segs;
}
function bindDaySeg(event, eventElement, seg) {
if (isEventDraggable(event)) {
draggableDayEvent(event, eventElement);
}
if (seg.isEnd && isEventResizable(event)) {
resizableDayEvent(event, eventElement, seg);
}
eventElementHandlers(event, eventElement);
// needs to be after, because resizableDayEvent might stopImmediatePropagation on click
}
/* Dragging
----------------------------------------------------------------------------*/
function draggableDayEvent(event, eventElement) {
var hoverListener = getHoverListener();
var dayDelta;
eventElement.draggable({
zIndex: 9,
delay: 50,
scroll: false,
opacity: opt('dragOpacity'),
revertDuration: opt('dragRevertDuration'),
start: function(ev, ui) {
trigger('eventDragStart', eventElement, event, ev, ui);
//hideEvents(event, eventElement);
hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
clearOverlays();
if (cell) {
//setOverflowHidden(true);
dayDelta = rowDelta*7 + colDelta * (opt('isRTL') ? -1 : 1);
renderDayOverlay(
addDays(cloneDate(event.start), dayDelta),
addDays(exclEndDay(event), dayDelta)
);
}else{
//setOverflowHidden(false);
dayDelta = 0;
}
}, ev, 'drag');
},
stop: function(ev, ui) {
hoverListener.stop();
clearOverlays();
trigger('eventDragStop', eventElement, event, ev, ui);
if (dayDelta) {
eventDrop(this, event, dayDelta, 0, event.allDay, ev, ui);
}else{
eventElement.css('filter', ''); // clear IE opacity side-effects
//showEvents(event, eventElement);
}
//setOverflowHidden(false);
}
});
}
}
fcViews.agendaWeek = AgendaWeekView;
function AgendaWeekView(element, calendar) {
var t = this;
// exports
t.render = render;
// imports
AgendaView.call(t, element, calendar, 'agendaWeek');
var opt = t.opt;
var renderAgenda = t.renderAgenda;
var formatDates = calendar.formatDates;
function render(date, delta) {
if (delta) {
addDays(date, delta * 7);
}
var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
var end = addDays(cloneDate(start), 7);
var visStart = cloneDate(start);
var visEnd = cloneDate(end);
var weekends = opt('weekends');
if (!weekends) {
skipWeekend(visStart);
skipWeekend(visEnd, -1, true);
}
t.title = formatDates(
visStart,
addDays(cloneDate(visEnd), -1),
opt('titleFormat')
);
t.start = start;
t.end = end;
t.visStart = visStart;
t.visEnd = visEnd;
renderAgenda(weekends ? 7 : 5);
}
}
fcViews.agendaDay = AgendaDayView;
function AgendaDayView(element, calendar) {
var t = this;
// exports
t.render = render;
t.addedView = null;
// imports
AgendaView.call(t, element, calendar, 'agendaDay');
var opt = t.opt;
var renderAgenda = t.renderAgenda;
var formatDate = calendar.formatDate;
function render(date, delta) {
if (delta) {
addDays(date, delta);
if (!opt('weekends')) {
skipWeekend(date, delta < 0 ? -1 : 1);
}
}
var start = cloneDate(date, true);
var end = addDays(cloneDate(start), 1);
t.title = formatDate(date, opt('titleFormat'));
t.start = t.visStart = start;
t.end = t.visEnd = end;
renderAgenda(1);
if(t.addedView) {
t.addedView.render(date);
}
}
}
setDefaults({
allDaySlot: true,
allDayText: 'all-day',
firstHour: 6,
slotMinutes: 30,
defaultEventMinutes: 120,
axisFormat: 'h(:mm)tt',
timeFormat: {
agenda: 'h:mm{ h:mm}'
},
dragOpacity: {
agenda: .5
},
minTime: 0,
maxTime: 24
});
// TODO: make it work in quirks mode (event corners, all-day height)
// TODO: test liquid width, especially in IE6
function AgendaView(element, calendar, viewName) {
var t = this;
// exports
t.renderAgenda = renderAgenda;
t.setWidth = setWidth;
t.setHeight = setHeight;
t.beforeHide = beforeHide;
t.afterShow = afterShow;
t.defaultEventEnd = defaultEventEnd;
t.timePosition = timePosition;
t.dayOfWeekCol = dayOfWeekCol;
t.dateCell = dateCell;
t.cellDate = cellDate;
t.cellIsAllDay = cellIsAllDay;
t.allDayRow = getAllDayRow;
t.allDayBounds = allDayBounds;
t.getHoverListener = function() { return hoverListener };
t.colContentLeft = colContentLeft;
t.colContentRight = colContentRight;
t.getDaySegmentContainer = function() { return daySegmentContainer };
t.getSlotJumpersTop = function() { return slotJumpersTop };
t.getSlotJumpersBottom = function() { return slotJumpersBottom };
t.getslotScroller = function() { return slotScroller };
t.getSlotContent = function() { return slotContent };
t.getSlotSegmentContainer = function() { return slotSegmentContainer };
t.getMinMinute = function() { return minMinute };
t.getMaxMinute = function() { return maxMinute };
t.getBodyContent = function() { return slotContent }; // !!??
t.getRowCnt = function() { return 1 };
t.getColCnt = function() { return colCnt };
t.getColWidth = function() { return colWidth };
t.getSlotHeight = function() { return slotHeight };
t.defaultSelectionEnd = defaultSelectionEnd;
t.renderDayOverlay = renderDayOverlay;
t.renderSelection = renderSelection;
t.renderSlotSelection = renderSlotSelection;
t.clearSelection = clearSelection;
t.reportDayClick = reportDayClick; // selection mousedown hack
t.dragStart = dragStart;
t.dragStop = dragStop;
t.updateGrid = updateGrid;
t.updateToday = updateToday;
t.setAxisFormat = setAxisFormat;
t.setStartOfBusiness = setStartOfBusiness;
t.setEndOfBusiness = setEndOfBusiness;
t.setWeekendDays = setWeekendDays;
t.setBindingMode = setBindingMode;
t.setSelectable = setSelectable;
// imports
View.call(t, element, calendar, viewName);
OverlayManager.call(t);
SelectionManager.call(t);
AgendaEventRenderer.call(t);
var opt = t.opt;
var trigger = t.trigger;
var clearEvents = t.clearEvents;
var renderOverlay = t.renderOverlay;
var clearOverlays = t.clearOverlays;
var reportSelection = t.reportSelection;
var unselect = t.unselect;
var daySelectionMousedown = t.daySelectionMousedown;
var slotSegHtml = t.slotSegHtml;
var formatDate = calendar.formatDate;
var setTimeIndicator = t.setTimeIndicator;
// locals
var dayTable;
var dayHead;
var dayHeadCells;
var dayBody;
var dayBodyCells;
var dayBodyCellInners;
var dayBodyFirstCell;
var dayBodyFirstCellStretcher;
var slotLayer;
var daySegmentContainer;
var allDayTable;
var allDayRow;
var slotJumpersTopContainer;
var slotJumpersTop;
var slotJumpersBottomContainer;
var slotJumpersBottom;
var slotScroller;
var slotContent;
var slotSegmentContainer;
var dayScroller;
var dayContent;
var daySegmentContainer;
var slotTable;
var slotTableFirstInner;
var axisFirstCells;
var gutterCells;
var divider;
var selectionHelper;
var viewWidth;
var viewHeight;
var axisWidth;
var colWidth;
var gutterWidth;
//var gutterAck = false;
var slotHeight; // TODO: what if slotHeight changes? (see issue 650)
var savedScrollTop;
var colCnt;
var slotCnt;
var coordinateGrid;
var hoverListener;
var colContentPositions;
var slotTopCache = {};
var tm;
var firstDay;
var nwe; // no weekends (int)
var rtl, dis, dit; // day index sign / translate
var minMinute, maxMinute;
var colFormat;
/* Rendering
-----------------------------------------------------------------------------*/
disableTextSelection(element.addClass('fc-agenda'));
function renderAgenda(c) {
colCnt = c;
updateOptions();
if (!dayTable) {
buildSkeleton();
}else{
clearEvents();
}
updateCells();
}
function updateOptions() {
tm = opt('theme') ? 'ui' : 'fc';
nwe = opt('weekends') ? 0 : 1;
firstDay = opt('firstDay');
if (rtl = opt('isRTL')) {
dis = -1;
dit = colCnt - 1;
}else{
dis = 1;
dit = 0;
}
minMinute = parseTime(opt('minTime'));
maxMinute = parseTime(opt('maxTime'));
colFormat = opt('columnFormat');
}
function buildSkeleton() {
var headerClass = tm + "-widget-header";
var contentClass = tm + "-widget-content";
var s;
var i;
var d;
var maxd;
var minutes;
var slotNormal = opt('slotMinutes') % 15 == 0;
s =
"<table style='width:100%' class='fc-agenda-days fc-border-separate' cellspacing='0'>" +
"<thead>" +
"<tr>" +
"<th class='fc-agenda-axis " + headerClass + "'><div class='fc-week-number'/></th>";
for (i=0; i<colCnt; i++) {
s +=
"<th class='fc- fc-col" + i + ' ' + headerClass + "'/>"; // fc- needed for setDayID
}
s +=
"<th class='fc-agenda-gutter " + headerClass + "'>&nbsp;</th>" +
"</tr>" +
"</thead>" +
"<tbody>" +
"<tr>" +
"<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";
for (i=0; i<colCnt; i++) {
s +=
"<td class='fc- fc-col" + i + ' ' + contentClass + "'>" + // fc- needed for setDayID
"<div>" +
"<div class='fc-day-content'>" +
"<div style='position:relative'>&nbsp;</div>" +
"</div>" +
"</div>" +
"</td>";
}
s +=
"<td class='fc-agenda-gutter " + contentClass + "'>&nbsp;</td>" +
"</tr>" +
"</tbody>" +
"</table>";
dayTable = $(s).appendTo(element);
dayHead = dayTable.find('thead');
dayHeadCells = dayHead.find('th').slice(1, -1);
dayBody = dayTable.find('tbody');
dayBodyCells = dayBody.find('td').slice(0, -1);
dayBodyCellInners = dayBodyCells.find('div.fc-day-content div');
dayBodyFirstCell = dayBodyCells.eq(0);
dayBodyFirstCellStretcher = dayBodyFirstCell.find('> div');
markFirstLast(dayHead.add(dayHead.find('tr')));
markFirstLast(dayBody.add(dayBody.find('tr')));
axisFirstCells = dayHead.find('th:first');
gutterCells = dayTable.find('.fc-agenda-gutter');
slotLayer =
$("<div style='position:absolute;z-index:2;left:0;width:100%'/>")
.appendTo(element);
if(opt('allDaySlot')) {
dayScroller = $("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto;'/>").appendTo(slotLayer);
dayContent = $("<div style='position:relative;width:100%;overflow:hidden;min-height:37px'/>").appendTo(dayScroller);
daySegmentContainer = $("<div style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(dayContent);
s =
"<table style='width:100%;' class='fc-agenda-allday' cellspacing='0'>" +
"<tr>" +
"<th class='" + headerClass + " fc-agenda-axis'>" + opt('allDayText') + "</th>" +
"<td>" +
"<div class='fc-day-content'><div style='position:relative;min-height:34px'/></div>" +
"</td>" +
"</tr>" +
"</table>";
allDayTable = $(s).appendTo(dayScroller);
allDayRow = allDayTable.find('tr');
dayBind(allDayRow.find('td'));
axisFirstCells = axisFirstCells.add(allDayTable.find('th:first'));
gutterCells = gutterCells.add(allDayTable.find('th.fc-agenda-gutter'));
divider = $(
"<div class='fc-agenda-divider " + headerClass + "'>" +
"<div class='fc-agenda-divider-inner'/>" +
"</div>"
).appendTo(slotLayer);
}else{
daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()
}
slotJumpersTopContainer = $("<div style='position:relative;width:100%;'/>").appendTo(slotLayer);
slotJumpersBottomContainer = $("<div style='position:relative;width:100%;'/>").appendTo(slotLayer);
slotScroller = $("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>").appendTo(slotLayer);
slotContent = $("<div style='position:relative;width:100%;overflow:hidden'/>").appendTo(slotScroller);
slotSegmentContainer = $("<div style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(slotContent);
for (i=0; i<colCnt; i++) {
slotJumpersTopContainer.append($('<div class="fc-slot-jumper-top"/>'));
slotJumpersBottomContainer.append($('<div class="fc-slot-jumper-bottom"/>'));
}
slotJumpersTop = slotJumpersTopContainer.children();
slotJumpersBottom = slotJumpersBottomContainer.children();
s =
"<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +
"<tbody>";
d = zeroDate();
maxd = addMinutes(cloneDate(d), maxMinute);
addMinutes(d, minMinute);
slotCnt = 0;
var startOfBusiness = opt("startOfBusiness") * (60/opt("slotMinutes"));
var endOfBusiness = (opt("endOfBusiness") - (opt("slotMinutes")/60)) * (60/opt("slotMinutes"));
for (i=0; d < maxd; i++) {
minutes = d.getMinutes();
var nonBusinessHours = (i < startOfBusiness || i > endOfBusiness) ? " fc-non-business-hours" : "";
s +=
"<tr class='fc-slot" + i + ' ' + (!minutes ? '' : 'fc-minor') + nonBusinessHours + "'>" +
"<th class='fc-agenda-axis " + headerClass + "'>" +
((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : '&nbsp;') +
"</th>" +
"<td class='" + contentClass + "'>" +
"<div style='position:relative'>&nbsp;</div>" +
"</td>" +
"</tr>";
addMinutes(d, opt('slotMinutes'));
slotCnt++;
}
s +=
"</tbody>" +
"</table>";
slotTable = $(s).appendTo(slotContent);
slotTableFirstInner = slotTable.find('div:first');
slotBind(slotTable.find('td'));
axisFirstCells = axisFirstCells.add(slotTable.find('th:first'));
}
function updateCells() {
var i;
var headCell;
var bodyCell;
var axisCell;
var date;
var today = clearTime(new Date());
axisCell = axisFirstCells[0];
if(opt('showWeekNumbers')) {
removeWeekNumber($(axisCell), colDate(0));
addWeekNumber($(axisCell), colDate(0));
}
for (i=0; i<colCnt; i++) {
date = colDate(i);
headCell = dayHeadCells.eq(i);
headCell.html(formatDate(date, colFormat));
bodyCell = dayBodyCells.eq(i);
setDayID(headCell.add(bodyCell), date, opt);
if (+date == +today) {
bodyCell.addClass(tm + '-state-highlight fc-today');
addTodayClass(bodyCell);
}else{
bodyCell.removeClass(tm + '-state-highlight fc-today');
removeTodayClass(bodyCell);
}
}
}
function updateGrid()
{
updateToday();
setTimeIndicator();
setAxisFormat();
setStartOfBusiness();
setEndOfBusiness();
setWeekendDays();
setBindingMode();
setSelectable();
}
function updateToday()
{
var i;
var bodyCell;
var date;
var today = clearTime(new Date());
for (i=0; i<colCnt; i++) {
date = colDate(i);
bodyCell = dayBodyCells.eq(i);
if (+date == +today) {
bodyCell.addClass(tm + '-state-highlight fc-today');
addTodayClass(bodyCell);
}else{
bodyCell.removeClass(tm + '-state-highlight fc-today');
removeTodayClass(bodyCell);
}
}
}
function setAxisFormat()
{
var slotNormal = opt('slotMinutes') % 15 == 0;
var d = zeroDate();
addMinutes(d, minMinute);
slotTable.find('th').each(function(index, element){
var minutes = d.getMinutes();
$(element).html((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : '&nbsp;');
addMinutes(d, opt('slotMinutes'));
});
}
function setStartOfBusiness()
{
updateBusinessHours();
}
function setEndOfBusiness()
{
updateBusinessHours();
}
function updateBusinessHours()
{
var startOfBusiness = opt("startOfBusiness") * (60/opt("slotMinutes"));
var endOfBusiness = (opt("endOfBusiness") - (opt("slotMinutes")/60)) * (60/opt("slotMinutes"));
slotTable.find('tr').each(function(index, element){
if(index < startOfBusiness || index > endOfBusiness)
$(element).addClass('fc-non-business-hours');
else
$(element).removeClass('fc-non-business-hours');
});
}
function setWeekendDays()
{
dayHeadCells.each(function(i, _cell) {
setDayID($(_cell), colDate(i), opt);
});
dayBodyCells.each(function(i, _cell) {
setDayID($(_cell), colDate(i), opt);
});
}
function setBindingMode()
{
dayBind(allDayRow.find('td'));
slotBind(slotTable.find('td'));
}
function setSelectable()
{
dayBind(allDayRow.find('td'));
slotBind(slotTable.find('td'));
}
function setHeight(height, dateChanged) {
if (height === undefined) {
height = viewHeight;
}
viewHeight = height;
slotTopCache = {};
var headHeight = dayBody.position().top;
var allDayHeight = opt('allDaySlot') ? 4 : 0; //if divider is present
var bodyHeight = Math.min( // total body height, including borders
height - headHeight, // when scrollbars
slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border
);
var maxAllDayHeight = Math.floor((bodyHeight - allDayHeight - 1) / 3);
dayScroller.css('max-height', maxAllDayHeight + 3);
allDayRow.find('div:first').children().css('max-height', maxAllDayHeight);
allDayHeight = allDayTable.height();
if(opt('allDaySlot')) {
divider.css('position', 'relative');
divider.css('top', allDayHeight);
slotScroller.css('top', allDayHeight + 4);
}
//allDayHeight = slotScroller.position().top; // including divider
bodyHeight = Math.min( // total body height, including borders
height - headHeight, // when scrollbars
slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border
);
dayBodyFirstCellStretcher
.height(bodyHeight - vsides(dayBodyFirstCell));
var slotScrollerHeight = bodyHeight - allDayHeight - 1 - (opt('allDaySlot') ? 4 : 0);
slotLayer.css('top', headHeight);
slotScroller.height(slotScrollerHeight);
slotHeight = slotTableFirstInner.height() + 1; // +1 for border
slotJumpersTopContainer.css('top', allDayHeight+1);
slotJumpersBottomContainer.css('top', slotScrollerHeight + allDayHeight + 1 - slotJumpersBottom.first().height());
if (dateChanged) {
resetScroll();
}
if(t.addedView) {
t.addedView.setHeight(height, dateChanged);
}
}
function setWidth(width) {
if (width === undefined) {
width = viewWidth;
}
viewWidth = width;
if(t.addedView) {
var outerWidth = Math.floor(element.parent().width() / 2);
element.css({'width' : outerWidth});
viewWidth = outerWidth;
}
colContentPositions.clear();
axisWidth = 0;
setOuterWidth(
axisFirstCells
.width('')
.each(function(i, _cell) {
axisWidth = Math.max(axisWidth, $(_cell).outerWidth());
}),
axisWidth
);
var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after axisWidth (for IE7)
//slotTable.width(slotTableWidth);
//var oldGutterWidth = gutterWidth;
gutterWidth = slotScroller.width() - slotTableWidth || dayScroller.width() - dayContent.width();
if (gutterWidth) {
/*if(!gutterAck) {
viewWidth -= gutterWidth;
gutterAck = true;
}*/
setOuterWidth(gutterCells, gutterWidth);
gutterCells
.show()
.prev()
.removeClass('fc-last');
}else{
/*if(gutterAck) {
viewWidth += oldGutterWidth;
gutterAck = false;
}*/
gutterCells
.hide()
.prev()
.addClass('fc-last');
}
colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt);
setOuterWidth(dayHeadCells.slice(0, -1), colWidth);
slotJumpersTop.each(function(i,e){
var jumper=$(e);
jumper.css('left',axisWidth + (colWidth*(i+1)) - 1 - jumper.width());
});
slotJumpersBottom.each(function(i,e){
var jumper=$(e);
jumper.css('left',axisWidth + (colWidth*(i+1)) - 1 - jumper.width());
});
if(t.addedView) {
t.addedView.setWidth(outerWidth);
}
}
function resetScroll() {
var d0 = zeroDate();
var scrollDate = cloneDate(d0);
scrollDate.setHours(opt('firstHour'));
var top = timePosition(d0, scrollDate) + 1; // +1 for the border
function scroll() {
slotScroller.scrollTop(top);
}
scroll();
setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
}
function beforeHide() {
savedScrollTop = slotScroller.scrollTop();
}
function afterShow() {
slotScroller.scrollTop(savedScrollTop);
}
/* Slot/Day clicking and binding
-----------------------------------------------------------------------*/
function dayBind(cells) {
cells.unbind('click dblclick');
if(opt('bindingMode') == 'double')
cells.dblclick(daySlotClick).mousedown(daySelectionMousedown);
else
cells.click(daySlotClick).mousedown(daySelectionMousedown);
}
function slotBind(cells) {
cells.unbind('click dblclick');
if(opt('bindingMode') == 'double')
cells.dblclick(slotClick).mousedown(slotSelectionMousedown);
else
cells.click(slotClick).mousedown(slotSelectionMousedown);
}
function daySlotClick(ev) {
var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
var date = colDate(col);
trigger('dayClick', dayBodyCells[col], date, true, ev);
}
function slotClick(ev) {
//if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
var date = colDate(col);
var rowMatch = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
if (rowMatch) {
var mins = parseInt(rowMatch[1]) * opt('slotMinutes');
var hours = Math.floor(mins/60);
date.setHours(hours);
date.setMinutes(mins%60 + minMinute);
trigger('dayClick', dayBodyCells[col], date, false, ev);
}else{
trigger('dayClick', dayBodyCells[col], date, true, ev);
}
//}
}
/* Semi-transparent Overlay Helpers
-----------------------------------------------------*/
function renderDayOverlay(startDate, endDate, refreshCoordinateGrid) { // endDate is exclusive
if (refreshCoordinateGrid) {
coordinateGrid.build();
}
var visStart = cloneDate(t.visStart);
var startCol, endCol;
if (rtl) {
startCol = dayDiff(endDate, visStart)*dis+dit+1;
endCol = dayDiff(startDate, visStart)*dis+dit+1;
}else{
startCol = dayDiff(startDate, visStart);
endCol = dayDiff(endDate, visStart);
}
startCol = Math.max(0, startCol);
endCol = Math.min(colCnt, endCol);
if (startCol < endCol) {
dayBind(
renderCellOverlay(0, startCol, 0, endCol-1)
);
}
}
function renderCellOverlay(row0, col0, row1, col1) { // only for all-day?
var rect = coordinateGrid.rect(row0, col0, row1, col1, slotLayer);
return renderOverlay(rect, slotLayer);
}
function renderSlotOverlay(overlayStart, overlayEnd) {
var dayStart = cloneDate(t.visStart);
var dayEnd = addDays(cloneDate(dayStart), 1);
for (var i=0; i<colCnt; i++) {
var stretchStart = new Date(Math.max(dayStart, overlayStart));
var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
if (stretchStart < stretchEnd) {
var col = i*dis+dit;
var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only use it for horizontal coords
var top = timePosition(dayStart, stretchStart);
var bottom = timePosition(dayStart, stretchEnd);
rect.top = top;
rect.height = bottom - top;
slotBind(
renderOverlay(rect, slotContent)
);
}
addDays(dayStart, 1);
addDays(dayEnd, 1);
}
}
/* Coordinate Utilities
-----------------------------------------------------------------------------*/
coordinateGrid = new CoordinateGrid(function(rows, cols) {
var e, n, p;
dayHeadCells.each(function(i, _e) {
e = $(_e);
n = e.offset().left;
if (i) {
p[1] = n;
}
p = [n];
cols[i] = p;
});
p[1] = n + e.outerWidth();
if (opt('allDaySlot')) {
e = allDayRow;
n = e.offset().top;
rows[0] = [n, n+e.outerHeight()];
}
var slotTableTop = slotContent.offset().top;
var slotScrollerTop = slotScroller.offset().top;
var slotScrollerBottom = slotScrollerTop + slotScroller.outerHeight();
function constrain(n) {
return Math.max(slotScrollerTop, Math.min(slotScrollerBottom, n));
}
for (var i=0; i<slotCnt; i++) {
rows.push([
constrain(slotTableTop + slotHeight*i),
constrain(slotTableTop + slotHeight*(i+1))
]);
}
});
hoverListener = new HoverListener(coordinateGrid);
colContentPositions = new HorizontalPositionCache(function(col) {
return dayBodyCellInners.eq(col);
});
function colContentLeft(col) {
return colContentPositions.left(col);
}
function colContentRight(col) {
return colContentPositions.right(col);
}
function dateCell(date) { // "cell" terminology is now confusing
return {
row: Math.floor(dayDiff(date, t.visStart) / 7),
col: dayOfWeekCol(date.getDay())
};
}
function cellDate(cell) {
var d = colDate(cell.col);
var slotIndex = cell.row;
if (opt('allDaySlot')) {
slotIndex--;
}
if (slotIndex >= 0) {
addMinutes(d, minMinute + slotIndex * opt('slotMinutes'));
}
return d;
}
function colDate(col) { // returns dates with 00:00:00
return addDays(cloneDate(t.visStart), col*dis+dit);
}
function cellIsAllDay(cell) {
return opt('allDaySlot') && !cell.row;
}
function dayOfWeekCol(dayOfWeek) {
return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt)*dis+dit;
}
// get the Y coordinate of the given time on the given day (both Date objects)
function timePosition(day, time) { // both date objects. day holds 00:00 of current day
day = cloneDate(day, true);
if (time < addMinutes(cloneDate(day), minMinute)) {
return 0;
}
if (time >= addMinutes(cloneDate(day), maxMinute)) {
return slotTable.height();
}
var slotMinutes = opt('slotMinutes'),
minutes = time.getHours()*60 + time.getMinutes() - minMinute,
slotI = Math.floor(minutes / slotMinutes),
slotTop = slotTopCache[slotI];
if (slotTop === undefined) {
slotTop = slotTopCache[slotI] = slotTable.find('tr:eq(' + slotI + ') td div')[0].offsetTop; //.position().top; // need this optimization???
}
return Math.max(0, Math.round(
slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
));
}
function allDayBounds() {
return {
left: axisWidth,
right: viewWidth - gutterWidth
}
}
function getAllDayRow(index) {
return allDayRow;
}
function defaultEventEnd(event) {
var start = cloneDate(event.start);
if (event.allDay) {
return start;
}
return addMinutes(start, opt('defaultEventMinutes'));
}
/* Selection
---------------------------------------------------------------------------------*/
function defaultSelectionEnd(startDate, allDay) {
if (allDay) {
return cloneDate(startDate);
}
return addMinutes(cloneDate(startDate), opt('slotMinutes'));
}
function renderSelection(startDate, endDate, allDay) { // only for all-day
if (allDay) {
if (opt('allDaySlot')) {
renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true);
}
}else{
renderSlotSelection(startDate, endDate);
}
}
function renderSlotSelection(startDate, endDate) {
var helperOption = opt('selectHelper');
coordinateGrid.build();
if (helperOption) {
var col = dayDiff(startDate, t.visStart) * dis + dit;
if (col >= 0 && col < colCnt) { // only works when times are on same day
var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only for horizontal coords
var top = timePosition(startDate, startDate);
var bottom = timePosition(startDate, endDate);
if (bottom > top) { // protect against selections that are entirely before or after visible range
rect.top = top;
rect.height = bottom - top;
rect.left += 2;
rect.width -= 5;
if ($.isFunction(helperOption)) {
var helperRes = helperOption(startDate, endDate);
if (helperRes) {
rect.position = 'absolute';
rect.zIndex = 8;
selectionHelper = $(helperRes)
.css(rect)
.appendTo(slotContent);
}
}else{
rect.isStart = true; // conside rect a "seg" now
rect.isEnd = true; //
selectionHelper = $(slotSegHtml(
{
title: '',
start: startDate,
end: endDate,
className: ['fc-select-helper'],
editable: false
},
rect
));
selectionHelper.css('opacity', opt('dragOpacity'));
}
if (selectionHelper) {
slotBind(selectionHelper);
slotContent.append(selectionHelper);
setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
setOuterHeight(selectionHelper, rect.height, true);
}
}
}
}else{
renderSlotOverlay(startDate, endDate);
}
}
function clearSelection() {
clearOverlays();
if (selectionHelper) {
selectionHelper.remove();
selectionHelper = null;
}
}
function slotSelectionMousedown(ev) {
if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button
unselect(ev);
var dates;
hoverListener.start(function(cell, origCell) {
clearSelection();
if (cell && (cell.col == origCell.col || !opt('selectHelper')) && !cellIsAllDay(cell)) {
var d1 = cellDate(origCell);
var d2 = cellDate(cell);
dates = [
d1,
addMinutes(cloneDate(d1), opt('slotMinutes')),
d2,
addMinutes(cloneDate(d2), opt('slotMinutes'))
].sort(cmp);
renderSlotSelection(dates[0], dates[3]);
}else{
dates = null;
}
}, ev);
$(document).one('mouseup', function(ev) {
hoverListener.stop();
if (dates) {
if (+dates[0] == +dates[1]) {
//reportDayClick(dates[0], false, ev);
}
reportSelection(dates[0], dates[3], false, ev);
}
});
}
}
function reportDayClick(date, allDay, ev) {
trigger('dayClick', dayBodyCells[dayOfWeekCol(date.getDay())], date, allDay, ev);
}
/* External Dragging
--------------------------------------------------------------------------------*/
function dragStart(_dragElement, ev, ui) {
hoverListener.start(function(cell) {
clearOverlays();
if (cell) {
if (cellIsAllDay(cell)) {
renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
}else{
var d1 = cellDate(cell);
var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes'));
renderSlotOverlay(d1, d2);
}
}
}, ev);
}
function dragStop(_dragElement, ev, ui) {
var cell = hoverListener.stop();
clearOverlays();
if (cell) {
trigger('drop', _dragElement, cellDate(cell), cellIsAllDay(cell), ev, ui);
}
}
}
function AgendaEventRenderer() {
var t = this;
// exports
t.renderEvents = renderEvents;
t.compileDaySegs = compileDaySegs; // for DayEventRenderer
t.clearEvents = clearEvents;
t.slotSegHtml = slotSegHtml;
t.bindDaySeg = bindDaySeg;
t.setTimeIndicator = setTimeIndicator;
// imports
DayEventRenderer.call(t);
var opt = t.opt;
var trigger = t.trigger;
//var setOverflowHidden = t.setOverflowHidden;
var isEventDraggable = t.isEventDraggable;
var isEventResizable = t.isEventResizable;
var eventEnd = t.eventEnd;
var reportEvents = t.reportEvents;
var reportEventClear = t.reportEventClear;
var eventElementHandlers = t.eventElementHandlers;
var setHeight = t.setHeight;
var setWidth = t.setWidth;
var getDaySegmentContainer = t.getDaySegmentContainer;
var getSlotJumpersTop = t.getSlotJumpersTop;
var getSlotJumpersBottom = t.getSlotJumpersBottom;
var getslotScroller = t.getslotScroller;
var getSlotContent = t.getSlotContent;
var getSlotSegmentContainer = t.getSlotSegmentContainer;
var getHoverListener = t.getHoverListener;
var getMaxMinute = t.getMaxMinute;
var getMinMinute = t.getMinMinute;
var timePosition = t.timePosition;
var colContentLeft = t.colContentLeft;
var colContentRight = t.colContentRight;
var renderDaySegs = t.renderDaySegs;
var resizableDayEvent = t.resizableDayEvent; // TODO: streamline binding architecture
var getColCnt = t.getColCnt;
var getColWidth = t.getColWidth;
var getSlotHeight = t.getSlotHeight;
var getBodyContent = t.getBodyContent;
var reportEventElement = t.reportEventElement;
var showEvents = t.showEvents;
var hideEvents = t.hideEvents;
var eventDrop = t.eventDrop;
var eventResize = t.eventResize;
var renderDayOverlay = t.renderDayOverlay;
var renderSlotSelection = t.renderSlotSelection;
var clearOverlays = t.clearOverlays;
var calendar = t.calendar;
var formatDate = calendar.formatDate;
var formatDates = calendar.formatDates;
var timeLineInterval;
/* Rendering
----------------------------------------------------------------------------*/
// draw a horizontal line indicating the current time (#143)
function setTimeIndicator()
{
var container = getBodyContent();
var timeline = container.children('.fc-timeline');
var arrow = container.children('.fc-timeline-arrow');
if (timeline.length == 0 || arrow.length == 0) { // if timeline isn't there, add it
timeline = $('<hr>').addClass('fc-timeline').appendTo(container);
arrow = $('<div>').addClass('fc-timeline-arrow').appendTo(container);
}
var cur_time = new Date();
var daycol = $('.fc-today', t.element);
if (daycol.length > 0) {
timeline.show();
arrow.show();
}
else {
timeline.hide();
arrow.hide();
return;
}
var secs = (cur_time.getHours() * 60 * 60) + (cur_time.getMinutes() * 60) + cur_time.getSeconds();
var percents = secs / 86400; // 24 * 60 * 60 = 86400, # of seconds in a day
timeline.css('top', Math.floor(container.height() * percents - 1) + 'px');
arrow.css('top', Math.floor(container.height() * percents - 1) - 5 + 'px');
var left = daycol.position().left;
var width = daycol.width();
timeline.css({ left: left + 'px', width: width + 'px' });
}
function renderEvents(events, modifiedEventId) {
reportEvents(events);
var i, len=events.length,
dayEvents=[],
slotEvents=[];
for (i=0; i<len; i++) {
if (events[i].allDay) {
dayEvents.push(events[i]);
}else{
slotEvents.push(events[i]);
}
}
if (opt('allDaySlot')) {
renderDaySegs(compileDaySegs(dayEvents), modifiedEventId, true);
setHeight(); // no params means set to viewHeight
setWidth(); // no params means set to viewWidth
}
renderSlotSegs(compileSlotSegs(slotEvents), modifiedEventId);
if (opt('currentTimeIndicator')) {
window.clearInterval(timeLineInterval);
timeLineInterval = window.setInterval(setTimeIndicator, 30000);
setTimeIndicator();
}
if(t.addedView) {
t.addedView.renderEvents(events, modifiedEventId);
}
}
function clearEvents() {
reportEventClear();
getDaySegmentContainer().empty();
getSlotSegmentContainer().empty();
if(t.addedView) {
t.addedView.clearEvents();
}
}
function compileDaySegs(events) {
var levels = stackSegs(sliceSegs(events, $.map(events, exclEndDay), t.visStart, t.visEnd)),
i, levelCnt=levels.length, level,
j, seg,
segs=[];
for (i=0; i<levelCnt; i++) {
level = levels[i];
for (j=0; j<level.length; j++) {
seg = level[j];
seg.row = 0;
seg.level = i; // not needed anymore
segs.push(seg);
}
}
return segs;
}
function compileSlotSegs(events) {
var colCnt = getColCnt(),
minMinute = getMinMinute(),
maxMinute = getMaxMinute(),
d = addMinutes(cloneDate(t.visStart), minMinute),
visEventEnds = $.map(events, slotEventEnd),
i, col,
j, level,
k, seg,
segs=[];
for (i=0; i<colCnt; i++) {
col = stackSegs(sliceSegs(events, visEventEnds, d, addMinutes(cloneDate(d), maxMinute-minMinute)));
countForwardSegs(col);
for (j=0; j<col.length; j++) {
level = col[j];
for (k=0; k<level.length; k++) {
seg = level[k];
seg.col = i;
seg.level = j;
segs.push(seg);
}
}
addDays(d, 1, true);
}
return segs;
}
function slotEventEnd(event) {
if (event.end) {
return cloneDate(event.end);
}else{
return addMinutes(cloneDate(event.start), opt('defaultEventMinutes'));
}
}
// renders events in the 'time slots' at the bottom
function renderSlotSegs(segs, modifiedEventId) {
var i, segCnt=segs.length, seg,
event,
classes,
top, bottom,
colI, levelI, forward,
leftmost,
availWidth,
outerWidth,
left,
html='',
eventElements,
eventElement,
triggerRes,
vsideCache={},
hsideCache={},
key, val,
contentElement,
height,
slotJumpersTop = getSlotJumpersTop(),
slotJumpersBottom = getSlotJumpersBottom(),
slotSegmentContainer = getSlotSegmentContainer(),
slotScroller = getslotScroller(),
rtl, dis, dit,
colCnt = getColCnt(),
colBoundaries = new Array(colCnt),
jumperReserve = 10;
if (rtl = opt('isRTL')) {
dis = -1;
dit = colCnt - 1;
}else{
dis = 1;
dit = 0;
}
// init column tops array
for(i=0;i<colCnt;i++) {
colBoundaries[i]={positions:new Array()};
}
// calculate position/dimensions, create html
for (i=0; i<segCnt; i++) {
seg = segs[i];
event = seg.event;
top = timePosition(seg.start, seg.start);
bottom = timePosition(seg.start, seg.end);
colI = seg.col;
levelI = seg.level;
forward = seg.forward || 0;
leftmost = colContentLeft(colI*dis + dit);
availWidth = colContentRight(colI*dis + dit) - leftmost;
availWidth = Math.min(availWidth-6, availWidth*.95); // TODO: move this to CSS
if (levelI) {
// indented and thin
outerWidth = availWidth / (levelI + forward + 1);
}else{
if (forward) {
// moderately wide, aligned left still
outerWidth = ((availWidth / (forward + 1)) - (12/2)) * 2; // 12 is the predicted width of resizer =
}else{
// can be entire width, aligned left
outerWidth = availWidth;
}
}
left = leftmost + // leftmost possible
(availWidth / (levelI + forward + 1) * levelI) // indentation
* dis + (rtl ? availWidth - outerWidth : 0); // rtl
seg.top = top;
seg.left = left;
seg.outerWidth = outerWidth;
seg.outerHeight = bottom - top;
html += slotSegHtml(event, seg);
}
slotSegmentContainer[0].innerHTML = html; // faster than html()
eventElements = slotSegmentContainer.children();
// retrieve elements, run through eventRender callback, bind event handlers
for (i=0; i<segCnt; i++) {
seg = segs[i];
event = seg.event;
eventElement = $(eventElements[i]); // faster than eq()
triggerRes = trigger('eventRender', event, event, eventElement);
if (triggerRes === false) {
eventElement.remove();
}else{
if (triggerRes && triggerRes !== true) {
eventElement.remove();
eventElement = $(triggerRes)
.css({
position: 'absolute',
top: seg.top,
left: seg.left
})
.appendTo(slotSegmentContainer);
}
seg.element = eventElement;
if (event._id === modifiedEventId) {
bindSlotSeg(event, eventElement, seg);
}else{
eventElement[0]._fci = i; // for lazySegBind
}
reportEventElement(event, eventElement);
}
}
lazySegBind(slotSegmentContainer, segs, bindSlotSeg);
// record event sides and title positions
for (i=0; i<segCnt; i++) {
seg = segs[i];
if (eventElement = seg.element) {
val = vsideCache[key = seg.key = cssKey(eventElement[0])];
seg.vsides = val === undefined ? (vsideCache[key] = vsides(eventElement, true)) : val;
val = hsideCache[key];
seg.hsides = val === undefined ? (hsideCache[key] = hsides(eventElement, true)) : val;
contentElement = eventElement.find('div.fc-event-content');
if (contentElement.length) {
seg.contentTop = contentElement[0].offsetTop;
}
}
}
// set all positions/dimensions at once
for (i=0; i<segCnt; i++) {
seg = segs[i];
if (eventElement = seg.element) {
eventElement[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
height = Math.max(t.getSlotHeight() - seg.vsides, seg.outerHeight - seg.vsides);
eventElement[0].style.height = height + 'px';
event = seg.event;
if (seg.contentTop !== undefined && height - seg.contentTop < 10) {
// not enough room for title, put it in the time header
eventElement.find('div.fc-event-time')
.text(formatDate(event.start, opt('timeFormat')) + ' - ' + event.title);
//.text(formatDates(event.start, event.end, opt('timeFormat')) + ' - ' + event.title);
eventElement.find('div.fc-event-title')
.remove();
}
colBoundaries[seg.col].positions.push({top:seg.top, bottom:seg.top+height+seg.vsides});
trigger('eventAfterRender', event, event, eventElement);
}
}
// sort column boundaries on top values and set min and max values
for(i=0;i<colCnt;i++) {
var min = null;
var currentCol = colBoundaries[i];
var currentColPositions = currentCol.positions;
currentColPositions = currentColPositions.sort(function(a,b){return a.top-b.top;});
$.each(currentColPositions,function(ei,ee){
if(min==null)
min=ee.bottom;
else
min=Math.min(min,ee.bottom);
});
currentCol.min=min;
currentCol.max=currentColPositions.length?currentColPositions[currentColPositions.length-1].top:null;
}
slotScroller.unbind('scroll').scroll(function(){
var currentPosition = $(this).scrollTop();
for(i=0;i<colCnt;i++) {
var currentCol = colBoundaries[i];
if(currentCol.min!=null && currentCol.min<=currentPosition+jumperReserve)
$(slotJumpersTop[i]).css('display','');
else
$(slotJumpersTop[i]).css('display','none');
if(currentCol.max!=null && currentCol.max>=currentPosition+slotScroller.height()-jumperReserve)
$(slotJumpersBottom[i]).css('display','');
else
$(slotJumpersBottom[i]).css('display','none');
}
}).trigger('scroll');
slotJumpersTop.each(function(i, jumper){
$(jumper).unbind('click').click(function(){
var targetTop=0;
var currentPosition = slotScroller.scrollTop();
$.each(colBoundaries[i].positions,function(ei,ee){
if(ee.bottom<=currentPosition+jumperReserve)
targetTop=ee.top;
return ee.top<currentPosition;
});
slotScroller.scrollTop(targetTop-t.getSlotHeight());
});
});
slotJumpersBottom.each(function(i, jumper){
$(jumper).unbind('click').click(function(){
var targetPosition=0;
var currentPosition = slotScroller.scrollTop();
$.each(colBoundaries[i].positions,function(ei,ee){
if(ee.top>=currentPosition+slotScroller.height()-jumperReserve)
{
targetPosition = ee;
return false;
}
});
slotScroller.scrollTop(
targetPosition.bottom-targetPosition.top+t.getSlotHeight()>slotScroller.height()?
targetPosition.top-t.getSlotHeight():
targetPosition.bottom-slotScroller.height()+t.getSlotHeight()+1 // +1 is a magic independent constant, used just to make the default scroll position look better
);
});
});
for (i=0; i<segCnt; i++) {
seg = segs[i];
if(seg.event.source && seg.event.source.background) {
$('td.fc-col' + seg.col, t.element).addClass('fc-source-bg');
}
}
}
function slotSegHtml(event, seg) {
var html = "<";
var url = event.url;
var skinCss = getSkinCss(event, opt);
var skinCssAttr = (skinCss ? " style='" + skinCss + "'" : '');
var classes = ['fc-event', 'fc-event-skin', 'fc-event-vert'];
if (isEventDraggable(event)) {
classes.push('fc-event-draggable');
}
if (seg.isStart) {
classes.push('fc-corner-top');
}
if (seg.isEnd) {
classes.push('fc-corner-bottom');
}
classes = classes.concat(event.className);
if (event.source) {
classes = classes.concat(event.source.className || []);
}
if (url) {
html += "a href='" + htmlEscape(event.url) + "'";
}else{
html += "div";
}
html +=
" class='" + classes.join(' ') + "'" +
" style='position:absolute;z-index:8;top:" + seg.top + "px;left:" + seg.left + "px;" + skinCss + "'" +
">" +
"<div class='fc-event-inner fc-event-skin'" + skinCssAttr + ">" +
"<div class='fc-event-head fc-event-skin'" + skinCssAttr + ">" +
"<div class='fc-event-time'>" +
htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
"</div>" +
"</div>" +
"<div class='fc-event-content'>" +
"<div class='fc-event-title'>" +
htmlEscape(event.title) +
"</div>" +
"</div>" +
"<div class='fc-event-bg'></div>" +
"</div>"; // close inner
if (seg.isEnd && isEventResizable(event)) {
html +=
"<div class='ui-resizable-handle ui-resizable-s'>=</div>";
}
html +=
"</" + (url ? "a" : "div") + ">";
return html;
}
function bindDaySeg(event, eventElement, seg) {
if (isEventDraggable(event)) {
draggableDayEvent(event, eventElement, seg.isStart);
}
if (seg.isEnd && isEventResizable(event)) {
resizableDayEvent(event, eventElement, seg);
}
eventElementHandlers(event, eventElement);
// needs to be after, because resizableDayEvent might stopImmediatePropagation on click
}
function bindSlotSeg(event, eventElement, seg) {
var timeElement = eventElement.find('div.fc-event-time');
if (isEventDraggable(event)) {
draggableSlotEvent(event, eventElement, timeElement);
}
if (seg.isEnd && isEventResizable(event)) {
resizableSlotEvent(event, eventElement, timeElement);
}
eventElementHandlers(event, eventElement);
}
/* Dragging
-----------------------------------------------------------------------------------*/
// when event starts out FULL-DAY
function draggableDayEvent(event, eventElement, isStart) {
var origWidth;
var revert;
var allDay=true;
var dayDelta;
var dis = opt('isRTL') ? -1 : 1;
var hoverListener = getHoverListener();
var colWidth = getColWidth();
var slotHeight = getSlotHeight();
var minMinute = getMinMinute();
eventElement.draggable({
zIndex: 9,
scroll: false,
opacity: opt('dragOpacity', 'month'), // use whatever the month view was using
revertDuration: opt('dragRevertDuration'),
start: function(ev, ui) {
trigger('eventDragStart', eventElement, event, ev, ui);
//hideEvents(event, eventElement);
origWidth = eventElement.width();
hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
clearOverlays();
if (cell) {
//setOverflowHidden(true);
revert = false;
dayDelta = colDelta * dis;
if (!cell.row) {
// on full-days
renderDayOverlay(
addDays(cloneDate(event.start), dayDelta),
addDays(exclEndDay(event), dayDelta)
);
resetElement();
}else{
// mouse is over bottom slots
if (isStart) {
if (allDay) {
// convert event to temporary slot-event
eventElement.width(colWidth - 10); // don't use entire width
setOuterHeight(
eventElement,
slotHeight * Math.round(
(event.end ? ((event.end - event.start) / MINUTE_MS) : opt('defaultEventMinutes'))
/ opt('slotMinutes')
)
);
eventElement.draggable('option', 'grid', [colWidth, 1]);
allDay = false;
}
else{
var cellDate = t.cellDate;
if (cell && (cell.col == origCell.col || !opt('selectHelper'))) {
var d1 = cellDate(cell);
var duration = event.end ? minDiff(event.end, event.start) : opt('defaultEventMinutes');
var d2 = addMinutes(cloneDate(d1, false), duration);
dates = [d1, d2].sort(cmp);
renderSlotSelection(dates[0], dates[1]);
}
}
}else{
revert = true;
}
}
revert = revert || (allDay && !dayDelta);
}else{
resetElement();
//setOverflowHidden(false);
revert = true;
}
eventElement.draggable('option', 'revert', revert);
}, ev, 'drag');
},
stop: function(ev, ui) {
hoverListener.stop();
clearOverlays();
trigger('eventDragStop', eventElement, event, ev, ui);
if (revert) {
// hasn't moved or is out of bounds (draggable has already reverted)
resetElement();
eventElement.css('filter', ''); // clear IE opacity side-effects
//showEvents(event, eventElement);
}else{
// changed!
var minuteDelta = 0;
if (!allDay) {
minuteDelta = Math.round((eventElement.offset().top - getBodyContent().offset().top) / slotHeight)
* opt('slotMinutes')
+ minMinute
- (event.start.getHours() * 60 + event.start.getMinutes());
}
eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);
}
//setOverflowHidden(false);
}
});
function resetElement() {
if (!allDay) {
eventElement
.width(origWidth)
.height('')
.draggable('option', 'grid', null);
allDay = true;
}
}
}
// when event starts out IN TIMESLOTS
function draggableSlotEvent(event, eventElement, timeElement) {
var origPosition;
var allDay=false;
var dayDelta;
var minuteDelta;
var prevMinuteDelta;
var dis = opt('isRTL') ? -1 : 1;
var hoverListener = getHoverListener();
var colCnt = getColCnt();
var colWidth = getColWidth();
var slotHeight = getSlotHeight();
eventElement.draggable({
zIndex: 9,
scroll: false,
grid: [colWidth, slotHeight],
axis: colCnt==1 ? 'y' : false,
opacity: opt('dragOpacity'),
revertDuration: opt('dragRevertDuration'),
start: function(ev, ui) {
trigger('eventDragStart', eventElement, event, ev, ui);
//hideEvents(event, eventElement);
origPosition = eventElement.position();
minuteDelta = prevMinuteDelta = 0;
hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
eventElement.draggable('option', 'revert', !cell);
clearOverlays();
if (cell) {
dayDelta = colDelta * dis;
if (opt('allDaySlot') && !cell.row) {
// over full days
if (!allDay) {
// convert to temporary all-day event
allDay = true;
timeElement.hide();
eventElement.draggable('option', 'grid', null);
}
renderDayOverlay(
addDays(cloneDate(event.start), dayDelta),
addDays(exclEndDay(event), dayDelta)
);
}else{
// on slots
resetElement();
}
}
}, ev, 'drag');
},
drag: function(ev, ui) {
ui.position.left = origPosition.left + (dayDelta * dis) * colWidth;
minuteDelta = Math.round((ui.position.top - origPosition.top) / slotHeight) * opt('slotMinutes');
if (minuteDelta != prevMinuteDelta) {
if (!allDay) {
updateTimeText(minuteDelta);
}
prevMinuteDelta = minuteDelta;
}
},
stop: function(ev, ui) {
var cell = hoverListener.stop();
clearOverlays();
trigger('eventDragStop', eventElement, event, ev, ui);
if (cell && (dayDelta || minuteDelta || allDay)) {
// changed!
eventDrop(this, event, dayDelta, allDay ? 0 : minuteDelta, allDay, ev, ui);
}else{
// either no change or out-of-bounds (draggable has already reverted)
resetElement();
eventElement.css('filter', ''); // clear IE opacity side-effects
eventElement.css(origPosition); // sometimes fast drags make event revert to wrong position
updateTimeText(0);
//showEvents(event, eventElement);
}
}
});
function updateTimeText(minuteDelta) {
var newStart = addMinutes(cloneDate(event.start), minuteDelta);
var newEnd;
if (event.end) {
newEnd = addMinutes(cloneDate(event.end), minuteDelta);
}
timeElement.text(formatDates(newStart, newEnd, opt('timeFormat')));
}
function resetElement() {
// convert back to original slot-event
if (allDay) {
timeElement.css('display', ''); // show() was causing display=inline
eventElement.draggable('option', 'grid', [colWidth, slotHeight]);
allDay = false;
}
}
}
/* Resizing
--------------------------------------------------------------------------------------*/
function resizableSlotEvent(event, eventElement, timeElement) {
var slotDelta, prevSlotDelta;
var slotHeight = getSlotHeight();
eventElement.resizable({
handles: {
s: 'div.ui-resizable-s'
},
grid: slotHeight,
start: function(ev, ui) {
slotDelta = prevSlotDelta = 0;
//hideEvents(event, eventElement);
eventElement.css('z-index', 9);
trigger('eventResizeStart', this, event, ev, ui);
},
resize: function(ev, ui) {
// don't rely on ui.size.height, doesn't take grid into account
slotDelta = Math.round((Math.max(slotHeight, eventElement.height()) - ui.originalSize.height) / slotHeight);
if (slotDelta != prevSlotDelta) {
timeElement.text(
formatDates(
event.start,
(!slotDelta && !event.end) ? null : // no change, so don't display time range
addMinutes(eventEnd(event), opt('slotMinutes')*slotDelta),
opt('timeFormat')
)
);
prevSlotDelta = slotDelta;
}
},
stop: function(ev, ui) {
trigger('eventResizeStop', this, event, ev, ui);
var minutesDelta = opt('slotMinutes')*slotDelta;
if(event.end===null) {
minutesDelta+=opt('defaultEventMinutes');
}
if (slotDelta) {
eventResize(this, event, 0, minutesDelta, ev, ui);
}else{
eventElement.css('z-index', 8);
//showEvents(event, eventElement);
// BUG: if event was really short, need to put title back in span
}
}
});
}
}
function countForwardSegs(levels) {
var i, j, k, level, segForward, segBack;
for (i=levels.length-1; i>0; i--) {
level = levels[i];
for (j=0; j<level.length; j++) {
segForward = level[j];
for (k=0; k<levels[i-1].length; k++) {
segBack = levels[i-1][k];
if (segsCollide(segForward, segBack)) {
segBack.forward = Math.max(segBack.forward||0, (segForward.forward||0)+1);
}
}
}
}
}
function View(element, calendar, viewName) {
var t = this;
// exports
t.element = element;
t.calendar = calendar;
t.name = viewName;
t.opt = opt;
t.trigger = trigger;
//t.setOverflowHidden = setOverflowHidden;
t.isEventDraggable = isEventDraggable;
t.isEventResizable = isEventResizable;
t.reportEvents = reportEvents;
t.eventEnd = eventEnd;
t.reportEventElement = reportEventElement;
t.reportEventClear = reportEventClear;
t.eventElementHandlers = eventElementHandlers;
t.showEvents = showEvents;
t.hideEvents = hideEvents;
t.eventDrop = eventDrop;
t.eventResize = eventResize;
t.selectedElement = null;
t.selectEvent = selectEvent;
// t.title
// t.start, t.end
// t.visStart, t.visEnd
// imports
var defaultEventEnd = t.defaultEventEnd;
var normalizeEvent = calendar.normalizeEvent; // in EventManager
var reportEventChange = calendar.reportEventChange;
// locals
var eventsByID = {};
var eventElements = [];
var eventElementsByID = {};
var options = calendar.options;
function opt(name, viewNameOverride) {
var v = options[name];
if (typeof v == 'object' && !v.length && !$.isArray(v)) {
return smartProperty(v, viewNameOverride || viewName);
}
return v;
}
function trigger(name, thisObj) {
return calendar.trigger.apply(
calendar,
[name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t])
);
}
function isEventDraggable(event) {
return isEventEditable(event) && !opt('disableDragging');
}
function isEventResizable(event) { // but also need to make sure the seg.isEnd == true
return isEventEditable(event) && !opt('disableResizing');
}
function isEventEditable(event) {
return firstDefined(event.editable, (event.source || {}).editable, opt('editable'));
}
/* Event Data
------------------------------------------------------------------------------*/
// report when view receives new events
function reportEvents(events) { // events are already normalized at this point
eventsByID = {};
var i, len=events.length, event;
for (i=0; i<len; i++) {
event = events[i];
if (eventsByID[event._id]) {
eventsByID[event._id].push(event);
}else{
eventsByID[event._id] = [event];
}
}
}
// returns a Date object for an event's end
function eventEnd(event) {
return event.end ? cloneDate(event.end) : defaultEventEnd(event);
}
/* Event Elements
------------------------------------------------------------------------------*/
// report when view creates an element for an event
function reportEventElement(event, element) {
eventElements.push(element);
if (eventElementsByID[event._id]) {
eventElementsByID[event._id].push(element);
}else{
eventElementsByID[event._id] = [element];
}
}
function reportEventClear() {
eventElements = [];
eventElementsByID = {};
}
// attaches eventClick, eventMouseover, eventMouseout
function eventElementHandlers(event, eventElement) {
eventElement
.click(function(ev) {
if (!eventElement.hasClass('ui-draggable-dragging') &&
!eventElement.hasClass('ui-resizable-resizing')) {
selectEvent(eventElement, true);
return trigger('eventClick', this, event, ev);
}
})
.hover(
function(ev) {
trigger('eventMouseover', this, event, ev);
},
function(ev) {
trigger('eventMouseout', this, event, ev);
}
);
eventElement.find('.fc-event-checkbox').click(function(ev) {
trigger('eventCheckClicked', this, $(this), event, ev);
});
// TODO: don't fire eventMouseover/eventMouseout *while* dragging is occuring (on subject element)
// TODO: same for resizing
}
function selectEvent(eventElement, noClick) {
if(t.name!='todo' || t.eventSelectLock<0) {
return false;
}
if(typeof eventElement=='undefined' || eventElement==null || eventElement.length==0) {
eventElement=t.getDaySegmentContainer().find($('.fc-event[data-repeat-hash="'+t.selectedElement+'"]:visible'));
}
if(eventElement.length==0) {
eventElement=t.element.find('.fc-event:visible:first');
}
if(eventElement.length==0) {
trigger('selectEmpty');
return false;
}
t.selectedElement=eventElement.attr('data-repeat-hash');
t.element.find('.fc-event-selected').removeClass('fc-event-selected');
eventElement.addClass('fc-event-selected');
var offset=eventElement.position().top;
if(offset<eventElement.outerHeight() || offset>t.getDaySegmentContainer().parent().height())
{
var top=t.getDaySegmentContainer().parent().scrollTop();
t.getDaySegmentContainer().parent().scrollTop(top+offset-(t.getDaySegmentContainer().parent().height()*0.2));
}
// Force event click callback, although its not pretty
if(!noClick) {
eventElement.trigger('mouseover').trigger('click');
}
}
function showEvents(event, exceptElement) {
eachEventElement(event, exceptElement, 'show');
}
function hideEvents(event, exceptElement) {
eachEventElement(event, exceptElement, 'hide');
}
function eachEventElement(event, exceptElement, funcName) {
event[funcName]();
// var elements = eventElementsByID[event._id],
// i, len = elements.length;
// for (i=0; i<len; i++) {
// if (!exceptElement || elements[i][0] != exceptElement[0]) {
// elements[i][funcName]();
// }
// }
}
/* Event Modification Reporting
---------------------------------------------------------------------------------*/
function eventDrop(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
var oldAllDay = event.allDay;
var eventId = event._id;
//moveEvents(eventsByID[eventId], dayDelta, minuteDelta, allDay);
moveEvents([event], dayDelta, minuteDelta, allDay);
trigger(
'eventDrop',
e,
event,
dayDelta,
minuteDelta,
allDay,
function() {
// TODO: investigate cases where this inverse technique might not work
//moveEvents(eventsByID[eventId], -dayDelta, -minuteDelta, oldAllDay);
moveEvents([event], -dayDelta, -minuteDelta, oldAllDay);
reportEventChange(eventId);
},
ev,
ui
);
reportEventChange(eventId);
}
function eventResize(e, event, dayDelta, minuteDelta, ev, ui) {
var eventId = event._id;
//elongateEvents(eventsByID[eventId], dayDelta, minuteDelta);
elongateEvents([event], dayDelta, minuteDelta);
trigger(
'eventResize',
e,
event,
dayDelta,
minuteDelta,
function() {
// TODO: investigate cases where this inverse technique might not work
//elongateEvents(eventsByID[eventId], -dayDelta, -minuteDelta);
elongateEvents([event], -dayDelta, -minuteDelta);
reportEventChange(eventId);
},
ev,
ui
);
reportEventChange(eventId);
}
/* Event Modification Math
---------------------------------------------------------------------------------*/
function moveEvents(events, dayDelta, minuteDelta, allDay) {
minuteDelta = minuteDelta || 0;
for (var e, len=events.length, i=0; i<len; i++) {
e = events[i];
if (allDay !== undefined) {
e.allDay = allDay;
}
addMinutes(addDays(e.start, dayDelta, true), minuteDelta);
if (e.end) {
e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);
}
normalizeEvent(e, options);
}
}
function elongateEvents(events, dayDelta, minuteDelta) {
minuteDelta = minuteDelta || 0;
for (var e, len=events.length, i=0; i<len; i++) {
e = events[i];
e.end = addMinutes(addDays(eventEnd(e), dayDelta, true), minuteDelta);
normalizeEvent(e, options);
}
}
}
function DayEventRenderer() {
var t = this;
// exports
t.renderDaySegs = renderDaySegs;
t.resizableDayEvent = resizableDayEvent;
// imports
var opt = t.opt;
var trigger = t.trigger;
var isEventDraggable = t.isEventDraggable;
var isEventResizable = t.isEventResizable;
var eventEnd = t.eventEnd;
var reportEventElement = t.reportEventElement;
var showEvents = t.showEvents;
var hideEvents = t.hideEvents;
var eventResize = t.eventResize;
var getRowCnt = t.getRowCnt;
var getColCnt = t.getColCnt;
var getColWidth = t.getColWidth;
var allDayRow = t.allDayRow;
var allDayBounds = t.allDayBounds;
var colContentLeft = t.colContentLeft;
var colContentRight = t.colContentRight;
var dayOfWeekCol = t.dayOfWeekCol;
var dateCell = t.dateCell;
var compileDaySegs = t.compileDaySegs;
var getDaySegmentContainer = t.getDaySegmentContainer;
var bindDaySeg = t.bindDaySeg; //TODO: streamline this
var formatDates = t.calendar.formatDates;
var renderDayOverlay = t.renderDayOverlay;
var clearOverlays = t.clearOverlays;
var clearSelection = t.clearSelection;
/* Rendering
-----------------------------------------------------------------------------*/
function renderDaySegs(segs, modifiedEventId, isAllDay) {
var segmentContainer = getDaySegmentContainer();
var rowDivs;
var rowCnt = getRowCnt();
var colCnt = getColCnt();
var i = 0;
var rowI;
var levelI;
var colHeights;
var j;
var segCnt = segs.length;
var seg;
var top;
var k;
segmentContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
daySegElementResolve(segs, segmentContainer.children());
daySegElementReport(segs);
daySegHandlers(segs, segmentContainer, modifiedEventId);
daySegCalcHSides(segs);
daySegSetWidths(segs);
daySegCalcHeights(segs);
rowDivs = getRowDivs();
// set row heights, calculate event tops (in relation to row top)
for (rowI=0; rowI<rowCnt; rowI++) {
levelI = 0;
colHeights = [];
for (j=0; j<colCnt; j++) {
colHeights[j] = 0;
}
while (i<segCnt && (seg = segs[i]).row == rowI) {
// loop through segs in a row
top = arrayMax(colHeights.slice(seg.startCol, seg.endCol));
seg.top = top;
if (typeof seg.outerHeight != "undefined") top += seg.outerHeight;
for (k=seg.startCol; k<seg.endCol; k++) {
colHeights[k] = top;
}
i++;
}
if(isAllDay) {
segmentContainer.parent().height(arrayMax(colHeights) ? arrayMax(colHeights) + 3 : 0);
}
rowDivs[rowI].height(arrayMax(colHeights));
}
daySegSetTops(segs, getRowTops(rowDivs));
$('.fc-source-bg', t.element).removeClass('fc-source-bg');
if(!isAllDay) { // month or multiweek view
for (i=0; i<segCnt; i++) {
seg = segs[i];
if(seg.event.source && seg.event.source.background) {
for(c=seg.startCol; c<seg.endCol; c++) {
$('td.fc-day' + (seg.row*7+c), t.element).addClass('fc-source-bg');
}
}
}
}
else { // agenda views
for (i=0; i<segCnt; i++) {
seg = segs[i];
if(seg.event.source && seg.event.source.background) {
for(c=seg.startCol; c<seg.endCol; c++) {
$('td.fc-col' + c, t.element).addClass('fc-source-bg');
}
}
}
}
}
function renderTempDaySegs(segs, adjustRow, adjustTop) {
var tempContainer = $("<div/>");
var elements;
var segmentContainer = getDaySegmentContainer();
var i;
var segCnt = segs.length;
var element;
tempContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
elements = tempContainer.children();
segmentContainer.append(elements);
daySegElementResolve(segs, elements);
daySegCalcHSides(segs);
daySegSetWidths(segs);
daySegCalcHeights(segs);
daySegSetTops(segs, getRowTops(getRowDivs()));
elements = [];
for (i=0; i<segCnt; i++) {
element = segs[i].element;
if (element) {
if (segs[i].row === adjustRow) {
element.css('top', adjustTop);
}
elements.push(element[0]);
}
}
return $(elements);
}
function daySegHTML(segs) { // also sets seg.left and seg.outerWidth
var rtl = opt('isRTL');
var i;
var segCnt=segs.length;
var seg;
var event;
var url;
var classes;
var bounds = allDayBounds();
var minLeft = bounds.left;
var maxLeft = bounds.right;
var leftCol;
var rightCol;
var left;
var right;
var titleWidth;
var skinCss;
var html = '';
// calculate desired position/dimensions, create html
for (i=0; i<segCnt; i++) {
seg = segs[i];
event = seg.event;
classes = ['fc-event', 'fc-event-skin', 'fc-event-hori'];
if (isEventDraggable(event)) {
classes.push('fc-event-draggable');
}
if (rtl) {
if (seg.isStart) {
classes.push('fc-corner-right');
}
if (seg.isEnd) {
classes.push('fc-corner-left');
}
leftCol = dayOfWeekCol(seg.end.getDay()-1);
rightCol = dayOfWeekCol(seg.start.getDay());
left = seg.isEnd ? colContentLeft(leftCol) : minLeft;
right = seg.isStart ? colContentRight(rightCol) : maxLeft;
}else{
if (seg.isStart) {
classes.push('fc-corner-left');
}
if (seg.isEnd) {
classes.push('fc-corner-right');
}
leftCol = dayOfWeekCol(seg.start.getDay());
rightCol = dayOfWeekCol(seg.end.getDay()-1);
left = seg.isStart ? colContentLeft(leftCol) : minLeft;
right = seg.isEnd ? colContentRight(rightCol) : maxLeft;
}
titleWidth = right - left - 2 - 2 - 2;
classes = classes.concat(event.className);
if (event.source) {
classes = classes.concat(event.source.className || []);
}
url = event.url;
skinCss = getSkinCss(event, opt);
if (url) {
html += "<a href='" + htmlEscape(url) + "'";
}else{
html += "<div";
}
html +=
" class='" + classes.join(' ') + "'" +
" style='position:absolute;z-index:8;left:"+left+"px;" + skinCss + "'" +
">" +
"<div" +
" class='fc-event-inner fc-event-skin'" +
" style='width:" + titleWidth + "px;z-index:inherit;" +
(skinCss ? skinCss : '') +
"'" +
//(skinCss ? " style='" + skinCss + "'" : '') +
">";
if (opt('dayEventSizeStrict')) {
html += "<div class='fc-event-title-strict'>";
}
if (!event.allDay && seg.isStart && opt('timeFormat')) {
html +=
"<span class='fc-event-time'>" +
htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
"</span>";
}
html += "<span class='fc-event-title'>" + htmlEscape(event.title.replace(/(\r\n|\n|\r)+/gm," ")) + "</span>";
if (opt('dayEventSizeStrict')) {
html += "</div>";
}
html += "</div>";
if (seg.isEnd && isEventResizable(event)) {
html +=
"<div class='ui-resizable-handle ui-resizable-" + (rtl ? 'w' : 'e') + "'>" +
"&nbsp;&nbsp;&nbsp;" + // makes hit area a lot better for IE6/7
"</div>";
}
html +=
"<div class='fc-event-bg'></div>" +
"</" + (url ? "a" : "div" ) + ">";
seg.left = left;
seg.outerWidth = right - left;
seg.startCol = leftCol;
seg.endCol = rightCol + 1; // needs to be exclusive
}
return html;
}
function daySegElementResolve(segs, elements) { // sets seg.element
var i;
var segCnt = segs.length;
var seg;
var event;
var element;
var triggerRes;
for (i=0; i<segCnt; i++) {
seg = segs[i];
event = seg.event;
element = $(elements[i]); // faster than .eq()
triggerRes = trigger('eventRender', event, event, element);
if (triggerRes === false) {
element.remove();
}else{
if (triggerRes && triggerRes !== true) {
triggerRes = $(triggerRes)
.css({
position: 'absolute',
left: seg.left
});
element.replaceWith(triggerRes);
element = triggerRes;
}
seg.element = element;
}
}
}
function daySegElementReport(segs) {
var i;
var segCnt = segs.length;
var seg;
var element;
for (i=0; i<segCnt; i++) {
seg = segs[i];
element = seg.element;
if (element) {
reportEventElement(seg.event, element);
}
}
}
function daySegHandlers(segs, segmentContainer, modifiedEventId) {
var i;
var segCnt = segs.length;
var seg;
var element;
var event;
// retrieve elements, run through eventRender callback, bind handlers
for (i=0; i<segCnt; i++) {
seg = segs[i];
element = seg.element;
if (element) {
event = seg.event;
if (event._id === modifiedEventId) {
bindDaySeg(event, element, seg);
}else{
element[0]._fci = i; // for lazySegBind
}
}
}
lazySegBind(segmentContainer, segs, bindDaySeg);
}
function daySegCalcHSides(segs) { // also sets seg.key
var i;
var segCnt = segs.length;
var seg;
var element;
var key, val;
var hsideCache = {};
// record event horizontal sides
for (i=0; i<segCnt; i++) {
seg = segs[i];
element = seg.element;
if (element) {
key = seg.key = cssKey(element[0]);
val = hsideCache[key];
if (val === undefined) {
val = hsideCache[key] = hsides(element, true);
}
seg.hsides = val;
}
}
}
function daySegSetWidths(segs) {
var i;
var segCnt = segs.length;
var seg;
var element;
for (i=0; i<segCnt; i++) {
seg = segs[i];
element = seg.element;
if (element) {
element[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
}
}
}
function daySegCalcHeights(segs) {
var i;
var segCnt = segs.length;
var seg;
var element;
var key, val;
var vmarginCache = {};
// record event heights
for (i=0; i<segCnt; i++) {
seg = segs[i];
element = seg.element;
if (element) {
key = seg.key; // created in daySegCalcHSides
val = vmarginCache[key];
if (val === undefined) {
val = vmarginCache[key] = vmargins(element);
}
seg.outerHeight = element[0].offsetHeight + val;
}
else // always set a value (issue #1108 )
seg.outerHeight = 0;
}
}
function getRowDivs() {
var i;
var rowCnt = getRowCnt();
var rowDivs = [];
for (i=0; i<rowCnt; i++) {
rowDivs[i] = allDayRow(i)
.find('td:first div.fc-day-content > div'); // optimal selector?
}
return rowDivs;
}
function getRowTops(rowDivs) {
var i;
var rowCnt = rowDivs.length;
var tops = [];
for (i=0; i<rowCnt; i++) {
tops[i] = rowDivs[i][0].offsetTop; // !!?? but this means the element needs position:relative if in a table cell!!!!
}
return tops;
}
function daySegSetTops(segs, rowTops) { // also triggers eventAfterRender
var i;
var segCnt = segs.length;
var seg;
var element;
var event;
for (i=0; i<segCnt; i++) {
seg = segs[i];
element = seg.element;
if (element) {
element[0].style.top = rowTops[seg.row] + (seg.top||0) + 'px';
event = seg.event;
trigger('eventAfterRender', event, event, element);
}
}
}
/* Resizing
-----------------------------------------------------------------------------------*/
function resizableDayEvent(event, element, seg) {
var rtl = opt('isRTL');
var direction = rtl ? 'w' : 'e';
var handle = element.find('div.ui-resizable-' + direction);
var isResizing = false;
// TODO: look into using jquery-ui mouse widget for this stuff
disableTextSelection(element); // prevent native <a> selection for IE
element
.mousedown(function(ev) { // prevent native <a> selection for others
ev.preventDefault();
})
.click(function(ev) {
if (isResizing) {
ev.preventDefault(); // prevent link from being visited (only method that worked in IE6)
ev.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called
// (eventElementHandlers needs to be bound after resizableDayEvent)
}
});
handle.mousedown(function(ev) {
if (ev.which != 1) {
return; // needs to be left mouse button
}
isResizing = true;
var hoverListener = t.getHoverListener();
var rowCnt = getRowCnt();
var colCnt = getColCnt();
var dis = rtl ? -1 : 1;
var dit = rtl ? colCnt-1 : 0;
var elementTop = element.css('top');
var dayDelta;
var helpers;
var eventCopy = $.extend({}, event);
var minCell = dateCell(event.start);
clearSelection();
$('body')
.css('cursor', direction + '-resize')
.one('mouseup', mouseup);
trigger('eventResizeStart', this, event, ev);
hoverListener.start(function(cell, origCell) {
if (cell) {
var r = Math.max(minCell.row, cell.row);
var c = cell.col;
if (rowCnt == 1) {
r = 0; // hack for all-day area in agenda views
}
if (r == minCell.row) {
if (rtl) {
c = Math.min(minCell.col, c);
}else{
c = Math.max(minCell.col, c);
}
}
dayDelta = (r*7 + c*dis+dit) - (origCell.row*7 + origCell.col*dis+dit);
var newEnd = addDays(eventEnd(event), dayDelta, true);
if (dayDelta) {
eventCopy.end = newEnd;
var oldHelpers = helpers;
helpers = renderTempDaySegs(compileDaySegs([eventCopy]), seg.row, elementTop);
helpers.find('*').css('cursor', direction + '-resize');
trigger('eventResizeHelperCreated', this, event, ev, element, helpers);
if (oldHelpers) {
oldHelpers.remove();
}
//hideEvents(event);
hideEvents(element);
}else{
if (helpers) {
//showEvents(event);
showEvents(element);
helpers.remove();
helpers = null;
}
}
clearOverlays();
renderDayOverlay(event.start, addDays(cloneDate(newEnd), 1)); // coordinate grid already rebuild at hoverListener.start
}
}, ev);
function mouseup(ev) {
trigger('eventResizeStop', this, event, ev);
$('body').css('cursor', '');
hoverListener.stop();
clearOverlays();
if (dayDelta) {
eventResize(this, event, dayDelta, 0, ev);
// event redraw will clear helpers
}
// otherwise, the drag handler already restored the old events
setTimeout(function() { // make this happen after the element's click event
isResizing = false;
},0);
}
});
}
}
//BUG: unselect needs to be triggered when events are dragged+dropped
function SelectionManager() {
var t = this;
// exports
t.select = select;
t.unselect = unselect;
t.reportSelection = reportSelection;
t.daySelectionMousedown = daySelectionMousedown;
// imports
var opt = t.opt;
var trigger = t.trigger;
var defaultSelectionEnd = t.defaultSelectionEnd;
var renderSelection = t.renderSelection;
var clearSelection = t.clearSelection;
// locals
var selected = false;
// unselectAuto
if (opt('selectable') && opt('unselectAuto')) {
$(document).mousedown(function(ev) {
var ignore = opt('unselectCancel');
if (ignore) {
if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match
return;
}
}
unselect(ev);
});
}
function select(startDate, endDate, allDay) {
unselect();
if (!endDate) {
endDate = defaultSelectionEnd(startDate, allDay);
}
renderSelection(startDate, endDate, allDay);
reportSelection(startDate, endDate, allDay);
}
function unselect(ev) {
if (selected) {
selected = false;
clearSelection();
trigger('unselect', null, ev);
}
}
function reportSelection(startDate, endDate, allDay, ev) {
selected = true;
trigger('select', null, startDate, endDate, allDay, ev);
}
function daySelectionMousedown(ev) { // not really a generic manager method, oh well
var cellDate = t.cellDate;
var cellIsAllDay = t.cellIsAllDay;
var hoverListener = t.getHoverListener();
var reportDayClick = t.reportDayClick; // this is hacky and sort of weird
if (ev.which == 1 && opt('selectable')) { // which==1 means left mouse button
unselect(ev);
var _mousedownElement = this;
var dates;
hoverListener.start(function(cell, origCell) { // TODO: maybe put cellDate/cellIsAllDay info in cell
clearSelection();
if (cell && cellIsAllDay(cell)) {
dates = [ cellDate(origCell), cellDate(cell) ].sort(cmp);
renderSelection(dates[0], dates[1], true);
}else{
dates = null;
}
}, ev);
$(document).one('mouseup', function(ev) {
hoverListener.stop();
if (dates) {
if (+dates[0] == +dates[1]) {
//reportDayClick(dates[0], true, ev);
}
reportSelection(dates[0], dates[1], true, ev);
}
});
}
}
}
function OverlayManager() {
var t = this;
// exports
t.renderOverlay = renderOverlay;
t.clearOverlays = clearOverlays;
// locals
var usedOverlays = [];
var unusedOverlays = [];
function renderOverlay(rect, parent) {
var e = unusedOverlays.shift();
if (!e) {
e = $("<div class='fc-cell-overlay' style='position:absolute;z-index:3'/>");
}
if (e[0].parentNode != parent[0]) {
e.appendTo(parent);
}
usedOverlays.push(e.css(rect).show());
return e;
}
function clearOverlays() {
var e;
while (e = usedOverlays.shift()) {
unusedOverlays.push(e.hide().unbind());
}
}
}
function CoordinateGrid(buildFunc) {
var t = this;
var rows;
var cols;
t.build = function() {
rows = [];
cols = [];
buildFunc(rows, cols);
};
t.cell = function(x, y) {
var rowCnt = rows.length;
var colCnt = cols.length;
var i, r=-1, c=-1;
for (i=0; i<rowCnt; i++) {
if (y >= rows[i][0] && y < rows[i][1]) {
r = i;
break;
}
}
for (i=0; i<colCnt; i++) {
if (x >= cols[i][0] && x < cols[i][1]) {
c = i;
break;
}
}
return (r>=0 && c>=0) ? { row:r, col:c } : null;
};
t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive
var origin = originElement.offset();
return {
top: rows[row0][0] - origin.top,
left: cols[col0][0] - origin.left,
width: cols[col1][1] - cols[col0][0],
height: rows[row1][1] - rows[row0][0]
};
};
}
function HoverListener(coordinateGrid) {
var t = this;
var bindType;
var change;
var firstCell;
var cell;
var origEvent;
t.start = function(_change, ev, _bindType) {
origEvent = ev;
change = _change;
firstCell = cell = null;
coordinateGrid.build();
mouse(ev);
bindType = _bindType || 'mousemove';
$(document).bind(bindType, mouse);
};
function mouse(ev) {
_fixUIEvent(ev); // see below
if(origEvent.pageX - ev.pageX == 0 && origEvent.pageY - ev.pageY == 0) {
return false;
}
var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) {
if (newCell) {
if (!firstCell) {
firstCell = newCell;
}
change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col);
}else{
change(newCell, firstCell);
}
cell = newCell;
}
}
t.stop = function() {
$(document).unbind(bindType, mouse);
return cell;
};
}
// this fix was only necessary for jQuery UI 1.8.16 (and jQuery 1.7 or 1.7.1)
// upgrading to jQuery UI 1.8.17 (and using either jQuery 1.7 or 1.7.1) fixed the problem
// but keep this in here for 1.8.16 users
// and maybe remove it down the line
function _fixUIEvent(event) { // for issue 1168
if (event.pageX === undefined) {
event.pageX = event.originalEvent.pageX;
event.pageY = event.originalEvent.pageY;
}
}
function HorizontalPositionCache(getElement) {
var t = this,
elements = {},
lefts = {},
rights = {};
function e(i) {
return elements[i] = elements[i] || getElement(i);
}
t.left = function(i) {
return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i];
};
t.right = function(i) {
return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i];
};
t.clear = function() {
elements = {};
lefts = {};
rights = {};
};
}
function addTodayText(cell, todayText)
{
target = cell.find(".fc-day-text");
target.html(todayText);
}
function removeTodayText(cell, todayText)
{
target = cell.find(".fc-day-text");
target.html('');
}
function addWeekNumber(cell, date)
{
target = cell.find(".fc-week-number");
target.html(getWeekNumber(date));
}
function removeWeekNumber(cell, date)
{
target = cell.find(".fc-week-number");
target.html('');
}
function addTodayClass(cell)
{
var classes = cell.attr('class').split(' ');
var filter = ['fc-state-highlight', 'fc-today', 'fc-widget-content', 'fc-source-bg'];
classes = $.grep(classes, function(el) {
if ($.inArray(el, filter) > -1) {
return false;
}
return true;
});
classes.push('fc-widget-header');
var target = $('.' + classes.join('.'));
target.addClass('fc-today');
}
function removeTodayClass(cell)
{
var classes = cell.attr('class').split(' ');
var filter = ['fc-state-highlight', 'fc-today', 'fc-widget-content', 'fc-source-bg'];
classes = $.grep(classes, function(el) {
if ($.inArray(el, filter) > -1) {
return false;
}
return true;
});
classes.push('fc-widget-header');
var target = $('.' + classes.join('.'));
target.removeClass('fc-today');
}
function getWeekNumber(date) {
//By tanguy.pruvot at gmail.com (2010)
//first week of year always contains 4th Jan, or 28 Dec (ISO)
var jan4 = new Date(date.getFullYear(),0,4 ,date.getHours());
//ISO weeks numbers begins on monday, so rotate monday:sunday to 0:6
var jan4Day = (jan4.getDay() - 1 + 7) % 7;
var days = Math.round((date - jan4) / 86400000);
var week = Math.floor((days + jan4Day ) / 7)+1;
//special cases
var thisDay = (date.getDay() - 1 + 7) % 7;
if (date.getMonth()==11 && date.getDate() >= 28) {
jan4 = new Date(date.getFullYear()+1,0,4 ,date.getHours());
jan4Day = (jan4.getDay() - 1 + 7) % 7;
if (thisDay < jan4Day) return 1;
var prevWeek = new Date(date.valueOf()-(86400000*7));
return getWeekNumber(prevWeek) + 1;
}
if (week == 0 && thisDay > 3 && date.getMonth()==0) {
var prevWeek = new Date(date.valueOf()-(86400000*7));
return getWeekNumber(prevWeek) + 1;
}
return week;
}
/* Additional view: list (by bruederli@kolabsys.com)
---------------------------------------------------------------------------------*/
function ListEventRenderer() {
var t = this;
// exports
t.renderEvents = renderEvents;
t.renderEventTime = renderEventTime;
t.compileDaySegs = compileSegs; // for DayEventRenderer
t.clearEvents = clearEvents;
t.lazySegBind = lazySegBind;
t.sortCmp = sortCmp;
// imports
DayEventRenderer.call(t);
var opt = t.opt;
var trigger = t.trigger;
var reportEvents = t.reportEvents;
var reportEventClear = t.reportEventClear;
var reportEventElement = t.reportEventElement;
var eventElementHandlers = t.eventElementHandlers;
var showEvents = t.showEvents;
var hideEvents = t.hideEvents;
var getListContainer = t.getDaySegmentContainer;
var calendar = t.calendar;
var formatDate = calendar.formatDate;
var formatDates = calendar.formatDates;
/* Rendering
--------------------------------------------------------------------*/
function clearEvents() {
reportEventClear();
getListContainer().empty();
}
function renderEvents(events, modifiedEventId) {
events.sort(sortCmp);
reportEvents(events);
renderSegs(compileSegs(events), modifiedEventId);
}
/*function compileSegs(events) {
var segs = [];
var colFormat = opt('titleFormat', 'day');
var firstDay = opt('firstDay');
var segmode = opt('listSections');
var event, i, dd, wd, md, seg, segHash, curSegHash, segDate, curSeg = -1;
var today = clearTime(new Date());
var weekstart = addDays(cloneDate(today), -((today.getDay() - firstDay + 7) % 7));
for (i=0; i < events.length; i++) {
event = events[i];
var eventEnd = event.end ? cloneDate(event.end) : cloneDate(event.start);
// skip events out of range
if (eventEnd < t.start || event.start > t.visEnd)
continue;
// define sections of this event
// create smart sections such as today, tomorrow, this week, next week, next month, ect.
segDate = cloneDate(event.start < t.start && eventEnd > t.start ? t.start : event.start, true);
dd = dayDiff(segDate, today);
wd = Math.floor(dayDiff(segDate, weekstart) / 7);
md = segDate.getMonth() + ((segDate.getYear() - today.getYear()) * 12) - today.getMonth();
// build section title
if (segmode == 'smart') {
if (dd < 0) {
segHash = opt('listTexts', 'past');
} else if (dd == 0) {
segHash = opt('listTexts', 'today');
} else if (dd == 1) {
segHash = opt('listTexts', 'tomorrow');
} else if (wd == 0) {
segHash = opt('listTexts', 'thisWeek');
} else if (wd == 1) {
segHash = opt('listTexts', 'nextWeek');
} else if (md == 0) {
segHash = opt('listTexts', 'thisMonth');
} else if (md == 1) {
segHash = opt('listTexts', 'nextMonth');
} else if (md > 1) {
segHash = opt('listTexts', 'future');
}
} else if (segmode == 'month') {
segHash = formatDate(segDate, 'MMMM yyyy');
} else if (segmode == 'week') {
segHash = opt('listTexts', 'week') + formatDate(segDate, ' W');
} else if (segmode == 'day') {
segHash = formatDate(segDate, colFormat);
} else {
segHash = '';
}
// start new segment
if (segHash != curSegHash) {
segs[++curSeg] = { events: [], start: segDate, title: segHash, daydiff: dd, weekdiff: wd, monthdiff: md };
curSegHash = segHash;
}
segs[curSeg].events.push(event);
}
return segs;
}*/
function compileSegs(events) {
var segs = {};
var colFormat = opt('columnFormat', t.name);
var firstDay = opt('firstDay');
var segmode = opt('listSections');
var event, i, j, dd, wd, md, seg, segHash, segDate;
var today = clearTime(new Date());
var weekstart = addDays(cloneDate(today), -((today.getDay() - firstDay + 7) % 7));
for (i=0; i < events.length; i++) {
event = events[i];
var eventEnd = event.end ? cloneDate(event.end) : cloneDate(event.start);
// skip events out of range
if (eventEnd < t.start || event.start > t.visEnd)
continue;
var boundEventStart = cloneDate(event.start < t.start ? t.start : event.start, true);
var boundEventEnd = cloneDate(eventEnd > t.visEnd ? t.visEnd : eventEnd, true);
var dayDuration = dayDiff(boundEventEnd, boundEventStart);
for(j = 0; j <= dayDuration; j++) {
segDate = cloneDate(boundEventStart);
segDate.setDate(segDate.getDate() + j);
// define sections of this event
// create smart sections such as today, tomorrow, this week, next week, next month, ect.
//segDate = cloneDate(event.start < t.start && eventEnd > t.start ? t.start : event.start, true);
dd = dayDiff(segDate, today);
wd = Math.floor(dayDiff(segDate, weekstart) / 7);
md = segDate.getMonth() + ((segDate.getYear() - today.getYear()) * 12) - today.getMonth();
// build section title
if (segmode == 'smart') {
if (dd < 0) {
segHash = opt('listTexts', 'past');
} else if (dd == 0) {
segHash = opt('listTexts', 'today');
} else if (dd == 1) {
segHash = opt('listTexts', 'tomorrow');
} else if (wd == 0) {
segHash = opt('listTexts', 'thisWeek');
} else if (wd == 1) {
segHash = opt('listTexts', 'nextWeek');
} else if (md == 0) {
segHash = opt('listTexts', 'thisMonth');
} else if (md == 1) {
segHash = opt('listTexts', 'nextMonth');
} else if (md > 1) {
segHash = opt('listTexts', 'future');
}
} else if (segmode == 'month') {
segHash = formatDate(segDate, 'MMMM yyyy');
} else if (segmode == 'week') {
segHash = opt('listTexts', 'week') + formatDate(segDate, ' W');
} else if (segmode == 'day') {
segHash = formatDate(segDate, colFormat);
} else {
segHash = '';
}
// start new segment
if (!(segHash in segs)) {
segs[segHash] = { events: [], start: segDate, title: segHash, daydiff: dd, weekdiff: wd, monthdiff: md };
}
segs[segHash].events.push(event);
}
}
return segs;
}
function sortCmp(a, b) {
/*var datediff = 0;
if(a.start != null && b.start != null) {
datediff = a.start.getTime() - b.start.getTime();
}
if(datediff == 0 && a.end != null && b.end != null) {
datediff = a.end.getTime() - b.end.getTime();
}
return datediff;*/
var retVal = a.start.getTime() - b.start.getTime();
if(retVal == 0) {
var aEnd = a.end ? a.end : a.start;
var bEnd = b.end ? b.end : b.start;
retVal = aEnd.getTime() - bEnd.getTime();
}
if(retVal == 0) {
if(a.compareString < b.compareString) {
retVal = -1;
}
else if(b.compareString < a.compareString) {
retVal = 1;
}
}
return retVal;
}
function renderSegs(segs, modifiedEventId) {
var tm = opt('theme') ? 'ui' : 'fc';
var headerClass = tm + "-widget-header";
var contentClass = tm + "-widget-content";
var i, j, seg, event, times, s, skinCss, skinCssAttr, classes, segContainer, eventElement, eventElements, triggerRes;
for (j=0; j < segs.length; j++) {
seg = segs[j];
if (seg.title) {
$('<div class="fc-list-header ' + headerClass + '">' + htmlEscape(seg.title) + '</div>').appendTo(getListContainer());
}
segContainer = $('<div>').addClass('fc-list-section ' + contentClass).appendTo(getListContainer());
s = '';
for (i=0; i < seg.events.length; i++) {
event = seg.events[i];
times = renderEventTime(event, seg);
skinCss = getSkinCss(event, opt);
skinCssAttr = (skinCss ? " style='" + skinCss + "'" : '');
classes = ['fc-event', 'fc-event-skin', 'fc-event-vert', 'fc-corner-top', 'fc-corner-bottom'].concat(event.className);
if (event.source && event.source.className) {
classes = classes.concat(event.source.className);
}
s +=
"<div class='" + classes.join(' ') + "'" + skinCssAttr + ">" +
"<div class='fc-event-inner fc-event-skin'" + skinCssAttr + ">" +
"<div class='fc-event-head fc-event-skin'" + skinCssAttr + ">" +
"<div class='fc-event-time'>" +
(times[0] ? '<span class="fc-col-date">' + times[0] + '</span> ' : '') +
(times[1] ? '<span class="fc-col-time">' + times[1] + '</span>' : '') +
"</div>" +
"</div>" +
"<div class='fc-event-content'>" +
"<div class='fc-event-title'>" +
htmlEscape(event.title.replace(/(\r\n|\n|\r)+/gm," ")) +
"</div>" +
"</div>" +
"<div class='fc-event-bg'></div>" +
"</div>" + // close inner
"</div>"; // close outer
}
segContainer[0].innerHTML = s;
eventElements = segContainer.children();
// retrieve elements, run through eventRender callback, bind event handlers
for (i=0; i < seg.events.length; i++) {
event = seg.events[i];
eventElement = $(eventElements[i]); // faster than eq()
triggerRes = trigger('eventRender', event, event, eventElement);
if (triggerRes === false) {
eventElement.remove();
} else {
if (triggerRes && triggerRes !== true) {
eventElement.remove();
eventElement = $(triggerRes).appendTo(segContainer);
}
if (event._id === modifiedEventId) {
eventElementHandlers(event, eventElement, seg);
} else {
eventElement[0]._fci = i; // for lazySegBind
}
reportEventElement(event, eventElement);
}
}
lazySegBind(segContainer, seg, eventElementHandlers);
}
markFirstLast(getListContainer());
}
// event time/date range to display
function renderEventTime(event, seg) {
var timeFormat = opt('timeFormat', 'list');
var timeFormatFull = opt('timeFormat', 'listFull');
var timeFormatFullAllDay = opt('timeFormat', 'listFullAllDay');
var dateFormat = opt('columnFormat');
var segmode = opt('listSections');
var eventEnd = event.end ? cloneDate(event.end) : cloneDate(event.start);
var duration = eventEnd.getTime() - event.start.getTime();
var datestr = '', timestr = '';
if (segmode == 'smart') {
if (event.start < seg.start) {
datestr = opt('listTexts', 'until') + ' ' + formatDate(eventEnd, (event.allDay || eventEnd.getDate() != seg.start.getDate()) ? dateFormat : timeFormat);
} else if (duration > DAY_MS) {
datestr = formatDates(event.start, eventEnd, dateFormat + '{ ' + dateFormat + '}');
} else if (seg.daydiff == 0) {
datestr = opt('listTexts', 'today');
} else if (seg.daydiff == 1) {
datestr = opt('listTexts', 'tomorrow');
} else if (seg.weekdiff == 0 || seg.weekdiff == 1) {
datestr = formatDate(event.start, 'dddd');
} else if (seg.daydiff > 1 || seg.daydiff < 0) {
datestr = formatDate(event.start, dateFormat);
}
} else if (segmode != 'day') {
datestr = formatDates(event.start, eventEnd, dateFormat + (duration > DAY_MS ? '{ ' + dateFormat + '}' : ''));
}
if (!datestr && event.allDay) {
if(dayDiff(eventEnd, event.start)) { //spans multiple days
timestr = formatDates(event.start, eventEnd, timeFormatFullAllDay);
}
else {
timestr = opt('allDayText');
}
} else if ((!datestr || !dayDiff(eventEnd, event.start)) && !event.allDay) {
if(dayDiff(eventEnd, event.start)) //spans multiple days
timestr = formatDates(event.start, eventEnd, timeFormatFull);
else if(duration)
timestr = formatDates(event.start, eventEnd, timeFormat);
else
timestr = formatDates(event.start, null, timeFormat);
}
return [datestr, timestr];
}
function lazySegBind(container, seg, bindHandlers) {
container.unbind('mouseover').mouseover(function(ev) {
var parent = ev.target, e = parent, i, event;
while (parent != this) {
e = parent;
parent = parent.parentNode;
}
if ((i = e._fci) !== undefined) {
e._fci = undefined;
event = seg.events[i];
bindHandlers(event, container.children().eq(i), seg);
$(ev.target).trigger(ev);
}
ev.stopPropagation();
});
}
}
fcViews.list = ListView;
function ListView(element, calendar) {
var t = this;
// exports
t.render = render;
t.select = dummy;
t.unselect = dummy;
t.getDaySegmentContainer = function(){ return body; };
// imports
View.call(t, element, calendar, 'list');
ListEventRenderer.call(t);
var opt = t.opt;
var trigger = t.trigger;
var clearEvents = t.clearEvents;
var reportEventClear = t.reportEventClear;
var formatDates = calendar.formatDates;
var formatDate = calendar.formatDate;
// overrides
t.setWidth = setWidth;
t.setHeight = setHeight;
// locals
var body;
var firstDay;
var nwe;
var tm;
var colFormat;
function render(date, delta) {
if (delta) {
addDays(date, opt('listPage') * delta);
}
t.start = t.visStart = cloneDate(date, true);
t.end = addDays(cloneDate(t.start), opt('listPage'));
t.visEnd = addDays(cloneDate(t.start), opt('listRange'));
addMinutes(t.visEnd, -1); // set end to 23:59
t.title = formatDates(date, t.visEnd, opt('titleFormat'));
updateOptions();
if (!body) {
buildSkeleton();
} else {
clearEvents();
}
}
function updateOptions() {
firstDay = opt('firstDay');
nwe = opt('weekends') ? 0 : 1;
tm = opt('theme') ? 'ui' : 'fc';
colFormat = opt('columnFormat', 'day');
}
function buildSkeleton() {
body = $('<div>').addClass('fc-list-content').appendTo(element);
}
function setHeight(height, dateChanged) {
body.css('height', (height-1)+'px').css('overflow', 'auto');
}
function setWidth(width) {
// nothing to be done here
}
function dummy() {
// Stub.
}
}
/* Additional view: table (by bruederli@kolabsys.com)
---------------------------------------------------------------------------------*/
function TableEventRenderer() {
var t = this;
// imports
ListEventRenderer.call(t);
var opt = t.opt;
var sortCmp = t.sortCmp;
var trigger = t.trigger;
var getOrigDate = t.getOrigDate;
var compileSegs = t.compileDaySegs;
var reportEvents = t.reportEvents;
var reportEventClear = t.reportEventClear;
var reportEventElement = t.reportEventElement;
var eventElementHandlers = t.eventElementHandlers;
var renderEventTime = t.renderEventTime;
var showEvents = t.showEvents;
var hideEvents = t.hideEvents;
var getListContainer = t.getDaySegmentContainer;
var lazySegBind = t.lazySegBind;
var calendar = t.calendar;
var formatDate = calendar.formatDate;
var formatDates = calendar.formatDates;
var prevMonth;
var nextMonth;
// exports
t.renderEvents = renderEvents;
t.scrollToDate = scrollToDate;
t.clearEvents = clearEvents;
t.prevMonthNav = prevMonth;
t.nextMonthNav = nextMonth;
/* Rendering
--------------------------------------------------------------------*/
function scrollToDate(date) {
var colFormat = opt('columnFormat', t.name);
var currentDate = cloneDate(date, false);
var nextDate;
var segHash;
var currSegHash;
var segFound = false;
if(currentDate.getDate() == 1) {
getListContainer().parent().scrollTop(0);
}
else {
while(!segFound) {
segHash = formatDate(currentDate, colFormat);
getListContainer().find('td.fc-list-header.fc-widget-header').each(function(){
currSegHash = $(this).html();
if(currSegHash == segHash) {
segFound = true;
var offset = $(this).position().top;
var top = getListContainer().parent().scrollTop();
getListContainer().parent().scrollTop(top + offset);
}
});
if(!segFound) {
nextDate = cloneDate(currentDate, false);
nextDate.setDate(nextDate.getDate()+1);
if(nextDate.getDate() > currentDate.getDate()) {
currentDate = cloneDate(nextDate, false);
}
else {
segFound = true;
getListContainer().parent().scrollTop(getListContainer().height());
}
}
}
}
}
function clearEvents() {
reportEventClear();
getListContainer().children('tbody').remove();
}
function renderEvents(events, modifiedEventId) {
events.sort(sortCmp);
reportEvents(events);
renderSegs(compileSegs(events), modifiedEventId);
getListContainer().removeClass('fc-list-smart fc-list-day fc-list-month fc-list-week').addClass('fc-list-' + opt('listSections'));
scrollToDate(getOrigDate());
}
function renderSegs(segs, modifiedEventId) {
var tm = opt('theme') ? 'ui' : 'fc';
var table = getListContainer();
var headerClass = tm + "-widget-header";
var contentClass = tm + "-widget-content";
var segHeader = null;
var tableCols = opt('tableCols');
var timecol = $.inArray('time', tableCols) >= 0;
var i, j, seg, event, times, s, bg, skinCss, skinCssAttr, skinClasses, rowClasses, segContainer, eventElements, eventElement, triggerRes;
prevMonth = $('<tbody class="fc-list-header"><tr><td class="fc-list-header fc-month-nav fc-month-prev ' + headerClass + '" colspan="' + tableCols.length + '">' + opt('buttonText', 'prevMonth') + '</td></tr></tbody>').appendTo(table);
prevMonth.click(function(){
var prevMonthDate = cloneDate(t.getOrigDate(), true);
prevMonthDate.setDate(0);
calendar.gotoDate(prevMonthDate);
trigger('prevClick');
});
for (j in segs) {
seg = segs[j];
bg = false;
if (seg.title) {
var segHeader = $('<tbody class="fc-list-header"><tr><td class="fc-list-header ' + headerClass + '" colspan="' + tableCols.length + '">' + htmlEscape(seg.title) + '</td></tr></tbody>').appendTo(table);
}
segContainer = $('<tbody>').addClass('fc-list-section ' + contentClass).appendTo(table);
s = '';
for (i=0; i < seg.events.length; i++) {
event = seg.events[i];
times = renderEventTime(event, seg);
skinCss = getSkinCss(event, opt);
skinCssAttr = (skinCss ? " style='" + skinCss + "'" : '');
skinClasses = ['fc-event-skin', 'fc-corner-left', 'fc-corner-right', 'fc-corner-top', 'fc-corner-bottom'].concat(event.className);
if (event.source && event.source.className) {
skinClasses = skinClasses.concat(event.source.className);
}
if(event.source && event.source.background) {
bg = true;
}
rowClasses = ['fc-'+dayIDs[event.start.getDay()], 'fc-event', 'fc-event-row'];
if(opt('weekendDays').length>0 && opt('weekendDays').indexOf(segs[j].start.getDay())!=-1)
rowClasses.splice(1, 0, 'fc-weekend-day');
if (seg.daydiff == 0) {
if(segHeader)
segHeader.addClass('fc-today');
rowClasses.push('fc-today');
rowClasses.push('fc-state-highlight');
}
s += "<tr class='" + rowClasses.join(' ') + "'>";
for (var col, c=0; c < tableCols.length; c++) {
col = tableCols[c];
if (col == 'handle') {
s += "<td class='fc-event-handle'" + skinCssAttr + "></td>";
} else if (col == 'title') {
s += "<td class='fc-event-title'>" + (event.title ? htmlEscape(event.title.replace(/(\r\n|\n|\r)+/gm," ")) : '&nbsp;') + "</td>";
} else if (col == 'date') {
s += "<td class='fc-event-date' colspan='" + (times[1] || !timecol ? 1 : 2) + "'>" + htmlEscape(times[0]) + "</td>";
} else if (col == 'time') {
if (times[1]) {
s += "<td class='fc-event-time' style='text-overflow: ellipsis; overflow: hidden;'>" + htmlEscape(times[1]) + "</td>";
}
} else {
s += "<td class='fc-event-" + col + "'>" + (event[col] ? htmlEscape(event[col]) : '&nbsp;') + "</td>";
}
}
s += "</tr>";
// IE doesn't like innerHTML on tbody elements so we insert every row individually
if (document.all) {
$(s).appendTo(segContainer);
s = '';
}
}
if (!document.all)
segContainer[0].innerHTML = s;
eventElements = segContainer.children();
// retrieve elements, run through eventRender callback, bind event handlers
for (i=0; i < seg.events.length; i++) {
event = seg.events[i];
eventElement = $(eventElements[i]); // faster than eq()
if(bg) {
eventElement.addClass('fc-source-bg');
}
triggerRes = trigger('eventRender', event, event, eventElement);
if (triggerRes === false) {
eventElement.remove();
} else {
if (triggerRes && triggerRes !== true) {
eventElement.remove();
eventElement = $(triggerRes).appendTo(segContainer);
}
if (event._id === modifiedEventId) {
eventElementHandlers(event, eventElement, seg);
} else {
eventElement[0]._fci = i; // for lazySegBind
}
reportEventElement(event, eventElement);
}
trigger('eventAfterRender', event, event, eventElement);
}
lazySegBind(segContainer, seg, eventElementHandlers);
markFirstLast(segContainer);
segContainer.addClass('fc-day-'+seg.start.getDay());
}
nextMonth = $('<tbody class="fc-list-header"><tr><td class="fc-list-header fc-month-nav fc-month-next ' + headerClass + '" colspan="' + tableCols.length + '">' + opt('buttonText', 'nextMonth') + '</td></tr></tbody>').appendTo(table);
nextMonth.click(function(){
var nextMonthDate = cloneDate(t.getOrigDate(), true);
nextMonthDate.setDate(1);
nextMonthDate.setMonth(nextMonthDate.getMonth() + 1);
calendar.gotoDate(nextMonthDate);
trigger('nextClick');
});
//markFirstLast(table);
}
}
fcViews.table = TableView;
function TableView(element, calendar) {
var t = this;
// exports
t.render = render;
t.select = dummy;
t.unselect = dummy;
t.getDaySegmentContainer = function(){return table;};
t.getOrigDate = function() {return origDate;};
t.updateGrid = updateGrid;
t.updateToday = updateToday;
t.setAxisFormat = setAxisFormat;
t.setStartOfBusiness = setStartOfBusiness;
t.setEndOfBusiness = setEndOfBusiness;
t.setWeekendDays = setWeekendDays;
t.setBindingMode = setBindingMode;
t.setSelectable = setSelectable;
// imports
View.call(t, element, calendar, 'table');
TableEventRenderer.call(t);
var opt = t.opt;
var trigger = t.trigger;
var clearEvents = t.clearEvents;
var reportEventClear = t.reportEventClear;
var formatDates = calendar.formatDates;
var formatDate = calendar.formatDate;
// overrides
t.setWidth = setWidth;
t.setHeight = setHeight;
// locals
var div;
var table;
var firstDay;
var nwe;
var tm;
var colFormat;
var datepicker;
var dateInfo;
var dateInfoNumber;
var dateInfoNumberDiv;
var dateInfoText;
var origDate;
function render(date, delta) {
/*if (delta) {
addDays(date, opt('listPage') * delta);
}
t.start = t.visStart = cloneDate(date, true);
t.end = addDays(cloneDate(t.start), opt('listPage'));
t.visEnd = addDays(cloneDate(t.start), opt('listRange'));*/
origDate = date;
if (delta) {
addMonths(date, delta);
date.setDate(1);
}
t.start = cloneDate(date, true);
t.start.setDate(1);
t.end = addMonths(cloneDate(t.start), 1);
t.visStart = cloneDate(t.start);
t.visEnd = cloneDate(t.end);
addMinutes(t.visEnd, -1); // set end to 23:59
t.title = formatDates(
t.visStart,
t.visEnd,
opt('titleFormat')
);
//t.title = (t.visEnd.getTime() - t.visStart.getTime() < DAY_MS) ? formatDate(date, opt('titleFormat')) : formatDates(date, t.visEnd, opt('titleFormat'));
updateOptions();
if (!table) {
buildSkeleton(origDate);
} else {
clearEvents();
if(opt('showDatepicker')) {
dateInfoNumberDiv.html(origDate.getDate());
dateInfoText.html(formatDates(origDate, null, opt('titleFormat', 'table')));
datepicker.datepicker('option','firstDay',firstDay);
datepicker.datepicker('setDate', origDate);
}
}
}
function updateOptions() {
firstDay = opt('firstDay');
nwe = opt('weekends') ? 0 : 1;
tm = opt('theme') ? 'ui' : 'fc';
colFormat = opt('columnFormat');
}
function buildSkeleton(date) {
var tableCols = opt('tableCols');
var s =
"<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
"<colgroup>";
for (var c=0; c < tableCols.length; c++) {
s += "<col class='fc-event-" + tableCols[c] + "' />";
}
s += "</colgroup>" +
"</table>";
if(opt('showDatepicker')) {
dateInfo = $('<div>').addClass('fc-table-dateinfo').appendTo(element);
dateInfoNumber = $('<div>').addClass('fc-table-dateinfo-number').appendTo(dateInfo);
dateInfoNumberDiv = $('<div>').appendTo(dateInfoNumber);
dateInfoNumberDiv.html(date.getDate());
dateInfoText = $('<div>').addClass('fc-table-dateinfo-text').appendTo(dateInfo);
dateInfoText.html(formatDates(origDate, null, opt('titleFormat', 'table')));
datepicker = $('<div>').addClass('fc-table-datepicker').appendTo(element);
datepicker.datepicker({
firstDay: opt('firstDay'),
weekendDays: opt('weekendDays'),
defaultDate: date,
showWeek: true,
weekHeader: '',
onSelect: function(dateText, inst) {
var date = new Date(dateText);
calendar.gotoDate(date);
trigger('datepickerClick', this, date);
},
});
}
div = $('<div>').addClass('fc-list-content').appendTo(element);
table = $(s).appendTo(div);
}
function updateGrid()
{
updateToday();
setAxisFormat();
setStartOfBusiness();
setEndOfBusiness();
setWeekendDays();
setBindingMode();
setSelectable();
}
function updateToday()
{
var today = clearTime(new Date());
var segHash = formatDate(today, colFormat);
$(table).find('.fc-list-header').each(function() {
$(this).removeClass('fc-today');
$(this).next().children().removeClass('fc-state-highlight');
if(segHash == $(this).find('td').html()) {
$(this).addClass('fc-today');
$(this).next().children().addClass('fc-state-highlight');
}
});
datepicker.datepicker('refresh');
}
function setAxisFormat()
{
// dummy
}
function setStartOfBusiness()
{
// dummy
}
function setEndOfBusiness()
{
// dummy
}
function setWeekendDays()
{
var weekendDays = opt('weekendDays');
$(table).find('.fc-list-section').each(function() {
var day=parseInt(this.className.match(/fc-day-(\d)/)[1],10);
if(weekendDays.indexOf(day)==-1)
$(this).children().removeClass('fc-weekend-day');
else
$(this).children().addClass('fc-weekend-day');
});
if(opt('showDatepicker'))
datepicker.datepicker('option','weekendDays',weekendDays);
}
function setBindingMode()
{
// dummy
}
function setSelectable()
{
// dummy
}
function setHeight(height, dateChanged) {
if(opt('showDatepicker')) {
var datepickerHeight = datepicker.height();
dateInfoText.css('padding-bottom', datepickerHeight - datepicker.children().outerHeight() + 3); //+3 for paddings
var textHeight = dateInfoText.outerHeight();
dateInfoNumber.css({'height': datepickerHeight - textHeight,
'font-size': 145 - textHeight});
dateInfoNumberDiv.height(145 - textHeight);
}
div.css('height', (height-div.position().top-2)+'px').css('overflow', 'auto');
}
function setWidth(width) {
var outerWidth = Math.floor(element.parent().width() / 2) - 8;
element.css({'left' : width, 'width' : outerWidth});
}
function dummy() {
// Stub.
}
}
function TodoEventRenderer() {
var t = this;
// exports
t.renderEvents = renderEvents;
t.clearEvents = clearEvents;
t.renderEventTime = renderEventTime;
t.compileDaySegs = compileSegs; // for DayEventRenderer
t.lazySegBind = lazySegBind;
t.sortCmp = sortCmp;
// imports
DayEventRenderer.call(t);
var opt = t.opt;
var sortCmp = t.sortCmp;
var trigger = t.trigger;
var compileSegs = t.compileDaySegs;
var reportEvents = t.reportEvents;
var reportEventClear = t.reportEventClear;
var reportEventElement = t.reportEventElement;
var eventElementHandlers = t.eventElementHandlers;
var renderEventTime = t.renderEventTime;
var showEvents = t.showEvents;
var hideEvents = t.hideEvents;
var getListContainer = t.getDaySegmentContainer;
var lazySegBind = t.lazySegBind;
var calendar = t.calendar;
var formatDate = calendar.formatDate;
var formatDates = calendar.formatDates;
var prevMonth;
var nextMonth;
function compileSegs(events) {
var segs = {};
var event, i;
//for (i=0; i < events.length; i++) {
for (i=events.length-1; i > -1; i--) {
event = events[i];
var segHash = event.repeatHash;
var eventEnd = event.end ? cloneDate(event.end) : cloneDate(event.start);
// skip events out of range
if ((event.completedOn && event.completedOn < t.start && (opt('showUnstartedEvents') || !event.start || event.completedOn > event.start)) ||
(!opt('showUnstartedEvents') && event.start && event.start > t.visEnd)) {
continue;
}
// start new segment
if (!(segHash in segs)) {
segs[segHash] = { events: [], id: segHash};
}
segs[segHash].events.push(event);
}
return segs;
}
function reverseSegs(oldSegs) {
var newSegs = {};
var keys = $.map(oldSegs, function (value, key) { return key; });
var values = $.map(oldSegs, function (value, key) { return value; });
for (i=keys.length-1; i > -1; i--) {
newSegs[keys[i]] = values[i];
}
return newSegs;
}
function sortCmp(a, b) {
/*var sd = a.start.getTime() - b.start.getTime();
var aEnd = a.end ? a.end : a.start;
var bEnd = b.end ? b.end : b.start;
return sd + (sd ? 0 : aEnd.getTime() - bEnd.getTime());*/
var aEnd = a.end ? a.end.getTime() : Infinity;
var bEnd = b.end ? b.end.getTime() : Infinity;
var aStart = a.start ? a.start.getTime() : Infinity;
var bStart = b.start ? b.start.getTime() : Infinity;
var aPriority = parseInt(a.priority, 10) || 10;
var bPriority = parseInt(b.priority, 10) || 10;
var statusSort = {
"NEEDS-ACTION": 1,
"IN-PROCESS": 2,
"COMPLETED": 3,
"CANCELLED": 4
};
if(aEnd < bEnd) {
return -1;
}
else if(bEnd < aEnd) {
return 1;
}
else if(aStart < bStart){
return -1;
}
else if(bStart < aStart) {
return 1;
}
else if(aPriority < bPriority) {
return -1;
}
else if(bPriority < aPriority) {
return 1;
}
else if(statusSort[a.status] < statusSort[b.status]) {
return -1;
}
else if(statusSort[b.status] < statusSort[a.status]) {
return 1;
}
else if(a.percent < b.percent) {
return -1;
}
else if(b.percent < a.percent) {
return 1;
}
else if(a.compareString < b.compareString) {
return -1;
}
else if(b.compareString < a.compareString) {
return 1;
}
else {
return 0;
}
}
// event time/date range to display
function renderEventTime(event) {
var timeFormat = opt('timeFormat', 'list');
return event.end? formatDate(event.end, timeFormat) : '';
}
function lazySegBind(container, seg, bindHandlers) {
container.unbind('mouseover').mouseover(function(ev) {
var parent = ev.target, e = parent, i, event;
while (parent != this) {
e = parent;
parent = parent.parentNode;
}
if ((i = e._fci) !== undefined) {
e._fci = undefined;
event = seg.events[i];
bindHandlers(event, container.children().eq(0), seg);
$(ev.target).trigger(ev);
}
ev.stopPropagation();
});
}
function clearEvents() {
reportEventClear();
getListContainer().children('tbody').remove();
}
function renderEvents(events, modifiedEventId) {
events.sort(sortCmp);
reportEvents(events);
renderSegs(reverseSegs(compileSegs(events)), modifiedEventId);
getListContainer().removeClass('fc-list-smart fc-list-day fc-list-month fc-list-week').addClass('fc-list-' + opt('listSections'));
//t.selectEvent();
t.applyFilters();
}
function renderSegs(segs, modifiedEventId) {
var tm = opt('theme') ? 'ui' : 'fc';
var table = getListContainer();
var headerClass = tm + "-widget-header";
var contentClass = tm + "-widget-content";
var segHeader = null;
var tableCols = opt('todoCols');
var timecol = $.inArray('time', tableCols) >= 0;
var i, j, iter, seg, event, times, s, skinCss, skinCssAttr, skinClasses, rowClasses, segContainer, eventElements, eventElement, triggerRes;
for (j in segs) {
seg = segs[j];
segContainer = $('<tbody>').addClass('fc-list-section ' + contentClass).appendTo(table);
s = '';
event = seg.events[0];
iter=0;
if(opt('showUnstartedEvents') && seg.events.length>1) {
for(;iter<seg.events.length; iter++) {
if(seg.events[iter].start<t.end) {
event = seg.events[iter];
break;
}
}
if(iter==seg.events.length) {
continue;
}
}
dueTime = renderEventTime(event);
skinCss = getSkinCss(event, opt);
skinCssAttr = (skinCss ? " style='" + skinCss + "'" : '');
skinClasses = ['fc-event-skin', 'fc-corner-left', 'fc-corner-right', 'fc-corner-top', 'fc-corner-bottom'].concat(event.className);
if (event.source && event.source.className) {
skinClasses = skinClasses.concat(event.source.className);
}
rowClasses = ['fc-event', 'fc-event-row'];
if(event.end && event.end.getTime() < cloneDate(t.start, true)) {
rowClasses.push('fc-event-pastdue');
}
else if(event.end && event.end.getTime() < addDays(cloneDate(t.start), 2, false).getTime()) {
rowClasses.push('fc-event-urgent');
}
if(event.filterStatus) {
rowClasses.push('fc-event-'+event.filterStatus);
}
s += "<tr class='" + rowClasses.join(' ') + "'>";
for (var col, c=0; c < tableCols.length; c++) {
col = tableCols[c];
if (col == 'handle') {
s += "<td class='fc-event-handle'" + skinCssAttr + "></td>";
} else if (col == 'check') {
s += "<td class='fc-event-check'>" + '<input type="checkbox" class="fc-event-checkbox" data-ind="false"/>' + "</td>";
} else if (col == 'priority') {
s += "<td class='fc-event-priority fc-event-priority-" + event.renderPriority + "'>" + (event.renderPriority ? '&nbsp;' : '') + "</td>";
} else if (col == 'time') {
s += "<td class='fc-event-time'>" + htmlEscape(dueTime) + "</td>";
} else if (col == 'title') {
s += "<td class='fc-event-title'>" + htmlEscape(event.title.replace(/(\r\n|\n|\r)+/gm, " ")) + "</td>";
} else if (col == 'location') {
s += "<td class='fc-event-location'>" + htmlEscape(event.location.replace(/(\r\n|\n|\r)+/gm, " ")) + "</td>";
} else if (col == 'status') {
s += "<td class='fc-event-status'></td>";
} else if (col == 'percent') {
s += "<td class='fc-event-percent'>" + event.percent + '%' + "</td>";
}
else {
s += "<td class='fc-event-" + col + "'>" + (event[col] ? htmlEscape(event[col]) : '&nbsp;') + "</td>";
}
}
s += "</tr>";
// IE doesn't like innerHTML on tbody elements so we insert every row individually
if (document.all) {
$(s).appendTo(segContainer);
s = '';
}
if (!document.all)
segContainer[0].innerHTML = s;
eventElements = segContainer.children();
// retrieve elements, run through eventRender callback, bind event handlers
eventElement = $(eventElements[0]); // faster than eq()
triggerRes = trigger('eventRender', event, event, eventElement);
if (triggerRes === false) {
eventElement.remove();
} else {
if (triggerRes && triggerRes !== true) {
eventElement.remove();
eventElement = $(triggerRes).appendTo(segContainer);
}
if (event._id === modifiedEventId) {
eventElementHandlers(event, eventElement, seg);
} else {
eventElement[0]._fci = iter; // for lazySegBind
}
reportEventElement(event, eventElement);
}
trigger('eventCheckDefault', event, event, eventElement.find('.fc-event-checkbox'));
trigger('eventAfterRender', event, event, eventElement);
lazySegBind(segContainer, seg, eventElementHandlers);
markFirstLast(segContainer);
}
//markFirstLast(table);
}
}
fcViews.todo = TodoView;
function TodoView(element, calendar) {
var t = this;
// exports
t.render = render;
t.select = dummy;
t.unselect = dummy;
t.getDaySegmentContainer = function(){ return table; };
t.applyFilters = applyFilters;
t.allowSelectEvent = allowSelectEvent;
t.eventSelectLock = 0;
t.updateGrid = updateGrid;
t.updateToday = updateToday;
t.setAxisFormat = setAxisFormat;
t.setStartOfBusiness = setStartOfBusiness;
t.setEndOfBusiness = setEndOfBusiness;
t.setWeekendDays = setWeekendDays;
t.setBindingMode = setBindingMode;
t.setSelectable = setSelectable;
// imports
View.call(t, element, calendar, 'todo');
TodoEventRenderer.call(t);
var opt = t.opt;
var trigger = t.trigger;
var clearEvents = t.clearEvents;
var reportEventClear = t.reportEventClear;
var formatDates = calendar.formatDates;
var formatDate = calendar.formatDate;
// overrides
t.setWidth = setWidth;
t.setHeight = setHeight;
// locals
var div;
var table;
var filter;
var filterTable;
var firstDay;
var nwe;
var tm;
var colFormat;
var currentDate;
var datepickers;
var dateInfo;
var dateInfoNumber;
var dateInfoNumberDiv;
var dateInfoText;
function render(date, delta) {
if (delta) {
addMonths(date, delta);
date.setDate(1);
}
currentDate = date;
var start = cloneDate(date, true);
var end = addDays(cloneDate(start), 1);
t.title = formatDate(date, opt('titleFormat'));
t.start = t.visStart = start;
t.end = t.visEnd = end;
updateOptions();
if (!table) {
buildSkeleton(date);
initFilters();
} else {
clearEvents();
filterTable.find('.fc-filter-table-footer').text(opt('buttonText', 'filtersFooter').replace('%date%', formatDates(date, null, opt('columnFormat', 'todo'))));
if(opt('showDatepicker')) {
dateInfoNumberDiv.html(date.getDate());
dateInfoText.html(formatDates(date, null, opt('titleFormat', 'todo')));
var defaultDate = cloneDate(date, true);
defaultDate.setHours(12);
defaultDate.setDate(1);
defaultDate.setMonth(currentDate.getMonth() - datepickers.length + 1);
datepickers.forEach(function(e, i){
defaultDate.setMonth(defaultDate.getMonth() + 1);
e.datepicker('option','firstDay',firstDay);
if((i===0 && datepickers.length<3) || (i===datepickers.length-2 && datepickers.length>2))
e.datepicker('setDate', date);
else
e.datepicker('setDate', defaultDate);
});
}
}
}
function updateOptions() {
firstDay = opt('firstDay');
nwe = opt('weekends') ? 0 : 1;
tm = opt('theme') ? 'ui' : 'fc';
colFormat = opt('columnFormat');
}
function buildSkeleton(date) {
var tableCols = opt('todoCols');
var s =
"<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
"<colgroup>";
for (var c=0; c < tableCols.length; c++) {
s += "<col class='fc-event-" + tableCols[c] + "' />";
}
s += "</colgroup>" +
"</table>";
if(opt('showDatepicker')) {
dateInfo = $('<div>').addClass('fc-table-dateinfo').appendTo(element);
dateInfoNumber = $('<div>').addClass('fc-table-dateinfo-number').appendTo(dateInfo);
dateInfoNumberDiv = $('<div>').appendTo(dateInfoNumber);
dateInfoNumberDiv.html(date.getDate());
dateInfoText = $('<div>').addClass('fc-table-dateinfo-text').appendTo(dateInfo);
dateInfoText.html(formatDates(date, null, opt('titleFormat', 'todo')));
datepickers = [$('<div>').addClass('fc-table-datepicker fc-table-datepicker-current').appendTo(element)];
datepickers[0].datepicker({
firstDay: opt('firstDay'),
weekendDays: opt('weekendDays'),
defaultDate: date,
showWeek: true,
weekHeader: '',
onSelect: function(dateText, inst) {
var date = new Date(dateText);
calendar.gotoDate(date);
trigger('datepickerClick', this, date);
}
});
}
filter = $('<div>').addClass('fc-filter').appendTo(element);
var ft = '<table class="fc-filter-table">' +
'<tr>' +
'<td class="fc-filter-table-header" colspan="2">'+opt('buttonText', 'filtersHeader')+'</td>' +
'</tr>';
if(opt('simpleFilters')) {
ft += '<tr>' +
'<td class="fc-filter-option fc-filter-action" data-type="filterAction">'+ opt('buttonText', 'filterAction') +'</td>' +
'<td class="fc-filter-option fc-filter-completed fc-filter-option-last" data-type="filterCompleted">'+ opt('buttonText', 'filterCompleted') +' *</td>' +
'</tr>';
}
else {
ft += '<tr>' +
'<td class="fc-filter-option fc-filter-action" data-type="filterAction">'+ opt('buttonText', 'filterAction') +'</td>' +
'<td class="fc-filter-option fc-filter-progress" data-type="filterProgress">'+ opt('buttonText', 'filterProgress') +'</td>' +
'</tr>' +
'<tr>' +
'<td class="fc-filter-option fc-filter-completed" data-type="filterCompleted">'+ opt('buttonText', 'filterCompleted') +' *</td>' +
'<td class="fc-filter-option fc-filter-canceled fc-filter-option-last" data-type="filterCanceled">'+ opt('buttonText', 'filterCanceled') +'</td>' +
'</tr>';
}
ft += '<tr>' +
'<td class="fc-filter-table-footer" colspan="2">'+opt('buttonText', 'filtersFooter').replace('%date%', formatDates(date, null, opt('columnFormat', 'todo')))+'</td>' +
'</tr>' +
'</table>';
filterTable = $(ft).appendTo(filter);
div = $('<div>').addClass('fc-list-content').appendTo(element);
table = $(s).appendTo(div);
}
function updateGrid()
{
updateToday();
setAxisFormat();
setStartOfBusiness();
setEndOfBusiness();
setWeekendDays();
setBindingMode();
setSelectable();
}
function updateToday()
{
if(opt('showDatepicker'))
datepickers.forEach(function(e){
e.datepicker('refresh');
});
}
function setAxisFormat()
{
// dummy
}
function setStartOfBusiness()
{
// dummy
}
function setEndOfBusiness()
{
// dummy
}
function setWeekendDays()
{
if(opt('showDatepicker'))
datepickers.forEach(function(e){
e.datepicker('option','weekendDays',opt('weekendDays'));
});
}
function setBindingMode()
{
// dummy
}
function setSelectable()
{
// dummy
}
function initFilters() {
filterTable.find('.fc-filter-option').each(function() {
if(opt('defaultFilters').indexOf($(this).attr('data-type')) != -1) {
filterToggle($(this));
}
$(this).click(function(){
filterToggle($(this));
});
});
}
function filterToggle(button) {
if(button.hasClass('fc-filter-option-selected')) {
button.removeClass('fc-filter-option-selected');
}
else {
button.addClass('fc-filter-option-selected');
}
applyFilters();
}
function applyFilters() {
filterTable.find('.fc-filter-option').each(function(){
if($(this).hasClass('fc-filter-option-selected')) {
t.getDaySegmentContainer().find('.fc-event-' + $(this).attr('data-type')).removeClass('fc-filter-hide');
}
else {
t.getDaySegmentContainer().find('.fc-event-' + $(this).attr('data-type')).addClass('fc-filter-hide');
}
});
opt('todoOptionalCols').forEach(function(item){
var itemsFilled = $('.fc-event-'+item.col+':visible').filter(function(){
return this.innerHTML!=='';
});
$('col.fc-event-'+item.col).toggleClass('fc-hidden-empty', !itemsFilled.length);
});
//if(!t.getDaySegmentContainer().find('.fc-event-selected:visible').length) {
t.selectEvent();
//}
}
function setHeight(height, dateChanged) {
if(opt('showDatepicker')) {
var datepickerHeight = datepickers[0].height();
dateInfoText.css('padding-bottom', datepickerHeight - datepickers[0].children().outerHeight() + 3); //+3 for paddings
var textHeight = dateInfoText.outerHeight();
dateInfoNumber.css({'height': datepickerHeight - textHeight,
'font-size': 145 - textHeight});
dateInfoNumberDiv.height(145 - textHeight);
}
div.css({'height': height-div.position().top-2, 'overflow': 'auto'});
}
function setWidth(width) {
element.width(width);
var slots = Math.floor((width - dateInfo.outerWidth() - 1) / datepickers[0].outerWidth());
if(slots > datepickers.length) {
var defaultDate = cloneDate(currentDate, true);
defaultDate.setHours(12);
defaultDate.setDate(1);
defaultDate.setMonth(currentDate.getMonth() + 1);
if(datepickers.length==1) {
datepickers.push($('<div>').addClass('fc-table-datepicker fc-table-datepicker-no-default').prependTo(element).datepicker({
firstDay: opt('firstDay'),
weekendDays: opt('weekendDays'),
defaultDate: cloneDate(defaultDate),
showWeek: true,
weekHeader: '',
hideIfNoPrevNext: true,
onSelect: function(dateText, inst) {
var date = new Date(dateText);
calendar.gotoDate(date);
trigger('datepickerClick', this, date);
}
}));
}
defaultDate.setMonth(defaultDate.getMonth() - datepickers.length + 1);
for(var i=datepickers.length; i<slots; i++) {
defaultDate.setMonth(defaultDate.getMonth() - 1);
datepickers.unshift($('<div>').addClass('fc-table-datepicker fc-table-datepicker-no-default').insertBefore(filter).datepicker({
firstDay: opt('firstDay'),
weekendDays: opt('weekendDays'),
defaultDate: cloneDate(defaultDate),
showWeek: true,
weekHeader: '',
hideIfNoPrevNext: true,
onSelect: function(dateText, inst) {
var date = new Date(dateText);
calendar.gotoDate(date);
trigger('datepickerClick', this, date);
}
}));
}
}
else {
while(datepickers.length>slots && datepickers.length>1) {
if(datepickers.length==2)
datepickers.pop().remove();
else
datepickers.shift().remove();
}
}
var hiddenWidth = 0;
opt('todoOptionalCols').forEach(function(e){
hiddenWidth += $('col.fc-event-'+e.col).hasClass('fc-hidden-empty') ? e.width : 0;
});
opt('todoColThresholds').forEach(function(e){
$('col.fc-event-'+e.col).toggleClass('fc-hidden-width', width<e.width-hiddenWidth);
});
}
function allowSelectEvent(value) {
if(value)
t.eventSelectLock++;
else
t.eventSelectLock--;
}
function dummy() {
// Stub.
}
}
})(jQuery);