Writing a Custom Widget for Google Calendars
The amount of users who rely on Google Calendars to organize their personal and professional lives is staggering. Seeing as most clients are comfortable and proficient with the technology, there is little reason to point them elsewhere when they ask for a custom widget to display upcoming events on their site.
In fact, the only trouble is that Google’s provided widget layouts are all – er, well they are all quite lame, and likely won’t match your current theme.
No worries! We can easily leverage Google’s calendar API and Javascript to create a fully customized Calendar widget showing the next N upcoming events in chronological order.
Let’s start by looking at the provided Google widgets..
Wow, that would look great in a Google webpage somewhere. But it probably doesn’t meet the layout you were looking for. So we’ll use some basic HTML and JS to manipulate an Atom feed from Google.
Getting the base URL for your Calendar
Getting the public URL for a calendar is easy.
- Click “settings” for the calendar in question
- click the XML icon in the Calendar Address section.

- Copy the URL.
Adding the necessary parameters to organize the feed
Alright, so we have a URL for an RSS feed. This RSS format has a few critical downsides.
- The dates are the date the event was created, not the date it will occur.
- By default, repeating events are listed as a single event
- The event name, summary and other details are all trapped in a single <content> element.
https://www.google.com/calendar/feeds/tkmmbaodloipo29vn3aor8idd4%40group.calendar.google.com/public/basic
https://www.google.com/calendar/feeds/tkmmbaodloipo29vn3aor8idd4@group.calendar.google.com/public/full
https://www.google.com/calendar/feeds/tkmmbaodloipo29vn3aor8idd4@group.calendar.google.com/public/full-noattendees
<entry> <id>...</id> <published>2011-08-08T14:16:34.000Z</published> <updated>2011-08-08T14:16:34.000Z</updated> <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'/> <title type='text'>Sangria & Tapas Tasting</title> <content type='text'> BLAH BLAH BLAH </content> <link rel='alternate' type='text/html' href='https://www.google.com/calendar/event?eid=cWcxa2U0ZnRhYnUwZWRpYzdnZTJzdXBwajggNDY1OWlmNW1yZGZ2a3Y0YWIzcGZqczY1Z2NAZw' title='alternate'/> <link rel='self' type='application/atom+xml' href='https://www.google.com/calendar/feeds/XXXXXX%40group.calendar.google.com/public/full-noattendees/qg1ke4ftabu0edic7ge2suppj8'/> <author><name>Calendar Name</name></author> <gd:comments> <gd:feedLink href='https://www.google.com/calendar/feeds/XXXXX%40group.calendar.google.com/public/full/qg1ke4ftabu0edic7ge2suppj8/comments'/> </gd:comments> <gd:eventStatus value='http://schemas.google.com/g/2005#event.confirmed'/> <gd:where valueString='Tasca Restaurant - 1612 Commonwealth Ave, Brighton, MA 02135 '/> <gd:when endTime='2011-08-15T21:00:00.000-04:00' startTime='2011-08-15T19:00:00.000-04:00'/> <gd:transparency value='http://schemas.google.com/g/2005#event.opaque'/> <gCal:sequence value='0'/> <gCal:uid value='qg1ke4ftabu0edic7ge2suppj8@google.com'/> </entry>
Parsing the Feed with JS and DOM
<h2>Upcomming Events</h2> <div id="status" style="display:none"></div> <div id="calendarFeed"></div>
Before we get into the JS I must credit a source I stumbled across while researching this. The objective is quite common, so it may not be the original source.
http://blog.csdn.net/runupwind/article/details/1655837
The JavaScript that parses the DOM and prints HTML
Just edit the backend URL here, unless you want to completely change the layout of things. You should be able to CSS alone to get the style you want.
Same Origin Policy applies, so you will need to call a backend script on the same domain that relays to Google.com
<!-- discovered on http://blog.csdn.net/runupwind/article/details/1655837 --> <script language="javascript" type="text/javascript"> <!-- // the max number of evewnts to show maxEvents = 7; var RSSRequestObject = false; // XMLHttpRequest Object var Backend = '/wp-content/uploads/2011/08/demo/backend.php'; // Backend call to same domain proxy (prevents 'is not allowed by Access-Control-Allow-Origin.') window.setInterval("update_timer()", 1200000); // update the data every 20 mins // DO NOT EDIT BELOW if (window.XMLHttpRequest) // try to create XMLHttpRequest RSSRequestObject = new XMLHttpRequest(); if (window.ActiveXObject) // if ActiveXObject use the Microsoft.XMLHTTP RSSRequestObject = new ActiveXObject("Microsoft.XMLHTTP"); /* * onreadystatechange function */ function ReqChange() { // If data received correctly if (RSSRequestObject.readyState==4) { // if data is valid if (RSSRequestObject.responseText.indexOf('invalid') == -1) { // Parsing Feeds var node = RSSRequestObject.responseXML.documentElement; // Get the calendar title var title = node.getElementsByTagName('title').item(0).firstChild.data; content = '<div class="channeltitle">'+title+'</div>'; // Browse events var items = node.getElementsByTagName('entry'); if (items.length == 0) { content += '<ul><li><div class=error>No events</div></li></ul>'; } else { content += '<ul>'; if(maxEvents > items.length) maxEvents = items.length; for (var n=0; n <= maxEvents-1; n++) { var itemTitle = items[n].getElementsByTagName('title').item(0).firstChild.data; // may have empty content if no event details were added try{ var Summary = items[n].getElementsByTagName('content').item(0).firstChild.data; }catch(e){ var Summary = ''; } var eventId=""; var baseUrl=items[n].getElementsByTagName('link').item(0).attributes.getNamedItem("href").value; //alert(calId); var itemLink = baseUrl; console.log(items[n].getElementsByTagName('when')); var roughStartDate=items[n].getElementsByTagName('when').item(0).attributes.getNamedItem("startTime").value; try { var mydate=new Date(roughStartDate); var readAs="" + (mydate.getMonth()+1) + "/" + mydate.getDate() + "/" + mydate.getFullYear(); var itemPubDate = '<span class="event-date">['+ readAs+']</span> '; } catch (e) { var itemPubDate = ''; } content += '<li>'+itemPubDate+'<a href="'+itemLink+'"><span class="event-summary">'+itemTitle+'</span></a></li>'; } content += '</ul>'; } // Display the result document.getElementById("calendarFeed").innerHTML = content; // Tell the reader the everything is done document.getElementById("status").innerHTML = "Done."; } else { // Tell the reader that there was error requesting data document.getElementById("status").innerHTML = "<div class=error>Error requesting data.<div>"; } HideShow('status'); } } /* * Main AJAX RSS reader request */ function RSSRequest() { // change the status to requesting data HideShow('status'); document.getElementById("status").innerHTML = "Requesting data ..."; // Prepare the request RSSRequestObject.open("GET", Backend , true); // Set the onreadystatechange function RSSRequestObject.onreadystatechange = ReqChange; // Send RSSRequestObject.send(null); } /* * Timer */ function update_timer() { RSSRequest(); } function HideShow(id){ var el = GetObject(id); if(el.style.display=="none") el.style.display=''; else el.style.display='none'; } function GetObject(id){ var el = document.getElementById(id); return(el); } RSSRequest(); //--></script>
Same-Origin who what?
Refer to wikipedia for the “what is same origin policy”
But to us it means that we can’t pull data from google and operate on it unless we call a script on the same host as our domain first. This script (PHP, ASP, python, whatever) has to pass the data to our front-end javascript.
My demo uses PHP, which is easy and readily available. But by no means the only approach.
<?php // Change this with your Google calendar feed $calendarURL = 'https://www.google.com/calendar/feeds/the-final-url-for-your-public-calendar'; // Nothing else to edit $feed = file_get_contents($calendarURL); header('Content-type: text/xml'); echo $feed; die(); ?>
Demo – First Attempt
Alright, so we have a decent XML feed with the info we need. We have a JS snippet to walk the DOM and pull out our info. And we are inserting the formatted HTML into our page.
Let’s have a look!
Yes, quite ugly without any styling, but there is a bigger issue, the dates are all wrong. No order, past and future.. no Good!
Customize the output
In order to get the future looking, chronological calendar we expect, we’ll need to tell Google Calendar to refine the results a bit..
Again, consulting the Google’s API documentation we learn of 4 critical modifiers:
- futureevents=true
- singleevents=true
- orderby=starttime
- sortorder=ascending
This will tell Google Calendar we only want future events, sorted by the start time, ascending. We also ask that recurring events be specified individually, and not as one event.
https://www.google.com/calendar/feeds/tkmmbaodloipo29vn3aor8idd4%40group.calendar.google.com/public/full-noattendees?futureevents=true&singleevents=true&orderby=starttime&sortorder=ascending
Demo 2
Styling it up
The choir is just to add the appropriate CSS styling to format the feed as you wish. I’m no designer, and your site is much different then mine. SO I issue a challenge to some of my CSS savvy readers.
Submit your best styling for this widget and I will include the winner’s css for everyone to awe over
C’mon! I wanna see rounded corners, pretty gradients, clear divisions and nice eye candy.


As I said, I am no designer, but here’s my quick and dirty attempt:
And the result..
http://edwardawebb.com/wp-content/uploads/2011/08/demo/demo_styled.html
Truth. But there is enough here I can run with, thanks!
Great article. I am really no scripting whiz here, and have been looking for something for awhile now. I guess my question would be, is there a reason it isn’t pulling the calendar feed data? On my webpage, and even in the example in the article above it just continues to state ‘requesting data…’
Hmm, something must be wrong on your side Nick. I do in fact see the calendar above and have used this code on a few sites delivered and accepted by clients.
Be sure you are calling the back-end on your own server that in turn calls Google. For security reasons you cannot use Javascript (or any client side code) to render content from a non-origin domain.
http://en.wikipedia.org/wiki/Same_origin_policy
I’ve got the same issue as Nick. Given that I’m working on a locally hosted html test page which links need to go in the two “var Backend” sections in the JS. Obviously the links in the JS up the page don’t work and need modifying in some way to allow this to work correctly?
@Bob, @Nick – My apologies.
I told Nick to use the same domain and realized I did not explain the impact of the Same Origin Policy in my original post at all. And worse yet I had the google URL in my JavaScript example !
I have updated my post above to explain the impact, and how to use a backend script on the same domain to work around the issue.
Your Users see the JS and CSS which operates on what –> your server requests from –> a google server. ( each –> represents a distinct HTTP requests,, 2 total)
Please let me know if adding a solution like the php snippet above does not address your issues.
(but of course troubleshoot in steps: paste google url into a browser, verify results ; place backend script URL in browser, should be identical ; layer JS solution and verify modifications)
Outstanding, it is much more useful then add a whole calendar because of simple agenda widget if Customer uses google cal!
Thank you! Will continue to browse your posts
I am having the same problem Nick and Bob were having. I followed your directions to make a separate php file and pasted my google url, but it just says requesting data. I don’t see any events calendar in your demo either (just a title in a box), so I don’t quite understand how this is supposed to work.
@Lauren, have a look at this page directly, and let me know if you still see issues,
http://edwardawebb.com/wp-content/uploads/2011/08/demo/demo2.html
An excellent writeup. Bookmarked for a later attempt.. thanks a lot!
I don’t understand where the php script goes and how it is used. Could you help me with that?
I got the php to work now I want to add start time to end time of the event and also the event location. What do I need to add to the javascript?
Glad to hear @Clark.
The JS script already pulls the start time with this line, var roughStartDate=items[n].getElementsByTagName(‘when’).item(0).attributes.getNamedItem(“startTime”).value
Add another to pull “endTime” and then format the contained string to pull out just the time and present how you’d like.