almost at 1.2

v1.2.x
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
- multiple event sources (using an array for the 'events' option)
- 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)
- Added the following options:

View File

@ -188,3 +188,6 @@ latex_documents = [
# If false, no module index is generated.
#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
$('#myCalendar').fullCalendar({
// initializes a calendar
// see options, data provider, and triggered events below
});
$('#calendar').fullCalendar({
$('#myCalendar').fullCalendar('nextMonth'); // move ahead one month
// put your options here
$('#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
The month that will be displayed when the calendar first loads.
@ -35,18 +25,11 @@ Options
Determines if all events on the calendar can be dragged & dropped. If
``true``, requires `jQuery UI <http://jqueryui.com/>`_ core and draggables.
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``
If ``true``, the calendar will always be 6 weeks tall. If ``false``, the
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``
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
top of the calendar.
**titleFormat**: string, default:``"Y F"``
A string defining format of the title above the calendar. The default
"Y F" creates strings such as "January 2009". Use the following
codes in your format string (similar to the PHP's date function):
* **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
**titleFormat**: string, default:``"F Y"``
A string defining the format of the title above the calendar. The default
"F Y" creates strings such as "January 2009". Consult the
:ref:`$.fullCalendar.formatDate <formatDate>` documentation for a full
list of commands.
**buttons**: boolean/hash, default:``true``
Determines whether navigation buttons will be displayed at the top of the
calendar. A hash with keys 'today', 'prev', and 'next' will determine if
each individual button is displayed. Strings can be provided to change
each button's text.
calendar. A hash such as ``{today:false, prev:true, next:true}`` can be
provided to display only certain buttons. A hash such as
``{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"``
Determines if times will be displayed before each event's title.
``"guess"`` displays times only for events with non-zero start or end times.
**timeFormat**: string, default: ``"gx"``
A string defining the format of dislayed of event times. The default "gx"
creates a string such as "9a". Use the following codes in your format
string (similar to PHP's date function):
* **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)
A string defining the format of dislayed event times. The default "gx"
creates a string such as "9a". Consult the
:ref:`$.fullCalendar.formatDate <formatDate>`
documentation for a full list of commands.
**eventDragOpacity**: float
The opacity of an event element while it is being dragged (0.0 - 1.0)
@ -99,21 +68,91 @@ Options
Controls the duration (in milliseconds) of the animation of an event
snapping back into place.
.. _EventDataProvider:
.. _TriggeredActions:
Event Data Provider
===================
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
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.
Or, a URL can be provided. This URL should return JSON for an array of
:ref:`CalEvent`. GET parameters, determined by the ``startParam`` and
``endParam`` options, will be inserted into the URL. These parameters
indicate the UNIX timestamp of the start of the first visible day
(inclusive) and the end of the last visible day (exclusive).
:ref:`CalEvents <CalEvents>`. GET parameters, determined by the
``startParam`` and ``endParam`` options, will be inserted into the URL.
These parameters indicate the UNIX timestamp of the start of the first
visible day (inclusive) and the end of the last visible day (exclusive).
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``
@ -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"``
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
of this GET parameter will be a UNIX timestamp denoting the start of the
first visible day (inclusive).
events from a JSON script. The value of this GET parameter will be a UNIX
timestamp denoting the start of the first visible day (inclusive).
**endParam**: string, default:``"end"``
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
of this GET parameter will be a UNIX timestamp denoting the end of the
last visible day (exclusive).
events from a JSON script. The value of this GET parameter will be a UNIX
timestamp denoting the end of the last visible day (exclusive).
**cacheParam**: string, default:``"_"``
When using a JSON url, a parameter of this name will
automatically be inserted into the URL to prevent the browser from
caching the response. The value will be the current millisecond time.
.. _TriggeredEvents:
Triggered Events
================
The following methods can be called on a FullCalendar that has already
been initialized:
**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")
**.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.
**.fullCalendar(** ``'removeEventSource'``, **source)**
Remove an event source. ``source`` must be the original array/string/function.
**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
================
A ``CalEvent`` is a data structure that frequents FullCalendar's API. It is
used when a custom event-fetcher needs to report to the :ref:`EventDataProvider`.
It is also used in various :ref:`TriggeredEvents`. Here are the properties of a
``CalEvent``\:
A CalEvent is a data structure that frequents FullCalendar's API. It is the
standardized currency used in :ref:`EventSources`. It is also passed to various
:ref:`Triggered Actions <TriggeredActions>`. Here are the properties of a
CalEvent:
**id**: integer/string,
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.
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"),
a string in ISO8601 format (ex: "2009-11-05T13:15:30Z") or an integer
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
(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.
(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)
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)
Overrides the master ``showTime`` property for this single event.
When giving events to the :ref:`EventDataProvider`, one can include other
properties beyond the ones listed. This is useful if you want to earmark your
events with additional data to be retrieved later during a
:ref:`Triggered Event <TriggeredEvents>`.
**className**: string/array (optional)
A CSS class (or array of classes) that will be attached to this event's
element.
**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.
Navigation Methods
==================
Extras
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:
**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)
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.
**$.parseISO8601(string, ignoreTimezone)**
Parses an ISO8601 string and returns a ``Date`` object
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)
**$.ISO8601String(date)**
Takes a ``Date`` object and returns an ISO8601 string
**$.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() ?>
<? begin_content() ?>
<div id='toc'>
<h1>Table of Contents</h1>
{{ toc }}
</div>
<div class='clear'></div>
{% block body %}{% endblock %}
<? end_content() ?>

View File

@ -30,9 +30,12 @@
$(document).ready(function() {
$('#calendar').gcalFullCalendar({
$('#calendar').fullCalendar({
// 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) {
window.open(event.url, 'gcalevent', 'width=700,height=600');
return false;

View File

@ -17,19 +17,30 @@
(function($) {
$.fn.fullCalendar = function(options) {
//
// Calls methods of a pre-existing instance
//
if (typeof options == 'string') {
var args = Array.prototype.slice.call(arguments, 1);
var ret; // ugly
var res;
this.each(function() {
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')
return this;
return ret;
if (typeof res != 'undefined')
return res;
return this;
}
//
// Process options
//
options = options || {};
var r2l = options.rightToLeft;
@ -48,77 +59,94 @@
var weekStart = (options.weekStart || 0) % 7;
var timeFormat = options.timeFormat || 'gx';
var titleFormat = options.titleFormat || (r2l ? 'Y F' : 'F Y');
//
// Rendering bug detection variables
//
var tdTopBug, trTopBug, tbodyTopBug, sniffBugs = true;
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 events = [];
var eventSources = options.eventSources || [];
if (options.events) eventSources.push(options.events);
var eventSources;
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 = [];
//
// Month navigation functions
//
function updateMonth() {
clearEvents();
function refreshMonth() {
clearEventElements();
render();
}
function today() {
date = new Date();
updateMonth();
}
function prevMonth() {
addMonths(date, -1);
updateMonth();
refreshMonth();
}
function nextMonth() {
addMonths(date, 1);
updateMonth();
refreshMonth();
}
function gotoToday() {
date = new Date();
refreshMonth();
}
function gotoMonth(year, month) {
date = new Date(year, month, 1);
updateMonth();
refreshMonth();
}
//
// Publicly accessible methods
//
$.data(this, 'fullCalendar', {
today: today,
refresh: refreshMonth,
prevMonth: prevMonth,
nextMonth: nextMonth,
today: gotoToday,
gotoMonth: gotoMonth,
refresh: updateMonth,
// event crud
//
// Event CRUD
//
addEvent: function(event) {
events.push(normalizeEvent(event));
clearEvents();
clearEventElements();
renderEvents();
},
updateEvent: function(event) {
event.start = cleanDate(event.start);
event.end = cleanDate(event.end);
event.start = $.fullCalendar.parseDate(event.start);
event.end = $.fullCalendar.parseDate(event.end);
var startDelta = event.start - event._start;
var msLength = event.end - event.start;
event._start = cloneDate(event.start);
@ -126,8 +154,8 @@
var e = events[i];
if (e.id === event.id && e !== event) {
e.start = new Date(e.start.getTime() + startDelta);
e.end = new Date(e.start.getTime() + msLength);
e._start = cloneDate(e.start);
e.end = new Date(e.start.getTime() + msLength);
for (var k in event) {
if (k && k != 'start' && k != 'end' && k.charAt(0) != '_') {
e[k] = event[k];
@ -135,14 +163,16 @@
}
}
}
clearEvents();
clearEventElements();
renderEvents();
},
removeEvent: function(eventId) {
if (typeof eventId == 'object') {
eventId = eventId.id;
}
// remove from the 'events' array
var newEvents = [];
for (var i=0; i<events.length; i++) {
if (events[i].id !== eventId) {
@ -150,6 +180,7 @@
}
}
events = newEvents;
// remove from static event sources
for (var i=0; i<eventSources.length; i++) {
var src = eventSources[i];
@ -163,10 +194,11 @@
eventSources[i] = newSrc;
}
}
clearEvents();
clearEventElements();
renderEvents();
},
getEventsById: function(eventId) {
var res = [];
for (var i=0; i<events.length; i++) {
@ -177,15 +209,23 @@
return res;
},
// event source crud
//
// Event Source CRUD
//
addEventSource: function(src) {
eventSources.push(src);
clearEvents();
renderEvents();
fetchEventSource(src, function() {
clearEventElements();
renderEvents();
});
},
removeEventSource: function(src) {
// remove from 'eventSources' array
var newSources = [];
for (var i=0; i<eventSources.length; i++) {
if (src !== eventSources[i]) {
@ -193,7 +233,17 @@
}
}
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();
}
@ -203,16 +253,26 @@
/*******************************************************************/
//
// Header & Table Rendering
//
/*******************************************************************/
//
// Build one-time DOM elements (header, month container)
//
var titleElement, todayButton, monthElement, monthElementWidth;
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 prevButton, nextButton;
if (bo == true || bo.today != false) {
todayButton = $("<input type='button' class='full-calendar-today' value='today'/>")
.click(today);
.click(gotoToday);
if (typeof bo.today == 'string') todayButton.val(bo.today);
buttons.append(todayButton);
}
@ -237,14 +297,13 @@
monthElement = $("<div class='full-calendar-month' style='position:relative'/>")
.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, monthTitle;
var thead, tbody, glass;
function render() {
@ -253,7 +312,7 @@
clearTime(date);
var year = date.getFullYear();
var month = date.getMonth();
monthTitle = formatTitle(date);
var monthTitle = $.fullCalendar.formatDate(date, titleFormat);
if (titleElement) titleElement.text(monthTitle);
clearTime(date);
@ -276,9 +335,14 @@
todayButton.css('visibility', 'visible');
}
}
var dayNames = $.fullCalendar.dayNames;
var dayAbbrevs = $.fullCalendar.dayAbbrevs;
if (!tbody) {
// first time, build all cells from scratch
var table = $("<table style='width:100%'/>").appendTo(monthElement);
thead = "<thead><tr>";
@ -313,6 +377,8 @@
}
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%' />")
.appendTo(monthElement)
.click(function(ev, ui) {
@ -324,9 +390,12 @@
});
}else{
// NOT first time, reuse as many cells as possible
var diff = numWeeks - tbody.find('tr').length;
if (diff < 0) {
// remove extra rows
tbody.find('tr:gt(' + (numWeeks-1) + ')').remove();
}
else if (diff > 0) {
@ -347,6 +416,7 @@
if (trs) tbody.append(trs);
}
// re-label and re-class existing cells
var d = cloneDate(start);
tbody.find('tr').each(function() {
for (var i=0; i<7; i++) {
@ -368,9 +438,11 @@
}
resizeTable();
setCellSizes();
if (sniffBugs) {
// nasty bugs in opera 9.25
// position() returning relative to direct parent
var tr = tbody.find('tr');
var td = tr.find('td');
var trTop = tr.position().top;
@ -381,37 +453,7 @@
sniffBugs = false;
}
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);
}
}
fetchEvents(renderEvents);
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 = [];
function renderEvents() {
@ -493,7 +563,9 @@
}
//
// Do the REAL rendering of the segments
//
var eventElements = []; // [[event, element], ...]
@ -535,7 +607,14 @@
roundE = seg.isEnd;
}
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>" +
(roundW ? "<td class='nw'/>" : '') +
"<td class='n'/>" +
@ -549,7 +628,6 @@
"<td class='s'/>" +
(roundE ? "<td class='se'/>" : '') + "</tr>");
buildEventText(event, element.find('td.c'));
if (event.cssClass) element.addClass(event.cssClass);
if (options.eventRender) {
var res = options.eventRender(event, element);
if (typeof res != 'undefined') {
@ -576,9 +654,33 @@
innerDiv.height(height);
}
}
//
// 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) {
element.click(function(ev) {
@ -607,10 +709,35 @@
}
eventElements.push([event, element]);
}
//
// 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 dayOverlay;
@ -658,7 +785,7 @@
}
if (options.eventDrop)
options.eventDrop.call(this, event, delta, ev, ui);
clearEvents();
clearEventElements();
renderEvents();
}
dayOverlay.hide();
@ -667,6 +794,11 @@
}
});
}
//
// Called DURING dragging, on every mouse move
//
function eventDrag(node, ev, ui) {
var oldTD = dragTD;
@ -690,9 +822,9 @@
}
//
// Record positions & dimensions of each TD
//
var dayX, dayY, dayX0, dayY0;
var currTD, currR, currC;
@ -717,6 +849,10 @@
dayX.push(dayX[dayX.length-1] + td.width());
currTD = null;
}
//
// Returns TD underneath coordinate (optimized)
//
function dayTD(x, y) {
var r=-1, c=-1;
@ -737,115 +873,128 @@
}
return currTD;
}
//
// Get a TD's date
//
function dayDate(node) {
function dayDate(td) {
var i, tds = tbody.get(0).getElementsByTagName('td');
for (i=0; i<tds.length; i++) {
if (tds[i] == node) break;
if (tds[i] == td) break;
}
var d = cloneDate(start);
return addDays(d, i);
}
//
// Return the # of days between 2 TD's
//
function dayDelta(node1, node2) {
function dayDelta(td1, td2) {
var i1, i2, trs = tbody.get(0).getElementsByTagName('tr');
for (var i=0; i<trs.length; i++) {
var tr = trs[i];
for (var j=0; j<7; j++) {
var td = tr.childNodes[j];
if (td == node1) i1 = i*7 + j*dis + dit;
if (td == node2) i2 = i*7 + j*dis + dit;
if (td == td1) i1 = i*7 + j*dis + dit;
if (td == td2) i2 = i*7 + j*dis + dit;
}
}
return i2 - i1;
}
function resizeTable() {
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();
}
function clearEvents() {
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;
/*******************************************************************/
//
// Event Sources & Fetching
//
/*******************************************************************/
//
// 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) {
callback(events);
}
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 = '';
for (var i=0; i<titleFormat.length; i++) {
var c = titleFormat.charAt(i);
if (c == 'F') s += monthNames[m];
else if (c == 'm') s += zeroPad(m);
else if (c == 'M') s += monthAbbrevs[m];
else if (c == 'n') s += m;
else if (c == 'Y') s += d.getFullYear();
else if (c == 'y') s += (d.getFullYear()+'').substring(2);
else s += c;
//
// Fetch from a particular source. Append to the 'events' array
//
function fetchEventSource(src, callback) {
var reportEvents = function(a) {
for (var i=0; i<a.length; i++) {
normalizeEvent(a[i]);
a[i].source = src;
}
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 resizeID = 0;
$(window).resize(function() {
$(window).resize(function() { // re-render table on window resize
if (!ignoreResizes) {
var rid = ++resizeID;
var rid = ++resizeID; // add a delay
setTimeout(function() {
if (rid == resizeID) {
// if the month width changed
if (monthElement.width() != monthElementWidth) {
clearEvents();
resizeTable();
clearEventElements();
setCellSizes();
_renderEvents();
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
//
function normalizeEvent(event) {
if (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.end = cleanDate(event.end);
event.end = $.fullCalendar.parseDate(event.end);
if (!event.end) event.end = addDays(cloneDate(event.start), 1);
return event;
}
@ -890,13 +1044,18 @@
}
//
// string utils
//
function zeroPad(n) {
return (n < 10 ? '0' : '') + n;
}
//
// 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) {
d.setMonth(d.getMonth() + n);
@ -922,48 +1081,99 @@
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) {
// derived from http://delete.me.uk/2005/03/iso8601.html
var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
"(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" +
"(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
var d = s.match(new RegExp(regexp));
if (!d) return null;
var offset = 0;
var date = new Date(d[1], 0, 1);
if (d[3]) { date.setMonth(d[3] - 1); }
if (d[5]) { date.setDate(d[5]); }
if (d[7]) { date.setHours(d[7]); }
if (d[8]) { date.setMinutes(d[8]); }
if (d[10]) { date.setSeconds(d[10]); }
if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
if (!ignoreTimezone) {
if (d[14]) {
offset = (Number(d[16]) * 60) + Number(d[17]);
offset *= ((d[15] == '-') ? 1 : -1);
//
// 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;
}
}
offset -= date.getTimezoneOffset();
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
var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
"(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" +
"(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
var d = s.match(new RegExp(regexp));
if (!d) return null;
var offset = 0;
var date = new Date(d[1], 0, 1);
if (d[3]) { date.setMonth(d[3] - 1); }
if (d[5]) { date.setDate(d[5]); }
if (d[7]) { date.setHours(d[7]); }
if (d[8]) { date.setMinutes(d[8]); }
if (d[10]) { date.setSeconds(d[10]); }
if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
if (!ignoreTimezone) {
if (d[14]) {
offset = (Number(d[16]) * 60) + Number(d[17]);
offset *= ((d[15] == '-') ? 1 : -1);
}
offset -= date.getTimezoneOffset();
}
return new Date(Number(date) + (offset * 60 * 1000));
}
return new Date(Number(date) + (offset * 60 * 1000));
};
$.ISO8601String = function(date) {
// 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";
};
// additional FullCalendar "extensions" should be attached to $.fullCalendar
})(jQuery);

83
gcal.js
View File

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