| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793 |
- 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',
- cacheParam: '_',
-
- // 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 = $(this).addClass('fc'),
- elementWidth,
- content = $("<div class='fc-content " + tm + "-widget-content' style='position:relative'/>").appendTo(this), // 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](
- $("<div class='fc-view fc-view-" + 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 (_element.offsetWidth !== 0) { // visible on the screen
- if (!elementWidth) {
- elementWidth = element.width();
- 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 / options.aspectRatio;
- }
-
-
-
- /* 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<eventSources.length; i++) {
- fetchEventSource(eventSources[i], sourceDone);
- }
- }
-
- // Fetch from a particular source. Append to the 'events' array
- function fetchEventSource(src, callback) {
- var prevViewName = view.name,
- prevDate = cloneDate(date),
- reportEvents = function(a) {
- if (prevViewName == view.name && +prevDate == +date) { // protects from fast switching
- for (var i=0; i<a.length; i++) {
- normalizeEvent(a[i], options);
- a[i].source = src;
- }
- events = events.concat(a);
- if (callback) {
- callback(a);
- }
- }
- },
- reportEventsAndPop = function(a) {
- reportEvents(a);
- popLoading();
- };
- if (typeof src == 'string') {
- var params = {};
- params[options.startParam] = Math.round(eventStart.getTime() / 1000);
- params[options.endParam] = Math.round(eventEnd.getTime() / 1000);
- params[options.cacheParam] = (new Date()).getTime();
- pushLoading();
- $.getJSON(src, params, reportEventsAndPop);
- }
- else if ($.isFunction(src)) {
- pushLoading();
- src(cloneDate(eventStart), cloneDate(eventEnd), reportEventsAndPop);
- }
- else {
- reportEvents(src); // src is an array
- }
- }
-
-
-
- /* Loading State
- -----------------------------------------------------------------------------*/
-
- var loadingLevel = 0;
-
- function pushLoading() {
- if (!loadingLevel++) {
- view.trigger('loading', _element, true);
- }
- }
-
- function popLoading() {
- if (!--loadingLevel) {
- view.trigger('loading', _element, false);
- }
- }
-
-
-
- /* Public Methods
- -----------------------------------------------------------------------------*/
-
- var publicMethods = {
-
- render: function() {
- render(0, true); // true forces size to updated
- },
-
- changeView: changeView,
-
- getView: function() {
- return view;
- },
-
- option: function(name, value) {
- if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {
- options[name] = value;
- sizeChanged();
- }
- },
-
- //
- // Navigation
- //
-
- prev: function() {
- render(-1);
- },
-
- next: function() {
- render(1);
- },
-
- prevYear: function() {
- addYears(date, -1);
- render();
- },
-
- nextYear: function() {
- addYears(date, 1);
- render();
- },
-
- today: function() {
- date = new Date();
- render();
- },
-
- gotoDate: function(year, month, dateNum) {
- if (typeof year == 'object') {
- date = cloneDate(year); // provided 1 argument, a Date
- }else{
- if (year != undefined) {
- date.setFullYear(year);
- }
- if (month != undefined) {
- date.setMonth(month);
- }
- if (dateNum != undefined) {
- date.setDate(dateNum);
- }
- }
- render();
- },
-
- incrementDate: function(years, months, days) {
- if (years != undefined) {
- addYears(date, years);
- }
- if (months != undefined) {
- addMonths(date, months);
- }
- if (days != undefined) {
- addDays(date, days);
- }
- render();
- },
-
- //
- // Event Manipulation
- //
-
- updateEvent: function(event) { // update an existing event
- var i, len = events.length, e,
- startDelta = event.start - event._start,
- endDelta = event.end ?
- (event.end - (event._end || view.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 = events[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(+view.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;
- normalizeEvent(e, options);
- }
- }
- normalizeEvent(event, options);
- eventsChanged();
- },
-
- renderEvent: function(event, stick) { // render a new event
- normalizeEvent(event, options);
- if (!event.source) {
- if (stick) {
- (event.source = eventSources[0]).push(event);
- }
- events.push(event);
- }
- eventsChanged();
- },
-
- removeEvents: function(filter) {
- if (!filter) { // remove all
- events = [];
- // clear all array sources
- for (var i=0; i<eventSources.length; i++) {
- if (typeof eventSources[i] == 'object') {
- eventSources[i] = [];
- }
- }
- }else{
- if (!$.isFunction(filter)) { // an event ID
- var id = filter + '';
- filter = function(e) {
- return e._id == id;
- };
- }
- events = $.grep(events, filter, true);
- // remove events from array sources
- for (var i=0; i<eventSources.length; i++) {
- if (typeof eventSources[i] == 'object') {
- eventSources[i] = $.grep(eventSources[i], filter, true);
- }
- }
- }
- eventsChanged();
- },
-
- clientEvents: function(filter) {
- if ($.isFunction(filter)) {
- return $.grep(events, filter);
- }
- else if (filter) { // an event ID
- filter += '';
- return $.grep(events, function(e) {
- return e._id == filter;
- });
- }
- return events; // else, return all
- },
-
- rerenderEvents: function() {
- view.rerenderEvents();
- },
-
- //
- // Event Source
- //
-
- addEventSource: function(source) {
- eventSources.push(source);
- fetchEventSource(source, function() {
- eventsChanged();
- });
- },
-
- removeEventSource: function(source) {
- eventSources = $.grep(eventSources, function(src) {
- return src != source;
- });
- // remove all client events from that source
- events = $.grep(events, function(e) {
- return e.source != source;
- });
- eventsChanged();
- },
-
- refetchEvents: function() {
- fetchEvents(eventsChanged);
- }
-
- };
-
- $.data(this, 'fullCalendar', publicMethods);
-
-
-
- /* Header
- -----------------------------------------------------------------------------*/
-
- var header,
- sections = options.header;
- if (sections) {
- header = $("<table class='fc-header'/>")
- .append($("<tr/>")
- .append($("<td class='fc-header-left'/>").append(buildSection(sections.left)))
- .append($("<td class='fc-header-center'/>").append(buildSection(sections.center)))
- .append($("<td class='fc-header-right'/>").append(buildSection(sections.right))))
- .prependTo(element);
- }
- function buildSection(buttonStr) {
- if (buttonStr) {
- var tr = $("<tr/>");
- $.each(buttonStr.split(' '), function(i) {
- if (i > 0) {
- tr.append("<td><span class='fc-header-space'/></td>");
- }
- var prevButton;
- $.each(this.split(','), function(j, buttonName) {
- if (buttonName == 'title') {
- tr.append("<td><h2 class='fc-header-title'> </h2></td>");
- 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 = $("<div class='fc-button-" + buttonName + " ui-state-default'>" +
- "<a><span class='ui-icon ui-icon-" + icon + "'/></a></div>");
- }
- else if (text) {
- button = $("<div class='fc-button-" + buttonName + " " + tm + "-state-default'>" +
- "<a><span>" + text + "</span></a></div>");
- }
- 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($("<td/>").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 $("<table/>").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;
- }
- }
-
- $(window).resize(function() {
- 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
- }
- }
- });
-
-
- // let's begin...
- changeView(options.defaultView);
-
- });
-
- 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 = [];
- }
- }
|