diff --git a/demos/gcal.html b/demos/gcal.html
index 3a968cf0d6..d9dc93b9cc 100644
--- a/demos/gcal.html
+++ b/demos/gcal.html
@@ -13,9 +13,14 @@
$(document).ready(function() {
$('#calendar').fullCalendar({
+
+ // THIS KEY WON'T WORK IN PRODUCTION!!!
+ // To make your own Google API key, follow the directions here:
+ // http://fullcalendar.io/docs/google_calendar/
+ googleCalendarApiKey: 'AIzaSyDcnW6WejpTOCffshGDDb4neIrXVUA1EAE',
// US Holidays
- events: 'https://www.googleapis.com/calendar/v3/calendars/usa__en@holiday.calendar.google.com/events?key=AIzaSyAjuKkq7EvbGztcj9eSAnIzqC1iFrpby8U',
+ events: 'usa__en@holiday.calendar.google.com',
eventClick: function(event) {
// opens events in a popup window
diff --git a/src/gcal/gcal.js b/src/gcal/gcal.js
index 0a6d55f079..bb0648f5dc 100644
--- a/src/gcal/gcal.js
+++ b/src/gcal/gcal.js
@@ -14,64 +14,128 @@
})(function($) {
+var API_BASE = 'https://www.googleapis.com/calendar/v3/calendars';
var fc = $.fullCalendar;
var applyAll = fc.applyAll;
fc.sourceNormalizers.push(function(sourceOptions) {
- if (sourceOptions.dataType == 'gcal' ||
- sourceOptions.dataType === undefined &&
- (sourceOptions.url || '').match(/^(http|https):\/\/www.googleapis.com\/calendar\/v3\/calendars/)) {
- sourceOptions.dataType = 'gcal';
- if (sourceOptions.editable === undefined) {
- sourceOptions.editable = false;
- }
+ var url = sourceOptions.url;
+ var match;
+
+ // if the Google Calendar ID hasn't been explicitly defined
+ if (!sourceOptions.googleCalendarId && url) {
+
+ // detect if the ID was specified as a single string
+ if ((match = /^[\w-]+@[\w-\.]+\.calendar\.google\.com$/.test(url))) {
+ sourceOptions.googleCalendarId = url;
+ }
+ // try to scrape it out of a V1 or V3 API feed URL
+ else if (
+ (match = /^https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/([^\/]*)/.exec(url)) ||
+ (match = /^https?:\/\/www.google.com\/calendar\/feeds\/([^\/]*)/.exec(url))
+ ) {
+ sourceOptions.googleCalendarId = decodeURIComponent(match[1]);
+ }
+ }
+
+ // make each google calendar source uneditable by default
+ if (sourceOptions.googleCalendarId) {
+ if (sourceOptions.editable == null) {
+ sourceOptions.editable = false;
}
+ }
});
fc.sourceFetchers.push(function(sourceOptions, start, end, timezone) {
- if (sourceOptions.dataType == 'gcal') {
- return transformOptions(sourceOptions, start, end, timezone);
+ if (sourceOptions.googleCalendarId) {
+ return transformOptions(sourceOptions, start, end, timezone, this); // `this` is the calendar
}
});
-function transformOptions(sourceOptions, start, end, timezone) {
-
+function transformOptions(sourceOptions, start, end, timezone, calendar) {
+ var url = API_BASE + '/' + encodeURI(sourceOptions.googleCalendarId) + '/events?callback=?'; // jsonp
+ var apiKey = sourceOptions.googleCalendarApiKey || calendar.options.googleCalendarApiKey;
var success = sourceOptions.success;
- var data = $.extend({}, sourceOptions.data || {}, {
- singleevents: true,
- 'max-results': 9999
+ var data;
+
+ function reportError(message, apiErrorObjs) {
+ var errorObjs = apiErrorObjs || [ { message: message } ]; // to be passed into error handlers
+ var consoleObj = window.console;
+ var consoleWarnFunc = consoleObj ? (consoleObj.warn || consoleObj.log) : null;
+
+ // call error handlers
+ (sourceOptions.googleCalendarError || $.noop).apply(calendar, errorObjs);
+ (calendar.options.googleCalendarError || $.noop).apply(calendar, errorObjs);
+
+ // print error to debug console
+ if (consoleWarnFunc) {
+ consoleWarnFunc.apply(consoleObj, [ message ].concat(apiErrorObjs || []));
+ }
+ }
+
+ if (!apiKey) {
+ reportError("Specify a Google Calendar API key (googleCalendarApiKey).");
+ return {}; // an empty source to use instead. won't fetch anything.
+ }
+
+ // The API expects an ISO8601 datetime with a time and timezone part.
+ // Since the calendar's timezone offset isn't always known, request the date in UTC and pad it by a day on each
+ // side, guaranteeing we will receive all events in the desired range, albeit a superset.
+ // .utc() will set a zone and give it a 00:00:00 time.
+ if (!start.hasZone()) {
+ start = start.clone().utc().add(-1, 'day');
+ }
+ if (!end.hasZone()) {
+ end = end.clone().utc().add(1, 'day');
+ }
+
+ data = $.extend({}, sourceOptions.data || {}, {
+ key: apiKey,
+ timeMin: start.format(),
+ timeMax: end.format(),
+ singleEvents: true,
+ maxResults: 9999
});
return $.extend({}, sourceOptions, {
- url: sourceOptions.url + '&callback=?',
- dataType: 'jsonp',
+ googleCalendarId: null, // prevents source-normalizing from happening again
+ url: url,
data: data,
- timezoneParam: 'ctz',
- startParam: 'start-min',
- endParam: 'start-max',
+ timezoneParam: 'timeZone',
+ startParam: false, // `false` omits this parameter. we already included it above
+ endParam: false, // same
success: function(data) {
var events = [];
- if (data.items) {
+ var successArgs;
+ var successRes;
+
+ if (data.error) {
+ reportError('Google Calendar API: ' + data.error.message, data.error.errors);
+ }
+ else if (data.items) {
$.each(data.items, function(i, entry) {
events.push({
id: entry.id,
title: entry.summary,
- start: entry.start.dateTime || entry.start.date,
- end: entry.end.dateTime || entry.end.date,
+ start: entry.start.dateTime || entry.start.date, // try timed. will fall back to all-day
+ end: entry.end.dateTime || entry.end.date, // same
url: entry.htmlLink,
location: entry.location,
description: entry.description
});
});
+
+ // call the success handler(s) and allow it to return a new events array
+ successArgs = [ events ].concat(Array.prototype.slice.call(arguments, 1)); // forward other jq args
+ successRes = applyAll(success, this, successArgs);
+ if ($.isArray(successRes)) {
+ return successRes;
+ }
}
- var args = [events].concat(Array.prototype.slice.call(arguments, 1));
- var res = applyAll(success, this, args);
- if ($.isArray(res)) {
- return res;
- }
+
return events;
}
});
@@ -81,7 +145,7 @@ function transformOptions(sourceOptions, start, end, timezone) {
// legacy
fc.gcalFeed = function(url, sourceOptions) {
- return $.extend({}, sourceOptions, { url: url, dataType: 'gcal' });
+ return $.extend({}, sourceOptions, { url: url });
};