Hackin' the MAX Scheduler

Ok, “hacking” may be too strong a word, but I thought I’d share a fun little diversion I tried at lunch. I noticed that the MAX 2010 Scheduler oddly did not list session times. You could browse by day of course, but I prefer to browse by session title. I was also curious to see when my session was. Unfortunately, the date and times for each session were not available in that view.

I then decided to do what any self-respecting web developer would do. I opened up the Developer tools in Chrome (think Firebug) and became to monitor what was going on. I noticed that the data for the scheduler came from a few JSON requests. I took a look at them and found that the JSON data for sessions actually contained the date and times - the front end just wasn't using it. So I quickly decided to play with this data myself. I began by creating a simple ColdFusion file that would proxy the JSON requests for me. An AIR app wouldn't need this - but I wanted something done super quick. I came up with the following:

<cfparam name="url.u" default=""> <cfset safeurls = "http://max.adobe.com/v1/events/ebdabc28-aab4-479f-86f3-6bd9d97b4cc7/speakers.json,http://max.adobe.com/v1/events/ebdabc28-aab4-479f-86f3-6bd9d97b4cc7/sessions.json"> <cfif url.u is "" or not isValid("url", url.u) or not listFindNoCase(variables.safeurls, url.u)> <cfabort/> </cfif> <cfif isNull(cacheGet("urlcache_#url.u#"))> <cfhttp url="#url.u#" timeout="20"> <cfset cachePut("urlcache_#url.u#", cfhttp.filecontent,1)> </cfif> <cfset data = cacheGet("urlcache_#url.u#")> <cfcontent type="application/json" reset="true"><cfoutput>#data#</cfoutput>

There isn't a lot here. Basically look for a URL to request in the URL scope (sorry if that sounds confusing) and validate the value against a list of URLs. Next - see if I have the request in the cache. If not, fetch it with cfhttp and store it for a day. Finally, get the data from the cache and output it to the screen. My front end was a bit more complex:

<html> <head> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script> <script> var speakers = [] var sessions = [] var searchReady = false function renderSession(session) { var res = "" res += "<div class='session'>" res += "<h2>"+session.name+"</h2>" res += "<b>Times:</b> " if(session.instances.length == 0) res += "None Scheduled<br/>" else { for(var i=0; i<session.instances.length;i++) { res += session.instances[i].date + " " + session.instances[i].time if(i+1 < session.instances.length) res+= " and " } res += "<br/>" } res += "<b>Speakers:</b> " if(session.speakers.length == 0) res += "None Assigned<br/>" else { for(var i=0; i<session.speakers.length;i++) { res += speakers[session.speakers[i]].name if(i+1 < session.speakers.length) res+= " and " } res += "<br/>" } res += session.description res += "</div>" return res } function renderSessions() { var totalSessions = 0 var filterTerm = $("#search").val() filterTerm = $.trim(filterTerm.toLowerCase()) var s = "" for(var i=0; i<sessions.length; i++) { if(filterTerm == '' || (sessions[i].name.toLowerCase().indexOf(filterTerm) >= 0 || sessions[i].description.toLowerCase().indexOf(filterTerm) >= 0 ) ) { totalSessions++ s += renderSession(sessions[i]) } } s = "<h2>Sessions ("+totalSessions+")</h2>" + s $("#sessions").html(s) } function loadSpeakers() { var speakerurl = "http://max.adobe.com/v1/events/ebdabc28-aab4-479f-86f3-6bd9d97b4cc7/speakers.json" $.getJSON('load.cfm?u='+speakerurl, {}, function(res,code) { for(var i=0; i<res.length; i++) { var speaker = {"name":res[i].lastname +", "+res[i].firstname} speakers[res[i].id] = speaker } loadSessions() }) } function loadSessions() { var sessionurl = "http://max.adobe.com/v1/events/ebdabc28-aab4-479f-86f3-6bd9d97b4cc7/sessions.json" $.getJSON('load.cfm?u='+sessionurl, {}, function(res,code) { sessions = res renderSessions() $("#status").text("") searchReady = true }) } $(document).ready(function() { $("#status").text("Please stand by. I'm loading a lot of data and doing really important, technical 'computer' stuff.") //begin by loading speakers loadSpeakers() $("#search").keyup(function() { var val = $(this).val() renderSessions() }) }) </script> <style> #status { font-style: italic; } .session { width: 500px; background-color: #ffff80; padding-top: 0px; padding-left: 5px; padding-right: 5px; margin-bottom: 10px; } #menu { float: left; width: 300px; background-color: #c0c0c0; border: 0.1em solid #000000; padding: 0px; margin-top: 18px; margin-right: 5px; } #sessions { overflow:auto; } </style> </head> <body> <span id="status"></span> <div id="menu"> Search:<br/> <input type="text" id="search"><br/> </div> <div id="sessions"> </div> </body> </html>

If you start looking in the document.ready block, you can see I begin by fetching my speakers. I store that result in a global variable. Once that's done, I then fetch my sessions. Once I have all my data, I can then render them. I wrote both a renderSessions() method and a renderSession() method. That may be overkill - but it worked out ok.

At that point - the only left was a simple search. I didn't bother with all the options the real system had. I just used a simple keyword search that matched against the title and the description. You can play with this here:

http://www.coldfusionjedi.com/demos/maxfix/

Obviously this was just built for fun. I wouldn't count on it working forever, nor would I be surprised if Adobe fixes the missing times in their next push. I mainly just wanted to show an example of how you could repurpose someone else's AJAX data for your own benefit. (And yes, now I know when I'm speaking - 3:30 on October 27th.)

Raymond Camden's Picture

About Raymond Camden

Raymond is a developer advocate for Extend by Auth0. He focuses on serverless and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support.

Lafayette, LA https://www.raymondcamden.com

Comments