Wow, this is a cool tip. I've had this in my queue to write about for a few weeks now but I was delayed due to MAX. Credit for this goes to Stephen Duncan Jr - I'm just passing it along - and to be honest - it kinda seems obvious now - but it's certainly not something I've thought of before. I've said it before - but I'll say it again. I love my readers.
Back in March I blogged about an interesting problem I ran into with jQuery and CFCs. This wasn't a jQuery specific issue, but as I use jQuery most of the time I ran into it there. I won't repeat the entire previous blog entry, but the basic problem was that there didn't seem to be a good way to post arrays of data to a back end CFC. jQuery does serialize the array, but it does it in a way that makes CFCs sad. My solution was to encode the array into JSON and update my CFC to accept either "real" arrays or JSON-encoded arrays. It worked... but I hated modifying my CFC.Fast forward a few months and a reader, Stephen Duncan JR, pointed out something interesting. If you read his comment, you will see he did something a bit different. Instead of simply passing data=json version of array, he passed a JSON-encoded version of a structure containing name-value pairs. He used the argumentCollection feature of CFCs.
If this is the first time you've heard of argumentCollection, don't be surprised. It is documented but not used very often. It's based on an even older feature, attributeCollection, that is used in custom tags. argumentCollection allows you to pass a structure of names and values. ColdFusion will treat this as if they were real arguments and values. So consider a structure data that contains:
name=ray
age=37
coolness=epic
If you pass argumentCollection=data to a CFC method (or any UDF), then ColdFusion acts as if you had passed arguments name, age, and coolness. As I said above, this isn't new at all, but I've never seen it used with an AJAX post like this. What's nice then is that on the server side, you can have a "proper" method without any if/else statement to see if the result was JSON. As a quick example, here is an updated version of the front end code based on the previous example. I went ahead and added jquery-json to the template to further simplify things.
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript" src="jquery.json.min.js"></script>
<script>
$(document).ready(function() {
var mydata = {data:[1,2,3,4,5,"Camden,Raymond"]};
$.post("test.cfc", {method:"handleArray",argumentCollection:$.toJSON(mydata), returnFormat:"plain"}, function(res) {
alert($.trim(res));
})
});
</script>
</head>
<body>
</body>
</html>
And as I said before, the back end CFC is now nice and simple:
<cffunction name="handleArray" access="remote" returnType="numeric">
<cfargument name="data" type="array" required="true">
<cfreturn arrayLen(arguments.data)>
</cffunction> </cfcomponent>
<cfcomponent>
Archived Comments
I've only been pairing jQuery with ColdFusion over the last year or so but in that time I've use both together heavily. This JSON and CfArgument technique was something I came across very early and constantly use it. I've ended up using JSON in so many more situations and ways than I'd previously ever though. I blame CF :) CF makes it a dream.
Thanks for posting this. It's a great clarification on the topic.
Nice tip, thanks!
Should also work with Ext.util.JSON.encode()
I love seeing solutions that use the syntax of the existing language.
With AJAX, two things I always remember to use are: cfsetting showdebugoutput="false" at the component level, and output="false" for the functions.
This is amazing! I enjoy argumentCollection on the server side, but I had NO idea that you could use it in URL-based invocation. Is this something new in CF9? Or have I just been unaware of it for a long time?
Since argumentCollection was supported server side with 6, I'd say this probably worked in 6. However, with CFCs only outputting WDDX back then, I don't think many people used them for AJAX.
Bananas! :) I don't know why, but this is just sort of awesome.
Nice! Exactly what I needed.
Now, has anyone had success getting CF to parse a JSON string into a cfcomponent object type? What I mean is:
<cffunction name="handleObj" access="remote">
<cfargument name="obj" type="cfcs.testobj">
...
</cffunction>
I would hope that if my testobj looked like:
<cfcomponent>
<cfproperty name="attr1" type="string">
</cfcomponent>
...that I could call method=handleObj&argumentCollection={"obj":{"attr1":"hello"}} and CF would recognize the object by it's signature and create it with the properties set, just as it can do when receiving a Flex object. But instead it errors with "the OBJ argument passed to the handlObj function is not of type cfcs.testobj".
On a whim I tried &argumentCollection={"obj":{"attr1":"hello","__type__":"cfcs.testobj"}}, but alas it doesn't work in reverse. ;-)
You would need to do it manually. Shouldn't be too hard though. Just use a populate method for your entities that would allow for a struct of name/value pairs.
Thanks Ray, this is true. Though I guess what I was ultimately hoping for was the implicit get & set methods that you also get when CF 8 creates an object automatically for you. eg: I would like a testobj.setAttr1() method to be called to validate the attr1 value received in the method call's JSON. Of course a populate() method could do this too, and is what I will have to resort to. I just was hoping to not have to reinvent the wheel (especially before our eventual move to CF9, which I think gives implicit get & set methods?)
Yeah, in CF9 if you were to run ob.setname(struct.name) that you can do some validation in a custom setter or let the implicit one just set the value.
And you want to be careful of course. If I see you passing a JSON string of complex data to a CFC I'm going to open up Firebug and see how much I can break it/mess with it. ;)
:-) I see your point...but on the other hand I don't see much difference between a complex dataset and a bunch of form values being submitted at once; it's all just data structure variations. Unless you were hoping I was going to be passing in strings of method call instructions ;-)
I never assume a person isn't too stupid to blindly trust Ajax requests. :)
Quick question... Is argumentCollection restricted to Form and URL scope?
Technically no. In _this_ example we are talking about making an Ajax request. The only way to pass arguments in that case is via form/url, so the answer is kind of yes in that regard.
A very cool answer. Thanks Ray. Actually. The reason I asked, was because I couldn't get it to work with Client scope. Your answer helped a lot though, because it encouraged me to look deeper into my issue, and there it was, a coding error. I guess however, that it is better to be specific when adding just a few arguments, especially in non Form, URL scopes, where there can be many in a collection.
Thank you for posting this and the previous blog - you've just saved me from a very big headache regarding this very issue.
We've an mapping tool supplied by a third party that generates polygons for us using javascript and I was struggling to deal polygons that generated lots of points i.e. in the case of curves. After I had constructed the polygon string it basically blew the string buffer on the post.
However using this method, I've been able to pass in much larger polygons that I have before, solving my problem and enabling me to work on the server rather than in the browser.
Ray, your example has 3 name/value pairs, but your code has an array of 5 elements.
Are you saying that each array element can be a name/value pair?
Um no. My argument was data and the value happened to be an array. That's all. I could have done this too:
var mydata = {data:[1,2,3,4,5,"Camden,Raymond"],goo:1,hoo:1};
That would be 3 args to the CFC (data, goo, and hoo), with data being an array, goo and hoo being 1.