7197 lines
192 KiB
JavaScript
7197 lines
192 KiB
JavaScript
/**
|
||
* @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> </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, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/'/g, ''')
|
||
.replace(/"/g, '"')
|
||
.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'> </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 + "'> </th>" +
|
||
"</tr>" +
|
||
"</thead>" +
|
||
"<tbody>" +
|
||
"<tr>" +
|
||
"<th class='fc-agenda-axis " + headerClass + "'> </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'> </div>" +
|
||
"</div>" +
|
||
"</div>" +
|
||
"</td>";
|
||
}
|
||
s +=
|
||
"<td class='fc-agenda-gutter " + contentClass + "'> </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')) : ' ') +
|
||
"</th>" +
|
||
"<td class='" + contentClass + "'>" +
|
||
"<div style='position:relative'> </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')) : ' ');
|
||
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') + "'>" +
|
||
" " + // 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," ")) : ' ') + "</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]) : ' ') + "</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 ? ' ' : '') + "</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]) : ' ') + "</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);
|