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 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: ' ◄ ', 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' }, //selectable: false, unselectAuto: true, dropAccept: '*' }; // 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 data = $.data(this, 'fullCalendar'); if (data) { var meth = data[options]; if (meth) { var r = meth.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'), elementOuterWidth, content = $("
").prependTo(_element), suggestedViewHeight, resizeUID = 0, ignoreWindowResize = 0, date = new Date(), viewName, // the current view name (TODO: look into getting rid of) view, // the current view viewInstances = {}, absoluteViewElement; if (options.isRTL) { element.addClass('fc-rtl'); } if (options.theme) { element.addClass('ui-widget'); } setYMD(date, options.year, options.month, options.date); /* View Rendering -----------------------------------------------------------------------------*/ function changeView(v) { if (v != viewName) { ignoreWindowResize++; // because setMinHeight might change the height before render (and subsequently setSize) is reached viewUnselect(); var oldView = view, newViewElement; if (oldView) { if (oldView.eventsChanged) { eventsDirty(); oldView.eventDirty = oldView.eventsChanged = false; } if (oldView.beforeHide) { oldView.beforeHide(); // called before changing min-height. if called after, scroll state is reset (in Opera) } setMinHeight(content, content.height()); oldView.element.hide(); }else{ setMinHeight(content, 1); // needs to be 1 (not 0) for IE7, or else view dimensions miscalculated } content.css('overflow', 'hidden'); if (viewInstances[v]) { (view = viewInstances[v]).element.show(); }else{ view = viewInstances[v] = fc.views[v]( newViewElement = absoluteViewElement = $("") .appendTo(content), options, v // the view's name ); } 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'); } viewName = v; render(); // after height has been set, will make absoluteViewElement's position=relative, then set to null content.css('overflow', ''); if (oldView) { setMinHeight(content, 1); } if (!newViewElement && view.afterShow) { view.afterShow(); // called after setting min-height/overflow, so in final scroll state (for Opera) } ignoreWindowResize--; } } function render(inc) { if (elementVisible()) { ignoreWindowResize++; // because view.renderEvents might temporarily change the height before setSize is reached viewUnselect(); if (suggestedViewHeight === undefined) { calcSize(); } if (!view.start || inc || date < view.start || date >= view.end) { view.render(date, inc || 0); // responsible for clearing events setSize(true); if (!eventStart || !options.lazyFetching || view.visStart < eventStart || view.visEnd > eventEnd) { fetchAndRenderEvents(); }else{ view.renderEvents(events); // don't refetch } } else if (view.sizeDirty || view.eventsDirty || !options.lazyFetching) { view.clearEvents(); if (view.sizeDirty) { setSize(); } if (options.lazyFetching) { view.renderEvents(events); // don't refetch }else{ fetchAndRenderEvents(); } } elementOuterWidth = element.outerWidth(); view.sizeDirty = false; view.eventsDirty = false; 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'); } } ignoreWindowResize--; view.trigger('viewDisplay', _element); } } function elementVisible() { return _element.offsetWidth !== 0; } function bodyVisible() { return $('body')[0].offsetWidth !== 0; } function viewUnselect() { if (view) { view.unselect(); } } // called when any event objects have been added/removed/changed, rerenders function eventsChanged() { eventsDirty(); if (elementVisible()) { view.clearEvents(); view.renderEvents(events); view.eventsDirty = false; } } // marks other views' events as dirty function eventsDirty() { $.each(viewInstances, function() { this.eventsDirty = true; }); } // called when we know the element size has changed function sizeChanged() { sizesDirty(); if (elementVisible()) { calcSize(); setSize(); viewUnselect(); view.rerenderEvents(); view.sizeDirty = false; } } // marks other views' sizes as dirty function sizesDirty() { $.each(viewInstances, function() { this.sizeDirty = true; }); } /* 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) { if (callback) { callback(events); } } }, i=0; for (; i