CF9 cfscript, passing dynamic array in arguments - coldfusion

I start this year with a big question.
When I do
myName = "Henry";
myCustomFunction( [ myName, "Thierry" ] );
It throws an error like myName is undefined. Why?
I can resolve this by doing
myName = "Henry";
aMyArrayArgument = [ myName, "Thierry" ];
myCustomFunction( aMyArrayArgument );
But still, I want to know why Coldfusion don't allow to pass [ myName, "Thierry" ] in arguments?
I use Framework One (FW/1) if it can help.
Thank you!

If your actual code looks more like this:
if (something)
{
myName = "Henry";
myCustomFunction( argname=[ myName, "Thierry" ] );
}
Then it's because the literal struct and array notation in CF is very badly written and buggy.
If you can confirm the circumstances it breaks, raise an issue with Adobe (there are several issues relating to this already; you may or not feel like checking for duplicates).

If I do this:
<cffunction name="xx" returntype="void">
<cfargument name="x" type="array">
<cfdump var="#arguments.x#">
</cffunction>
<cfset myname = "dan">
<cfset xx([myname, 'bracuk']) >
The code runs without error and I see the dump. There must be something else going on with your code.

Related

queryEach or .each() not Working at CF 11, Why?

I was seaching in google better form to interate over "query" in coldfusion, since im new in the company im working, and im trying to get more from CF
Here my attempts:
My models:
<cffunction hint="Foo" name="Foo" access="public" returntype="query">
<!--- Argumentos --->
<cfargument hint="Something" name="ArgComBus" type="string" required="no" default="">
<cfargument hint="Other thing" name="ArgPar" type="string" required="no" default="">
<cfscript>
queryService = new Query();
queryService.setSql("
SELECT
column1,
column2,
FROM
tab_bar
WHERE
1=1
#arguments.ArgComBus#
");
queryService.setDataSource(session.Dsn);
if(Len(Trim(arguments.ArgPar))){
Evaluate(arguments.ArgPar);
}
queryResult = queryService.execute();
qBus = queryResult.getResult();
</cfscript>
<cfreturn qBus>
</cffunction>
My script
<cfscript>
arrFoo = arrayNew(1);
qFoo = this.Foo(
ArgComBus = " AND column1 = #variables.bar# ");
// First Attempt - The each method was not found.
qFoo.each(function (foo) {
arrFoo.append(foo);
});
// Second Attempt - Variable QUERYEACH is undefined.
queryEach(qFoo, function (foo) {
arrFoo.append(foo);
});
writeDump(arrFoo);
</cfscript>
My Server Dump
InstallKit Native Windows
appserver Tomcat
productlevel Developer
productname ColdFusion Server
productversion 11,0,05,293506
rootdir C:\CFusion11\cfusion
I even used getMetaData() on my query variable qFoo and that return that is array... so when i tried use something like that (trying to convert array in query?)
cfQuery = createObject("java", "coldfusion.sql.QueryTable").init(qFoo);
.each() and queryEach() same answer... i even tried use arrayEach() but return the object is coldfusion.sql.QueryTable and not array
You are running ColdFusion 11.
The queryEach() function was not added until ColdFusion 2016:
reference 1 (from cfdocs)
reference 2 (from Adobe docs)
Originally I had posted that the each() member function was not available in Adobe ColdFusion 11. Aquitaine pointed out in the comments that it actually is. I incorrectly referenced the Each() function for Lucee that works with collections. The Each() function related to this question is actually the script version of the ArrayEach() tag function. Which is available in ColdFusion 11 (it was actually added in ColdFusion 10). Sorry for the confusion.
The documentation may be wrong. I could not get the function to work as Each() except under ColdFusion 2018. For ColdFusion 11 I could only get it to work as ArrayEach().
reference 3 (from cfdocs)
reference 4 (from Adobe docs)
Here are a couple of examples on how to loop over a query in ColdFusion 11 (borrowed from cfdocs):
// Define our query
platform = ["Adobe ColdFusion", "Railo", "Lucee"];
myQuery = queryNew(" ");
queryAddColumn(myQuery, "platform", "CF_SQL_VARCHAR", platform);
// By row index
for (i = 1; i <= myQuery.recordCount; i++) {
writeOutput("<li>#myQuery["platform"][i]#</li>");
}
// By query
for (row in myQuery) {
writeOutput("<li>#row.platform#</li>");
}
// By arrayeach
writeOutput("<h3>By arrayeach:</h3>");
function printArray(vendor, index)
{
writeOutput("<li>#vendor#</li>");
}
arrayEach(platform,printArray);
I created a gist for you on TryCF.com so you can see this code in action and play around with it if you like. Just click here to run the code.

Coldfusion opencsv csvWriter - I get writeNext method was not found

I have the following code:
<cfset csvFilename = 'myCSV.csv'>
<cfset fileWriter = createobject("java","java.io.FileWriter").init("#csvFileName#")>
<cfset csvWriter = createObject("java","com.opencsv.CSVWriter").init(fileWriter, ",")>
<cfset csvWriter.writeNext('{"1","2"}', true)>
<cfset csvWriter.flush()>
<cfset fileWriter.close()>
<cfset csvWriter.close()>
When I run the page I get this error message:
The writeNext method was not found.
Either there are no methods with the specified method name and
argument types or the writeNext method is overloaded with argument
types that ColdFusion cannot decipher reliably. ColdFusion found 0
methods that match the provided arguments. If this is a Java object
and you verified that the method exists, use the javacast function to
reduce ambiguity.
I have searched the internet and cannot seem to find any examples of using csvWriter with Coldfusion and I am not sure why it is not working. I have a working example of csvReader and ReadNext, but not WriteNext. Any ideas of what I am doing wrong? I have tried to do a javacast but that didn't work either.
<cfset csvWriter.writeNext(javacast("string",'1,2'))>
I am using Coldfusion 11 with opencsv-3.8.jar
According to the API, that overload of writeNext() expects a String[], or an array of Strings. CF arrays are a little different than Java's, but they are compatible. Either of these would work:
<cfset csvWriter.writeNext( ["1","2"], true)>
<cfset csvWriter.writeNext( javacast("String[]", ["3","4"]), javacast("boolean", true))>
As an aside, skip the call to fileWriter.close(). When you call CSVWriter.close(), it closes the underlying writer for you. Calling both will cause an error.
<cfset csvFilename = 'myCSV.csv'>
Without a full path, I am not sure where that file will end up. Always specifying a full path can save a lot of head scratching later on ;-)

Invoking function with dynamic array of arguments

If I have
<cfset arr_arguments = ["a","b","c"]>
<cfunction name="someFunction">
<cfargument name="someArgumentOne">
<cfargument name="someArgumentTwo">
<cfargument name="someArgumentThree">
</cffunction>
Is there any way to invoke someFunction with the arguments arr_arguments, similar to someFunction("a","b","c")? I of course know I can use argumentCollection to pass a (keyed) structure to a function, but I am specifically asking about passing in an (keyless) array. In JS this could be easily done with someFunction.apply(this,arr_arguments), but in coldfusion I just can't find any way of doing this.
Unnamed arguments are passed into a function as a structure with numeric keys matching the positions of the arguments defined in the function arguments. So instead of passing the named arguments, you can convert your array to a struct with numeric keys, and then pass the struct in with argumentCollection:
<cfset arr_arguments = {"1"="a","2"="b","3"="c"}>
<cfset someFunction(argumentCollection=arr_arguments)>
You can easily convert an array to a struct with numeric keys like this:
<cfset args = {}>
<cfloop from="1" to="#arrayLen(arr_arguments)#" index="i">
<cfset args[i] = arr_arguments[i]>
</cfloop>
In Coldfusion 10, you can use the invoke function to do this. It appears to be an undocumented way to pass an array of arguments in order. Example in cfscript:
invoke('', 'someFunction', ['a','b','c']);
The first argument of invoke is the component name (or an empty string for UDFs). The second is the function name, and third is an argument array. Note that both component and function names must be passed as strings.
I tested this both with and without defined argument names and the order of arguments was preserved.
Ok if you are looking to call a function with an arbitary array of arguments the best option I can suggest is to make a wrapper function. See below example code
<cfscript>
function testMe(
required string arg1,
required string arg2
) {
writedump(arguments);
}
function invokeFunction(
required inFunction,
required array functionArguments
) {
var stcArguments = {};
// Introspect to get the method parameters
var arrFunctionArguments = GetMetaData(arguments.inFunction).parameters;
// Now figure out what we are iterating to
var numArgumentsToParse = Min(ArrayLen(arguments.functionArguments),ArrayLen(arrFunctionArguments));
// Populate the arguments structure
for (var i=1;i<=numArgumentsToParse;i++) {
stcArguments[arrFunctionArguments[i].name] = arguments.functionArguments[i];
}
// And try to call it
return arguments.inFunction(
argumentCollection = stcArguments
);
}
invokeFunction(testMe,[1,2]); // Works fine
invokeFunction(testMe,[1,2,3,4]); // Just skips 3 and 4
// invokeFunction(testMe,[1]); // Errors due to not enough arguments
// invokeFunction(fakeFunctionName,[1]); // Errors due to undefined function
</cfscript>
This will of course error if either of the following happen
One or more arguments are not of correct type
The function doesn't actually exist
Not all required arguments are passed in
But you can handle that outside. This will use introspection to figure out the argument names, populate the structure accordingly, and call the requested function.
Here's a UDF which will do that for you:
<cfscript>
function test_function(a,b,c){
writeOutput("arguments.a"&arguments.a&"<br>");
writeOutput("arguments.b"&arguments.b&"<br>");
writeOutput("arguments.c"&arguments.c&"<br>");
return arguments.a & "::" & arguments.b & "::" & arguments.c;
}
function invoke_positional(func,arr){
var metaData = GetMetaData(func);
var args={};
for(var pos=1;pos<=ArrayLen(arr);pos++){
args[metaData.parameters[pos].name]=arr[pos];
}
return arguments.func(argumentCollection=args);
}
data = ["StringOne","StringTwo",22];
result = invoke_positional(test_function,data);
</cfscript>
<cfoutput>
<p>
Returned: #result#
</p>
</cfoutput>
invoke_positional works by using the UDF's metadata to build up a named set of parameters and then uses them with argumentCollection=
So, one option I have found out to work is to create an arguments object separately from the actual real arguments scope and simply add the values to it using a numeric index.
<cfset args_input = createObject("java","com.naryx.tagfusion.cfm.engine.cfArgStructData").init()>
<cfloop from="1" to="#arraylen(arr_arguments)#" index="i">
<cfset args_input[i] = arr_arguments[i]>
</cfloop>
<cfset someFunction(argumentCollection=args_input)>
If you encounter this situation using adobe coldfusion I would advice you to try
<cfset correctClass = arguments.getClass().getName()>
To get the string to pass to the createObject. Or if that doesn't work just have a dedicated function to return an empty arguments object
<cfset args_input = this.generateArgumentObject()>
and
<cffunction name="generateArgumentObject">
<cfreturn arguments>
</cffunction>
Oh well, all I know for sure is that it will work for sure in OpenBD and based on a post I saw in the past on Ben Nadel's blog I am pretty sure this should work in Adobe ColdFusion as well.

How to create Java object in CFSCRIPT?

I am using ColdFusion 9.1.0
I am trying to create a java object using CFSCRIPT and I just can't get the right combination of stuff to work.
This works perfectly:
<cfobject action="create" type="Java" class="CyberSource" name="auth">
<cfset VARIABLES.ResponseString = auth.runTransaction(LOCAL.PropsFile,LOCAL.MyXML)>
When I do this, I get an error:
LOCAL.MyObject = createObject("java", "CyberSource.auth");
LOCAL.ResponseString = auth.runTransaction(LOCAL.PropsFile,LOCAL.MyXML);
This is the error I get:
Object Instantiation Exception.
Class not found: CyberSource.auth
The object is an external piece of code available to ColdFusion. I don't see what the problem is. Do you?
The classname is CyberSource and the variable you are trying to assign the instance to is "auth" in your tagbased approach. You mixed it up with "MyObject".
LOCAL.auth = createObject("java", "CyberSource");
LOCAL.ResponseString = LOCAL.auth.runTransaction(LOCAL.PropsFile,LOCAL.MyXML);
This should work.
One thing to be aware of.
The java class names are case sensitive!
// Fail
myFile = createObject( 'java', 'java.io.file' );
// Win!
myFile = createObject( 'java', 'java.io.File' );
And to call their constructor, use .init() eg.
myFile = createObject( 'java', 'java.io.File' ).init( '/Users/Mike/Dev/Test' );

How do I run a static method on a CFC without using cfinvoke?

How do I invoke a static method on a CFC without using cfinvoke? I know that I can do this:
<cfinvoke component="MyComponent" method="myStaticMethod' arg1="blah" returnvariable=myReturnVar>
I would like to be able to invoke this method the same way I would a UDF:
<cfset myReturnVar = MyComponent.myStaticMethod(blah)>
This, however, does not work. Is there syntax that I am messing up or is this just not possible?
not possible, since there's no "static method" in ColdFusion.
The <cfinvoke> line in your question is the same as:
myReturnVar = CreateObject("component", "MyComponent").myStaticMethod(arg1="blah");
You need to create the object first.
<cfset MyComponent = createObject("component","MyComponent") />
<cfset myReturnVar = MyComponent.myMethod(blah) />
I know this is a really old post but here's an updated answer for more modern CF/Lucee supporting static constructs for anyone who might stumble across this like I did :-)
based off of:
https://modern-cfml.ortusbooks.com/cfml-language/components/static-
component MyComponent{
static {
appendToArgValue : "text"
}
public static function myStaticMethod( arg1 ){
return arg1 & static.appendToArgValue;
};
}
MyComponent::myStaticMethod("blah")
Lucee documentation: https://docs.lucee.org/guides/lucee-5/component-static.html