CFLOOP and URL vars

Posted At : August 12, 2008 7:38 AM | Posted By : Dave
Related Categories: ColdFusion 8

I will preface this by stating you will probably not want to do this. However, I figured I would blog about it anyways as the outcome was very strange.

In a system I was working on I was attempting to reuse some code to expedite some new development. What I did was create a wrapper for an existing cfm. I used cfinclude to bring in the code we were going to reuse and then looped over it. The code that was being looped generated images for output to a browser. So instead of an image tag pointing to a .jpg for instance, it pointed to our image.cfm. I was looping this image code to generate large amounts of images in one call.

Here is the code that was doing the loop and returning an array of what was done. The image.cfm file was expecting url vars so we just set them prior to the include. We then built an array of the image path generated and the url var structure. This way the code could then loop through was was generated later.

<CFSET ResArray = arrayNew(2)>

<CFLOOP QUERY="getItems">
   <CFSET url.a = item_id>
   <CFSET url.height = 300>
   <CFSET url.width = 200>
   <CFINCLUDE TEMPLATE="image.cfm">
   <CFSET ResArray[i][1] = 'generated image path'>
   <CFSET ResArray[i][2] = url>
</CFLOOP>

The strange part in all this was what came out at the end in the array. Every second dimension of the array was identical. It was always the information from the last row in the query. So this leads me to believe that even though I am setting the second dimension of the array what the url struct is it does not matter. It appears to just make a reference to the url struct.

I then went on to do some further checking. I tried changing the line that sets dimension 2 to the url struct to this:

<CFSET ResArray[i][2] = structCopy(url)>

This had no effect. The output was still the same. Then to throw another wrench into the works I added this gem to the code:

<CFIF currentRow EQ 5>
   <CFSET url.extra = 5>
</CFIF>

I knew there were 10 rows in the return so I picked the middle row. What happened was strange. Every array element generated from 5 - 10 contained the "extra" item. Where rows 1 - 4 did not. However, all the items still had all the rest of their values from the last array item.

Then I tried to use structDelete then structnew at the start of each loop. This had not effect except for one strange thing. The "extra" item vanished from all output, even row 5.

There you have it. I am not sure where to go from here except back to the drawing board and rewrite some code into a CFC. I probably should have converted it to a CFC in the first place.

Till next time,

--Dave

Comments
Did you try duplicate()?
# Posted By todd sharp | 8/12/08 9:14 AM
At first I thought "Of course, your not initializing url with structNew()", but I noticed that you did mention that you tried that later, and also It looked like assigning structCopy(url) should also work. I did a little testing to see if I could figure out what might be going on. It looks like structCopy() doesn't do what you would expect when used on the url scope (probably because it is a special scope) It does create a new instance, but the keys still seem to reference the same values (which is probably why you can add a new key and it only shows up in the later copies).

Anyway - the solution seems to be to use duplicate() instead of structCopy() when dealing with the url scope. It does just what you would expect.
# Posted By Lee | 8/12/08 10:18 AM
Duplicate() did do it. StructCopy does not seem to make a copy of the url struct.
# Posted By Dave Ferguson | 8/12/08 10:53 AM
Yeah, structures and query objects use references in CF.

Just a thought, try refactoring your code:

<CFSET ResArray = arrayNew(1)>

<CFLOOP QUERY="getItems">
<CFINCLUDE TEMPLATE="image.cfm">
<CFSET ResArray[getItems.currentRow]['path'] = 'generated image path'>
<CFSET ResArray[getItems.currentRow]['url']['a'] = getItems.phnumb>
<CFSET ResArray[getItems.currentRow]['url']['height'] = 300>
<CFSET ResArray[getItems.currentRow]['url']['width'] = 200>
</CFLOOP>



<cfdump var="#resArray#"/>
# Posted By Dutch Rapley | 8/12/08 11:12 AM
I used the wrong column name in previous comment


<CFLOOP QUERY="getItems">
<CFINCLUDE TEMPLATE="image.cfm">
<CFSET ResArray[getItems.currentRow]['path'] = 'generated image path'>
<CFSET ResArray[getItems.currentRow]['url']['a'] = getItems.item_id>
<CFSET ResArray[getItems.currentRow]['url']['height'] = 300>
<CFSET ResArray[getItems.currentRow]['url']['width'] = 200>
</CFLOOP>
# Posted By Dutch Rapley | 8/12/08 11:15 AM
Sorry, after reading the blog entry again, I do realize you need the URL Structure for use inside the included image.cfm file. Here is the code again, updated appropriately.

<CFSET ResArray = arrayNew(1)>

<CFLOOP QUERY="getItems">

<CFSET url = StructNew() />
<CFSET url.a = item_id>
<CFSET url.height = 300>
<CFSET url.width = 200>

<CFINCLUDE TEMPLATE="image.cfm">

<CFSET ResArray[getItems.currentRow]['path'] = 'generated image path'>
<CFSET ResArray[getItems.currentRow]['url']['a'] = getItems.item_id>
<CFSET ResArray[getItems.currentRow]['url']['height'] = 300>
<CFSET ResArray[getItems.currentRow]['url']['width'] = 200>

</CFLOOP>
# Posted By Dutch Rapley | 8/12/08 11:22 AM
Dutch,
Your last comment is just what I thought at first, but try it out. StructNew() doesn't work when you assign it to the url scope. You have to use x = duplicate(url) to create a deep copy of the url scope.
# Posted By Lee | 8/12/08 11:30 AM
hehe, I admit, I should have tested it.

One more time! Put the following inside your CFLOOP.

<CFSET url_vars = StructNew() />
<CFSET url_vars.a = item_id />
<CFSET url_vars.height = 300 />
<CFSET url_vars.width = 200 />
<cfset url = Duplicate(url_vars) />

<CFINCLUDE TEMPLATE="image.cfm">

<CFSET ResArray[getItems.currentRow]['path'] = 'generated image path' />
<CFSET ResArray[getItems.currentRow]['url'] = Duplicate(url_vars) />
# Posted By Dutch Rapley | 8/12/08 11:44 AM