Brandon asks:
I have a UDF that gets called a few times each page request. I need this UDF to save the results of each request to an array. Then I need it to output that array to another UDF. Is that possible?
Here is an example
whatever_page.cfm (included on request)
<cfset newButton("first")>
another_page.cfm (also included on request)
<cfset newButton("nextButton")>So you can see that multiple templates are calling this function. I would like this function to then store the results somehow so that they can all be output together in a central location. Is that even possible?
This needs to be done because in my app plugins are installed and try to create buttons. Then once all the templates call the newButton function, another function retrieves that array (or struct, doesn't matter) and loops over it, creating the dynamic buttons.
Is this possible? Of course it. ColdFusion is everything and the kitchen sink! Seriously though - yes, this is possible, and there are a few different ways of doing it. The basic issue is finding a way to store each button so you get it later. As you can probably guess, this involves using one of ColdFusion's built in scopes - but which?
The Request scope is the best choice for this type of functionality. It will only last for the current request and it provides a simple way for multiple files to share the same data. Here is a simple example:
<cfscript>
function newButton(b) {
if(!structKeyExists(request,"buttons")) request.buttons = [];
arrayAppend(request.buttons, b);
}
function getButtons() { return request.buttons; }
</cfscript>
<cfset newButton("Nuke the Zoo!")>
<cfset newButton("Obama 08!")>
<cfset newButton("Bunnies have 2D8 HP")>
<cfdump var="#getButtons()#">
This script has 2 UDFs. One for adding a button to an array, and another for returning the array. The second UDF isn't technically needed. I could have just dumped request.buttons. By the same token the first UDF isn't necessary either. But together they provide a nice, abstract way to store and retrieve the data.
Now you may ask - what would happen if someone else does: request.buttons = "Foo"? That would obviously screw things up pretty bad. In cases where you control the code, as you are here, it isn't such a big deal. If your button display code all of a sudden started breaking because the buttons weren't a proper array, you would have a good idea of what to look for. You mentioned you were building an application with plugins. I'd be willing to bet you will need this functionality in multiple places. Instead of using the root Request scope, it may make sense to work in your own area underneath the request scope. If your app was code named Foo, then perhaps request.foo.buttons would be more sensible. Or request.foo.ui.buttons. You get the idea. You can even abstract this name within another UDF:
function getStore() {
if(!structKeyExists(request,"corestore")) request.corestore = {};
return "corestore";
}
function newButton(b) {
if(!structKeyExists(request[getStore()],"buttons")) request[getStore()].buttons = [];
arrayAppend(request[getStore()].buttons, b);
}
function getButtons() { return request[getStore()].buttons; }
This version is like the first one, except that I've added a getStore() UDF to create a root level key for my data. Notice I create the structure if it doesn't exist, and then I return the name. The other UDFs don't' care about this name. They just store what they need to. You could get even fancier of course but hopefully this gives you an idea on how to solve your problem.
Archived Comments
I am so torn! That works perfectly, and is similar to what I was thinking in my head. But then is it "right" to call the request scope from a UDF? Common sense tells me to use whatever works, but so often we grimace when UDF's call the application, request or any other persistent scope.
But other than that slight hesitation, the suggestion is perfect! Thank you very much!
One of these days I am going to have to stop by your wish list. Thank you so much!
I know I've said - many times - UDFs should be abstract, black boxed, etc. However in this case I think it is a perfectly fine usecase for the situation. You have a need for a storage mechanism that - by design - must exist outside the UDF itself.
@brandon,
personally if i was you, i would make a cfc and encapsulate these two functions along with the request scope storage in it.
reason being is so that down the road, you're not looking at the code and going wtf?
If you have hesitation accessing the request scope, why not just pass it in to the function. Slight code change to reference the argument variable instead of the request scope directly.
if you do make it into a cfc, you could do the same or just create the object in the request scope and use the local cfc (this/variables) scopes.
I would have just gone with a cfc, but with the nature of the app it just wouldn't work. The plugins are created and installed by people other than myself, so to tell them that they have to reference the cfc (and the directory) gets a little to overwhelming for some people. It is easier to just let them know that they need to call the setButton("buttonName") function somewhere in the header of their root plugin page.
I know that sometime down the road I might take a look and wonder what is going on, but fortunately I am in the habit of writing more notes than code. (When I first started Forta hammered that point into me).