Skip to content
Share this..

Writing a Custom Widget for Google Calendars

2011 August 14
by Eddie

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.

google calendar final1 Writing a Custom Widget for Google Calendars

Google Calendars are easy and everywhere, with a robust API we can leverage

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..

default calendar 300x224 Writing a Custom Widget for Google Calendars

Default "Agenda" layout for Google Calendar

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.

  1. Click “settings” for the calendar in question
  2. click the XML icon in the Calendar Address section.
    publicURL 150x150 Writing a Custom Widget for Google Calendars
  3. 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.
But by exploring Google’s API documentation we learn of a better format, their custom Atom feed!
So here is the provided URL from the last section:
https://www.google.com/calendar/feeds/tkmmbaodloipo29vn3aor8idd4%40group.calendar.google.com/public/basic
We need to modify the “basic” projection, and replace it with “full”:
https://www.google.com/calendar/feeds/tkmmbaodloipo29vn3aor8idd4@group.calendar.google.com/public/full
But since we don’t care about attendees in this case we shrink the payload a little bit:
https://www.google.com/calendar/feeds/tkmmbaodloipo29vn3aor8idd4@group.calendar.google.com/public/full-noattendees
That looks much better!  Now we get custom fields like gd:when and gd:where
<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 &amp; 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

Now that all the pieces we need have their own little pockets, we can start parsing into a presentable format.
First we start with a simple block of HTML that will house the populated calendar.
<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

That looks much better!

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.

 

13 Responses leave one →
  1. Eddie permalink*
    August 14, 2011

    As I said, I am no designer, but here’s my quick and dirty attempt:

    <style>
    #container{
    padding:8px;
    -moz-border-radius: 1em 2em 1em 2em;
    border-radius: 1em 2em 1em 2em;
    width: 300px;
     
     
     
     /* fallback */
      background-color: #cff;
      background-repeat: repeat-x;
     
      /* Safari 4-5, Chrome 1-9 */
      background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#cff), to(#fff));
     
      /* Safari 5.1, Chrome 10+ */
      background: -webkit-linear-gradient(top, #cff, #fff);
     
      /* Firefox 3.6+ */
      background: -moz-linear-gradient(top, #cff, #fff);
     
      /* IE 10 */
      background: -ms-linear-gradient(top, #cff, #fff);
     
      /* Opera 11.10+ */
      background: -o-linear-gradient(top, #cff, #fff);
    }
    .channeltitle{
    font-size:14pt;
     
    margin:2px auto;
    width:60%;
    }
    li{
    margin:5px auto;
    margin-left:60px;
    padding:8px 16px;
    background-color:#0000cc;
    -moz-border-radius: 1em 2em 1em 2em;
    border-radius: 1em 2em 1em 2em;
    list-style:none;
    bullet-style:none;
    }
    .event-date{
    margin-left:-100px;
    margin-right:10px;
    }
    .event-summary{
    color:white;
    }
    </style>

    And the result..
    http://edwardawebb.com/wp-content/uploads/2011/08/demo/demo_styled.html

  2. justpassingthrough permalink
    August 17, 2011

    As I said, I am no designer..

    Truth. But there is enough here I can run with, thanks!

  3. nick permalink
    February 15, 2012

    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…’

  4. Eddie permalink*
    February 15, 2012

    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

  5. Bob permalink
    February 16, 2012

    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?

  6. Eddie permalink*
    February 17, 2012

    @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)

  7. March 26, 2012

    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 :)

  8. Lauren permalink
    July 16, 2012

    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.

  9. Eddie permalink*
    September 4, 2012

    @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

  10. ReanimationXP permalink
    October 28, 2012

    An excellent writeup. Bookmarked for a later attempt.. thanks a lot!

  11. Clark permalink
    February 13, 2013

    I don’t understand where the php script goes and how it is used. Could you help me with that?

  12. Clark permalink
    February 13, 2013

    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?

  13. Eddie permalink*
    February 15, 2013

    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.

Leave a Reply

Note: You can use basic XHTML in your comments. Your email address will never be published.

Subscribe to this comment feed via RSS