mirror of
https://github.com/wassname/fullcalendar.git
synced 2026-07-25 13:10:31 +08:00
537 lines
11 KiB
JavaScript
537 lines
11 KiB
JavaScript
|
|
|
|
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;
|
|
|
|
ResourceManager.call(t, options);
|
|
|
|
// 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, .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;
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
}
|