A few days ago a client of mine, Rich Swier of HuB, asked if I could build him two quick demos that made use of the Eventbrite API. I whipped up the two demos for him and once done, he graciously allowed me to share it on my blog. (Thank you Rich!) I will warn you that this code was written for ColdFusion 8 so it is entirely tag based. At the very end a mod was made for ColdFusion 9. Obviously it could be converted to script and perhaps improved in other ways as well, but I hope this is useful for folks who want to play with the Eventbrite API in the future.
Before making use of these demos yourself, you will want to get yourself an authorization token for your account. After going to developer.eventbrite.com, click Get Started. Note the green button on the right.
When I tested this a few days ago, it worked fine for me, but failed for Rich as he wasn't the primary account owner for his organization. Unfortunately the "failure" was a blank white screen. I got a response from Eventbrite on their forums (https://groups.google.com/forum/#!forum/eventbrite-api) about this within a day, but I'm not sure if it is fixed yet. If you do get a "white screen of death" error though check to see if there are other people in your group using Eventbrite.
Once logged in, add a new app, and when done, make note of the OAuth token. My API makes use of this to make authenticated calls for your organization. Finding the ID of your organization can be a bit weird too. Unfortunately I can't find the tip that helped me with this before (I believe it was an Eventbrite staff member), but in order to get your organization ID, login, go to your profile page, and find your organizer page URL:
The organizer ID is the numeric part at the end of your URL. I've highlighted it in the screen shot above. It is a bit silly that the value isn't called out specifically either on the developer page or on your profile, but, there it is.
Ok, so finally, with your token and organizer ID, you can perform some basic searches against the API. For Rich, I only needed Search, but their API supports pretty much everything imaginable as far as I can tell. I created a simple CFC that is initialized with the auth token. First, the Application.cfc used to set up the CFC.
<cfcomponent output="false">
<cfset this.name = "RichSwierEventBrite">
<cffunction name="onApplicationStart" access="public" returntype="boolean" output="false">
<cfset application.eventBrite = {}>
<cfset application.eventBrite.token = "secret">
<cfset application.eventBrite.orgid = "3983270067">
<cfset application.eventBriteApi = createObject("component", "eventbrite").init(application.eventBrite.token)>
<cfreturn true>
</cffunction>
<cffunction name="onRequestStart" access="public" returntype="boolean" output="false">
<!--- TODO: remove 1 --->
<cfif structKeyExists(url, "init") or 1>
<cfset onApplicationStart()>
</cfif>
<cfreturn true>
</cffunction>
</cfcomponent>
And here is the CFC itself. Again, this was written for ColdFusion 8, so pardon all the darn tags. I could have used script I suppose, but I ran into some issues with even script-based UDFs on his server so I went the safe route. Also note I ran into cfhttp issues with the https server Eventbrite used. This is why I used a workaround described a few years ago on my blog. On ColdFusion 10 I didn't have the issue at all.
<cfcomponent output="false">
<cffunction name="init" access="public" returnType="eventbrite" output="false">
<cfargument name="token" type="string" required="true">
<cfset variables.token = arguments.token>
<cfreturn this>
</cffunction>
<cffunction name="getEvents" access="public" returnType="struct" output="false">
<cfargument name="organizationid" type="string" required="true">
<cfargument name="startdate" type="date" required="false">
<cfargument name="enddate" type="date" required="false">
<cfset var result = "">
<cfset var content = "">
<cfset var apiurl = "https://www.eventbriteapi.com/v3/events/search?organizer.id=#arguments.organizationid#">
<cfset var startUTC = "">
<cfset var endUTC = "">
<cfset var objSecurity = "">
<cfset var storeProvider = "">
<cfif structKeyexists(arguments, "startdate")>
<cfset startUTC = getIsoTimeString(arguments.startdate, true)>
<cfset apiurl &= "&start_date.range_start=#startUTC#">
</cfif>
<cfif structKeyexists(arguments, "enddate")>
<cfset endUTC = getIsoTimeString(arguments.enddate, true)>
<cfset apiurl &= "&start_date.range_end=#endUTC#">
</cfif>
<cfset apiurl &= "&token=#variables.token#">
<cfset objSecurity = createObject("java", "java.security.Security") />
<cfset storeProvider = objSecurity.getProvider("JsafeJCE") />
<cfset objSecurity.removeProvider("JsafeJCE") />
<cfhttp url="#apiurl#" result="result">
<cfset objSecurity.insertProviderAt(storeProvider, 1) />
<cfset content = deserializeJSON(result.filecontent)>
<cfreturn content>
</cffunction>
<cfscript>
// I take the given date/time object and return the string that
// reprsents the date/time using the ISO 8601 format standard.
// The returned value is always in the context of UTC and therefore
// uses the special UTC designator ("Z"). The function will
// implicitly convert your date/time object to UTC (as part of
// the formatting) unless you explicitly ask it not to.
function getIsoTimeString(datetime) {
if(!structKeyExists(arguments, "convertToUTC")) {
convertToUTC = true;
}
if ( convertToUTC ) {
datetime = dateConvert( "local2utc", datetime );
}
// When formatting the time, make sure to use "HH" so that the
// time is formatted using 24-hour time.
return(dateFormat( datetime, "yyyy-mm-dd" ) &
"T" &
timeFormat( datetime, "HH:mm:ss" ) &
"Z"
);
}
</cfscript>
</cfcomponent>
Nothing special really. I just construct the URL and fire off the call. That UDF at the end may be found on CFLib: getIsoTimeString. As I said, I built a grand total of one function for the API because that's all he needed. Now let's look at the actual demos. The first demo makes use of Google Maps. I perform a search for future events, get their locations, and display them on a map.
For the most part, this was simple, but the issue I ran into (and this is a reported issue with the API), is the location information for the venue wasn't including the full address. There is a venue API that returns the proper information, but that would mean adding N more calls over http to fetch the data. I simply warned Rich that the address won't be working properly until they fix the API. To be clear, I'm talking about the textual part of the address. The longitude and latitude work just fine. Right now when you click you get some detail and the ability to click for the Eventbrite URL.
Now let's look at the code that generates the map.
<!--- only events today and onwards --->
<cfset dtNow = now()>
<cfif structKeyExists(url, "clearcache")>
<cfset cacheRemove("eventbrite.futureEvents")>
</cfif>
<cfset events = cacheGet("eventbrite.futureEvents")>
<cfif isNull(events)>
<cfset eventData = application.eventBriteApi.getEvents(application.eventBrite.orgid,dtNow)>
<cfset cachePut("eventbrite.futureEvents", eventData.events,0.5)>
<cfset events = eventData.events>
</cfif>
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#map_canvas { width: 500px; height: 500px; }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
function initialize() {
var latlng = new google.maps.LatLng(41.5, -98);
var myOptions = {
zoom: 3,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);
<cfloop index="x" from="1" to="#arrayLen(events)#">
<cfset event = events[x]>
<!--- conditionally format address --->
<cfset address = "">
<cfif structKeyExists(event.venue, "name") and event.venue.name is not "undefined">
<cfset address &= event.venue.name>
</cfif>
<cfif structKeyExists(event.venue, "address_1") and event.venue.address_1 is not "undefined">
<cfset address &= "<br/>" & event.venue.address_1>
</cfif>
<cfif structKeyExists(event.venue, "address_2") and event.venue.address_2 is not "undefined">
<cfset address &= "<br/>" & event.venue.address_2>
</cfif>
<cfif structKeyExists(event.venue, "city") and event.venue.city is not "undefined">
<cfset address &= "<br/>" & event.venue.city>
</cfif>
<cfif structKeyExists(event.venue, "state") and event.venue.state is not "undefined">
<cfset address &= ", " & event.venue.state>
</cfif>
<!--- format times --->
<cfset startDate = listFirst(event.start.local, "T")>
<cfset startDate = dateFormat(startDate, "mm/dd/yy")>
<cfset startTime = listLast(event.start.local, "T")>
<!--- strips off seconds, which is silly --->
<cfset startTime = mid(startTime, 1, len(startTime)-3)>
<cfset startTime = timeFormat(startTime, "h:mm tt")>
<cfset endDate = listFirst(event.end.local, "T")>
<cfset endTime = listLast(event.end.local, "T")>
<!--- strips off seconds, which is silly --->
<cfset endTime = mid(endTime, 1, len(endTime)-3)>
<cfset endTime = timeFormat(endTime, "h:mm tt")>
<!--- currently assumes same date --->
<cfset dateStr = startDate & " at " & startTime & " to " & endTime>
<cfoutput>
var pos = new google.maps.LatLng(#event.venue.latitude#,#event.venue.longitude#);
var marker#x# = new google.maps.Marker({
map: map,
position: pos,
title: "#jsStringFormat(event.name.text)#"
});
var infowindow#x# = new google.maps.InfoWindow({
content: '<b>#jsStringFormat(event.name.text)#</b><p/>#jsStringFormat(address)#<br/>#jsStringFormat(dateStr)#<br/><a href="#event.url#">Details</a>',
maxWidth: 250
});
google.maps.event.addListener(marker#x#, 'click', function() {
infowindow#x#.open(map,marker#x#);
});
</cfoutput>
</cfloop>
}
</script>
</head>
<body onload="initialize()">
<div id="map_canvas"></div>
</body>
</html>
Again - pretty simple. Get the events and then just iterate over them to create map markers using the Google API. I don't like the ColdFusion code inside the JavaScript block there and I'd probably consider moving that outside. That way when I create the markers the code would be simpler. (And obviously, if this were a production application I'd use something like FW/1 and do most of that work in the controller/service layer anyway.)
So that's maps. The next demo he requested was a calendar. I decided to use the FullCalendar jQuery plugin as it worked well for me before. (See my blog post from a few years back.) In order to work with the client-side code, I wrote a quick CFC to handle calls to my application scoped Eventbrite API. It also handles shaping the data so it makes the FullCalendar happy. I could have added some RAM-based caching here, but I found that the API was fast enough for month based searches where it didn't feel necessary. Then again - I'd probably consider adding it anyway just to ensure you don't hit any rate limits.
<!---
Main wrapper to calls to the EB api. Handles the caching so EB can be a bit simpler.
Also handles converting EB events for FullCalendar
--->
<cfcomponent output="false">
<cffunction name="getEvents" access="remote" output="false" returnformat="json">
<cfargument name="start" type="string" required="true">
<cfargument name="end" type="string" required="true">
<cfset var eventData = application.eventBriteApi.getEvents(application.eventbrite.orgid,parseDateTime(arguments.start), parseDateTime(arguments.end))>
<cfset var result = []>
<cfset var x = "">
<cfset var event = "">
<cfloop index="x" from="1" to="#arrayLen(eventData.events)#">
<cfset event = {}>
<cfset event["title"] = eventData.events[x].name.text>
<cfset event["start"] = eventData.events[x].start.local>
<cfset event["end"] = eventData.events[x].end.local>
<cfset event["url"] = eventData.events[x].url>
<cfset arrayAppend(result, event)>
</cfloop>
<cfreturn result>
</cffunction>
</cfcomponent>
Finally, here is the front end using FullCalendar. It is a page with just the calendar and a DOM element used to handle the loading of remote data. I could have designed that a bit better but I assumed the client would have their own ideas about that.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<link rel='stylesheet' href='fullcalendar/fullcalendar.css' />
<script src='fullcalendar/lib/jquery.min.js'></script>
<script src='fullcalendar/lib/moment.min.js'></script>
<script src='fullcalendar/fullcalendar.min.js'></script>
<script>
$(document).ready(function() {
$('#calendar').fullCalendar({
eventSources: [
{
url:'api.cfc?method=getevents'
}
],
loading:function(isLoading, view) {
if(isLoading) {
$("#loading").html("<i>Loading data...</i>");
} else {
$("#loading").html("");
}
}
});
});
</script>
</head>
<body>
<div id="loading"></div>
<div id='calendar'></div>
</body>
</html>
I have attached the code as a zip to this blog entry. Note that the token and organization ID are not in the Application.cfc file so you will need to set them yourself.