Earlier this week I blogged on JSONP (What in the heck is JSONP and why would you use it?). If that terms means nothing to you, be sure to read that article first. I thought it might be useful to talk about how you could write a ColdFusion API to support JSONP (as a producer I mean, for other people to use your API).
As we know (or hopefully now!), ColdFusion 8 made it very easy to serve JSON via Ajax. Consider the following method:
<cffunction name="getPerson" access="public" returnType="struct" output="false"> <cfset var s = {}> <cfset s.name = "Raymond"> <cfset s.age = "35"> <cfset s.suaveness = randRange(50,150)> <cfreturn s> </cffunction>
If I wanted to expose that structure in JSON, all I'd need to do is change access to remote and specify JSON in the URL. I'd request it like so:
http://www.visitthewishlist.com/foo.cfc?method=getPerson&returnFormat=json
The returnFormat there simply asks ColdFusion to convert the structure to JSON before sending it back to the client.
Ok, so what about JSONP? JSONP stands for JSON with Padding and that presents a problem to us. We can't just return a JSON string, we have to return a JSON string plus the padding, which is a function call. ColdFusion does support a "Don't do squat with my result, just return it" version - you just change returnFormat to plain. So given that, here is how I'd build support for JSONP:
<cfcomponent output="false"><cffunction name="getPerson" access="public" returnType="struct" output="false"> <cfset var s = {}> <cfset s.name = "Raymond"> <cfset s.age = "35"> <cfset s.suaveness = randRange(50,150)> <cfreturn s> </cffunction>
<cffunction name="remoteGetPerson" access="remote" returnType="any" returnFormat="plain" output="false"> <cfargument name="callback" type="string" required="false"> <cfset var data = getPerson()>
<!--- serialize ---> <cfset data = serializeJSON(data)>
<!--- wrap ---> <cfif structKeyExists(arguments, "callback")> <cfset data = arguments.callback & "(" & data & ")"> </cfif>
<cfreturn data> </cffunction>
</cfcomponent>
I've added a new method here, remoteGetPerson. Normally this would call an application scoped CFC, but in this case my method resides in the same file. I serialize the data and then - optionally - wrap it with the callback. Arguments.callback is expected to be a function named defined in your client side code.
Notice that I hard coded the returnFormat. In the past I've recommended against that. I've said that the caller should request a format and the CFC method should be neutral - but in this case we are specifically building in support for JSON or JSONP. I made the padding optional - well - for no good reason I guess. I noticed Yahoo supports JSONP in their requests, but allows the callback function to be optional. Since JSON is just string data, it will work fine with returnFormat=plain.
So to test this, I wrote the following incredibly interesting demo:
<html><head> <script src="/jquery/jquery.js"></script> <script>
function runTest() { var surl = 'http://www.raymondcamden.com/demos/jsonp/test.cfc?method=remoteGetPerson&callback=?' $.getJSON(surl, function(data) { var res = 'Name: '+data.NAME+'<br/>' res += 'Age: '+data.AGE+'<br/>' res += 'Suaveness: '+data.SUAVENESS if(data.SUAVENESS > 100) res+=' (Rico-worthy)' res+='<br/>' $("#result").html(res) }) }
$(document).ready(function() { $("#testBtn").click(runTest) });
</script> </head>
<body> <input type="button" value="Test" id="testBtn"> <div id="result"></div> </body> </html>
This is a simpler version of what I wrote for InsideRIA. I've added a simple button that, when clicked, runs a call to my CFC method. Now obviously this is in the same domain so JSONP is overkill, but in theory, folks could download the code (fix the jQuery reference - yes - I know I need to switch to Google CDN!) and still be able to run it.
Two quick notes:
I used jQuery in the example above, but you could use any other framework of your choosing. As long as you use jQuery. Kidding. (Not really.)
Using Firebug to test? (Of course you are!) I was surprised to find that these Ajax requests do not show up under XHR. I had forgotten that JSONP works by injecting script blocks into your page. Just switch to Net/All or Net/HTML and you can see the requests.
Archived Comments
It would be cool if you could use OnMissingMethod() in remote calls. Then, you could have a CFC decorate the target CFC and wrap its returns in the callback method. Something like:
data = target[ missingMethodName ]( missingMethodArguments )
return( callback & "(" & data & ")" );
Obviously, that syntax is not proper, but it would be a cool idea... but not possible.
Very nice code. I really like to work with this but my problem is i am not able to run this in my local. In the firebug it says syntax error. I am using CF8 and i am a new be...
var surl = 'http://www.coldfusionjedi.c...
when i used the above surl it is working but when i use
var surl = 'test.cfc?method=remoteGetPerson&callback=?'
as my cfc is in the same folder as cfm file. why it is going wrong for me? Please help me.
Any more details on the syntax error? Line #? What happens if you specify a full url for surl, ie, http://localhost/etc.
Thank you, Raymond! I've been looking all day for this -- a clear set of code that addressed what was needed on both sides of the HTML/CF implementation of JSONP AJAX style calls. Everyone has missed big chunks of how it actually works and inserted obscuring frills that just seem to hide what was really going on. Thanks again for a clear explanation and code that actually worked when I copied and ran it. Rarer than you'd think.
No problem. Glad to help.
@Raymond: The link in the first sentence of the first paragraph of your article is no longer live. Looks like they have archived your posts. I found the link to the archived article:
http://www.developria.com/2...
Thanks - will edit the post.
Very, very, very cool. It works like a dream.
Another good post Ray.
Just noticed, The link to http://www.developria.com/2... is redirecting to http://www.adobe.com/devnet... now, I was wondering if you happened to have a copy of that post anywhere else?
If not for me, but for others trying to figure out what the heck jsonp is too.
Thanks
See: http://www.raymondcamden.co...
I'll republish that post - it was a good one. (I'll probably forget to mention it here, so just check the blog. Daily. Hourly! ;)
Reprinted: http://www.raymondcamden.co...