I was on a project recently where the team was faced with a daunting list of scheduling-related backlog stories. When looking at the entire feature, about 20% of these stories dealt with custom functionality and 80% of them dealt with issues that are common to any scheduling or calendar application: start times, end times, visibility/privacy, conflicts, notifications, etc etc. When I asked for more specific requirements on these stories, the answer was – either directly or indirectly – along the lines of “we want it to work just like Google (or Outlook) calendar.” That made sense, because those calendars work the way you would expect any calendar to work.
If code can “smell,” then so can user stories. Like any developer who has ever been asked to build something that works exactly like Facebook, I would prefer to invent something new and useful, not something unoriginal and redundant. These stories smelled a little like wheel reinvention to me, so I proposed integrating with an existing calendar system instead of writing one from scratch. After all, if you want it to work just like Google calendar, why don’t you just, ya know, use Google calendar?
Plus, as everyone now knows from Part 1 of this post, Someday is a Lie, I am a through-and-through die-hard calendar superfreak.
In general, the challenge in this situation is to show that integrating with an existing system will save everyone a lot of time (aka money) and effort (aka money). And by “everyone,” I mean users too. Most of your users will already know how to use Facebook, or Gmail or whatever. It’s often a win-win-win. Of course, there is a risk that your application can break if the API or general functionality of the external system changes. To mitigate this risk, a thorough layer of automated tests should guarantee the contracts between the two.
The Proof of Concept
I focused on one story where, given a scheduled event (in Google calendar), the user should be able to see a list of event attendees (the roster) and mark them as present. It should be noted that Google calendar already has the concept of “guests” – email addresses of people who have been invited to the event and their respective responses (whether they intend to attend or not).
But this is a little different – the event roster and attendance records are maintained by a custom REST web service. Only someone who is authorized (the class instructor or facility employee) should be able to check in roster members. This could be a gym employee checking in members of a kickboxing class, or a craft store instructor checking in members of a quilting class. Maybe the gym member’s prepaid sessions are decremented, or maybe the craft class attendance stats are collected so the owner can determine how to adjust the class offerings. It doesn’t really matter, but let’s just say that it’s the 20% custom functionality part.
The Google Calendar API
Google Calendar has been around since 2006. Actually, 2009 if you don’t count three years of beta (which proves that you can easily spend that long working out the kinks of a calendar application). At the time of this blog’s writing, the Google Calendar API is on its third version. V1 and V2 were deprecated for three years before being end-of-life’d a week ago. Three years is a pretty generous deprecation period, so if this is news to you, you should probably get out more.
- It is well-documented. General samples are provided for each language, and each API call has a “Try it!” section where you can call into your own Google calendar with authentication on or off (if the call allows it). A good place to start is GET calendars/calendarId/events, noting that your default calendarId is your Gmail address.
- I didn’t want to focus on authentication/authorization. That, too, is well-documented and standard, as it [only] supports OAuth 2.0. I could probably write a whole article on just the OAuth part. Others already have. The proof in my proof-of-concept wasn’t about calendar access – I trust that it will “just work.” It assumes that users have authenticated to both the Google calendar API as well as my own custom one (ideally, seamlessly), and that they have created events on calendars they have access to.
- It was too heavy for my user story. The POC is about the guy who just needs to check people in to an event. Maybe he doesn’t even have the ability to create events. Maybe he doesn’t even have access to the full app (which would be a shame, because it would use all kinds of cool Google calendar API calls and probably blow his mind). Maybe he’s using Windows XP on a crusty old Dell laptop. Poor guy.
Go Go Gadget Calendar!
Calendar gadgets come in two flavors: event gadgets and sidebar gadgets. Basically, event gadgets sit on the events of the calendar itself (sometimes with a little icon or whatever), while sidebar gadgets… run in the sidebar (go figure). Event gadgets seem appropriate for push-only situations (think weather or stocks) but sidebar gadgets are actually iframes that can serve up anything you want.
The POC Architecture & Code
Remember that this is a proof-of-concept after all. I know this flow is a little weird, but it was a quick and dirty solution given the tools I had readily available.
All source for this project is publicly available at https://github.com/in-the-keyhole/khs-google-calendar-gadget. If you’re considering running it yourself, note that there are several files containing the path “http://yoursite.com” that will obviously need to be replaced with your host location. You might want to do that right off the bat so it doesn’t bite you later.
eventGadget.xml houses the HTML and is able to use Angular directives like it would normally. Since the code is written in a reusable way, things that are specific to the “skin” – css and image files – are hosted in a subdirectory. Since I chose a random “craft store class” theme, I called it “craft.”
subscribeEventCallback method is fired whenever a user opens a calendar event. At that point, the app is hardcoded to load event ID 7203, but you can change the back end to have matching event ID’s – just inspect the network traffic to find the real ones. I also logged the event ID to the console (from
subscribeEventCallback) for your convenience.
Docker is not required for this project, but I was looking for an excuse to play with it. My instructions for deploying the WAR to the webapps folder using a volume are the result of an experiment and may not be the recommended approach for a properly “dockerized” Java web service. If you’re using Linux and you’re interested in docker, go for it. If you’re using Windows, I suggest that you save your docker adventure for another day.
Also note that there is no database in the diagram. Attendee records live in memory and will be lost as soon as you stop and start this web service, tomcat or the docker container it may be running in. Persistence was clearly not the focus here.
The documentation and samples are not-so-helpful. Unlike the bulk of the Calendar API, there’s less to go on when you’re in gadget land. The samples seemed hodgepodged together: Some of them have minified source or relative URLs that don’t work. Some use “gadgeturl?https://www.google.com” and some use “gadgeturl?https://developers.google.com”, which is kind of confusing and probably wrong.
Gadgets must be publicly available on the interwebs. Not your local web server, not your company’s intranet. The INTERNET, folks. Google is loading your gadget, so it needs to be able to access it. This might be a show-stopper if you’re working on a top-secret app for the CIA or Coca-Cola.
Gadgets are cache-happy. When you want to make changes to your gadget, you’ll need to redeploy them to your hosting site. Then you’ll want to refresh the gadget to see those changes, right? Not so fast! You can’t just hit the refresh button. That would be silly. Gadgets are normally cached for 1 hour, so you have to invalidate the gadget cache. You can accomplish this by either adding a random query parameter to the end of your gadget URL or using the Limited Invalidation API. That seemed like a lot of work for a POC, so I went with the former.
Develop it locally. Because of the public internet and caching issues I just mentioned, it is so much easier to develop the front-end piece locally, then wait until you’re almost ready to go live before you attempt to turn it into a gadget. All of that uploading and cache-invalidating gets tiresome after a while, and 95% of your time will probably be focused on non-gadget, custom front-end functionality anyway.
Understand and handle CORS. Cross-origin resource sharing (or the lack thereof) will likely be an obstacle for you at some point. Google calendar can find your XML file and the files it references, and there are generally no issues as long as those are all on the same server. GET calls to another server though, or anything that posts data (POST, PUT, etc) will puke if a legit Access-Control-Allow-Origin header isn’t returned from the preflight request to the server.
- On the server: Since I used a Java Jersey web service, I had to do CORS its way. Adding Access-Control-Allow-Origin=”*” to the response directly in the method implementation only worked for the GET requests, not for the PUT. I ended up having to use a filter, probably because filters (and interceptors) are executed at a different point in the response phase.
- On the client: The front end doesn’t usually have to worry about CORS, but it does if it tries to do something fancy like take advantage of @font-face in CSS3 and load its own font file. I struggled with this until I finally found this post at 2 am. Unfortunately for me, I wasn’t using AWS but rather a more restricted server. I gave up on loading my own font file and decided to use the Google Fonts API instead. Of course, Google calendar knows and trusts Google fonts. I didn’t really want to use my own font anyway.
HTTPS -> HTTP is a no go. As I mentioned, I hosted my gadget files on a somewhat restricted server. It didn’t have an SSL cert installed, and I wasn’t in a position to buy and install one myself. The browser freaks out (you’ll see logs in the dev console) when the gadget, which is loaded from the calendar via HTTPS, tries to load something (say, your .js file) via HTTP. I used Chrome, where you can either pass “–allow-running-insecure-content” on startup or you can click and configure the little shield icon in the address bar.
Paths must be absolute. It might be because the gadget container is in charge of loading things, but it didn’t seem to like
<script src="/whatever.js">. It wanted
<script src="http://yoursite.com/whatever.js"> instead. This could also be a side effect of the aforementioned HTTPS issue.
The API calls available to a Google calendar gadget are mutually exclusive. There’s google.calendar, google.calendar.utils, and google.calendar.read. Based on the names, I initially assumed that the first one included the other two. I was wrong.
Did I run into obstacles in this project? Definitely. Would I develop another Google calendar gadget if I had the choice? I’m not sure… maybe. Any technology has its appropriate time and place, and I think it makes sense for this use case. In hindsight, most of my struggles were in the deployment of the gadget, not in the development of it. Some of these could be easily remedied with a proper environment setup. Plus, lessons are always learned the hard way the first time around.
Calendar gadgets are just normal web code, and the idea that you can use Angular or jQuery or whatever you want is kind of cool! I’m way impressed by the calendar API as a whole, and this is just one way to interact with it. Knowing the possibilities and limitations of any technology can help you make more informed decisions about whether it is a viable solution to the problems at hand. I am an advocate for “solving the right problems,” so make sure that’s what you’re doing when you work on a date/time-focused application. Or for that matter, any application.