var fc = $.fullCalendar = {}; var views = fc.views = {}; /* Defaults -----------------------------------------------------------------------------*/ var defaults = { // display defaultView: 'month', aspectRatio: 1.35, header: { left: 'title', center: '', right: 'today prev,next' }, weekends: true, // editing //editable: false, //disableDragging: false, //disableResizing: false, allDayDefault: true, // event ajax 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: ' ◄ ', next: ' ► ', prevYear: ' << ', nextYear: ' >> ', today: 'today', month: 'month', week: 'week', day: 'day' }, // jquery-ui theming theme: false, buttonIcons: { prev: 'circle-triangle-w', next: 'circle-triangle-e' } }; // right-to-left defaults var rtlDefaults = { header: { left: 'next,prev today', center: '', right: 'title' }, buttonText: { prev: ' ► ', next: ' ◄ ', prevYear: ' >> ', nextYear: ' << ' }, buttonIcons: { prev: 'circle-triangle-e', next: 'circle-triangle-w' } }; // function for adding/overriding defaults var setDefaults = fc.setDefaults = function(d) { $.extend(true, defaults, d); } /* .fullCalendar jQuery function -----------------------------------------------------------------------------*/ $.fn.fullCalendar = function(options) { // method calling if (typeof options == 'string') { var args = Array.prototype.slice.call(arguments, 1), res; this.each(function() { var r = $.data(this, 'fullCalendar')[options].apply(this, args); if (res == undefined) { res = r; } }); if (res != undefined) { return res; } return this; } // pluck the 'events' and 'eventSources' options var eventSources = options.eventSources || []; delete options.eventSources; if (options.events) { eventSources.push(options.events); delete options.events; } // first event source reserved for 'sticky' events eventSources.unshift([]); // initialize options options = $.extend(true, {}, defaults, (options.isRTL || options.isRTL==undefined && defaults.isRTL) ? rtlDefaults : {}, options ); var tm = options.theme ? 'ui' : 'fc'; // for making theme classes this.each(function() { /* Instance Initialization -----------------------------------------------------------------------------*/ // element var _element = this, element = $(_element).addClass('fc'), elementWidth, content = $("
").prependTo(_element), // relative for ie6 contentHeight; if (options.isRTL) { element.addClass('fc-rtl'); } if (options.theme) { element.addClass('ui-widget'); } // view managing var date = new Date(), viewName, view, // the current view viewInstances = {}; if (options.year != undefined && options.year != date.getFullYear()) { date.setDate(1); date.setMonth(0); date.setFullYear(options.year); } if (options.month != undefined && options.month != date.getMonth()) { date.setDate(1); date.setMonth(options.month); } if (options.date != undefined) { date.setDate(options.date); } /* View Rendering -----------------------------------------------------------------------------*/ function changeView(v) { if (v != viewName) { fixContentSize(); if (view) { if (view.eventsChanged) { eventsDirtyExcept(view); view.eventsChanged = false; } view.element.hide(); } if (viewInstances[v]) { (view = viewInstances[v]).element.show(); if (view.shown) { view.shown(); } }else{ view = viewInstances[v] = $.fullCalendar.views[v]( $("
").appendTo(content), options); } if (header) { // update 'active' view button header.find('div.fc-button-' + viewName).removeClass(tm + '-state-active'); header.find('div.fc-button-' + v).addClass(tm + '-state-active'); } view.name = viewName = v; render(); unfixContentSize(); } } function render(inc, forceUpdateSize) { if ((elementWidth = _element.offsetWidth) !== 0) { // visible on the screen if (!contentHeight) { contentHeight = calculateContentHeight(); } if (inc || !view.date || +view.date != +date) { // !view.date means it hasn't been rendered yet fixContentSize(); view.render(date, inc || 0, contentHeight, function(callback) { // dont refetch if new view contains the same events (or a subset) if (!eventStart || view.visStart < eventStart || view.visEnd > eventEnd) { fetchEvents(callback); }else{ callback(events); // no refetching } }); unfixContentSize(); view.date = cloneDate(date); } else if (view.sizeDirty || forceUpdateSize) { view.updateSize(contentHeight); view.rerenderEvents(); } else if (view.eventsDirty) { // ensure events are rerendered if another view messed with them // pass in 'events' b/c event might have been added/removed view.clearEvents(); view.renderEvents(events); } if (header) { // update title text header.find('h2.fc-header-title').html(view.title); // enable/disable 'today' button var today = new Date(); if (today >= view.start && today < view.end) { header.find('div.fc-button-today').addClass(tm + '-state-disabled'); }else{ header.find('div.fc-button-today').removeClass(tm + '-state-disabled'); } } view.sizeDirty = false; view.eventsDirty = false; view.trigger('viewDisplay', _element); } } // marks other views' events as dirty function eventsDirtyExcept(exceptView) { $.each(viewInstances, function() { if (this != exceptView) { this.eventsDirty = true; } }); } // called when any event objects have been added/removed/changed, rerenders function eventsChanged() { view.clearEvents(); view.renderEvents(events); eventsDirtyExcept(view); } // marks other views' sizes as dirty function sizesDirtyExcept(exceptView) { $.each(viewInstances, function() { if (this != exceptView) { this.sizeDirty = true; } }); } // called when we know the element size has changed function sizeChanged(fix) { contentHeight = calculateContentHeight(); if (fix) { fixContentSize(); } view.updateSize(contentHeight); if (fix) { unfixContentSize(); } sizesDirtyExcept(view); view.rerenderEvents(true); } // calculate what the height of the content should be function calculateContentHeight() { if (options.contentHeight) { return options.contentHeight; } else if (options.height) { return options.height - (header ? header.height() : 0) - horizontalSides(content); } return elementWidth / Math.max(options.aspectRatio, .5); } /* Event Sources and Fetching -----------------------------------------------------------------------------*/ var events = [], eventStart, eventEnd; // Fetch from ALL sources. Clear 'events' array and populate function fetchEvents(callback) { events = []; eventStart = cloneDate(view.visStart); eventEnd = cloneDate(view.visEnd); var queued = eventSources.length, sourceDone = function() { if (--queued == 0) { if (callback) { callback(events); } } }, i=0; for (; i") .append($("") .append($("").append(buildSection(sections.left))) .append($("").append(buildSection(sections.center))) .append($("").append(buildSection(sections.right)))) .prependTo(element); } function buildSection(buttonStr) { if (buttonStr) { var tr = $(""); $.each(buttonStr.split(' '), function(i) { if (i > 0) { tr.append(""); } var prevButton; $.each(this.split(','), function(j, buttonName) { if (buttonName == 'title') { tr.append("

 

"); if (prevButton) { prevButton.addClass(tm + '-corner-right'); } prevButton = null; }else{ var buttonClick; if (publicMethods[buttonName]) { buttonClick = publicMethods[buttonName]; } else if (views[buttonName]) { buttonClick = function() { button.removeClass(tm + '-state-hover'); changeView(buttonName) }; } if (buttonClick) { if (prevButton) { prevButton.addClass(tm + '-no-right'); } var button, icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null, text = smartProperty(options.buttonText, buttonName); if (icon) { button = $("
" + "
"); } else if (text) { button = $(""); } 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($("").appendTo(tr)); if (prevButton) { prevButton.addClass(tm + '-no-right'); }else{ button.addClass(tm + '-corner-left'); } prevButton = button; } } } }); if (prevButton) { prevButton.addClass(tm + '-corner-right'); } }); return $("").append(tr); } } /* Resizing -----------------------------------------------------------------------------*/ var contentSizeFixed = false, resizeCnt = 0; function fixContentSize() { if (!contentSizeFixed) { contentSizeFixed = true; content.css({ overflow: 'hidden', height: contentHeight }); // TODO: previous action might have caused scrollbars // which will make the window width more narrow, possibly changing the aspect ratio } } function unfixContentSize() { if (contentSizeFixed) { content.css({ overflow: 'visible', height: '' }); if ($.browser.msie && ($.browser.version=='6.0' || $.browser.version=='7.0')) { // in IE6/7 the inside of the content div was invisible // bizarre hack to get this work... need both lines content[0].clientHeight; content.hide().show(); } contentSizeFixed = false; } } function windowResize() { if (!contentSizeFixed) { if (view.date) { // view has already been rendered var rcnt = ++resizeCnt; // add a delay setTimeout(function() { if (rcnt == resizeCnt && !contentSizeFixed) { var newWidth = element.width(); if (newWidth != elementWidth) { elementWidth = newWidth; sizeChanged(true); view.trigger('windowResize', _element); } } }, 200); }else{ render(); // render for first time // was probably in a 0x0 iframe that has just been resized } } }; $(window).resize(windowResize); // let's begin... changeView(options.defaultView); // in IE, when in 0x0 iframe, initial resize never gets called, so do this... if ($.browser.msie && !$('body').width()) { setTimeout(function() { render(); content.hide().show(); // needed for IE 6 view.rerenderEvents(); // needed for IE 7 }, 0); } }); return this; }; /* Important Event Utilities -----------------------------------------------------------------------------*/ var fakeID = 0; function normalizeEvent(event, options) { event._id = event._id || (event.id == undefined ? '_fc' + fakeID++ : event.id + ''); if (event.date) { if (!event.start) { event.start = event.date; } delete event.date; } event._start = cloneDate(event.start = parseDate(event.start)); event.end = parseDate(event.end); if (event.end && event.end <= event.start) { event.end = null; } event._end = event.end ? cloneDate(event.end) : null; if (event.allDay == undefined) { event.allDay = options.allDayDefault; } if (event.className) { if (typeof event.className == 'string') { event.className = event.className.split(/\s+/); } }else{ event.className = []; } } // TODO: if there is no title or start date, return false to indicate an invalid event