almost at 1.2

This commit is contained in:
Adam Shaw 2009-05-30 05:54:53 +00:00
parent 05a4a07073
commit ff281feb33
7 changed files with 799 additions and 419 deletions

View file

@ -3,6 +3,9 @@ version 1.2
- cssClass attribute for CalEvents - cssClass attribute for CalEvents
- multiple event sources (using an array for the 'events' option) - multiple event sources (using an array for the 'events' option)
- the 'events' option for fullCalendar() and gcalFullCalendar() is now optional - the 'events' option for fullCalendar() and gcalFullCalendar() is now optional
- bugs w/ month date formatting
- change behavior for parsing number strings (now as unix timestamp)
- allow multiple classes in cssClass
version 1.1 (5/10/09) version 1.1 (5/10/09)
- Added the following options: - Added the following options:

View file

@ -188,3 +188,6 @@ latex_documents = [
# If false, no module index is generated. # If false, no module index is generated.
#latex_use_modindex = True #latex_use_modindex = True
highlight_language = 'javascript'

View file

@ -1,31 +1,21 @@
.. highlight:: javascript Main Usage
==========
Usage The following code initializes a FullCalendar within an element with ID 'calendar'::
=====
.. code-block:: javascript $('#calendar').fullCalendar({
$('#myCalendar').fullCalendar({ // put your options here
// initializes a calendar
// see options, data provider, and triggered events below
});
$('#myCalendar').fullCalendar('nextMonth'); // move ahead one month })
$('#myCalendar').fullCalendar('currentMonth'); // go to current month
$('#myCalendar').fullCalendar('prevMonth'); // move back one month
$('#myCalendar').fullCalendar('gotoMonth', year, month);
// go to an arbitrary month. 'month' is zero-based
$('#myCalender').fullCalendar('refresh');
// re-fetch events for the current month
Options
======= .. _GeneralOptions:
General Options
===============
**year**, **month**: integers **year**, **month**: integers
The month that will be displayed when the calendar first loads. The month that will be displayed when the calendar first loads.
@ -35,19 +25,12 @@ Options
Determines if all events on the calendar can be dragged & dropped. If Determines if all events on the calendar can be dragged & dropped. If
``true``, requires `jQuery UI <http://jqueryui.com/>`_ core and draggables. ``true``, requires `jQuery UI <http://jqueryui.com/>`_ core and draggables.
Can be overridden on a per-event basis with the ``draggable`` property of Can be overridden on a per-event basis with the ``draggable`` property of
each :ref:`CalEvent <CalEvent>` object. each :ref:`CalEvent <CalEvents>`.
**fixedWeeks**: boolean, default:``true`` **fixedWeeks**: boolean, default:``true``
If ``true``, the calendar will always be 6 weeks tall. If ``false``, the If ``true``, the calendar will always be 6 weeks tall. If ``false``, the
calendar's height will vary per month. calendar's height will vary per month.
**weekStart**: integer, default:``0``
The day-of-week each week begins. 0 = Sunday (default),
1 = Monday (for UK users), 2 = Tuesday, etc.
**rightToLeft**: boolean, default:``false``
Displays the calendar right-to-left (for Arabic and Hebrew languages)
**abbrevDayHeadings**: boolean, default:``true`` **abbrevDayHeadings**: boolean, default:``true``
Whether to display "Sun" versus "Sunday" for days of the week. Whether to display "Sun" versus "Sunday" for days of the week.
@ -55,42 +38,28 @@ Options
Determines whether a title such as "January 2009" will be displayed at the Determines whether a title such as "January 2009" will be displayed at the
top of the calendar. top of the calendar.
**titleFormat**: string, default:``"Y F"`` **titleFormat**: string, default:``"F Y"``
A string defining format of the title above the calendar. The default A string defining the format of the title above the calendar. The default
"Y F" creates strings such as "January 2009". Use the following "F Y" creates strings such as "January 2009". Consult the
codes in your format string (similar to the PHP's date function): :ref:`$.fullCalendar.formatDate <formatDate>` documentation for a full
list of commands.
* **F** - January through December
* **m** - 01 through 12 (leading zeros)
* **M** - Jan through Dec
* **n** - 1 through 12
* **Y** - Examples: 1999 or 2003
* **y** - Examples: 99 or 03
**buttons**: boolean/hash, default:``true`` **buttons**: boolean/hash, default:``true``
Determines whether navigation buttons will be displayed at the top of the Determines whether navigation buttons will be displayed at the top of the
calendar. A hash with keys 'today', 'prev', and 'next' will determine if calendar. A hash such as ``{today:false, prev:true, next:true}`` can be
each individual button is displayed. Strings can be provided to change provided to display only certain buttons. A hash such as
each button's text. ``{today:false, prev:"Last Month", next:"Next Month"}`` can be provided
to display only certain buttons AND change a button's text.
**showTime**: boolean/ ``"guess"``, default:``"guess"`` **showTime**: boolean/ ``"guess"``, default:``"guess"``
Determines if times will be displayed before each event's title. Determines if times will be displayed before each event's title.
``"guess"`` displays times only for events with non-zero start or end times. ``"guess"`` displays times only for events with non-zero start or end times.
**timeFormat**: string, default: ``"gx"`` **timeFormat**: string, default: ``"gx"``
A string defining the format of dislayed of event times. The default "gx" A string defining the format of dislayed event times. The default "gx"
creates a string such as "9a". Use the following codes in your format creates a string such as "9a". Consult the
string (similar to PHP's date function): :ref:`$.fullCalendar.formatDate <formatDate>`
documentation for a full list of commands.
* **a** - am or pm
* **A** - AM or PM
* **x** - a or p
* **X** - A or P
* **g** - 1 through 12 (hour)
* **G** - 0 through 23 (hour, military time)
* **h** - 01 through 12 (hour, leading zeros)
* **H** - 00 through 23 (hour, military time and leading zeros)
* **i** - 00 to 59 (minute, leading zeros)
**eventDragOpacity**: float **eventDragOpacity**: float
The opacity of an event element while it is being dragged (0.0 - 1.0) The opacity of an event element while it is being dragged (0.0 - 1.0)
@ -100,20 +69,90 @@ Options
snapping back into place. snapping back into place.
.. _EventDataProvider:
Event Data Provider .. _TriggeredActions:
===================
Triggered Actions
=================
The following options are *functions* that get executed every time something
special happens:
**monthDisplay**: function(year, month, monthTitle)
Triggered once when the calendar loads and every time the
calendar's month is changed. ``month`` is zero-based. ``monthTitle``
contains the new title of the month (ex: "January 2009")
**loading**: function(isLoading)
Triggered with a ``true`` argument when the calendar begins fetching
events via AJAX. Triggered with ``false`` when done.
**resize**: function()
Triggered after the calendar has recovered from a resize (due to the window
being resized).
``this`` is set to the main element
**dayClick**: function(dayDate)
Triggered when the user clicks on a day. ``dayDate`` is a Date object with
it's time set to 00:00:00.
``this`` is set to the TD element of the clicked day.
**eventRender**: function(calEvent, element)
Triggered before an element is rendered for the given :ref:`CalEvent <CalEvents>`.
``element`` is the jQuery element that will be used by default. You can modify
this element or return a brand new element that will be used instead.
This function is great for attaching other jQuery plugins to each event
element, such as a `qTip <http://craigsworks.com/projects/qtip/docs/>`_
tooltip effect.
**eventClick**, **eventMouseover**, **eventMouseout**: function(calEvent, jsEvent)
Triggered on click/mouseover/mouseout actions for an event.
``calEvent`` holds that event's information (date, title, etc).
``jsEvent`` holds the native javascript event (with information about click position, etc).
``this`` is set to the event's element
For ``eventClick``, return ``false`` to prevent the browser from going to
the event's URL.
**eventDragStart**, **eventDragStop**: function(calEvent, jsEvent, ui)
Triggered before/after an event is dragged (but not necessarily moved to a new day).
``calEvent`` holds that event's information (date, title, etc).
``jsEvent`` holds the native javascript event (with information about click position, etc).
``ui`` holds the jQuery UI object.
``this`` is set to the event's element
**eventDrop**: function(calEvent, dayDelta, jsEvent, ui)
Triggered when dragging stops and the event has moved to a *different* day.
``dayDelta`` holds the number of days the event was moved forward (a positive number)
or backwards (a negative number).
``dayDelta`` is elegant for dealing with multi-day and repeating events.
If updating a remote database, just add the ``dayDelta`` to the start
and end times of all events with the given ``calEvent.id``
.. _EventSources:
Event Feeds and Sources
=======================
The following options determine *how* events get on the calendar:
**events**: array/string/function **events**: array/string/function
An array of :ref:`CalEvent` can be used to hardcode events into the An array of :ref:`CalEvents <CalEvents>` can be used to hardcode events into the
calendar. calendar.
Or, a URL can be provided. This URL should return JSON for an array of Or, a URL can be provided. This URL should return JSON for an array of
:ref:`CalEvent`. GET parameters, determined by the ``startParam`` and :ref:`CalEvents <CalEvents>`. GET parameters, determined by the
``endParam`` options, will be inserted into the URL. These parameters ``startParam`` and ``endParam`` options, will be inserted into the URL.
indicate the UNIX timestamp of the start of the first visible day These parameters indicate the UNIX timestamp of the start of the first
(inclusive) and the end of the last visible day (exclusive). visible day (inclusive) and the end of the last visible day (exclusive).
Or, a function can be provided for custom fetching. The function is Or, a function can be provided for custom fetching. The function is
queried every time event data is needed. The function is passed a ``start`` queried every time event data is needed. The function is passed a ``start``
@ -140,91 +179,48 @@ Event Data Provider
} }
**eventSources**: array
Similar to the ``events`` options, except one may specify *multiple* sources.
For example, one may specify an array of JSON URL's, an array of custom
functions, an array of hardcoded event arrays, or any combination.
**startParam**: string, default:``"start"`` **startParam**: string, default:``"start"``
A GET parameter of this name will be inserted into the URL when fetching A GET parameter of this name will be inserted into the URL when fetching
events from a JSON script (when ``event`` is a URL string). The value events from a JSON script. The value of this GET parameter will be a UNIX
of this GET parameter will be a UNIX timestamp denoting the start of the timestamp denoting the start of the first visible day (inclusive).
first visible day (inclusive).
**endParam**: string, default:``"end"`` **endParam**: string, default:``"end"``
A GET parameter of this name will be inserted into the URL when fetching A GET parameter of this name will be inserted into the URL when fetching
events from a JSON script (when ``event`` is a URL string). The value events from a JSON script. The value of this GET parameter will be a UNIX
of this GET parameter will be a UNIX timestamp denoting the end of the timestamp denoting the end of the last visible day (exclusive).
last visible day (exclusive).
**cacheParam**: string, default:``"_"`` **cacheParam**: string, default:``"_"``
When using a JSON url, a parameter of this name will When using a JSON url, a parameter of this name will
automatically be inserted into the URL to prevent the browser from automatically be inserted into the URL to prevent the browser from
caching the response. The value will be the current millisecond time. caching the response. The value will be the current millisecond time.
.. _TriggeredEvents: The following methods can be called on a FullCalendar that has already
been initialized:
Triggered Events **.fullCalendar(** ``'addEventSource'``, **source)**
================ Adds an event source. ``source`` may be an array/string/function just as in
the ``events`` option. Events will be immediately fetched from this source
and placed on the calendar.
**monthDisplay**: function(year, month, monthTitle) **.fullCalendar(** ``'removeEventSource'``, **source)**
Triggered once when the calendar loads and every time the Remove an event source. ``source`` must be the original array/string/function.
calendar's month is changed. ``month`` is zero-based. ``monthTitle``
contains the new title of the month (ex: "January 2009")
**loading**: function(isLoading)
Triggered with a ``true`` argument when the calendar begins fetching
events via AJAX. Triggered with ``false`` when done.
**resize**: function()
Triggered after the calendar has recovered from a resize (due to the window
pane being resized).
``this`` is set to the main element
**dayClick**: function(dayDate)
Triggered when the user clicks on a day. ``dayDate`` is a Date object with
it's time set to 00:00:00.
``this`` is set to the TD element of the clicked day.
**eventRender**: function(calEvent, element)
Triggered before an element is rendered for the given ``calEvent``.
``element`` is the jQuery element that will be used by default. You can modify
this element or return a brand new element that will be used instead.
**eventClick**, **eventMouseover**, **eventMouseout**: function(calEvent, domEvent)
Triggered on click/mouseover/mouseout actions for an event.
``calEvent`` holds that event's information (date, title, etc).
``domEvent`` holds the native DOM event (with information about click position, etc).
``this`` is set to the event's TABLE element
For ``eventClick``, return ``false`` to prevent the browser from going to
calEvent's URL.
**eventDragStart**, **eventDragStop**: function(calEvent, domEvent, ui)
Triggered before/after an event is dragged (but not necessarily moved to a new day).
``calEvent`` holds that event's information (date, title, etc).
``domEvent`` holds the native DOM event (with information about click position, etc).
``ui`` holds the jQuery UI object.
``this`` is set to the event's TABLE element
**eventDrop**: function(calEvent, dayDelta, domEvent, ui)
Triggered when dragging stops and the event has moved to a *different* day.
``dayDelta`` holds the number of days the event was moved forward (a positive number)
or backwards (a negative number).
``dayDelta`` is elegant for dealing with multi-day and repeating events.
If updating a remote database, just add the ``dayDelta`` to the start
and end times of all events with the given ``calEvent.id``
.. _CalEvent:
.. _CalEvents:
CalEvent Objects CalEvent Objects
================ ================
A ``CalEvent`` is a data structure that frequents FullCalendar's API. It is A CalEvent is a data structure that frequents FullCalendar's API. It is the
used when a custom event-fetcher needs to report to the :ref:`EventDataProvider`. standardized currency used in :ref:`EventSources`. It is also passed to various
It is also used in various :ref:`TriggeredEvents`. Here are the properties of a :ref:`Triggered Actions <TriggeredActions>`. Here are the properties of a
``CalEvent``\: CalEvent:
**id**: integer/string, **id**: integer/string,
Uniquely identifies the given event. Absolutely essential for multi-day Uniquely identifies the given event. Absolutely essential for multi-day
@ -240,7 +236,7 @@ It is also used in various :ref:`TriggeredEvents`. Here are the properties of a
A javascript Date object indicating the date/time an event begins. A javascript Date object indicating the date/time an event begins.
Events with ambiguous time-of-day should use 00:00:00. Events with ambiguous time-of-day should use 00:00:00.
When reporting to the :ref:`EventDataProvider`, for convenience, In an :ref:`Event Source <EventSources>`, for convenience,
one can also use a string in IETF format (ex: "Wed, 18 Oct 2009 13:00:00 EST"), one can also use a string in IETF format (ex: "Wed, 18 Oct 2009 13:00:00 EST"),
a string in ISO8601 format (ex: "2009-11-05T13:15:30Z") or an integer a string in ISO8601 format (ex: "2009-11-05T13:15:30Z") or an integer
UNIX timestamp. UNIX timestamp.
@ -249,9 +245,9 @@ It is also used in various :ref:`TriggeredEvents`. Here are the properties of a
A javascript Date object indicating the date/time an event ends A javascript Date object indicating the date/time an event ends
(exclusively). If an event has an ambiguous end time, ``end`` should be (exclusively). If an event has an ambiguous end time, ``end`` should be
set to midnight of the next day. This is implied if ``end`` is omitted. set to midnight of the next day. This is implied if ``end`` is omitted.
(For convenience with the :ref:`EventDataProvider`). (For convenience with an :ref:`Event Source <EventSources>`).
IETF and ISO8601 strings can be used for the :ref:`EventDataProvider`. IETF and ISO8601 strings can be used with an :ref:`Event Source <EventSources>`.
**draggable**: boolean (optional) **draggable**: boolean (optional)
Overrides the master ``draggable`` property for this single event. Overrides the master ``draggable`` property for this single event.
@ -259,20 +255,189 @@ It is also used in various :ref:`TriggeredEvents`. Here are the properties of a
**showTime**: boolean/ ``"guess"`` (optional) **showTime**: boolean/ ``"guess"`` (optional)
Overrides the master ``showTime`` property for this single event. Overrides the master ``showTime`` property for this single event.
When giving events to the :ref:`EventDataProvider`, one can include other **className**: string/array (optional)
properties beyond the ones listed. This is useful if you want to earmark your A CSS class (or array of classes) that will be attached to this event's
events with additional data to be retrieved later during a element.
:ref:`Triggered Event <TriggeredEvents>`.
**source**: array/string/function (automatic)
A reference to the original array, JSON URL, or function the event
came from. Do not worry about populating this value, FullCalendar will
do this automatically.
The following methods can be called on a FullCalendar that has already been
initialized. These methods get/add/remove/update the event elements that
currently reside on the calendar. Note that when you are using a JSON feed or custom
event source, your event is never *permanently* deleted, because it may be
refetched from the source at a later time. It is up to the developer to delete
the event(s) from any database.
**.fullCalendar(** ``'addEvent'``, **calEvent)**
Add an event to the current month on-the-fly. ``calEvent`` is an object
containing at least an id, title, and start date.
**.fullCalendar(** ``'updateEvent'``, **calEvent)**
Report modifications to the given :ref:`CalEvent <CalEvents>` and redraw.
``calEvent`` must be the *actual CalEvent object*, as retrieved from a
:ref:`Triggered Action <TriggeredActions>` or ``getEventsById`` (see below).
A set of repeating events will all be affected.
**.fullCalendar(** ``'removeEvent'``, **calEventOrId)**
Remove elements belonging to the given :ref:`CalEvent <CalEvents>`. If the
event is repeating, all occurences of the event will be removed. The
second argument may be a CalEvent's ID, or the CalEvent object itself.
**.fullCalendar(** ``'getEventsById'`` , **eventId)**
Returns a list of :ref:`CalEvents <CalEvents>` with the given ID that are
currently being displayed.
Extras
Navigation Methods
==================
The following methods may be called on a FullCalendar that has already been
initialized:
**.fullCalendar(** ``'prevMonth'`` **)**
Visits the previous month.
**.fullCalendar(** ``'nextMonth'`` **)**
Visits the next month.
**.fullCalendar(** ``'gotoMonth'``, **year, month)**
Visits an arbitrary month. ``month`` is zero-based (0 is January, 1 is
February, etc).
**.fullCalendar(** ``'today'`` **)**
Visits the current month.
**.fullCalendar(** ``'refresh'`` **)**
Refetch and redraw the events for the current month.
Locale
====== ======
FullCalendar provides some extra date utilities\: Use the following options to change the calendar's locale:
**$.parseISO8601(string, ignoreTimezone)** **weekStart**: integer, default:``0``
Parses an ISO8601 string and returns a ``Date`` object The day-of-week each week begins. 0 = Sunday (default),
1 = Monday (for UK users), 2 = Tuesday, etc.
**$.ISO8601String(date)** **rightToLeft**: boolean, default:``false``
Takes a ``Date`` object and returns an ISO8601 string Displays the calendar right-to-left (for Arabic and Hebrew)
The following *variables* may be reassigned or modified to globally change the
text for months and days:
**$.fullCalendar.monthNames**
Default: ``['January', 'February', 'March', ...]``
**$.fullCalendar.monthAbbrevs**
Default: ``['Jan', 'Feb', 'Mar', ...]``
**$.fullCalendar.dayNames**
Default: ``['Sunday', 'Monday', 'Tuesday', ...]``
**$.fullCalendar.dayAbbrevs**
Default: ``['Sun', 'Mon', 'Tue', ...]``
Notice these variables are attached to the main **$** jQuery object.
The :ref:`GeneralOptions` ``titleFormat`` and ``timeFormat`` may also be of
interest to those wanting to change locale.
Date Parsing and Formatting
===========================
The following utilities are always available. These typically come in handy
when creating a custom event source:
.. _formatDate:
**$.fullCalendar.formatDate(date, format)**
Format a javascript Date object into a string. ``format`` may contain
one or more of the following commands (similar to PHP's date function):
* **F** - January through December
* **M** - Jan through Dec
* **n** - 1 through 12 (month)
* **m** - 01 through 12 (month, leading zeros)
* **Y** - Examples: 1999 or 2003
* **y** - Examples: 99 or 03
* **a** - am or pm
* **A** - AM or PM
* **x** - a or p
* **X** - A or P
* **g** - 1 through 12 (hour)
* **G** - 0 through 23 (hour, military time)
* **h** - 01 through 12 (hour, leading zeros)
* **H** - 00 through 23 (hour, military time and leading zeros)
* **i** - 00 to 59 (minute, leading zeros)
**$.fullCalendar.parseDate(string)**
Parse a string and return a javascript Date object. The string may be
in ISO8601 format, IETF format, or a UNIX timestamp.
**$.fullCalendar.parseISO8601(string, ignoreTimezone)**
Parse an ISO8601 string into a javascript Date object.
Notice these functions are attached to the main **$** jQuery object.
Google Calendar
===============
To integrate with your Google Calendar, you must first **make your calendar public**:
#. In the Google Calendar interface, locate the "My Calendar" box on the left.
#. Click the arrow next to the calendar you need.
#. A menu will appear. Click "Share this calendar."
#. Check "Make this calendar public."
#. Make sure "Share only my free/busy information" is *unchecked*.
#. Click "Save."
Then, you must obtain your calendar's **XML feed URL**.
#. In the Google Calendar interface, locate the "My Calendar" box on the left
#. Click the arrow next to the calendar you need.
#. A menu will appear. Click "Calendar settings."
#. In the "Calendar Address" section of the screen, click the XML badge.
#. Your feed's URL will appear.
The API for integrating a Google Calendar feed has changed since
FullCalendar 1.1. The ``$.fullCalendar.gcalFeed`` function now produces
an event source that can be passed to the ``events`` or ``eventSources``
option::
$('#calendar').fullCalendar({
events: $.fullCalendar.gcalFeed(
"http://www.google.com/calendar/feeds/...", // feed URL
{ className: 'gcal-events' } // optional options
)
});
Here is a list of available options:
* **className** - CSS class to attach to each event from this Google Calendar
* **draggable** - whether to allow dragging (default: ``false``)
See *gcal.html* in the *examples* directory for a complete example.

View file

@ -3,6 +3,13 @@
<? fullcalendar_nav() ?> <? fullcalendar_nav() ?>
<? begin_content() ?> <? begin_content() ?>
<div id='toc'>
<h1>Table of Contents</h1>
{{ toc }}
</div>
<div class='clear'></div>
{% block body %}{% endblock %} {% block body %}{% endblock %}
<? end_content() ?> <? end_content() ?>

View file

@ -30,9 +30,12 @@
$(document).ready(function() { $(document).ready(function() {
$('#calendar').gcalFullCalendar({ $('#calendar').fullCalendar({
// US Holidays // US Holidays
events: 'http://www.google.com/calendar/feeds/usa__en%40holiday.calendar.google.com/public/basic', events: $.fullCalendar.gcalFeed(
'http://www.google.com/calendar/feeds/usa__en%40holiday.calendar.google.com/public/basic',
{draggable: false, className: 'mygcal'}
),
eventClick: function(event) { eventClick: function(event) {
window.open(event.url, 'gcalevent', 'width=700,height=600'); window.open(event.url, 'gcalevent', 'width=700,height=600');
return false; return false;

View file

@ -18,18 +18,29 @@
$.fn.fullCalendar = function(options) { $.fn.fullCalendar = function(options) {
//
// Calls methods of a pre-existing instance
//
if (typeof options == 'string') { if (typeof options == 'string') {
var args = Array.prototype.slice.call(arguments, 1); var args = Array.prototype.slice.call(arguments, 1);
var ret; // ugly var res;
this.each(function() { this.each(function() {
var r = $.data(this, 'fullCalendar')[options].apply(this, args); var r = $.data(this, 'fullCalendar')[options].apply(this, args);
if (typeof ret == 'undefined') ret = r; if (typeof res == 'undefined') res = r;
}); });
if (typeof ret == 'undefined') if (typeof res != 'undefined')
return res;
return this; return this;
return ret;
} }
//
// Process options
//
options = options || {}; options = options || {};
var r2l = options.rightToLeft; var r2l = options.rightToLeft;
@ -48,77 +59,94 @@
var weekStart = (options.weekStart || 0) % 7; var weekStart = (options.weekStart || 0) % 7;
var timeFormat = options.timeFormat || 'gx'; var timeFormat = options.timeFormat || 'gx';
var titleFormat = options.titleFormat || (r2l ? 'Y F' : 'F Y'); var titleFormat = options.titleFormat || (r2l ? 'Y F' : 'F Y');
//
// Rendering bug detection variables
//
var tdTopBug, trTopBug, tbodyTopBug, sniffBugs = true; var tdTopBug, trTopBug, tbodyTopBug, sniffBugs = true;
this.each(function() { this.each(function() {
var date = options.year ? new Date(options.year, options.month || 0, 1) : new Date();
var start, end, today, numWeeks; //
// Instance variables
//
var date = options.year ? // holds the year & month of current month
new Date(options.year, options.month || 0, 1) :
new Date();
var start, end; // first & last VISIBLE dates
var today;
var numWeeks;
var ignoreResizes = false; var ignoreResizes = false;
var events = []; var events = [];
var eventSources = options.eventSources || [];
var eventSources; if (options.events) eventSources.push(options.events);
var eo = options.events;
if (eo) {
if (typeof eo == 'string' || $.isFunction(eo)) {
eventSources = [eo];
}else{
var item = eo[0];
if (item) {
if (typeof item == 'string' || $.isFunction(item))
eventSources = eo;
else {
eventSources = [eo];
}
}
}
}
else eventSources = [];
function updateMonth() {
clearEvents(); //
// Month navigation functions
//
function refreshMonth() {
clearEventElements();
render(); render();
} }
function today() {
date = new Date();
updateMonth();
}
function prevMonth() { function prevMonth() {
addMonths(date, -1); addMonths(date, -1);
updateMonth(); refreshMonth();
} }
function nextMonth() { function nextMonth() {
addMonths(date, 1); addMonths(date, 1);
updateMonth(); refreshMonth();
}
function gotoToday() {
date = new Date();
refreshMonth();
} }
function gotoMonth(year, month) { function gotoMonth(year, month) {
date = new Date(year, month, 1); date = new Date(year, month, 1);
updateMonth(); refreshMonth();
} }
//
// Publicly accessible methods
//
$.data(this, 'fullCalendar', { $.data(this, 'fullCalendar', {
today: today, refresh: refreshMonth,
prevMonth: prevMonth, prevMonth: prevMonth,
nextMonth: nextMonth, nextMonth: nextMonth,
today: gotoToday,
gotoMonth: gotoMonth, gotoMonth: gotoMonth,
refresh: updateMonth,
// event crud
//
// Event CRUD
//
addEvent: function(event) { addEvent: function(event) {
events.push(normalizeEvent(event)); events.push(normalizeEvent(event));
clearEvents(); clearEventElements();
renderEvents(); renderEvents();
}, },
updateEvent: function(event) { updateEvent: function(event) {
event.start = cleanDate(event.start); event.start = $.fullCalendar.parseDate(event.start);
event.end = cleanDate(event.end); event.end = $.fullCalendar.parseDate(event.end);
var startDelta = event.start - event._start; var startDelta = event.start - event._start;
var msLength = event.end - event.start; var msLength = event.end - event.start;
event._start = cloneDate(event.start); event._start = cloneDate(event.start);
@ -126,8 +154,8 @@
var e = events[i]; var e = events[i];
if (e.id === event.id && e !== event) { if (e.id === event.id && e !== event) {
e.start = new Date(e.start.getTime() + startDelta); e.start = new Date(e.start.getTime() + startDelta);
e.end = new Date(e.start.getTime() + msLength);
e._start = cloneDate(e.start); e._start = cloneDate(e.start);
e.end = new Date(e.start.getTime() + msLength);
for (var k in event) { for (var k in event) {
if (k && k != 'start' && k != 'end' && k.charAt(0) != '_') { if (k && k != 'start' && k != 'end' && k.charAt(0) != '_') {
e[k] = event[k]; e[k] = event[k];
@ -135,7 +163,7 @@
} }
} }
} }
clearEvents(); clearEventElements();
renderEvents(); renderEvents();
}, },
@ -143,6 +171,8 @@
if (typeof eventId == 'object') { if (typeof eventId == 'object') {
eventId = eventId.id; eventId = eventId.id;
} }
// remove from the 'events' array
var newEvents = []; var newEvents = [];
for (var i=0; i<events.length; i++) { for (var i=0; i<events.length; i++) {
if (events[i].id !== eventId) { if (events[i].id !== eventId) {
@ -150,6 +180,7 @@
} }
} }
events = newEvents; events = newEvents;
// remove from static event sources // remove from static event sources
for (var i=0; i<eventSources.length; i++) { for (var i=0; i<eventSources.length; i++) {
var src = eventSources[i]; var src = eventSources[i];
@ -163,7 +194,8 @@
eventSources[i] = newSrc; eventSources[i] = newSrc;
} }
} }
clearEvents();
clearEventElements();
renderEvents(); renderEvents();
}, },
@ -177,15 +209,23 @@
return res; return res;
}, },
// event source crud
//
// Event Source CRUD
//
addEventSource: function(src) { addEventSource: function(src) {
eventSources.push(src); eventSources.push(src);
clearEvents(); fetchEventSource(src, function() {
clearEventElements();
renderEvents(); renderEvents();
});
}, },
removeEventSource: function(src) { removeEventSource: function(src) {
// remove from 'eventSources' array
var newSources = []; var newSources = [];
for (var i=0; i<eventSources.length; i++) { for (var i=0; i<eventSources.length; i++) {
if (src !== eventSources[i]) { if (src !== eventSources[i]) {
@ -193,7 +233,17 @@
} }
} }
eventSources = newSources; eventSources = newSources;
clearEvents();
// remove events from 'events' array
var newEvents = [];
for (var i=0; i<events.length; i++) {
if (events[i].source !== src) {
newEvents.push(events[i]);
}
}
events = newEvents;
clearEventElements();
renderEvents(); renderEvents();
} }
@ -203,16 +253,26 @@
/*******************************************************************/
//
// Header & Table Rendering
//
/*******************************************************************/
//
// Build one-time DOM elements (header, month container)
//
var titleElement, todayButton, monthElement, monthElementWidth; var titleElement, todayButton, monthElement, monthElementWidth;
var header = $("<div class='full-calendar-header'/>").appendTo(this); var header = $("<div class='full-calendar-header'/>").appendTo(this);
if (bo) { if (bo) { // "button options"
var buttons = $("<div class='full-calendar-buttons'/>").appendTo(header); var buttons = $("<div class='full-calendar-buttons'/>").appendTo(header);
var prevButton, nextButton; var prevButton, nextButton;
if (bo == true || bo.today != false) { if (bo == true || bo.today != false) {
todayButton = $("<input type='button' class='full-calendar-today' value='today'/>") todayButton = $("<input type='button' class='full-calendar-today' value='today'/>")
.click(today); .click(gotoToday);
if (typeof bo.today == 'string') todayButton.val(bo.today); if (typeof bo.today == 'string') todayButton.val(bo.today);
buttons.append(todayButton); buttons.append(todayButton);
} }
@ -239,12 +299,11 @@
.appendTo($("<div class='full-calendar-month-wrap'/>").appendTo(this)); .appendTo($("<div class='full-calendar-month-wrap'/>").appendTo(this));
//
// Build the TABLE cells for the current month. (calls event fetch+render code)
//
var thead, tbody, glass;
var thead, tbody, glass, monthTitle;
function render() { function render() {
@ -253,7 +312,7 @@
clearTime(date); clearTime(date);
var year = date.getFullYear(); var year = date.getFullYear();
var month = date.getMonth(); var month = date.getMonth();
monthTitle = formatTitle(date); var monthTitle = $.fullCalendar.formatDate(date, titleFormat);
if (titleElement) titleElement.text(monthTitle); if (titleElement) titleElement.text(monthTitle);
clearTime(date); clearTime(date);
@ -277,8 +336,13 @@
} }
} }
var dayNames = $.fullCalendar.dayNames;
var dayAbbrevs = $.fullCalendar.dayAbbrevs;
if (!tbody) { if (!tbody) {
// first time, build all cells from scratch
var table = $("<table style='width:100%'/>").appendTo(monthElement); var table = $("<table style='width:100%'/>").appendTo(monthElement);
thead = "<thead><tr>"; thead = "<thead><tr>";
@ -313,6 +377,8 @@
} }
tbody = $(tbody + "</tbody>").appendTo(table); tbody = $(tbody + "</tbody>").appendTo(table);
// a protective coating over the TABLE
// intercepts mouse clicks and prevents text-selection
glass = $("<div style='position:absolute;top:0;left:0;z-index:1;width:100%' />") glass = $("<div style='position:absolute;top:0;left:0;z-index:1;width:100%' />")
.appendTo(monthElement) .appendTo(monthElement)
.click(function(ev, ui) { .click(function(ev, ui) {
@ -325,8 +391,11 @@
}else{ }else{
// NOT first time, reuse as many cells as possible
var diff = numWeeks - tbody.find('tr').length; var diff = numWeeks - tbody.find('tr').length;
if (diff < 0) { if (diff < 0) {
// remove extra rows
tbody.find('tr:gt(' + (numWeeks-1) + ')').remove(); tbody.find('tr:gt(' + (numWeeks-1) + ')').remove();
} }
else if (diff > 0) { else if (diff > 0) {
@ -347,6 +416,7 @@
if (trs) tbody.append(trs); if (trs) tbody.append(trs);
} }
// re-label and re-class existing cells
var d = cloneDate(start); var d = cloneDate(start);
tbody.find('tr').each(function() { tbody.find('tr').each(function() {
for (var i=0; i<7; i++) { for (var i=0; i<7; i++) {
@ -368,9 +438,11 @@
} }
resizeTable(); setCellSizes();
if (sniffBugs) { if (sniffBugs) {
// nasty bugs in opera 9.25
// position() returning relative to direct parent
var tr = tbody.find('tr'); var tr = tbody.find('tr');
var td = tr.find('td'); var td = tr.find('td');
var trTop = tr.position().top; var trTop = tr.position().top;
@ -381,37 +453,7 @@
sniffBugs = false; sniffBugs = false;
} }
fetchEvents(renderEvents);
events = [];
var completed = eventSources.length;
var reportEvents = function(a) {
for (var i=0; i<a.length; i++) normalizeEvent(a[i]);
events = events.concat(a);
if (--completed == 0) {
if (options.loading) options.loading(false);
renderEvents(events);
}
};
if (options.loading) options.loading(true);
for (var i=0; i<eventSources.length; i++) {
var src = eventSources[i];
if (typeof src == 'string') {
var params = {};
params[options.startParam || 'start'] = Math.round(start.getTime() / 1000);
params[options.endParam || 'end'] = Math.round(end.getTime() / 1000);
params[options.cacheParam || '_'] = (new Date()).getTime();
$.getJSON(src, params, reportEvents);
}
else if ($.isFunction(src)) {
src(start, end, reportEvents);
}
else if (src) {
reportEvents(src);
}
}
ignoreResizes = false; ignoreResizes = false;
@ -421,10 +463,38 @@
} }
//
// Adjust dimensions of the cells, based on container's width
//
function setCellSizes() {
var tbodyw = tbody.width();
var cellw = Math.floor(tbodyw / 7);
var cellh = Math.round(cellw * .85);
thead.find('th')
.filter(':lt(6)').width(cellw).end()
.filter(':eq(6)').width(tbodyw - cellw*6);
tbody.find('td').height(cellh);
glass.height(monthElement.height());
monthElementWidth = monthElement.width();
}
/*******************************************************************/
//
// Event Rendering
//
/*******************************************************************/
//
// Render the 'events' array. First, break up into segments
//
var eventMatrix = []; var eventMatrix = [];
function renderEvents() { function renderEvents() {
@ -493,7 +563,9 @@
} }
//
// Do the REAL rendering of the segments
//
var eventElements = []; // [[event, element], ...] var eventElements = []; // [[event, element], ...]
@ -535,7 +607,14 @@
roundE = seg.isEnd; roundE = seg.isEnd;
} }
left2 = left2.position().left + left2.width(); left2 = left2.position().left + left2.width();
var element = $("<table class='event' />") var cl = event.className;
if (typeof cl == 'string') {
cl = ' ' + cl;
}
else if (typeof cl == 'object') {
cl = ' ' + cl.join(' ');
}
var element = $("<table class='event" + (cl || '') + "' />")
.append("<tr>" + .append("<tr>" +
(roundW ? "<td class='nw'/>" : '') + (roundW ? "<td class='nw'/>" : '') +
"<td class='n'/>" + "<td class='n'/>" +
@ -549,7 +628,6 @@
"<td class='s'/>" + "<td class='s'/>" +
(roundE ? "<td class='se'/>" : '') + "</tr>"); (roundE ? "<td class='se'/>" : '') + "</tr>");
buildEventText(event, element.find('td.c')); buildEventText(event, element.find('td.c'));
if (event.cssClass) element.addClass(event.cssClass);
if (options.eventRender) { if (options.eventRender) {
var res = options.eventRender(event, element); var res = options.eventRender(event, element);
if (typeof res != 'undefined') { if (typeof res != 'undefined') {
@ -578,7 +656,31 @@
} }
//
// create the text-contents of an event segment
//
function buildEventText(event, element) {
$("<span class='event-title' />")
.text(event.title)
.appendTo(element);
var st = typeof event.showTime == 'undefined' ? showTime : event.showTime;
if (st != false) {
if (st == true || st == 'guess' &&
(event.start.getHours() || event.start.getMinutes() ||
event.end.getHours() || event.end.getMinutes())) {
var timeStr = $.fullCalendar.formatDate(event.start, timeFormat);
var timeElement = $("<span class='event-time' />");
if (r2l) element.append(timeElement.text(' ' + timeStr));
else element.prepend(timeElement.text(timeStr + ' '));
}
}
}
//
// Attach event handlers to an event segment
//
function initEventElement(event, element) { function initEventElement(event, element) {
element.click(function(ev) { element.click(function(ev) {
@ -609,7 +711,32 @@
} }
//
// Remove all event segments from DOM
//
function clearEventElements() {
for (var i=0; i<eventElements.length; i++)
eventElements[i][1].remove();
eventElements = [];
}
/*******************************************************************/
//
// Drag & Drop (and cell-coordinate stuff)
//
/*******************************************************************/
//
// Attach jQuery UI draggable
//
var dragStartTD, dragTD; var dragStartTD, dragTD;
var dayOverlay; var dayOverlay;
@ -658,7 +785,7 @@
} }
if (options.eventDrop) if (options.eventDrop)
options.eventDrop.call(this, event, delta, ev, ui); options.eventDrop.call(this, event, delta, ev, ui);
clearEvents(); clearEventElements();
renderEvents(); renderEvents();
} }
dayOverlay.hide(); dayOverlay.hide();
@ -668,6 +795,11 @@
}); });
} }
//
// Called DURING dragging, on every mouse move
//
function eventDrag(node, ev, ui) { function eventDrag(node, ev, ui) {
var oldTD = dragTD; var oldTD = dragTD;
dragTD = dayTD(ev.pageX, ev.pageY); dragTD = dayTD(ev.pageX, ev.pageY);
@ -690,9 +822,9 @@
} }
//
// Record positions & dimensions of each TD
//
var dayX, dayY, dayX0, dayY0; var dayX, dayY, dayX0, dayY0;
var currTD, currR, currC; var currTD, currR, currC;
@ -718,6 +850,10 @@
currTD = null; currTD = null;
} }
//
// Returns TD underneath coordinate (optimized)
//
function dayTD(x, y) { function dayTD(x, y) {
var r=-1, c=-1; var r=-1, c=-1;
var rmax=dayY.length-1, cmax=dayX.length-1; var rmax=dayY.length-1, cmax=dayX.length-1;
@ -738,23 +874,31 @@
return currTD; return currTD;
} }
function dayDate(node) { //
// Get a TD's date
//
function dayDate(td) {
var i, tds = tbody.get(0).getElementsByTagName('td'); var i, tds = tbody.get(0).getElementsByTagName('td');
for (i=0; i<tds.length; i++) { for (i=0; i<tds.length; i++) {
if (tds[i] == node) break; if (tds[i] == td) break;
} }
var d = cloneDate(start); var d = cloneDate(start);
return addDays(d, i); return addDays(d, i);
} }
function dayDelta(node1, node2) { //
// Return the # of days between 2 TD's
//
function dayDelta(td1, td2) {
var i1, i2, trs = tbody.get(0).getElementsByTagName('tr'); var i1, i2, trs = tbody.get(0).getElementsByTagName('tr');
for (var i=0; i<trs.length; i++) { for (var i=0; i<trs.length; i++) {
var tr = trs[i]; var tr = trs[i];
for (var j=0; j<7; j++) { for (var j=0; j<7; j++) {
var td = tr.childNodes[j]; var td = tr.childNodes[j];
if (td == node1) i1 = i*7 + j*dis + dit; if (td == td1) i1 = i*7 + j*dis + dit;
if (td == node2) i2 = i*7 + j*dis + dit; if (td == td2) i2 = i*7 + j*dis + dit;
} }
} }
return i2 - i1; return i2 - i1;
@ -765,87 +909,92 @@
function resizeTable() {
var tbodyw = tbody.width(); /*******************************************************************/
var cellw = Math.floor(tbodyw / 7); //
var cellh = Math.round(cellw * .85); // Event Sources & Fetching
thead.find('th') //
.filter(':lt(6)').width(cellw).end() /*******************************************************************/
.filter(':eq(6)').width(tbodyw - cellw*6);
tbody.find('td').height(cellh);
glass.height(monthElement.height()); //
monthElementWidth = monthElement.width(); // Fetch from ALL sources. Clear 'events' array and populate
//
function fetchEvents(callback) {
events = [];
var queued = eventSources.length;
var sourceDone = function() {
if (--queued == 0) {
if (options.loading) {
options.loading(false);
} }
if (callback) {
function clearEvents() { callback(events);
for (var i=0; i<eventElements.length; i++)
eventElements[i][1].remove();
eventElements = [];
}
function buildEventText(event, element) {
$("<span class='event-title' />")
.text(event.title)
.appendTo(element);
var st = typeof event.showTime == 'undefined' ? showTime : event.showTime;
if (st != false) {
var h = event.start.getHours();
var m = event.start.getMinutes();
if (st == true || st == 'guess' && (h || m || event.end.getHours() || event.end.getMinutes())) {
var s = '';
for (var i=0; i<timeFormat.length; i++) {
var c = timeFormat.charAt(i);
if (c == 'a') s += h<12 ? 'am' : 'pm';
else if (c == 'A') s += h<12 ? 'AM' : 'PM';
else if (c == 'x') s += h<12 ? 'a' : 'p';
else if (c == 'X') s += h<12 ? 'A' : 'P';
else if (c == 'g') s += h%12 || 12;
else if (c == 'G') s += h;
else if (c == 'h') s += zeroPad(h%12 || 12);
else if (c == 'H') s += zeroPad(h);
else if (c == 'i') s += zeroPad(m);
else s += c;
}
var timeElement = $("<span class='event-time' />");
if (r2l) element.append(timeElement.text(' ' + s));
else element.prepend(timeElement.text(s + ' '));
} }
} }
};
if (options.loading) {
options.loading(true);
}
for (var i=0; i<eventSources.length; i++) {
fetchEventSource(eventSources[i], sourceDone);
}
} }
function formatTitle(d) {
var m = d.getMonth(); //
var s = ''; // Fetch from a particular source. Append to the 'events' array
for (var i=0; i<titleFormat.length; i++) { //
var c = titleFormat.charAt(i);
if (c == 'F') s += monthNames[m]; function fetchEventSource(src, callback) {
else if (c == 'm') s += zeroPad(m); var reportEvents = function(a) {
else if (c == 'M') s += monthAbbrevs[m]; for (var i=0; i<a.length; i++) {
else if (c == 'n') s += m; normalizeEvent(a[i]);
else if (c == 'Y') s += d.getFullYear(); a[i].source = src;
else if (c == 'y') s += (d.getFullYear()+'').substring(2); }
else s += c; events = events.concat(a);
if (callback) {
callback(a);
}
};
if (typeof src == 'string') {
var params = {};
params[options.startParam || 'start'] = Math.round(start.getTime() / 1000);
params[options.endParam || 'end'] = Math.round(end.getTime() / 1000);
params[options.cacheParam || '_'] = (new Date()).getTime();
$.getJSON(src, params, reportEvents);
}
else if ($.isFunction(src)) {
src(start, end, reportEvents);
}
else {
reportEvents(src);
} }
return s;
} }
/*******************************************************************/
//
// Begin "Main" Execution
//
/*******************************************************************/
var e = this; var e = this;
var resizeID = 0; var resizeID = 0;
$(window).resize(function() { $(window).resize(function() { // re-render table on window resize
if (!ignoreResizes) { if (!ignoreResizes) {
var rid = ++resizeID; var rid = ++resizeID; // add a delay
setTimeout(function() { setTimeout(function() {
if (rid == resizeID) { if (rid == resizeID) {
// if the month width changed
if (monthElement.width() != monthElementWidth) { if (monthElement.width() != monthElementWidth) {
clearEvents(); clearEventElements();
resizeTable(); setCellSizes();
_renderEvents(); _renderEvents();
if (options.resize) options.resize.call(e); if (options.resize) options.resize.call(e);
} }
@ -854,7 +1003,10 @@
} }
}); });
render(); render(); // let's begin...
}); });
@ -863,24 +1015,26 @@
// string utilities
function zeroPad(n) {
return (n < 10 ? '0' : '') + n;
}
/***************************************************************************/
//
// Utilities
//
/***************************************************************************/
//
// event utils // event utils
//
function normalizeEvent(event) { function normalizeEvent(event) {
if (event.date) { if (event.date) {
event.start = event.date; event.start = event.date;
event.date = undefined; // can i do this? delete event.date;
} }
event.start = cleanDate(event.start); event.start = $.fullCalendar.parseDate(event.start);
event._start = cloneDate(event.start); event._start = cloneDate(event.start);
event.end = cleanDate(event.end); event.end = $.fullCalendar.parseDate(event.end);
if (!event.end) event.end = addDays(cloneDate(event.start), 1); if (!event.end) event.end = addDays(cloneDate(event.start), 1);
return event; return event;
} }
@ -890,13 +1044,18 @@
} }
//
// string utils
//
function zeroPad(n) {
return (n < 10 ? '0' : '') + n;
}
//
// date utils // date utils
//
var monthNames = ['January','February','March','April','May','June','July','August','September','October','November','December'];
var monthAbbrevs = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
var dayNames = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
var dayAbbrevs = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
function addMonths(d, n, keepTime) { function addMonths(d, n, keepTime) {
d.setMonth(d.getMonth() + n); d.setMonth(d.getMonth() + n);
@ -922,15 +1081,73 @@
return new Date(+d); return new Date(+d);
} }
function cleanDate(d) {
if (typeof d == 'string')
return $.parseISO8601(d, true) || Date.parse(d) || new Date(parseInt(d));
if (typeof d == 'number')
return new Date(d * 1000);
return d;
}
$.parseISO8601 = function(s, ignoreTimezone) { //
// globally accessible date formatting & parsing
//
$.fullCalendar = {
monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
monthAbbrevs: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
dayAbbrevs: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
formatDate: function(d, format) {
var f = $.fullCalendar.dateFormatters;
var s = '';
for (var i=0; i<format.length; i++) {
var c = format.charAt(i);
if (f[c]) {
s += f[c](d);
}else{
s += c;
}
}
return s;
},
dateFormatters: {
'a': function(d) { return d.getHours() < 12 ? 'am' : 'pm' },
'A': function(d) { return d.getHours() < 12 ? 'AM' : 'PM' },
'x': function(d) { return d.getHours() < 12 ? 'a' : 'p' },
'X': function(d) { return d.getHours() < 12 ? 'A' : 'P' },
'g': function(d) { return d.getHours() % 12 || 12 },
'G': function(d) { return d.getHours() },
'h': function(d) { return zeroPad(d.getHours() %12 || 12) },
'H': function(d) { return zeroPad(d.getHours()) },
'i': function(d) { return zeroPad(d.getMinutes()) },
'F': function(d) { return $.fullCalendar.monthNames[d.getMonth()] },
'm': function(d) { return zeroPad(d.getMonth() + 1) },
'M': function(d) { return $.fullCalendar.monthAbbrevs[d.getMonth()] },
'n': function(d) { return d.getMonth() + 1 },
'Y': function(d) { return d.getFullYear() },
'y': function(d) { return (d.getFullYear()+'').substring(2) },
'c': function(d) {
// ISO8601. derived from http://delete.me.uk/2005/03/iso8601.html
return d.getUTCFullYear() +
"-" + zeroPad(d.getUTCMonth() + 1) +
"-" + zeroPad(d.getUTCDate()) +
"T" + zeroPad(d.getUTCHours()) +
":" + zeroPad(d.getUTCMinutes()) +
":" + zeroPad(d.getUTCSeconds()) +
"Z";
}
},
parseDate: function(s) {
if (typeof s == 'object')
return s; // already a Date object
if (typeof s == 'undefined')
return null;
if (typeof s == 'number')
return new Date(s * 1000);
return $.fullCalendar.parseISO8601(s, true) ||
Date.parse(s) ||
new Date(parseInt(s) * 1000);
},
parseISO8601: function(s, ignoreTimezone) {
// derived from http://delete.me.uk/2005/03/iso8601.html // derived from http://delete.me.uk/2005/03/iso8601.html
var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" + var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
"(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" + "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" +
@ -953,17 +1170,10 @@
offset -= date.getTimezoneOffset(); offset -= date.getTimezoneOffset();
} }
return new Date(Number(date) + (offset * 60 * 1000)); return new Date(Number(date) + (offset * 60 * 1000));
}
}; };
$.ISO8601String = function(date) { // additional FullCalendar "extensions" should be attached to $.fullCalendar
// derived from http://delete.me.uk/2005/03/iso8601.html
return date.getUTCFullYear() +
"-" + zeroPad(date.getUTCMonth() + 1) +
"-" + zeroPad(date.getUTCDate()) +
"T" + zeroPad(date.getUTCHours()) +
":" + zeroPad(date.getUTCMinutes()) +
":" + zeroPad(date.getUTCSeconds()) +
"Z";
};
})(jQuery); })(jQuery);

41
gcal.js
View file

@ -21,23 +21,17 @@
(function($) { (function($) {
$.fn.gcalFullCalendar = function(options) { $.fullCalendar.gcalFeed = function(feedUrl, options) {
var feedURL; feedUrl = feedUrl.replace(/\/basic$/, '/full');
if (options && typeof options.events == 'string') { options = options || {};
feedURL = options.events; var draggable = options.draggable || false;
}
else return this.fullCalendar(options);
feedURL = feedURL.replace(/\/basic$/, '/full'); return function(start, end, callback) {
$.getJSON(feedUrl + "?alt=json-in-script&callback=?",
$.extend(options, {
events: function(start, end, callback) {
$.getJSON(feedURL + "?alt=json-in-script&callback=?",
{ {
'start-min': $.ISO8601String(start), 'start-min': $.fullCalendar.formatDate(start, 'c'),
'start-max': $.ISO8601String(end), 'start-max': $.fullCalendar.formatDate(end, 'c'),
'singleevents': true 'singleevents': true
}, },
function(data) { function(data) {
@ -48,29 +42,24 @@
$.each(entry['link'], function(j, link) { $.each(entry['link'], function(j, link) {
if (link.type == 'text/html') url = link.href; if (link.type == 'text/html') url = link.href;
}); });
var showTime = entry['gd$when'][0]['startTime'].indexOf('T') != -1;
events.push({ events.push({
id: entry['gCal$uid']['value'], id: entry['gCal$uid']['value'],
url: url, url: url,
title: entry['title']['$t'], title: entry['title']['$t'],
start: $.parseISO8601(entry['gd$when'][0]['startTime'], true), start: $.fullCalendar.parseDate(entry['gd$when'][0]['startTime']),
end: $.parseISO8601(entry['gd$when'][0]['endTime'], true), end: $.fullCalendar.parseDate(entry['gd$when'][0]['endTime']),
location: entry['gd$where'][0]['valueString'], location: entry['gd$where'][0]['valueString'],
description: entry['content']['$t'], description: entry['content']['$t'],
allDay: entry['gd$when'][0]['startTime'].indexOf('T') == -1, showTime: showTime,
draggable: false className: [showTime ? 'nobg' : null, options.className],
draggable: draggable
}); });
}); });
callback(events); callback(events);
}); });
},
eventRender: function(event, element) {
if (!event.allDay) element.addClass('nobg');
} }
}); }
return this.fullCalendar(options);
};
})(jQuery); })(jQuery);