Skip to content

Instantly share code, notes, and snippets.

@sachinsmc
Created March 11, 2020 18:56
Show Gist options
  • Save sachinsmc/67e80164187d506124446848d88ca66e to your computer and use it in GitHub Desktop.
Save sachinsmc/67e80164187d506124446848d88ca66e to your computer and use it in GitHub Desktop.
Calender
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<!------ Include the above in your HEAD tag ---------->
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,500,500i,700,700i,900,900i"
rel="stylesheet">
<script>
$(document).ready(function() {
var date = new Date();
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();
/* className colors
className: default(transparent), important(red), chill(pink), success(green), info(blue)
*/
/* initialize the external events
-----------------------------------------------------------------*/
$('#external-events div.external-event').each(function() {
// create an Event Object (http://arshaw.com/fullcalendar/docs/event_data/Event_Object/)
// it doesn't need to have a start or end
var eventObject = {
title: $.trim($(this).text()) // use the element's text as the event title
};
// store the Event Object in the DOM element so we can get to it later
$(this).data('eventObject', eventObject);
// make the event draggable using jQuery UI
$(this).draggable({
zIndex: 999,
revert: true, // will cause the event to go back to its
revertDuration: 0 // original position after the drag
});
});
/* initialize the calendar
-----------------------------------------------------------------*/
var calendar = $('#calendar').fullCalendar({
header: {
left: 'title',
center: 'agendaDay,agendaWeek,month',
right: 'prev,next today'
},
editable: true,
firstDay: 1, // 1(Monday) this can be changed to 0(Sunday) for the USA system
selectable: true,
defaultView: 'month',
axisFormat: 'h:mm',
columnFormat: {
month: 'ddd', // Mon
week: 'ddd d', // Mon 7
day: 'dddd M/d', // Monday 9/7
agendaDay: 'dddd d'
},
titleFormat: {
month: 'MMMM yyyy', // September 2009
week: "MMMM yyyy", // September 2009
day: 'MMMM yyyy' // Tuesday, Sep 8, 2009
},
allDaySlot: false,
selectHelper: true,
select: function(start, end, allDay) {
var title = prompt('Event Title:');
if (title) {
calendar.fullCalendar('renderEvent', {
title: title,
start: start,
end: end,
allDay: allDay
},
true // make the event "stick"
);
}
calendar.fullCalendar('unselect');
},
droppable: true, // this allows things to be dropped onto the calendar !!!
drop: function(date, allDay) { // this function is called when something is dropped
// retrieve the dropped element's stored Event Object
var originalEventObject = $(this).data('eventObject');
// we need to copy it, so that multiple events don't have a reference to the same object
var copiedEventObject = $.extend({}, originalEventObject);
// assign it the date that was reported
copiedEventObject.start = date;
copiedEventObject.allDay = allDay;
// render the event on the calendar
// the last `true` argument determines if the event "sticks" (http://arshaw.com/fullcalendar/docs/event_rendering/renderEvent/)
$('#calendar').fullCalendar('renderEvent', copiedEventObject, true);
// is the "remove after drop" checkbox checked?
if ($('#drop-remove').is(':checked')) {
// if so, remove the element from the "Draggable Events" list
$(this).remove();
}
},
events: [{
title: 'All Day Event',
start: new Date(y, m, 1)
},
{
id: 999,
title: 'Repeating Event',
start: new Date(y, m, d - 3, 16, 0),
allDay: false,
className: 'info'
},
{
id: 999,
title: 'Repeating Event',
start: new Date(y, m, d + 4, 16, 0),
allDay: false,
className: 'info'
},
{
title: 'Meeting',
start: new Date(y, m, d, 10, 30),
allDay: false,
className: 'important'
},
{
title: 'Lunch',
start: new Date(y, m, d, 12, 0),
end: new Date(y, m, d, 14, 0),
allDay: false,
className: 'important'
},
{
title: 'Birthday Party',
start: new Date(y, m, d + 1, 19, 0),
end: new Date(y, m, d + 1, 22, 30),
allDay: false,
},
{
title: 'Click for Google',
start: new Date(y, m, 28),
end: new Date(y, m, 29),
url: 'https://ccp.cloudaccess.net/aff.php?aff=5188',
className: 'success'
}
],
});
});
</script>
<style>
body {
margin-bottom: 40px;
margin-top: 40px;
text-align: center;
font-size: 14px;
font-family: 'Roboto', sans-serif;
background: url(http://www.digiphotohub.com/wp-content/uploads/2015/09/bigstock-Abstract-Blurred-Background-Of-92820527.jpg);
}
#wrap {
width: 1100px;
margin: 0 auto;
}
#external-events {
float: left;
width: 150px;
padding: 0 10px;
text-align: left;
}
#external-events h4 {
font-size: 16px;
margin-top: 0;
padding-top: 1em;
}
.external-event {
/* try to mimick the look of a real event */
margin: 10px 0;
padding: 2px 4px;
background: #3366CC;
color: #fff;
font-size: .85em;
cursor: pointer;
}
#external-events p {
margin: 1.5em 0;
font-size: 11px;
color: #666;
}
#external-events p input {
margin: 0;
vertical-align: middle;
}
#calendar {
/* float: right; */
margin: 0 auto;
width: 900px;
background-color: #FFFFFF;
border-radius: 6px;
box-shadow: 0 1px 2px #C3C3C3;
-webkit-box-shadow: 0px 0px 21px 2px rgba(0, 0, 0, 0.18);
-moz-box-shadow: 0px 0px 21px 2px rgba(0, 0, 0, 0.18);
box-shadow: 0px 0px 21px 2px rgba(0, 0, 0, 0.18);
}
</style>
</head>
<body>
<div id='wrap'>
<div id='calendar'></div>
<div style='clear:both'></div>
</div>
</body>
</html>
/*!
* FullCalendar v1.6.4
* Docs & License: http://arshaw.com/fullcalendar/
* (c) 2013 Adam Shaw
*/
/*
* Use fullcalendar.css for basic styling.
* For event drag & drop, requires jQuery UI draggable.
* For event resizing, requires jQuery UI resizable.
*/
(function($, undefined) {
var defaults = {
// display
defaultView: "month",
aspectRatio: 1.35,
header: {
left: "title",
center: "",
right: "today prev,next"
},
weekends: true,
weekNumbers: false,
weekNumberCalculation: "iso",
weekNumberTitle: "W",
// 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",
week: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}",
day: "dddd, MMM d, yyyy"
},
columnFormat: {
month: "ddd",
week: "ddd M/d",
day: "dddd M/d"
},
timeFormat: {
// for event elements
"": "h(:mm)t" // default
},
// locale
isRTL: false,
firstDay: 0,
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: "<span class='fc-text-arrow'>‹</span>",
next: "<span class='fc-text-arrow'>›</span>",
prevYear: "<span class='fc-text-arrow'>«</span>",
nextYear: "<span class='fc-text-arrow'>»</span>",
today: "today",
month: "month",
week: "week",
day: "day"
},
// jquery-ui theming
theme: false,
buttonIcons: {
prev: "circle-triangle-w",
next: "circle-triangle-e"
},
//selectable: false,
unselectAuto: true,
dropAccept: "*",
handleWindowResize: true
};
// right-to-left defaults
var rtlDefaults = {
header: {
left: "next,prev today",
center: "",
right: "title"
},
buttonText: {
prev: "<span class='fc-text-arrow'>›</span>",
next: "<span class='fc-text-arrow'>‹</span>",
prevYear: "<span class='fc-text-arrow'>»</span>",
nextYear: "<span class='fc-text-arrow'>«</span>"
},
buttonIcons: {
prev: "circle-triangle-e",
next: "circle-triangle-w"
}
};
var fc = ($.fullCalendar = { version: "1.6.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;
}
options = options || {};
// 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.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;
// 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 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 if (elementVisible()) {
// mainly for the public API
calcSize();
_renderView(inc);
}
}
function initialRender() {
tm = options.theme ? "ui" : "fc";
element.addClass("fc");
if (options.isRTL) {
element.addClass("fc-rtl");
} else {
element.addClass("fc-ltr");
}
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) {
element.prepend(headerElement);
}
changeView(options.defaultView);
if (options.handleWindowResize) {
$(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 destroy() {
if (currentView) {
trigger("viewDestroy", currentView, currentView, currentView.element);
currentView.triggerEventDestroy();
}
$(window).unbind("resize", windowResize);
header.destroy();
content.remove();
element.removeClass("fc fc-rtl ui-widget");
}
function elementVisible() {
return element.is(":visible");
}
function bodyVisible() {
return $("body").is(":visible");
}
/* View Rendering
-----------------------------------------------------------------------------*/
function changeView(newViewName) {
if (!currentView || newViewName != currentView.name) {
_changeView(newViewName);
}
}
function _changeView(newViewName) {
ignoreWindowResize++;
if (currentView) {
trigger("viewDestroy", currentView, currentView, currentView.element);
unselect();
currentView.triggerEventDestroy(); // trigger 'eventDestroy' for each event
freezeContentHeight();
currentView.element.remove();
header.deactivateButton(currentView.name);
}
header.activateButton(newViewName);
currentView = new fcViews[newViewName](
$(
"<div class='fc-view fc-view-" +
newViewName +
"' style='position:relative'/>"
).appendTo(content),
t // the calendar object
);
renderView();
unfreezeContentHeight();
ignoreWindowResize--;
}
function renderView(inc) {
if (
!currentView.start || // never rendered before
inc ||
date < currentView.start ||
date >= currentView.end // or new date range
) {
if (elementVisible()) {
_renderView(inc);
}
}
}
function _renderView(inc) {
// assumes elementVisible
ignoreWindowResize++;
if (currentView.start) {
// already been rendered?
trigger("viewDestroy", currentView, currentView, currentView.element);
unselect();
clearEvents();
}
freezeContentHeight();
currentView.render(date, inc || 0); // the view's render method ONLY renders the skeleton, nothing else
setSize();
unfreezeContentHeight();
(currentView.afterRender || noop)();
updateTitle();
updateTodayButton();
trigger("viewRender", currentView, currentView, currentView.element);
currentView.trigger("viewDisplay", _element); // deprecated
ignoreWindowResize--;
getAndRenderEvents();
}
/* Resizing
-----------------------------------------------------------------------------*/
function updateSize() {
if (elementVisible()) {
unselect();
clearEvents();
calcSize();
setSize();
renderEvents();
}
}
function calcSize() {
// assumes elementVisible
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, 0.5)
);
}
}
function setSize() {
// assumes elementVisible
if (suggestedViewHeight === undefined) {
calcSize(); // for first time
// NOTE: we don't want to recalculate on every renderView because
// it could result in oscillating heights due to scrollbars.
}
ignoreWindowResize++;
currentView.setHeight(suggestedViewHeight);
currentView.setWidth(content.width());
ignoreWindowResize--;
elementOuterWidth = element.outerWidth();
}
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
-----------------------------------------------------------------------------*/
// TODO: going forward, most of this stuff should be directly handled by the view
function refetchEvents() {
// can be called as an API method
clearEvents();
fetchAndRenderEvents();
}
function rerenderEvents(modifiedEventID) {
// can be called as an API method
clearEvents();
renderEvents(modifiedEventID);
}
function renderEvents(modifiedEventID) {
// TODO: remove modifiedEventID hack
if (elementVisible()) {
currentView.setEventData(events); // for View.js, TODO: unify with renderEvents
currentView.renderEvents(events, modifiedEventID); // actually render the DOM elements
currentView.trigger("eventAfterAllRender");
}
}
function clearEvents() {
currentView.triggerEventDestroy(); // trigger 'eventDestroy' for each event
currentView.clearEvents(); // actually remove the DOM elements
currentView.clearEventData(); // for View.js, TODO: unify with clearEvents
}
function getAndRenderEvents() {
if (
!options.lazyFetching ||
isFetchNeeded(currentView.visStart, currentView.visEnd)
) {
fetchAndRenderEvents();
} else {
renderEvents();
}
}
function fetchAndRenderEvents() {
fetchEvents(currentView.visStart, currentView.visEnd);
// ... will call reportEvents
// ... which will call renderEvents
}
// called when event data arrives
function reportEvents(_events) {
events = _events;
renderEvents();
}
// called when a single event's data has been changed
function reportEventChange(eventID) {
rerenderEvents(eventID);
}
/* Header Updating
-----------------------------------------------------------------------------*/
function updateTitle() {
header.updateTitle(currentView.title);
}
function updateTodayButton() {
var today = new Date();
if (today >= currentView.start && today < currentView.end) {
header.disableButton("today");
} else {
header.enableButton("today");
}
}
/* 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);
}
function next() {
renderView(1);
}
function prevYear() {
addYears(date, -1);
renderView();
}
function nextYear() {
addYears(date, 1);
renderView();
}
function today() {
date = new Date();
renderView();
}
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);
}
/* Height "Freezing"
-----------------------------------------------------------------------------*/
function freezeContentHeight() {
content.css({
width: "100%",
height: content.height(),
overflow: "hidden"
});
}
function unfreezeContentHeight() {
content.css({
width: "",
height: "",
overflow: ""
});
}
/* 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();
}
}
function trigger(name, thisObj) {
if (options[name]) {
return options[name].apply(
thisObj || _element,
Array.prototype.slice.call(arguments, 2)
);
}
}
/* 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;
// 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 text = smartProperty(options.buttonText, buttonName); // why are we using smartProperty here?
var button = $(
"<span class='fc-button fc-button-" +
buttonName +
" " +
tm +
"-state-default'>" +
(icon
? "<span class='fc-icon-wrap'>" +
"<span class='ui-icon ui-icon-" +
icon +
"'/>" +
"</span>"
: text) +
"</span>"
)
.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);
disableTextSelection(button);
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);
}
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");
}
}
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.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) {
if (options.eventDataTransform) {
events = $.map(events, options.eventDataTransform);
}
if (source.eventDataTransform) {
events = $.map(events, source.eventDataTransform);
}
// TODO: this technique is not ideal for static array event sources.
// For arrays, we'll want to process all events right in the beginning, then never again.
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;
// retrieve any outbound GET/POST $.ajax data from the options
var customData;
if ($.isFunction(source.data)) {
// supplied as a function that returns a key/value object
customData = source.data();
} else {
// supplied as a straight key/value object
customData = source.data;
}
// use a copy of the custom data so we can modify the parameters
// and not affect the passed-in object.
var data = $.extend({}, customData || {});
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
}
}
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);
}
/* 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.backgroundColor = event.backgroundColor;
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;
}
cache.push(event);
}
reportEvents(cache);
}
function removeEvents(filter) {
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);
}
}
}
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, getView());
}
}
function popLoading() {
if (!--loadingLevel) {
trigger("loading", null, false, getView());
}
}
/* 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 && 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 (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 dayDiff(d1, d2) {
// d1 - d2
return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_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, 9, 0);
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()];
},
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";
},
w: function(d, o) {
// local
return o.weekNumberCalculation(d);
},
W: function(d) {
// ISO
return iso8601Week(d);
}
};
fc.dateFormatters = dateFormatters;
/* thanks jQuery UI (https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js)
*
* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
* `date` - the date to get the week for
* `number` - the number of the week within the year that contains this date
*/
function iso8601Week(date) {
var time;
var checkDate = new Date(date.getTime());
// Find Thursday of this week starting on Monday
checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));
time = checkDate.getTime();
checkDate.setMonth(0); // Compare with Jan 1
checkDate.setDate(1);
return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
}
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);
// why don't we check for seconds/ms too?
}
/* 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)
);
}
/* Misc Utils
-----------------------------------------------------------------------------*/
//TODO: arraySlice
//TODO: isFunction, grep ?
function noop() {}
function dateCompare(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 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) {
cell.each(function(i, _cell) {
_cell.className = _cell.className.replace(
/^fc-\w*/,
"fc-" + dayIDs[date.getDay()]
);
// 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 skipHiddenDays = t.skipHiddenDays;
var getCellsPerWeek = t.getCellsPerWeek;
var formatDate = calendar.formatDate;
function render(date, delta) {
if (delta) {
addMonths(date, delta);
date.setDate(1);
}
var firstDay = opt("firstDay");
var start = cloneDate(date, true);
start.setDate(1);
var end = addMonths(cloneDate(start), 1);
var visStart = cloneDate(start);
addDays(visStart, -((visStart.getDay() - firstDay + 7) % 7));
skipHiddenDays(visStart);
var visEnd = cloneDate(end);
addDays(visEnd, (7 - visEnd.getDay() + firstDay) % 7);
skipHiddenDays(visEnd, -1, true);
var colCnt = getCellsPerWeek();
var rowCnt = Math.round(dayDiff(visEnd, visStart) / 7); // should be no need for Math.round
if (opt("weekMode") == "fixed") {
addDays(visEnd, (6 - rowCnt) * 7); // add weeks to make up for it
rowCnt = 6;
}
t.title = formatDate(start, opt("titleFormat"));
t.start = start;
t.end = end;
t.visStart = visStart;
t.visEnd = visEnd;
renderBasic(rowCnt, colCnt, 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 skipHiddenDays = t.skipHiddenDays;
var getCellsPerWeek = t.getCellsPerWeek;
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);
skipHiddenDays(visStart);
var visEnd = cloneDate(end);
skipHiddenDays(visEnd, -1, true);
var colCnt = getCellsPerWeek();
t.start = start;
t.end = end;
t.visStart = visStart;
t.visEnd = visEnd;
t.title = formatDates(
visStart,
addDays(cloneDate(visEnd), -1),
opt("titleFormat")
);
renderBasic(1, colCnt, false);
}
}
fcViews.basicDay = BasicDayView;
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 skipHiddenDays = t.skipHiddenDays;
var formatDate = calendar.formatDate;
function render(date, delta) {
if (delta) {
addDays(date, delta);
}
skipHiddenDays(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;
renderBasic(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.colLeft = colLeft;
t.colRight = colRight;
t.colContentLeft = colContentLeft;
t.colContentRight = colContentRight;
t.getIsCellAllDay = function() {
return true;
};
t.allDayRow = allDayRow;
t.getRowCnt = function() {
return rowCnt;
};
t.getColCnt = function() {
return colCnt;
};
t.getColWidth = function() {
return colWidth;
};
t.getDaySegmentContainer = function() {
return daySegmentContainer;
};
// 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 renderOverlay = t.renderOverlay;
var clearOverlays = t.clearOverlays;
var daySelectionMousedown = t.daySelectionMousedown;
var cellToDate = t.cellToDate;
var dateToCell = t.dateToCell;
var rangeToSegments = t.rangeToSegments;
var formatDate = calendar.formatDate;
// locals
var table;
var head;
var headCells;
var body;
var bodyRows;
var bodyCells;
var bodyFirstCells;
var firstRowCellInners;
var firstRowCellContentInners;
var daySegmentContainer;
var viewWidth;
var viewHeight;
var colWidth;
var weekNumberWidth;
var rowCnt, colCnt;
var showNumbers;
var coordinateGrid;
var hoverListener;
var colPositions;
var colContentPositions;
var tm;
var colFormat;
var showWeekNumbers;
var weekNumberTitle;
var weekNumberFormat;
/* Rendering
------------------------------------------------------------*/
disableTextSelection(element.addClass("fc-grid"));
function renderBasic(_rowCnt, _colCnt, _showNumbers) {
rowCnt = _rowCnt;
colCnt = _colCnt;
showNumbers = _showNumbers;
updateOptions();
if (!body) {
buildEventContainer();
}
buildTable();
}
function updateOptions() {
tm = opt("theme") ? "ui" : "fc";
colFormat = opt("columnFormat");
// week # options. (TODO: bad, logic also in other views)
showWeekNumbers = opt("weekNumbers");
weekNumberTitle = opt("weekNumberTitle");
if (opt("weekNumberCalculation") != "iso") {
weekNumberFormat = "w";
} else {
weekNumberFormat = "W";
}
}
function buildEventContainer() {
daySegmentContainer = $(
"<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>"
).appendTo(element);
}
function buildTable() {
var html = buildTableHTML();
if (table) {
table.remove();
}
table = $(html).appendTo(element);
head = table.find("thead");
headCells = head.find(".fc-day-header");
body = table.find("tbody");
bodyRows = body.find("tr");
bodyCells = body.find(".fc-day");
bodyFirstCells = bodyRows.find("td:first-child");
firstRowCellInners = bodyRows.eq(0).find(".fc-day > div");
firstRowCellContentInners = bodyRows.eq(0).find(".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");
bodyRows.filter(":last").addClass("fc-last");
bodyCells.each(function(i, _cell) {
var date = cellToDate(Math.floor(i / colCnt), i % colCnt);
trigger("dayRender", t, date, $(_cell));
});
dayBind(bodyCells);
}
/* HTML Building
-----------------------------------------------------------*/
function buildTableHTML() {
var html =
"<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
buildHeadHTML() +
buildBodyHTML() +
"</table>";
return html;
}
function buildHeadHTML() {
var headerClass = tm + "-widget-header";
var html = "";
var col;
var date;
html += "<thead><tr>";
if (showWeekNumbers) {
html +=
"<th class='fc-week-number " +
headerClass +
"'>" +
htmlEscape(weekNumberTitle) +
"</th>";
}
for (col = 0; col < colCnt; col++) {
date = cellToDate(0, col);
html +=
"<th class='fc-day-header fc-" +
dayIDs[date.getDay()] +
" " +
headerClass +
"'>" +
htmlEscape(formatDate(date, colFormat)) +
"</th>";
}
html += "</tr></thead>";
return html;
}
function buildBodyHTML() {
var contentClass = tm + "-widget-content";
var html = "";
var row;
var col;
var date;
html += "<tbody>";
for (row = 0; row < rowCnt; row++) {
html += "<tr class='fc-week'>";
if (showWeekNumbers) {
date = cellToDate(row, 0);
html +=
"<td class='fc-week-number " +
contentClass +
"'>" +
"<div>" +
htmlEscape(formatDate(date, weekNumberFormat)) +
"</div>" +
"</td>";
}
for (col = 0; col < colCnt; col++) {
date = cellToDate(row, col);
html += buildCellHTML(date);
}
html += "</tr>";
}
html += "</tbody>";
return html;
}
function buildCellHTML(date) {
var contentClass = tm + "-widget-content";
var month = t.start.getMonth();
var today = clearTime(new Date());
var html = "";
var classNames = ["fc-day", "fc-" + dayIDs[date.getDay()], contentClass];
if (date.getMonth() != month) {
classNames.push("fc-other-month");
}
if (+date == +today) {
classNames.push("fc-today", tm + "-state-highlight");
} else if (date < today) {
classNames.push("fc-past");
} else {
classNames.push("fc-future");
}
html +=
"<td" +
" class='" +
classNames.join(" ") +
"'" +
" data-date='" +
formatDate(date, "yyyy-MM-dd") +
"'" +
">" +
"<div>";
if (showNumbers) {
html += "<div class='fc-day-number'>" + date.getDate() + "</div>";
}
html +=
"<div class='fc-day-content'>" +
"<div style='position:relative'> </div>" +
"</div>" +
"</div>" +
"</td>";
return html;
}
/* Dimensions
-----------------------------------------------------------*/
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);
cell
.find("> div")
.css(
"min-height",
(i == rowCnt - 1 ? rowHeightLast : rowHeight) - vsides(cell)
);
}
});
}
function setWidth(width) {
viewWidth = width;
colPositions.clear();
colContentPositions.clear();
weekNumberWidth = 0;
if (showWeekNumbers) {
weekNumberWidth = head.find("th.fc-week-number").outerWidth();
}
colWidth = Math.floor((viewWidth - weekNumberWidth) / colCnt);
setOuterWidth(headCells.slice(0, -1), colWidth);
}
/* Day clicking and binding
-----------------------------------------------------------*/
function dayBind(days) {
days.click(dayClick).mousedown(daySelectionMousedown);
}
function dayClick(ev) {
if (!opt("selectable")) {
// if selectable, SelectionManager will worry about dayClick
var date = parseISO8601($(this).data("date"));
trigger("dayClick", this, date, true, ev);
}
}
/* Semi-transparent Overlay Helpers
------------------------------------------------------*/
// TODO: should be consolidated with AgendaView's methods
function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) {
// overlayEnd is exclusive
if (refreshCoordinateGrid) {
coordinateGrid.build();
}
var segments = rangeToSegments(overlayStart, overlayEnd);
for (var i = 0; i < segments.length; i++) {
var segment = segments[i];
dayBind(
renderCellOverlay(
segment.row,
segment.leftCol,
segment.row,
segment.rightCol
)
);
}
}
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 = dateToCell(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 = cellToDate(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);
colPositions = new HorizontalPositionCache(function(col) {
return firstRowCellInners.eq(col);
});
colContentPositions = new HorizontalPositionCache(function(col) {
return firstRowCellContentInners.eq(col);
});
function colLeft(col) {
return colPositions.left(col);
}
function colRight(col) {
return colPositions.right(col);
}
function colContentLeft(col) {
return colContentPositions.left(col);
}
function colContentRight(col) {
return colContentPositions.right(col);
}
function allDayRow(i) {
return bodyRows.eq(i);
}
}
function BasicEventRenderer() {
var t = this;
// exports
t.renderEvents = renderEvents;
t.clearEvents = clearEvents;
// imports
DayEventRenderer.call(t);
function renderEvents(events, modifiedEventId) {
t.renderDayEvents(events, modifiedEventId);
}
function clearEvents() {
t.getDaySegmentContainer().empty();
}
// TODO: have this class (and AgendaEventRenderer) be responsible for creating the event container div
}
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 skipHiddenDays = t.skipHiddenDays;
var getCellsPerWeek = t.getCellsPerWeek;
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);
skipHiddenDays(visStart);
var visEnd = cloneDate(end);
skipHiddenDays(visEnd, -1, true);
var colCnt = getCellsPerWeek();
t.title = formatDates(
visStart,
addDays(cloneDate(visEnd), -1),
opt("titleFormat")
);
t.start = start;
t.end = end;
t.visStart = visStart;
t.visEnd = visEnd;
renderAgenda(colCnt);
}
}
fcViews.agendaDay = AgendaDayView;
function AgendaDayView(element, calendar) {
var t = this;
// exports
t.render = render;
// imports
AgendaView.call(t, element, calendar, "agendaDay");
var opt = t.opt;
var renderAgenda = t.renderAgenda;
var skipHiddenDays = t.skipHiddenDays;
var formatDate = calendar.formatDate;
function render(date, delta) {
if (delta) {
addDays(date, delta);
}
skipHiddenDays(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);
}
}
setDefaults({
allDaySlot: true,
allDayText: "all-day",
firstHour: 6,
slotMinutes: 30,
defaultEventMinutes: 120,
axisFormat: "h(:mm)tt",
timeFormat: {
agenda: "h:mm{ - h:mm}"
},
dragOpacity: {
agenda: 0.5
},
minTime: 0,
maxTime: 24,
slotEventOverlap: true
});
// 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.afterRender = afterRender;
t.defaultEventEnd = defaultEventEnd;
t.timePosition = timePosition;
t.getIsCellAllDay = getIsCellAllDay;
t.allDayRow = getAllDayRow;
t.getCoordinateGrid = function() {
return coordinateGrid;
}; // specifically for AgendaEventRenderer
t.getHoverListener = function() {
return hoverListener;
};
t.colLeft = colLeft;
t.colRight = colRight;
t.colContentLeft = colContentLeft;
t.colContentRight = colContentRight;
t.getDaySegmentContainer = function() {
return daySegmentContainer;
};
t.getSlotSegmentContainer = function() {
return slotSegmentContainer;
};
t.getMinMinute = function() {
return minMinute;
};
t.getMaxMinute = function() {
return maxMinute;
};
t.getSlotContainer = function() {
return slotContainer;
};
t.getRowCnt = function() {
return 1;
};
t.getColCnt = function() {
return colCnt;
};
t.getColWidth = function() {
return colWidth;
};
t.getSnapHeight = function() {
return snapHeight;
};
t.getSnapMinutes = function() {
return snapMinutes;
};
t.defaultSelectionEnd = defaultSelectionEnd;
t.renderDayOverlay = renderDayOverlay;
t.renderSelection = renderSelection;
t.clearSelection = clearSelection;
t.reportDayClick = reportDayClick; // selection mousedown hack
t.dragStart = dragStart;
t.dragStop = dragStop;
// 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 renderOverlay = t.renderOverlay;
var clearOverlays = t.clearOverlays;
var reportSelection = t.reportSelection;
var unselect = t.unselect;
var daySelectionMousedown = t.daySelectionMousedown;
var slotSegHtml = t.slotSegHtml;
var cellToDate = t.cellToDate;
var dateToCell = t.dateToCell;
var rangeToSegments = t.rangeToSegments;
var formatDate = calendar.formatDate;
// locals
var dayTable;
var dayHead;
var dayHeadCells;
var dayBody;
var dayBodyCells;
var dayBodyCellInners;
var dayBodyCellContentInners;
var dayBodyFirstCell;
var dayBodyFirstCellStretcher;
var slotLayer;
var daySegmentContainer;
var allDayTable;
var allDayRow;
var slotScroller;
var slotContainer;
var slotSegmentContainer;
var slotTable;
var selectionHelper;
var viewWidth;
var viewHeight;
var axisWidth;
var colWidth;
var gutterWidth;
var slotHeight; // TODO: what if slotHeight changes? (see issue 650)
var snapMinutes;
var snapRatio; // ratio of number of "selection" slots to normal slots. (ex: 1, 2, 4)
var snapHeight; // holds the pixel hight of a "selection" slot
var colCnt;
var slotCnt;
var coordinateGrid;
var hoverListener;
var colPositions;
var colContentPositions;
var slotTopCache = {};
var tm;
var rtl;
var minMinute, maxMinute;
var colFormat;
var showWeekNumbers;
var weekNumberTitle;
var weekNumberFormat;
/* Rendering
-----------------------------------------------------------------------------*/
disableTextSelection(element.addClass("fc-agenda"));
function renderAgenda(c) {
colCnt = c;
updateOptions();
if (!dayTable) {
// first time rendering?
buildSkeleton(); // builds day table, slot area, events containers
} else {
buildDayTable(); // rebuilds day table
}
}
function updateOptions() {
tm = opt("theme") ? "ui" : "fc";
rtl = opt("isRTL");
minMinute = parseTime(opt("minTime"));
maxMinute = parseTime(opt("maxTime"));
colFormat = opt("columnFormat");
// week # options. (TODO: bad, logic also in other views)
showWeekNumbers = opt("weekNumbers");
weekNumberTitle = opt("weekNumberTitle");
if (opt("weekNumberCalculation") != "iso") {
weekNumberFormat = "w";
} else {
weekNumberFormat = "W";
}
snapMinutes = opt("snapMinutes") || opt("slotMinutes");
}
/* Build DOM
-----------------------------------------------------------------------*/
function buildSkeleton() {
var headerClass = tm + "-widget-header";
var contentClass = tm + "-widget-content";
var s;
var d;
var i;
var maxd;
var minutes;
var slotNormal = opt("slotMinutes") % 15 == 0;
buildDayTable();
slotLayer = $(
"<div style='position:absolute;z-index:2;left:0;width:100%'/>"
).appendTo(element);
if (opt("allDaySlot")) {
daySegmentContainer = $(
"<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>"
).appendTo(slotLayer);
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'/></div>" +
"</td>" +
"<th class='" +
headerClass +
" fc-agenda-gutter'> </th>" +
"</tr>" +
"</table>";
allDayTable = $(s).appendTo(slotLayer);
allDayRow = allDayTable.find("tr");
dayBind(allDayRow.find("td"));
slotLayer.append(
"<div class='fc-agenda-divider " +
headerClass +
"'>" +
"<div class='fc-agenda-divider-inner'/>" +
"</div>"
);
} else {
daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()
}
slotScroller = $(
"<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>"
).appendTo(slotLayer);
slotContainer = $(
"<div style='position:relative;width:100%;overflow:hidden'/>"
).appendTo(slotScroller);
slotSegmentContainer = $(
"<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>"
).appendTo(slotContainer);
s =
"<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +
"<tbody>";
d = zeroDate();
maxd = addMinutes(cloneDate(d), maxMinute);
addMinutes(d, minMinute);
slotCnt = 0;
for (i = 0; d < maxd; i++) {
minutes = d.getMinutes();
s +=
"<tr class='fc-slot" +
i +
" " +
(!minutes ? "" : "fc-minor") +
"'>" +
"<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(slotContainer);
slotBind(slotTable.find("td"));
}
/* Build Day Table
-----------------------------------------------------------------------*/
function buildDayTable() {
var html = buildDayTableHTML();
if (dayTable) {
dayTable.remove();
}
dayTable = $(html).appendTo(element);
dayHead = dayTable.find("thead");
dayHeadCells = dayHead.find("th").slice(1, -1); // exclude gutter
dayBody = dayTable.find("tbody");
dayBodyCells = dayBody.find("td").slice(0, -1); // exclude gutter
dayBodyCellInners = dayBodyCells.find("> div");
dayBodyCellContentInners = dayBodyCells.find(".fc-day-content > div");
dayBodyFirstCell = dayBodyCells.eq(0);
dayBodyFirstCellStretcher = dayBodyCellInners.eq(0);
markFirstLast(dayHead.add(dayHead.find("tr")));
markFirstLast(dayBody.add(dayBody.find("tr")));
// TODO: now that we rebuild the cells every time, we should call dayRender
}
function buildDayTableHTML() {
var html =
"<table style='width:100%' class='fc-agenda-days fc-border-separate' cellspacing='0'>" +
buildDayTableHeadHTML() +
buildDayTableBodyHTML() +
"</table>";
return html;
}
function buildDayTableHeadHTML() {
var headerClass = tm + "-widget-header";
var date;
var html = "";
var weekText;
var col;
html += "<thead>" + "<tr>";
if (showWeekNumbers) {
date = cellToDate(0, 0);
weekText = formatDate(date, weekNumberFormat);
if (rtl) {
weekText += weekNumberTitle;
} else {
weekText = weekNumberTitle + weekText;
}
html +=
"<th class='fc-agenda-axis fc-week-number " +
headerClass +
"'>" +
htmlEscape(weekText) +
"</th>";
} else {
html += "<th class='fc-agenda-axis " + headerClass + "'> </th>";
}
for (col = 0; col < colCnt; col++) {
date = cellToDate(0, col);
html +=
"<th class='fc-" +
dayIDs[date.getDay()] +
" fc-col" +
col +
" " +
headerClass +
"'>" +
htmlEscape(formatDate(date, colFormat)) +
"</th>";
}
html +=
"<th class='fc-agenda-gutter " +
headerClass +
"'> </th>" +
"</tr>" +
"</thead>";
return html;
}
function buildDayTableBodyHTML() {
var headerClass = tm + "-widget-header"; // TODO: make these when updateOptions() called
var contentClass = tm + "-widget-content";
var date;
var today = clearTime(new Date());
var col;
var cellsHTML;
var cellHTML;
var classNames;
var html = "";
html +=
"<tbody>" +
"<tr>" +
"<th class='fc-agenda-axis " +
headerClass +
"'> </th>";
cellsHTML = "";
for (col = 0; col < colCnt; col++) {
date = cellToDate(0, col);
classNames = ["fc-col" + col, "fc-" + dayIDs[date.getDay()], contentClass];
if (+date == +today) {
classNames.push(tm + "-state-highlight", "fc-today");
} else if (date < today) {
classNames.push("fc-past");
} else {
classNames.push("fc-future");
}
cellHTML =
"<td class='" +
classNames.join(" ") +
"'>" +
"<div>" +
"<div class='fc-day-content'>" +
"<div style='position:relative'> </div>" +
"</div>" +
"</div>" +
"</td>";
cellsHTML += cellHTML;
}
html += cellsHTML;
html +=
"<td class='fc-agenda-gutter " +
contentClass +
"'> </td>" +
"</tr>" +
"</tbody>";
return html;
}
// TODO: data-date on the cells
/* Dimensions
-----------------------------------------------------------------------*/
function setHeight(height) {
if (height === undefined) {
height = viewHeight;
}
viewHeight = height;
slotTopCache = {};
var headHeight = dayBody.position().top;
var allDayHeight = slotScroller.position().top; // including divider
var 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));
slotLayer.css("top", headHeight);
slotScroller.height(bodyHeight - allDayHeight - 1);
// the stylesheet guarantees that the first row has no border.
// this allows .height() to work well cross-browser.
slotHeight = slotTable.find("tr:first").height() + 1; // +1 for bottom border
snapRatio = opt("slotMinutes") / snapMinutes;
snapHeight = slotHeight / snapRatio;
}
function setWidth(width) {
viewWidth = width;
colPositions.clear();
colContentPositions.clear();
var axisFirstCells = dayHead.find("th:first");
if (allDayTable) {
axisFirstCells = axisFirstCells.add(allDayTable.find("th:first"));
}
axisFirstCells = axisFirstCells.add(slotTable.find("th:first"));
axisWidth = 0;
setOuterWidth(
axisFirstCells.width("").each(function(i, _cell) {
axisWidth = Math.max(axisWidth, $(_cell).outerWidth());
}),
axisWidth
);
var gutterCells = dayTable.find(".fc-agenda-gutter");
if (allDayTable) {
gutterCells = gutterCells.add(allDayTable.find("th.fc-agenda-gutter"));
}
var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after axisWidth (for IE7)
gutterWidth = slotScroller.width() - slotTableWidth;
if (gutterWidth) {
setOuterWidth(gutterCells, gutterWidth);
gutterCells
.show()
.prev()
.removeClass("fc-last");
} else {
gutterCells
.hide()
.prev()
.addClass("fc-last");
}
colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt);
setOuterWidth(dayHeadCells.slice(0, -1), colWidth);
}
/* Scrolling
-----------------------------------------------------------------------*/
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 afterRender() {
// after the view has been freshly rendered and sized
resetScroll();
}
/* Slot/Day clicking and binding
-----------------------------------------------------------------------*/
function dayBind(cells) {
cells.click(slotClick).mousedown(daySelectionMousedown);
}
function slotBind(cells) {
cells.click(slotClick).mousedown(slotSelectionMousedown);
}
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 = cellToDate(0, 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
-----------------------------------------------------*/
// TODO: should be consolidated with BasicView's methods
function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) {
// overlayEnd is exclusive
if (refreshCoordinateGrid) {
coordinateGrid.build();
}
var segments = rangeToSegments(overlayStart, overlayEnd);
for (var i = 0; i < segments.length; i++) {
var segment = segments[i];
dayBind(
renderCellOverlay(
segment.row,
segment.leftCol,
segment.row,
segment.rightCol
)
);
}
}
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) {
for (var i = 0; i < colCnt; i++) {
var dayStart = cellToDate(0, i);
var dayEnd = addDays(cloneDate(dayStart), 1);
var stretchStart = new Date(Math.max(dayStart, overlayStart));
var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
if (stretchStart < stretchEnd) {
var rect = coordinateGrid.rect(0, i, 0, i, slotContainer); // 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, slotContainer));
}
}
}
/* 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 = slotContainer.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 * snapRatio; i++) {
// adapt slot count to increased/decreased selection slot count
rows.push([
constrain(slotTableTop + snapHeight * i),
constrain(slotTableTop + snapHeight * (i + 1))
]);
}
});
hoverListener = new HoverListener(coordinateGrid);
colPositions = new HorizontalPositionCache(function(col) {
return dayBodyCellInners.eq(col);
});
colContentPositions = new HorizontalPositionCache(function(col) {
return dayBodyCellContentInners.eq(col);
});
function colLeft(col) {
return colPositions.left(col);
}
function colContentLeft(col) {
return colContentPositions.left(col);
}
function colRight(col) {
return colPositions.right(col);
}
function colContentRight(col) {
return colContentPositions.right(col);
}
function getIsCellAllDay(cell) {
return opt("allDaySlot") && !cell.row;
}
function realCellToDate(cell) {
// ugh "real" ... but blame it on our abuse of the "cell" system
var d = cellToDate(0, cell.col);
var slotIndex = cell.row;
if (opt("allDaySlot")) {
slotIndex--;
}
if (slotIndex >= 0) {
addMinutes(d, minMinute + slotIndex * snapMinutes);
}
return d;
}
// 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)
.find("td div")[0].offsetTop;
// .eq() is faster than ":eq()" selector
// [0].offsetTop is faster than .position().top (do we really need this optimization?)
// a better optimization would be to cache all these divs
}
return Math.max(
0,
Math.round(
slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
)
);
}
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 = dateToCell(startDate).col;
if (col >= 0 && col < colCnt) {
// only works when times are on same day
var rect = coordinateGrid.rect(0, col, 0, col, slotContainer); // 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";
selectionHelper = $(helperRes)
.css(rect)
.appendTo(slotContainer);
}
} 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);
slotContainer.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 && !getIsCellAllDay(cell)) {