The newer versions of ColdFusion (I believe CF 8 and 9) allow you to create structs with object literal notation similar to JSON.
My question is, are there specific benefits (execution efficiency maybe) to using object literal notation over individual assignments for data that is essentially static?
For example:
With individual assignments you would do something like this:
var user = {};
user.Fname = "MyFirstnam";
user.Lname = "MyLastName";
user.titles = [];
ArrayAppend(user.titles,'Mr');
ArrayAppend(user.titles,'Dr.');
Whereas with object literals you would do something like.
var user = {Fname = "MyFirstnam",
Lname = "MyLastName",
titles = ['Mr','Dr']};
Now this limited example is admittedly simple, but if titles was an array of structures (Say an array of addresses), the literal notation becomes awkward to work with.
Before I tried anything, I thought straight away that literals would be faster, as you create everything you need in runtime, and don't need to waste time creating variables, then calling functions to append and all that.
I then wrote a little test that produces a chart with the results. You got me curious there :-)
The results prove I was right, as the graph shows a staggering difference as you can see:
But remember that although one would jump and go with literal notation, I think it's important to remember that literal notation can be awkward, and will most of the times confuse more.
Obviously, if you are developing a page that really needs the speed boost, literal notation is what you're looking for, but be aware that sometimes on CF8, it will produce some strange behaviour.
Just to show you the sort of tests I run:
<cfset aLiterals = arrayNew(1) />
<cfset aDirect = arrayNew(1) />
<cfsilent>
<cfloop from="1" to="10000" index="mm">
<!--- LITERAL --->
<!--- start timer --->
<cfset start = getTickcount() />
<cfloop from="1" to="1000" index="ii">
<cfset user = {Fname = "MyFirstnam", Lname = "MyLastName", titles = ['Mr','Dr']} />
</cfloop>
<!--- end timer --->
<cfset end = getTickCount()>
<!--- Display total time --->
<cfset total = end-start>
<cfset arrayAppend(aLiterals,total) />
<!--- DIRECT --->
<!--- start timer --->
<cfset start1 = getTickcount() />
<cfloop from="1" to="1000" index="jj">
<cfset user = {} />
<cfset user.Fname = "MyFirstnam" />
<cfset user.Lname = "MyLastName" />
<cfset user.titles = [] />
<cfset ArrayAppend(user.titles,'Mr') />
<cfset ArrayAppend(user.titles,'Dr.') />
</cfloop>
<!--- end timer --->
<cfset end1 = getTickCount()>
<!--- Display total time --->
<cfset total1 = end1-start1>
<cfset arrayAppend(aDirect,total1) />
</cfloop>
</cfsilent>
<!--- The cfchart --->
<cfchart format="png" xaxistitle="function" yaxistitle="Loading Time (in secs.)">
<cfchartseries type="bar" serieslabel="literal">
<cfchartdata item="literal" value="#arrayAvg(aLiterals)#">
</cfchartseries>
<cfchartseries type="bar" serieslabel="direct">
<cfchartdata item="direct" value="#arrayAvg(aDirect)#">
</cfchartseries>
</cfchart>
Hope this helps you.
Literal notation is declarative programming, not procedural programming.
With literal notation, you tell the computer what it is you want clearly and in a single step. Without literal notation, you build what you want slowly, piece by piece, and without clarity.
Note that literal notation in CF8 is awkward and flawed, at best. It should rarely be used, and then only in simple cases. The literal notation in CF9 is fine.
For CF8, you can define helpers:
function $A() {
var r = [ ];
var i = 0;
var m = ArrayLen(Arguments);
for(i = 1; i <= m; i += 1)
ArrayAppend(r, Arguments[i]);
return r;
}
function $S() {
return StructCopy(Arguments);
}
And use them as such:
var user = $S(
Fname = "MyFirstnam",
Lname = "MyLastName",
titles = $A('Mr', 'Dr')
);
These helpers work all the time, preserve struct key case (struct keys are not simply uppercased but are cased as you typed them), and nest recursively without bound.
Related
OK, so I've been banging my head against this one for a while and getting nowhere. I've been attempting to take the contents of a variable and parse the contained string into parts that would then be ingested into 5 separate variables. Seems simple enough right? Well, it has not proven to be simple at all, at least for me.
So I have a variable (PageContent) that contains the trimmed content from a CFHTTP request.
The PageContent variable now contains:
<tdnowrapalign=right>07/18/2020 13:00</td>
<tdalign=right>1002.12</td>
<tdalign=right>2,874,887</td>
<tdalign=right>12,766</td>
<tdalign=right>13,038</td>
It seems like there should be an easy way to write a loop that would loop over the tags in the "PageContent" variable assigning the content of each tag to a different variable. But every way I try to parse the data in the variable I either get an error (Complex object types cannot be converted to simple values.) or I end up with the content that I originally had in the "PageContent" variable repeated within the loop.
For instance, if I had a loop that would run through 5 iterations and could grab the contents of the tags assigning each to a variable then the desired result would be:
DateTime = "07/18/2020 13:00"
Elevation = "1002.12"
Storage = "2,874,887"
Outflow = "12,766"
Inflow = "13,038"
After trying every example I could find here and elsewhere online I'm now on something like my 100th attempt. Now I'm trying to use regular expressions to grab the contents of the tags and assign them to variables but no luck there. What I ended up with was the entire contents of the PageContent variable being stuffed into each one of the variables. The result was not really unexpected since I don't know of a way to differentiate between the 3 identical "tdalign" tags, but still it seems like at least the first variable would have worked since the tag was different "tdnowrapalign".
<cfset i=5/>
<cfloop index = "LoopCount" from = "1" to = #i#>
<cfif i EQ 1>
<cfset dataDateTime = Replace(PageContent, "<[tdnowrapalign][^>]*>(.+?)</[td]>","","ALL")>
<cfelseif i EQ 2>
<cfset elevation = Replace(PageContent, "<[tdalign][^>]*>(.+?)</[td]>","","ALL")>
<cfelseif i EQ 3>
<cfset storage = Replace(PageContent, "<[tdalign][^>]*>(.+?)</[td]>","","ALL")>
<cfelseif i EQ 4>
<cfset outflow = Replace(PageContent, "<[tdalign][^>]*>(.+?)</[td]>","","ALL")>
<cfelseif i EQ 5>
<cfset inflow = Replace(PageContent, "<[tdalign][^>]*>(.+?)</[td]>","","ALL")>
</cfif>
<cfoutput>
<cfif isdefined("dataDateTime")>
dataDateTime = #dataDateTime#<br>
</cfif>
<cfif isdefined("elevation")>
elevation = #elevation#<br>
</cfif>
<cfif isdefined("storage")>
storage = #storage#<br>
</cfif>
<cfif isdefined("outflow")>
outflow = #outflow#<br>
</cfif>
<cfif isdefined("inflow")>
inflow = #inflow#<br>
</cfif>
</cfoutput>
<cfset i = i - 1>
</cfloop>
Does anyone know if there is a way to get to the desired outcome I described where I end up with 5 variables containing the contents of the tags contained within the "PageContent" variable?
One way of doing it would be like this
<cfset PageContent = '<tdnowrapalign=right>07/18/2020 13:00</td>
<tdalign=right>1002.12</td>
<tdalign=right>2,874,887</td>
<tdalign=right>12,766</td>
<tdalign=right>13,038</td>' />
<cfset data = ListToArray(PageContent, '</td>', false, true) />
<cfset DateTime = ListLast(data[1], '>') />
<cfset Elevation = ListLast(data[2], '>') />
<cfset Storage = ListLast(data[3], '>') />
<cfset Outflow = ListLast(data[4], '>') />
<cfset Inflow = ListLast(data[5], '>') />
Demo: https://trycf.com/gist/b4f3b630bd1cbdc505d07a7d79b68ef5/acf?theme=monokai
It's been awhile since I've written this type of ColdFusion code, hence the question.
I am returning values from a .NET webservice into ColdFusion. The webservice returns an array of strings. The following code...
<cfoutput>
<cfset xArrayItems=#GetRequestedUserSettings.settingValues.getString()#>
<cfset xLen=ArrayLen(GetRequestedUserSettings.settingValues.getString())>
<cfloop index="x" from=1 to="#xLen#">
#xArrayItems[x]#<br />
</cfloop>
</cfoutput>
results in the following output ...
maxsize=50
isdomainadmin=False
seenwelcome=False
I want to put those name/value pairs into a meaningful structure so that I can reference them farther down in the code. I actually need to pass them in as a cfinvokearguments for the next webservice call.
Could someone please be kind enough to remind me how to do this in CF8? Most of what I am finding refers to newer versions.
I ended up with something quite similar to what #Henry you provided.
<cfset UserSettings = structNew()>
<cfset xArrayItems= GetRequestedUserSettings.settingValues.getString()>
<cfset xLen=ArrayLen(GetRequestedUserSettings.settingValues.getString())>
<cfloop index="x" from=1 to="#xLen#">
<cfset varName = ListGetAt(xArrayItems[x], 1, "=")>
<cfset varValue = ListGetAt(xArrayItems[x], 2, "=")>
<cfset "UserSettings.#varname#" = varValue>
</cfloop>
Not sure if an Array or a Struct is a better solution, but the both work in the end.
Slightly more readable version I can come up with that will work with CF8:
<cfset UserSettings = {}>
<cfset xArrayItems = GetRequestedUserSettings.settingValues.getString()>
<cfloop array="#xArrayItems#" index="item">
<cfset varname = ListFirst(item, "=")>
<cfset varvalue = ListRest(item, "=")>
<cfset UserSettings[varname] = varvalue>
</cfloop>
I've tried everything I've found that might be applicable to this, without success to date.
I have variables that look like
x000Foo
I'm trying to create one of these dynamically in form scope from query results and have tried the following, and a few others, without success:
<cfloop query="qFormFields">
<cfset "form.x000#fieldname#" = 0>
You have attempted to dereference a scalar variable of type class coldfusion.sql.QueryColumn as a structure with members
<cfset "form.x[000]#fieldname#" = 0>
The value x000AA_report cannot be converted to a number.
<cfset form["x000#fieldname#"] = 0>
The value x000AA_report cannot be converted to a number.
</cfloop
I know it's related to the zeros, but I'm not sure how to get around it without resorting to renaming these variables throughout the application.
I'm on ColdFusion2016
I'm not sure if this is what you're trying to do: but here's how you can do dynamic variables:
<cfset fieldname = "foo">
<cfset form["x000" & fieldname] = 0>
<cfdump var="#form#">
<!--- variable form.x000Foo = 0 --->
Runnable example on TryCF.com
you can try evaluate function read more
<cfloop query="qFormFields">
<cfset fieldvalue = Evaluate("form.x000#fieldname#")>
<cfdump var="#fieldvalue#">
</cfloop>
let me know if it is working or not.
The typical way government offices make you record mileage is to enter one number per input box like the example below:
So what I am trying to do is create one input box that can split the session variable into an array. Then when that session variable is split into an array I would like it to set each value in the array to its own session variable.
Mileage Input:
123456 -<cfoutput>#session.checkout.vehicle.mileage#</cfoutput>
array [1,2,3,4,5,6]
6 -<cfoutput>#session.checkout.vehicle.mileage1#</cfoutput>
5 -<cfoutput>#session.checkout.vehicle.mileage2#</cfoutput>
4 -<cfoutput>#session.checkout.vehicle.mileage3#</cfoutput>
3 -<cfoutput>#session.checkout.vehicle.mileage4#</cfoutput>
2 -<cfoutput>#session.checkout.vehicle.mileage5#</cfoutput>
1 -<cfoutput>#session.checkout.vehicle.mileage6#</cfoutput>
So then I will be able to prefill in an already created form that has the boxes split for only one per box.
Where I am super confused and trying to comprehend is that there will not always be 6 variables. Let's say the mileage is 2344. I am assuming it will need to know to start backwards, counting from the right to the left. That's why I started 6 at #session.checkout.vehicle.mileage1#
Hopefully I have not super confused anyone with what I am trying to do. And any help would be greatly appreciated!!
<cfparam name="form.mileage" default="#session.checkout.vehicle.mileage#">
...
<label for="mileage">Mileage:</label>
<input type="text" name="mileage"
id="mileage"
value="<cfoutput>#form.mileage#</cfoutput>">
Edit:
The issue I am having with this is let's say the mileage is 9000 all 0's will not show. (which is great for the first two zero's in (009000) but after the 9 those 0's would still need to show.) Do you any ideas for that issue? Or should this be a new question?
<cfset Mileage = "9000" />
<cfif mileage is not "Exempt">
<cfset Mileage = NumberFormat(trim(Mileage),"000000") />
<cfset MilArray = ReMatch("\d",Mileage) />
<cfelse>
<cfset MilArray = ["E","x","e","m","p","t"]>
</cfif>
<cfdump var="#MilArray#">
<cfif MilArray[1] is not "0">
<!---Section6 First box Odometer Reading--->
<cfpdfformparam name="E" value="#MilArray[1]#">
<cfelse>
<cfpdfformparam name="E" value="">
</cfif>
If I'm understanding, you want to divide the string into six easy to work with variables, or whatever the length of the variable is.
<cfset Mileage = "123456" />
<cfset MilArray = ReMatch("\d",Mileage) />
<cfdump var="#MilArray#" />
You can actually stick a Reverse() in there to reverse the string, this may be handy because you can have [1] at the ones place, [2] at tens, [3] at hundreds.
<cfset Mileage = "123456" />
<cfset MileageR = Reverse(Mileage) />
<cfset MilArray = ReMatch("\d",MileageR) />
<cfdump var="#MilArray#" />
\d by itself in regular expressions just means "one digit". It's the same as [0-9].
As the CFDUMP will show, ReMatch will split your mileage into an easy to work with array. If you use the reverse as above, you can say "The last digit of your mileage is #MilArray[1]#.", as an example.
Edit:
you know the \d ? is there a way to have it be either \d or only the word Exempt? is it possible to create both of those?
There are a few ways.
You can say
<cfif mileage is not "Exempt">
...
<cfelse>
<cfset MilArray = ["Exempt"]>
</cfif>
which creates a one dimensional array populated with "Exempt" as the only element, which might be useful later in your code so you know MilArray is always an array, or you can simply always work with the <cfif mileage is not "Exempt">.
A regex to accomplish the same thing is possible but it achieves the same as the above cfif, and you'd have to write exempt backwards if you're using reverse, like this
<cfset MilArray = ReMatchNoCase("\d|^Exempt$|^tpmexE$)",trim(Mileage)) />
<cfif MilArray[1] is "tpmexE"><cfset milArray = ["Exempt"] /></cfif>
Edit #2:
<cfif isDefined("session") and structKeyExists(session, 'checkout') and structKeyExists(session.checkout, 'info') and structKeyExists(session.checkout.info, 'oreading')>
<cfif isDefined("#MilArray[6]#") eq "">
<cfpdfformparam name="E" value="">
<!---Section6 First box Odometer Reading--->
<cfelse>
<cfpdfformparam name="E" value="#MilArray[6]#">
</cfif>
</cfif>
This is a task for ArrayIsDefined() (link)
<cfif isDefined("session") and structKeyExists(session, 'checkout') and structKeyExists(session.checkout, 'info') and structKeyExists(session.checkout.info, 'oreading')>
<cfset MilArray = ReMatch("\d",session.checkout.info.oreading) />
<cfif not ArrayIsDefined(MilArray,6)>
<cfpdfformparam name="E" value="">
<!---Section6 First box Odometer Reading--->
<cfelse>
<cfpdfformparam name="E" value="#MilArray[6]#">
</cfif>
.... I assume that it continues on down from here... <cfif not ArrayIsDefined(MilArray,5)>........</cfif>
</cfif>
Finally, while there's contention here on whether to use StructKeyExists() over IsDefined(), there's a narrow field where isDefined() fails.
(Don't put structures in the top level and in the variables scope. Cold Fusion gets confused--IE, don't create an object called "variables.form" or "variables.url"). Beyond that, It's mostly just semantics.
Anyway. once you have the above code working (because it's your code and your familiar with it), you might find it useful to switch to the easier to read IsDefined() version, because isDefined can check several levels deep in one condition.
<cfif isDefined("session.checkout.info.oreading')>
<cfset MilArray = ReMatch("\d",session.checkout.info.oreading) />
<cfif not ArrayIsDefined(MilArray,6)>
<cfpdfformparam name="E" value="">
<!---Section6 First box Odometer Reading--->
<cfelse>
<cfpdfformparam name="E" value="#MilArray[6]#">
</cfif>
</cfif>
Edit 3:
Leigh points out
Why so complicated? Can't you just left pad the value with spaces or zeroes? Then change the regex to check for either a digit or space? Then the array will always have six elements
This can be achieved like this:
<cfset Mileage = "exempt" />
<cfif mileage is not "Exempt">
<cfset Mileage = NumberFormat(trim(Mileage),"000000") />
<cfset MilArray = ReMatch("\d",Mileage) />
<cfelse>
<cfset MilArray = ["E","x","e","m","p","t"]>
</cfif>
<cfdump var="#MilArray#">
Which would conveniently drop Exempt into place (handy that it's 6 characters).
You need to do some prechecking before you start generating the pdf to make sure that mileage variable is Exempt or or numeric.
<cfif len((trim(mileage)) gt 6 or not ((isNumeric(trim(mileage))
or mileage is "exempt")>
<!--- The 6 above is a len-check, you may need to update that number to
something else later, but you'll have to put the same number of 0s
in the NumberFormat function.
If you change that number, and the 0s, you'll need to pad the
"Exempt array"... ie ["E","x","e","m","p","t"," "] --->
....raise a flag...
</cfif>
Here is a simpler way.
originalNumber = "123";
sixDigitNumber = right(("000000" & originalNumber), 6);
<cfoutput>
<cfloop from="1" to = "6" index="position">
do something useful with #Mid(sixDigitNumber, position, 1)#
</cfloop>
<cfoutput>
Is there a logical difference between the following two blocks? And is there one form more correct than the other? They would both reside in their own function--something I omitted here.
<cfset local.result = 1 />
<cfset local.i = 1 />
<cfloop from="1" to="5" index="i">
<cfset result = result * i />
</cfloop>
And
<cfset local.result = 1 />
<cfset local.i = 1 />
<cfloop from="1" to="5" index="i">
<cfset local.result = local.result * local.i />
</cfloop>
Yes. In your second example you are making the exact same result; however, you have improved readability by explicitly identifying the scope you intend to modify--which is a good thing.
ColdFusion, will first search the LOCAL scope, so, you have not saved ColdFusion much processing; however, the code is cleaner now. If result existed in the CLIENT or COOKIE scope you would have saved ColdFusion the work of having to first evaluate four or five other scopes.
I once used to use the 'var result = 0;' style of localizing variables to a function, but, I now explicitly identify all my scopes to help ensure I have correctly scoped all variables and make the code easier to understand for others.
To summarize, the code is exactly the same to the machine but is now easier to understand for the human.
One suggestion... change:
<cfset local.i = 1 />
<cfloop from="1" to="5" index="i">
to
<cfloop from="1" to="5" index="local.i">
one less line of code, even more clear what's going on.