gcal.js
5.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/*!
* FullCalendar v2.3.1 Google Calendar Plugin
* Docs & License: http://fullcalendar.io/
* (c) 2015 Adam Shaw
*/
(function(factory) {
if (typeof define === 'function' && define.amd) {
define([ 'jquery' ], factory);
}
else if (typeof exports === 'object') { // Node/CommonJS
module.exports = factory(require('jquery'));
}
else {
factory(jQuery);
}
})(function($) {
var API_BASE = 'https://www.googleapis.com/calendar/v3/calendars';
var fc = $.fullCalendar;
var applyAll = fc.applyAll;
fc.sourceNormalizers.push(function(sourceOptions) {
var googleCalendarId = sourceOptions.googleCalendarId;
var url = sourceOptions.url;
var match;
// if the Google Calendar ID hasn't been explicitly defined
if (!googleCalendarId && url) {
// detect if the ID was specified as a single string.
// will match calendars like "asdf1234@calendar.google.com" in addition to person email calendars.
if (/^[^\/]+@([^\/\.]+\.)*(google|googlemail|gmail)\.com$/.test(url)) {
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))
) {
googleCalendarId = decodeURIComponent(match[1]);
}
if (googleCalendarId) {
sourceOptions.googleCalendarId = googleCalendarId;
}
}
if (googleCalendarId) { // is this a Google Calendar?
// make each Google Calendar source uneditable by default
if (sourceOptions.editable == null) {
sourceOptions.editable = false;
}
// We want removeEventSource to work, but it won't know about the googleCalendarId primitive.
// Shoehorn it into the url, which will function as the unique primitive. Won't cause side effects.
// This hack is obsolete since 2.2.3, but keep it so this plugin file is compatible with old versions.
sourceOptions.url = googleCalendarId;
}
});
fc.sourceFetchers.push(function(sourceOptions, start, end, timezone) {
if (sourceOptions.googleCalendarId) {
return transformOptions(sourceOptions, start, end, timezone, this); // `this` is the calendar
}
});
function transformOptions(sourceOptions, start, end, timezone, calendar) {
var url = API_BASE + '/' + encodeURIComponent(sourceOptions.googleCalendarId) + '/events?callback=?'; // jsonp
var apiKey = sourceOptions.googleCalendarApiKey || calendar.options.googleCalendarApiKey;
var success = sourceOptions.success;
var data;
var timezoneArg; // populated when a specific timezone. escaped to Google's liking
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 googleCalendarApiKey. See http://fullcalendar.io/docs/google_calendar/");
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');
}
// when sending timezone names to Google, only accepts underscores, not spaces
if (timezone && timezone != 'local') {
timezoneArg = timezone.replace(' ', '_');
}
data = $.extend({}, sourceOptions.data || {}, {
key: apiKey,
timeMin: start.format(),
timeMax: end.format(),
timeZone: timezoneArg,
singleEvents: true,
maxResults: 9999
});
return $.extend({}, sourceOptions, {
googleCalendarId: null, // prevents source-normalizing from happening again
url: url,
data: data,
startParam: false, // `false` omits this parameter. we already included it above
endParam: false, // same
timezoneParam: false, // same
success: function(data) {
var events = [];
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) {
var url = entry.htmlLink;
// make the URLs for each event show times in the correct timezone
if (timezoneArg) {
url = injectQsComponent(url, 'ctz=' + timezoneArg);
}
events.push({
id: entry.id,
title: entry.summary,
start: entry.start.dateTime || entry.start.date, // try timed. will fall back to all-day
end: entry.end.dateTime || entry.end.date, // same
url: url,
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;
}
}
return events;
}
});
}
// Injects a string like "arg=value" into the querystring of a URL
function injectQsComponent(url, component) {
// inject it after the querystring but before the fragment
return url.replace(/(\?.*?)?(#|$)/, function(whole, qs, hash) {
return (qs ? qs + '&' : '?') + component + hash;
});
}
});