code optimizing in coldfusoin, as String are immutable in Coldfusion - coldfusion

I am new to Coldfusion and trying to build a string something like
<cfif qRoute.a IS NOT "">
<cfset str= qRoute.a>
<cfif qRoute.b IS NOT "">
<cfset str= str& " / "& qRoute.b>
</cfif>
<cfif qRoute.c IS NOT "" >
<cfset str= str& " / "& qRoute.c>
</cfif>
</cfif>
But it seems to me to be very basic technique. Is there a better way to write the code.

This would perhaps be an approach. it's a complete stand-alone repro, but the bit you want to look at is the listAppend() stuff:
<cfscript>
qRoute = queryNew("");
queryAddColumn(qRoute, "a", "varchar", ["","a2","a3","a4"]);
queryAddColumn(qRoute, "b", "varchar", ["","","b3","b4"]);
queryAddColumn(qRoute, "c", "varchar", ["","","","c4"]);
</cfscript>
<cfloop query="qRoute">
<cfif not len(qRoute.a)>
<cfcontinue>
</cfif>
<cfset str = "">
<cfset str = listAppend(str, qRoute.a)>
<cfset str = listAppend(str, qRoute.b)>
<cfset str = listAppend(str, qRoute.c)>
<cfset str = listChangeDelims(str, "/")>
<cfoutput>[#str#]<br></cfoutput>
</cfloop>
On ColdFusion 9, this outputs:
[a2]
[a3/b3]
[a4/b4/c4]
Is that more what you're after?
The code for more recent versions of CFML would be much nicer, but you're kinda hamstrung by using an obsolete version of CF (which I'm sure is outwit your control, but it irks me to have to write such clumsy code)

Related

How do you track templates calls in ColdFusion?

How do you track template path in ColdFusion?
I.E.
I have the following folder and file structure
index.cfm
<cfset ArrayAppend(request.Trace, '/')>
<cfdump var=#request.trace#>
foo
index.cfm
<cfset ArrayAppend(request.Trace, '/foo/')>
<cfinclude template='../'>
bar
index.cfm
ArrayAppend(request.Trace,'/foo/bar/')>
<cfinclude template='../'>
When I call foo/bar/index.cfm,
request.Trace equals:
'/foo/bar/'
'/foo/'
'/'
How could I do this without specifically declaring each folder name?
Have a look at:
expandPath(".")
getBaseTemplatePath()
getCurrentTemplatePath()
CGI.CF_TEMPLATE_PATH
CGI.PATH_TRANSLATED
CGI.SCRIPT_NAME
If you want the template stack trace, use this:
<cfset templateTrace = []>
<cfset tagTrace = createObject("java","java.lang.Exception").init().TagContext>
<cfloop array="#tagTrace#" index="tagInfo">
<cfset templateTrace.add(tagInfo.Template)>
</cfloop>
<cfdump var="#templateTrace#">
This will output all templates passed up to this call.
Not ideal but this worked for me.
<cfset currentFile = GetCurrentTemplatePath()>
<cfset currentDir = GetDirectoryFromPath(currentFile)>
<cfset webroot = expandPath("/")>
<cfset m_Trace = Replace(currentDir, webroot , '\')>
<cfset ArrayAppend (request.Trace, m_Trace )>

How to avoid evaluate()

How can I write the equivalent for
<cfset lang = Evaluate("SERVER.Locale.#LocaleName#.#SESSION.Locale#")>
without using Evaluate.
I tried something like
<cfset lang = SERVER[Locale][#LocaleName#][#SESSION.Locale#]>
after referring this, but it was not working.
UPDATE
<cfset localename = "test">
<cfset session.locale = "en">
<cfif Not IsDefined("SERVER.LOCALE")>
<cfset SERVER.Locale = StructNew()>
</cfif>
<cfif IsDefined("SERVER.Locale.#LocaleName#")>
<cfset StructDelete(SERVER.Locale, "#LocaleName#")>
</cfif>
<cfset "SERVER.Locale.#LocaleName#" = StructNew()>
<!---<cfset lang = Evaluate("SERVER.Locale.#LocaleName#.#SESSION.Locale#")>--->
<!---<cfset lang = SERVER["Locale.#LocaleName#.#SESSION.Locale#"]>--->
<cfset lang = SERVER.Locale[LocaleName][SESSION.Locale]>
<cfoutput>#lang#</cfoutput>
ERROR:
Element en is undefined in a CFML structure referenced as part of an expression.
This:
<cfset lang = Evaluate("SERVER.Locale.#LocaleName#.#SESSION.Locale#")>
Should be revised to this:
<cfset lang = SERVER.Locale[LocaleName][SESSION.Locale]>
You should not have the [Locale] in square brackets like that.
I think the error message (which you should always post with your question) probably actually explains that to you, dunnit?

Coldfusion 10 - Element [n] is undefined in a Java object of type class coldfusion.runtime.Array

I recently upgraded a system from CF8 to CF10 and have one bug that I'm having problems tracking down. It has to do with a remote API call that gets a JSON string back and that string then gets converted to a query object. That's where I'm coming across the error:
Element [n] is undefined in a Java object of type class coldfusion.runtime.Array. The problem is in the function that converts the string to a query.
<cffunction name="CFjsonToQuery" access="public" returntype="query" output="no">
<cfargument name="cfData" required="yes" type="struct"/>
<cfset var LOCAL = {}/>
<cfset LOCAL.tmpQry = QueryNew( ArrayToList(ARGUMENTS.cfData.Data.COLUMNS) ) />
<cfloop index = "i" from = "1" to = "#ArrayLen(ARGUMENTS.cfData.Data.DATA)#">
<cfset LOCAL.Row = QueryAddRow(LOCAL.tmpQry) />
<cfloop index="k" from="1" to="#ArrayLen(ARGUMENTS.cfData.Data.DATA[i])#">
<cfset LOCAL.colName = ARGUMENTS.cfData.Data.COLUMNS[K]/>
<cfset QuerySetCell(LOCAL.tmpQry,LOCAL.colName,ARGUMENTS.cfData.Data.DATA[i][k],LOCAL.Row)/>
</cfloop>
</cfloop>
<cfreturn LOCAL.tmpQry/>
</cffunction>
Anywhere the JSON returns 'null' (i.e. "...","19107-3609",null,null,null,"...") the error is thrown. I've tried using isNull to check if it's null in the cfloop:
<cfif isNull(ARGUMENTS.cfData.Data.DATA[i][k])>
<cfset ARGUMENTS.cfData.Data.DATA[i][k] = 'I AM NULL'/>
</cfif>
EDIT - here's a simplified example - the issue is the way the newer deserializeJson() works I believe:
<cfset jstr = '{"SUCCESS":true,"ERRORS":[],"DATA":{"COLUMNS":["ID","FNAME","LNAME"],"DATA":[[390132,"steve",null]]}}'/>
<cfset cfData = deserializeJson(jstr) />
<cfloop index = "i" from = "1" to = "#ArrayLen(cfData.Data.DATA)#">
<cfset Row = QueryAddRow(tmpQry) />
<cfloop index="k" from="1" to="#ArrayLen(cfData.Data.DATA[i])#">
<cfset colName = cfData.Data.COLUMNS[K]/>
<cfset QuerySetCell(tmpQry,colName,cfData.Data.DATA[i][k],Row)/>
</cfloop>
</cfloop>
I've tried all sorts of tests for empty string, isNull etc. and I'm still not sure how to get the query object built if deserializejson returns:
[undefined array element] Element 3 is undefined in a Java object of type class coldfusion.runtime.Array.
This does seem to work:
<cfset cfData = deserializeJson(returnData,'FALSE') />
<cfset qryData = cfData.data />
This lets me then use qryData as if it were a normal cfquery.
You can do a check if the element is undefined using the CF Function ArrayIsDefined(array, elementIndex)
What I've done for now is add 'FALSE' to the deserializeJSON strictMapping flag and that seems to automatically create a query object? I'll admit though this is getting into the underpinnings of CF10 and I could be wrong on that. I'll update my code above for visual clarity.

Consuming a webservice code simplification

UPDATED CODE TO LATEST ITERATION
The following function consumes a webservice that returns address details based on zip code (CEP). I'm using this function to parse the xml and populate an empty query with the address details. I would like to know if there is a more elegant way to achieve the same result. It seems to be a waste to create an empty query and populate it...
Any ideas could my method be modified or the code factored/simplified?
<!--- ****** ACTION: getAddress (consumes web-service to retrieve address details) --->
<cffunction name="getAddress" access="remote" returntype="any" output="false">
<!--- Defaults: strcep (cep (Brazilian zip-code) string webservice would look for), search result returned from webservice --->
<cfargument name="cep" type="string" default="00000000">
<cfset var searchResult = "">
<cfset var nodes = "">
<cfset var cfhttp = "">
<cfset var stateid = 0>
<cfset var tmp = structNew()>
<!--- Validate cep string --->
<cfif IsNumeric(arguments.cep) AND Len(arguments.cep) EQ 8>
<cftry>
<!--- Consume webservice --->
<cfhttp method="get" url="http://www.bronzebusiness.com.br/webservices/wscep.asmx/cep?strcep=#arguments.cep#"></cfhttp>
<cfset searchResult = xmlparse(cfhttp.FileContent)>
<cfset nodes = xmlSearch(searchResult, "//tbCEP")>
<!--- If result insert address data into session struct --->
<cfif arrayLen(nodes)>
<cfset tmp.streetType = nodes[1].logradouro.XmlText>
<cfset tmp.streetName = nodes[1].nome.XmlText>
<cfset tmp.area = nodes[1].bairro.XmlText>
<cfset tmp.city = nodes[1].cidade.XmlText>
<cfset tmp.state = nodes[1].uf.XmlText>
<cfset tmp.cep = arguments.cep>
<!--- Get state id and add to struct --->
<cfset stateid = model("state").findOneByStateInitials(tmp.state)>
<cfset tmp.stateid = stateid.id>
<cfreturn tmp>
</cfif>
<!--- Display error if any --->
<cfcatch type="any">
<cfoutput>
<h3>Sorry, but there was an error.</h3>
<p>#cfcatch.message#</p>
</cfoutput>
</cfcatch>
</cftry>
</cfif>
</cffunction>
<!--- ****** END ACTION getAddress --->
The calling code:
<!--- Get address data based on CEP --->
<cfset session.addressData = getAddress(cep=params.newMember.cep)>
I can't test this because I don't have an example XML file / CEP to test with, but here is a minor rewrite that addresses four things:
Instead of using cfparam and some strange "params" structure, you should pass the CEP into the function as an argument.
The function shouldn't directly modify session data. Instead, you should return the result and let the calling code assign it to the session (or wherever else it might be needed). I'll show this in a 2nd code example.
Cache the xml result per CEP -- assuming this doesn't change often. (You'll have to improve it further if you want time-based manual cache invalidation, but I can help add that if necessary)
Don't use StructInsert. It's not necessary and you're just writing it the long way for the sake of writing it the long way. There is no benefit.
Again, this isn't tested, but hopefully it's helpful:
<cffunction name="getAddress" access="remote" returntype="any" output="false">
<cfargument name="cep" type="string" default="00000000" /><!--- (cep (Brazilian zip-code) string webservice would look for) --->
<cfset var searchResult = "">
<cfset var nodes = "">
<cfset var cfhttp = "">
<cfset var stateid = 0 />
<cfset var tmp = structNew()>
<!--- Validate cep string --->
<cfif IsNumeric(arguments.cep) AND Len(arguments.cep) EQ 8>
<cfif not structKeyExists(application.cepCache, arguments.cep)><!--- or cache is expired: you'd have to figure this part out --->
<!--- Consume webservice --->
<cftry>
<cfhttp method="get" url="http://www.bronzebusiness.com.br/webservices/wscep.asmx/cep?strcep=#arguments.cep#" />
<cfset searchResult = xmlparse(cfhttp.FileContent)>
<cfset nodes = xmlSearch(searchResult, "//tbCEP")>
<!--- If result insert address data into session struct --->
<cfif arrayLen(nodes)>
<cfset tmp.streetType = nodes[1].logradouro.XmlText />
<cfset tmp.streetName = nodes[1].nome.XmlText />
<cfset tmp.area = nodes[1].bairro.XmlText />
<cfset tmp.city = nodes[1].cidade.XmlText />
<cfset tmp.state = nodes[1].uf.XmlText />
<cfset tmp.cep = arguments.cep />
<!--- Get state id and add to struct --->
<cfset stateid = model("state").findOneByStateInitials(session.addressData.state)>
<cfset tmp.stateid = stateid.id />
</cfif>
<cfreturn duplicate(tmp) />
<!--- Display error if any --->
<cfcatch type="any">
<h3>Sorry, but there was an error.</h3>
<p>#cfcatch.message#</p>
</cfcatch>
</cftry>
<cfelse>
<!--- cache exists and is not expired, so use it --->
<cfreturn duplicate(application.cepCache[arguments.cep]) />
</cfif>
</cfif>
<!---
<!--- Redirect to page two of the sign up process --->
<cfset redirectTo(controller="assine", action="perfil")>
--->
</cffunction>
Notice that I commented out the redirect you had at the end. That's because with my function, you'll be returning a value, and the redirect should be done after that, like so:
<cfset session.addressData = getAddress("some-CEP-value") />
<cfset redirectTo(controller="assine", action="perfil")>
If you're going to leave out the caching (As you say in a comment you will), then here is a version that makes no attempt at caching:
<cffunction name="getAddress" access="remote" returntype="any" output="false">
<cfargument name="cep" type="string" default="00000000" /><!--- (cep (Brazilian zip-code) string webservice would look for) --->
<cfset var searchResult = "">
<cfset var nodes = "">
<cfset var cfhttp = "">
<cfset var stateid = 0 />
<cfset var tmp = structNew()>
<!--- Validate cep string --->
<cfif IsNumeric(arguments.cep) AND Len(arguments.cep) EQ 8>
<!--- Consume webservice --->
<cftry>
<cfhttp method="get" url="http://www.bronzebusiness.com.br/webservices/wscep.asmx/cep?strcep=#arguments.cep#" />
<cfset searchResult = xmlparse(cfhttp.FileContent)>
<cfset nodes = xmlSearch(searchResult, "//tbCEP")>
<!--- If result insert address data into session struct --->
<cfif arrayLen(nodes)>
<cfset tmp.streetType = nodes[1].logradouro.XmlText />
<cfset tmp.streetName = nodes[1].nome.XmlText />
<cfset tmp.area = nodes[1].bairro.XmlText />
<cfset tmp.city = nodes[1].cidade.XmlText />
<cfset tmp.state = nodes[1].uf.XmlText />
<cfset tmp.cep = arguments.cep />
<!--- Get state id and add to struct --->
<cfset stateid = model("state").findOneByStateInitials(session.addressData.state)>
<cfset tmp.stateid = stateid.id />
</cfif>
<cfreturn duplicate(tmp) />
<!--- Display error if any --->
<cfcatch type="any">
<h3>Sorry, but there was an error.</h3>
<p>#cfcatch.message#</p>
</cfcatch>
</cftry>
</cfif>
<!---
<!--- Redirect to page two of the sign up process --->
<cfset redirectTo(controller="assine", action="perfil")>
--->
</cffunction>
Note that I did leave in the use of duplicate(). What this does is return a duplicate of the object (in this case, the struct). This is much more important when you start to work on applications where you're passing complex values into and out of functions over and over again. Using duplicate() causes things to be passed by value instead of by reference. It may not bite you in this case, but it's a good habit to get into.
I would also still use the function argument and return a value -- but it's arguable that this is my personal preference. In a way it is. I believe that a function should be fully encapsulated; a total "black box". You give it some input and it gives you back some output. It should not modify anything outside of itself. (Again, just my opinion.)
So assuming you're using this function as part of a larger multi-step process, you should still use it the same way I've described above. The only difference is that you're setting the session variable outside of the function body. Just as previously:
<cfset session.addressData = getAddress("some-CEP-value") />
<cfset redirectTo(controller="assine", action="perfil")>
That looks pretty straightforward. CF doesn't (yet?) have any magical XML-to-Query functions, but that would be pretty cool. If you wanted, you could probably write up an XSL transform to go from XML to WDDX so that you could use the cfwddx tag ... but that's probably putting the cart before the horse.
You need to move your arrayLen() if block into the try block. As it stands, if the cfhttp tag throws an error, the nodes variable will be a string and not an array, thus causing the arrayLen() to throw another error.
Minor nitpick: I wouldn't add a row to the query until inside the arrayLen() block. That way, the calling code can check recordCount to see if the result was a success.
Beyond that ... that's pretty much how it's done.

What is the correct syntax for replacing two characters at once in ColdFusion?

I'm trying to replace two special characters using the replace function in ColdFusion:
<cfset MyQuery = "/Attribute One\/Attribute Two\/Attribute Three\">
<cfset MyString = Replace(MyQuery, "/", "<li>", "ALL")>
<ul>
<cfoutput>#MyString#</cfoutput>
</ul>
This works well, but I need to correctly close my li tags. I tried adding the following which did not work:
<cfset MyString = Replace(MyQuery, "/", "<li>", "ALL") AND Replace(MyQuery, "\", "</li>", "ALL")>
First question: Is this the right way to what I'm attempting? Or should I just store my html inside the database along with the attributes?
Second question: If my approach is correct, could someone please provide an example of the correct syntax?
Many thanks!
Nested replace? Just replace "/" first to avoid affecting "":
<cfset MyQuery = "/Attribute One\/Attribute Two\/Attribute Three\" />
<cfset MyString = Replace(MyQuery, "/", "<li>", "ALL") />
<cfset MyString = Replace(MyString, "\", "</li>", "ALL") />
<cfoutput>#MyString#</cfoutput>
<cfset MyString = Replace(Replace(MyQuery, "/", "<li>", "ALL"), "\", "</li>", "ALL") />
<cfoutput>#MyString#</cfoutput>
List manipulation can be another the way to do this:
<cfset MyQuery = "/Attribute One\/Attribute Two\/Attribute Three\">
<cfset MyString = "" />
<cfloop list="#MyQuery#" index="li" delimiters="\/">
<cfset MyString &= "<li>#li#</li>" />
</cfloop>
<cfoutput>#MyString#</cfoutput>