Passing A Variable Number Of "Nameless" Arguments Using CFInvoke And ArgumentCollection

Posted March 30, 2007 at 8:39 AM by Ben Nadel

Tags: ColdFusion

I have been trying to figure out how to send a variable, logic-based selection of arguments as unnamed values to a function. My original thought was to use ColdFusion's CFInvoke and CFInvokeArgument, but CFInvokeArgument requires a name attribute (which is what I was trying to avoid). I couldn't just call the function using function notation (ex. "Test()") as I needed to involve logic in my argument selection (which only CFInvoke would allow).

Sean Corfield had a good idea to use the index-like keys. But for some reason, it didn't click in my head until Christoph Schmitz suggested the same thing. While both suggested this solution, I think it only dawned on me "how" to accomplish this when Chris said it (right place, right time). Both are clearly rock stars though.

So anyway, here is my test to see that it works. These are two functions that I tested with. The first, DumpArgs() is the ColdFusion UDF that just dumps out its arguments. The second function, StructCreate() is just a utility function (seen all over the place) that builds a struct on the fly and returns it:

  • <cffunction
  • name="DumpArgs"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="Does nothing but CFDump out the arguments.">
  •  
  • <!--- Dump out arguments. --->
  • <cfdump
  • var="#ARGUMENTS#"
  • label="ARGUMENTS Scope"
  • />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="StructCreate"
  • access="public"
  • returntype="struct"
  • output="false"
  • hint="Returns a created struct based on the passed in key-value pairs.">
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />
  •  
  • <!--- Create the return struct. --->
  • <cfset LOCAL.Struct = StructNew() />
  •  
  • <!---
  • Populate the struct using the name-value pairs
  • of passed in arguments.
  • --->
  • <cfloop
  • item="LOCAL.Key"
  • collection="#ARGUMENTS#">
  •  
  • <cfset LOCAL.Struct[ LOCAL.Key ] = ARGUMENTS[ LOCAL.Key ] />
  • </cfloop>
  •  
  • <!--- Return struct. --->
  • <cfreturn LOCAL.Struct />
  • </cffunction>

Ok, now I need to create some values that I may OR may not need to pass in. It's friday, let's build a struct of hot girls that we can choose from:

  • <!--- Create a struct of girls. --->
  • <cfset objGirls = StructNew() />
  •  
  • <!--- Create girl. --->
  • <cfset objGirls[ "Samantha" ] = StructCreate(
  • Name = "Samantha",
  • Hair = "Brunette",
  • Hotness = 9.0
  • ) />
  •  
  • <!--- Create girl. --->
  • <cfset objGirls[ "Niki" ] = StructCreate(
  • Name = "Niki",
  • Hair = "Brunette",
  • Hotness = 8.0
  • ) />
  •  
  • <!--- Create girl. --->
  • <cfset objGirls[ "Lori" ] = StructCreate(
  • Name = "Lori",
  • Hair = "Blonde",
  • Hotness = 7.0
  • ) />
  •  
  • <!--- Create girl. --->
  • <cfset objGirls[ "Tamoko" ] = StructCreate(
  • Name = "Tamoko",
  • Hair = "Black",
  • Hotness = 8.0
  • ) />
  •  
  • <!--- Create girl. --->
  • <cfset objGirls[ "Ashley" ] = StructCreate(
  • Name = "Ashley",
  • Hair = "Brunette",
  • Hotness = 8.0
  • ) />
  •  
  • <!--- Create girl. --->
  • <cfset objGirls[ "Libby" ] = StructCreate(
  • Name = "Libby",
  • Hair = "Brunette",
  • Hotness = 9.0
  • ) />
  •  
  • <!--- Create girl. --->
  • <cfset objGirls[ "Christina" ] = StructCreate(
  • Name = "Christina",
  • Hair = "Blonde",
  • Hotness = 9.0
  • ) />

Now that we have a our function in place and our possible values, let's create a variable-length argument collection based on some business logic:

  • <!---
  • Create an argument collection of girls. The
  • purpose of this is to build up a variable number
  • of "nameless" arguments.
  • --->
  • <cfset objArgs = StructNew() />
  •  
  • <!---
  • Loop over the girls to figure out which ones we
  • want to pass in. For now, let's get all the 9.0
  • who are also brunette.
  • --->
  • <cfloop
  • item="strGirl"
  • collection="#objGirls#">
  •  
  • <!--- Get a reference to the girl. --->
  • <cfset objGirl = objGirls[ strGirl ] />
  •  
  • <!--- Check to see if this girl makes the cut. --->
  • <cfif (
  • (objGirl.Hair EQ "Brunette") AND
  • (objGirl.Hotness EQ 9.0)
  • )>
  •  
  • <!---
  • We want to include this girl in our argument
  • collection. In order to do that we are going to
  • add her to the argument collection struct with an
  • index-based naming convention so that the function
  • will use it like an array. In order to do this
  • dynamically, we can use the struct-count to get
  • the current index.
  • --->
  • <cfset objArgs[ StructCount( objArgs ) + 1 ] = objGirl />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  •  
  • <!---
  • Now that we have built up the argument
  • collection, we can simply invoke the method and pass
  • in the arguments struct.
  • --->
  • <cfinvoke
  • method="DumpArgs"
  • argumentcollection="#objArgs#"
  • />

This gives us the following CFDump:


 
 
 

 
Variable Length Argument Collection CFDump  
 
 
 

That works perfectly! I think the thing that finally clicked in my head when Chris suggested the argumentCollection was how to build the struct without having to use an incrementing value (ie. keeping some sort of argument count variable). By using the StructCount() I was able to just keep appending values to the struct and not having to worry about what index I was actually working on. Sweet!

Note: Using argumentCollection can also be done with standard function invokation. It does not require the use of CFInvoke.




Reader Comments

Mar 30, 2007 at 11:13 AM // reply »
2 Comments

Ben, take a look at my experimental CFC framework. This is exactly how i pass data between different methods. There are a few different viewlets demonstrating how it all works.

http://labs.webapper.net/projects/CFCFramework/index.cfm


Mar 30, 2007 at 12:51 PM // reply »
20 Comments

FWIW, #dumpargs(argumentcollection=objArgs)# would work just as well as <cfinvoke method="DumpArgs" argumentcollection="#objArgs#" />


Mar 30, 2007 at 2:48 PM // reply »
11,241 Comments

@Steve,

The PPT looks cool. I will take try to take a look at the other files this weekend.

@Matt,

Yeah, good point. I was just using CFInvoke cause that's how I started out. But you are absolutely right.


Apr 4, 2007 at 6:31 PM // reply »
11,241 Comments

@Steve,

I finally got around to going through your CFC framework. It looks pretty interesting. I see what you are saying about passing around all of your arguments like this; you are continually passing your ARGUMENTS scope onto other methods.

I don't know enough MVC / Fusebox to comment on the functionality, but it does look very clean. I am sure it will positively influence my future thought processes.


Oct 24, 2010 at 11:12 AM // reply »
10 Comments

Hi Ben,

I am trying to do this, but in my cases I want the arguments to be passed in a specific order. The argumentcollection trick does not work in this case, as the elements inside a struct have no specific order. Any cool ideas for that?

Greetings from the Netherlands
Martijn

By the way I loved your preso about the CF application framework for CFUG-NL!


Oct 24, 2010 at 12:21 PM // reply »
11,241 Comments

@Martijn,

Very interesting! I think you uncovered a difference that I was not aware of. It seems that argumentCollection has a different behavior in CFInvoke than it does have in a standard method invocation. If you switch to using CFInvoke, this should work.

Thanks for the insight!


Oct 25, 2010 at 2:28 AM // reply »
10 Comments

Hi Ben,

Thanks for replying. I reckon you mean I should switch to CFInvokeArguments, instead of passing an argumentcollection? Because passing in an argumentcollection would mean passing a struct, inside which the elements can never be trusted to have any specific order, right?


Oct 25, 2010 at 8:41 AM // reply »
11,241 Comments

@Martijn,

I think you found a bug. I am going to blog about it this morning.


Oct 25, 2010 at 10:50 AM // reply »
11,241 Comments

@Martijn,

The ArgumentCollection actually is getting funkier in ColdFusion 9. I did a good bit of digging this morning based on our conversation:

http://www.bennadel.com/blog/2042-ColdFusion-Ordered-ArgumentCollection-Behavior-Depends-On-ColdFusion-Version-And-Invocation-Context.htm

I have to believe this is entering "bug" territory.


Oct 28, 2010 at 6:44 PM // reply »
25 Comments

If you need to pass unnamed arguments to a function, create the arguments in an array and then loop over the array passing them via numbered <cfinvokeargument> tags to the function.

Here's a variation of your own code from a different blog post:

<cfset args = []>
<cfset args[1] = "Katie (1st Arg)">
<cfset args[2] = "Colleen (2nd Arg)">
<cfset args[3] = "nameless third arg">

<cffunction name="echoArguments">
<cfargument name="namedArg1">
<cfargument name="namedArg2">
<cfreturn arguments>
</cffunction>

<cfinvoke method="echoArguments" returnvariable="result">
<cfloop index="i" from="1" to="#arrayLen(args)#">
<cfinvokeargument name="#i#" value="#args[i]#">
</cfloop>
</cfinvoke>

<cfdump var="#result#">

I don't see a way of passing an array of arguments to a function using standard function-passing notation though.

I guess depending on one's perspective, this is either a benefit of tag-based code, or a shortcoming of normal code. It'd be solved by allowing an argumentCollection to be an array though, that's for sure. Maybe for CF10?

--
Adam


Oct 29, 2010 at 9:43 AM // reply »
11,241 Comments

@Adam,

Now this is funny - this CFInvokeArgument approach is actually part of the idea that Sean had originally told me about; and, when I saw it, I think I simply equated the set of CFInvokeArgument tags to be the same, functionally speaking, as argumentCollection. I think this is where the conversation in the other blog posts comes from.

I guess it would appear that CFInvokeArgument is not functionally the same as argumentCollection.


Oct 29, 2010 at 10:00 AM // reply »
11,241 Comments

@Adam,

I just ran some CFInvokeArgument-based code in CF9 and what I'm seeing is that CFInvokeArgument will properly map 1/2 to args1/args2, but *only* if the target method has named arguments defined. This appears to act like the argumentCollection as well.

Bottom line, CF9 simply makes passing arbitrary non-named arguments to a method a non-trivial task. Looks like an evaluate() approach for N-ordered arguments might be the only approach.

I agree with you - being able to use an array for argumentCollection would be awesome :)


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 22, 2013 at 5:35 PM
Script Tags, jQuery, And Html(), Text() And Contents()
This is still an issue 2 years later. jQuery is supposed to remediate these cross browser issues, no? I have been unable to find any statement from the jQuery team calling this behavior "by de ... read »
May 22, 2013 at 12:44 PM
Ask Ben: Query Loop Inside CFScript Tags
In cf10, if you call a function that has: local.result = {}; local.result.msg = ""; local.svc = new query(); local.svc.setSQL("SELECT * FROM..."); local.obj = local.svc.exe ... read »
May 22, 2013 at 12:29 PM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben: What version of Java are you using? Also, did you test users.id to see what Java reports as the data type? I wonder if it's not a Java primitive data type, but getting returned as something ... read »
May 22, 2013 at 11:47 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Dana, Awesome - so it looks like this bug was fixed in ColdFusion 10. Thanks so much for double-checking that. ... read »
May 22, 2013 at 11:37 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
When I c&p and run on cf10, I get: Selected User IDs: 1,4 User 1 selected: YES - YES User 2 selected: NO - NO User 3 selected: NO - NO User 4 selected: YES - YES User 5 selected: NO - ... read »
May 22, 2013 at 11:27 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Tom, Good thought, but no dice. Both of these still exhibit the same behavior: users.id[ users.currentRow ] users[ "id" ][ users.currentRow ] It's just something whacky happening with ... read »
May 22, 2013 at 11:07 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
Could your problem be that "users.id" is actually an ARRAY, not a single value? Perhaps try it again with "users.id[1]" (I only have CF8 here at work). ... read »
May 22, 2013 at 7:52 AM
Nested Views, Routing, And Deep Linking With AngularJS
Hi, Just a quick thank you. As it happens, for my own purposes, the pending ui-router work being done in native angular is likely the one I'll adopt, but your exploration, code and documentation of ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools