It is a travesty beyond travesty that the cfparam tag does not support a 'hint' attribute. So I aims to fix that with a custom tag! This is the first time I've messed with custom tags, but so far I cannot get them to work.
I created a file called param.cfm, it merely outputs a cfparam that transposes the provided ATTRIBUTES but allows us to specify a hint attribute on the <cf_param> tag to sate that need.
I placed this file into c:\inetpub\wwwroot\project\custom-tags.
In my application.cfc, it states simply:
<cfset THIS.name = hash( getCurrentTemplatePath() )>
<cfset THIS.customTagPaths = 'c:|inetpub\wwwroot\project\custom-tags'>
<cf_param name="test" type="string" default="foo" hint="YES! WE CAN PUT HINTS ON PARAMS!">
Well, CF comes back and says:
Expression Exception - in c:/inetpub/wwwroot/project/application.cfc: line 17
Cannot find CFML template for custom tag param.
I did some research and saw that there are supposed to be 4 locations CF will look for custom tags:
Well, I have per-application settings enabled in the CF ACP, and I have both named and defined the custom tags path for this application, yet CF still cannot find it. So what am I doing wrong here?
Just a FYI, I tried putting this tag in the same directory as application.cfc and boom, it found it. So the problem cannot be related to this code running outside methods in the application.cfc. It simply is ignoring the THIS.customTagPaths value that was provided.
Also, the forum editor is very screwy. For 'protection' reasons, probably, it changed my custom tag path to use a pipe ( | ), but the path is correct. I cut and paste it into the run prompt and up came the directory with my custom tag in it. I also provided EVERY application variable before aborting just to make sure things were configured up the wazoo (technical term). I've even restarted the Application Server service. Someone suggested that "once CF finds a CF tag (or doesn't), it caches that result until a service/server restart.
I'm sorry, I don't really have an answer for you except for routine stuff. Does CF have read/execute permission to that directory? Have you tried dropping the cfm into the CF custom_tags server directory?
I am curious though, why do you think (so strongly) that cfparam needs a hint attribute? What purpose would it server?
I placed the param.cfm into c:\coldfusion-10\cfusion\CustomTags and CF could not find the tag.
As for the HINT attribute, I just love the thing. I am a stickler for comments, and I hate having to provide a comment line for each param I create (and I param about EVERY variable that I will use) It's just a cleaner implementation if I can provide the hint inline with the param declaration. I was being a bit facetious with saying it was adamant, but I'd love to extend cfparam to support it, and this seems like the next best thing.
Also, how would I determine (without providing simple file read/writes) that CF has read/write rights to a specified folder? Is there a NTFS account that is supposed to be on it? What account does CF use to determine those rights?
I would guess that, for whatever reason, those paths are not yet ready at that point in the application (you're trying this from App.cfc, right)? I would bet that if you tried it from a cfm that they would work.
Hints are not really there for commenting though. They exist for creating API documentation. Since cfparams cannot really be a part of an API, it doesn't really make sense for them to have hint attributes.
Right, well I could change the tag's attribute to use a more semantic value like: <cf_param name="foo" type="string" default="der" info="Information about the param goes here">
If invoking the custom tag from the pseudo-constructor area of the application.cfc works, I'm not sure if executing it from a request would be any different. It worked when in the same directory as the calling page, but not when placed in the CF Custom Tags directory or when specified in the per-app THIS.customTagPaths variable.
Aegis Kleais wrote:
If invoking the custom tag from the pseudo-constructor area of the application.cfc works, I'm not sure if executing it from a request would be any different.
I suggest you try it. That would give you your answer.
I had to go through TONS of commenting out (application.cfc, it's oAS, oSS, oRS, oR methods), and then go into IIS and start disabling IIS URL rewrite rules that I had setup in order to emulate a simple non-modified request.... but I did it!
...and it doesn't work.
The application.cfc sets values, including the customTagPaths variable. The oAS was commented out, but returns true. The oRS was commented out and returns true. The oR was commented out, and I added:
and it returns void. I then call: http://localhost/file.cfm which has the following code in it:
<cf_param name="test" type="string" default="foo" info="Info about tag">
<p>No go, boss.</p>
And up comes "Hello. No go, boss". The debug info states that ColdFusion could not find the param custom tag.
I have verified that this simply doesn't work (on any version of CF that supports the customTagPaths setting in Application.cfc).
I cannot see anywhere that it's stated that this is by design, or what. If it's by design, I'd call it a bit shonky.
It also does not work in Railo.
That said, I'm with Jason: if you need to put comments by your <cfparam> tags, then just put comments. Creating a custom tag just so you can embed a comment in it is... um... a bit misguided, IMO. Comments are not supposed to be part of your executable code, and to write your code in such a way as they are part of your execute code is simply wrong-headed. Sorry.
Well, if I understand correctly, the first time a page is rendered, it compiles to code that omits all comment processing.
Even if I don't "extend" the <cfparam> tag with this attribute, I can see the value in custom tags, however if they don't work properly, it kind of harkens back to a bigger problem. I can resolve to just comment out my params for the time being, but I'm a bit stubborn with this custom tag problem.
I cross checked Nadel, Forta and Camden's sites and couldn't find anything regarding a discovery that customTagPaths was broken/ignored. Just seems like a hard pill to swallow for such a glaring issue.
I really don't want to put my custom tags into the CF server, because I won't have access to doing this on my host. Putting them in the same directory hierarchy would just gnaw at my over zealous nature to keep my directories clean and organized....
Sorry, I was not clear.
The customTagsPath thing doesn't work for custom tag calls from the pseudoconstructor. It works fine everywhere else.
This doesn't really surprise me now that I think about it.
CF is basically doing this, under the hood:
appSettingAndHandlers = new Application() theApp.setName(appSettingAndHandlers.name) theApp.setCustomTagPaths(appSettingAndHandlers.customTagPaths) //etc if not theApp.started() then theApp.start() appSettingAndHandlers.onApplicationStart() // etc
So whilst it gathers the settings from the pseudo constructor of Application.cfc, just because there's a line of code declaring the setting's value doesn't mean the value has been applied to anything yet.
Bear in mind Application.cfc is just a normal CFC, and the act of doing this:
this.customTagPaths = "stuff here";
merely sets a variable. Setting that variable does not magically make the variable mean something, or execute any other code.
Heh. That's quite interesting. I never thought of any of this stuff before.
But I'm still confused. Something HAS to be borked.
THIS.customTagPaths (which is merely a variable set in the application component) has specific functionality to the operation of the application. Like how you can use THIS.sessionManagement = true in order to change the default value of 'no/false', and ColdFusion will look at that value in order to determine whether SESSION scope can be used in the application.
I thought all application.cfc app variables were to be set in the pseudo-constructor, outside of all methods, and put into the THIS scope. That's what I did, and yet, ColdFusion never checks the paths defined there for where to look for Custom Tags.
Look at the code in the pseudo-constructor this way. This is how it runs everytime (every request) that Application.cfc is instantiated.
1. <!--- A variable named "name" is created in the this scope --->
2. <cfset THIS.name = hash( getCurrentTemplatePath() )>
4. <!-- A variable named "customTagPaths" is created in the this scope --->
5. <cfset THIS.customTagPaths = 'c:|inetpub\wwwroot\project\custom-tags'>
7. <!--- You try to use the param.cfm custom tag --->
8. <cf_param name="test" type="string" default="foo" hint="YES! WE CAN PUT HINTS ON PARAMS!">
Did any code get executed between lines 5 and 8 to actually tell CF to take the value set in line 5 and make that a custom tag path? The answer is no. At this point in the code THIS.customTagPaths is just a string variable sitting in the Application.cfc object. ColdFusion does not come across that line and say "Oh, this is a special variable, let me pause this thread, run out and set those custom tag paths, and then go back and continue processing this code".
AFTER the pseudo constructor code finishes running CF will start processing that stuff. It will go set up the custom tag paths, it will create per-application mappings, etc etc. Then it will start firing event handlers. oAS will run, oSS will run, etc. Finally it will include the requested template.
When it comes across like 8 above it will search for the param.cfm where it can (same directory, server directory), but at this point it has not yet executed any code that will set up custom tags per-application based on the value in App.cfc's pseudo constructor.
I hope this makes sense.
OK, I'm following you so far, and testing verifies this.
If I set:
<cfset THIS.applicationTimeout = "I love figgy pudding.">
Logically, we know that this is wrong, as ColdFusion WILL expect this to be a value returned by the createTimeSpan() BIF. BUT the code is valid, because if we dump THIS immediately after it, we see that value set in the assigned variable. For all practical purposes, all we did here is set a variable. So like you explained, customTagPaths IS just a string which happens to be a path, but is nothing more to CF at the moment.
Then, like you explained, after the pseudo-constructor is processed, CF runs through it's logic:
> Has the Application Started yet? No? I need to run onApplicationStart()
> Has a Session Started yet? etc...
But having understood that, you and I both know the THIS.customTagPaths variable to HAVE particular importance when defined in the APPLICATION.CFC's THIS scope. So where is ColdFusion when it gets to a point where it SHOULD care about this variable and the way it should have an effect on how ColdFusion handles (in this case) the location of Custom Tags on a per-application basis?
I mean, I moved the <cf_param> tag into the onRequest() method for testing at a different point in the request process, but are we saying that even at THAT point, we cannot use Custom Tags?
OK, I think I'm getting somewhere.
CF DOES NOT give us grief if we set those special THIS-scoped variables to whatever the heck we want while processing the pseudo-constructor. HOWEVER, the moment it fires off the oAS(), CF barks at me and says "Hey, the value you set in the application.cfc's THIS-scope variable called 'applicationTimeout', I cannot accept 'work darnit, work!', cause it's not a number.
CF seems to do logical checking on those special variables at the earliest, in the oAS().
Using THAT knowledge, we know that CF sanity-checks those special variables in the oAS() to ensure they are valid, and probably goes so far as to actually process them internally to modify the application's behavior (ie, like changing the default 'sessionManagement' from 'false' to 'true').
Now, we know that CF will NOT process the oAS() on subsequent requests IF it successfully started up. Before app initialization, CF goes:
onApplicationStart() > onSessionStart() > onRequestStart() > onRequest()/onCFCRequest() > onRequestEnd()
After app initialization, CF goes:
onRequestStart() > onRequest()/onCFCRequest() > onRequestEnd()
But at THAT point, the oAS() ran and validated all code, so even when we're processing a request after app initialization, ColdFusion is probably ignoring all psuedo-constructor logic that changes the THIS scope variables (in effect, caching what the pre-app init run through defined and configured CF to run as)
....am I barking up the right tree here?
I did this in the pseudo-constructor:
<cfset THIS.name = hash( getCurrentTemplatePath() )>
<cfset THIS.applicationTimeout = createTimeSpan( 0,4,0,0 )>
<cfset THIS.customTagPaths = 'c:\inetpub\wwwroot\project\custom-tags'>
I then commented out the code in my oAS() but did a <cfreturn true>
I then commented out the code in my oRS() but at the top I added:
<cf_param name="der" type="string" default="foo">
I also modified my param.cfm so at the top I added:
<cfabort showerror="This better work!">
And when I ran the request, I GOT THE ABORT'S MESSAGE IN THE PARAM.CFM TAG!
It seems the line of logic you set me down was right. In the Pseudo-constructor, CF doesn't care WHAT you do, what variables you set. But once the oAS() starts, it will ensure that special variables in the application.cfc's THIS scope are sanity-checked, and then use their values to modify internal-behavior.
I took it one step futher and moved that <cf_param> call INSIDE the oAS() (right before it returned true), and the code still ran! This must mean that CF does its sanity checks before it processes the code in the oAS() method.
Yep, that was my expectation.
I meant to come back to you before (I was waylaied with someone else's problem...) to clarify that the only place the this.customTagPaths look-ups don't work is in the pseudo-constructor, but once we're at the stage of any of the event handlers running (oAS, oSS, oRS), then your custom tag calls will work fine. You've discovered this off yer own bat.
Nah, not on my own. I'd have never have come to the logical conclusion (and subsequent testing to prove it) without the help you provided. (Top notch, as always!)
I REALLY think that this is a mechanic of ColdFusion that warrants official documentation. But in the same way, this level of comprehension is what I love to know. It's not that "We do THIS because THIS is how it's been done." It's that "We do THIS because if THAT, when THIS is THAT". In other words, we now KNOW why CF behaves the way it does. Rather than being given the fish so that we can eat, we now know HOW to get a fish so that the problem of being hungry doesn't elude us.
I'll run this by my co-worker. We always love sharing AHHH! So THAT's why-moments after things like this stump us for hours at a time.
The great thing is that this type of behavior is probably duplicated in other areas of ColdFusion; so since we learned it here, that logical thinking can actually prove to help us prevent problems in other areas. Comprehension will always be better than compensation.
What am I? Chopped Liver?
Anyway, you marked the wrong part of this thread as the correct answer. It should have been #11.
Glad you got it working (even though I don't agree with what you're trying to do).
Thanks for the assist, chopped liver.
Thanks for the assist, chopped liver.
As an aside, I've taken the liberty of summarising this on my blog: http://adamcameroncoldfusion.blogspot.co.uk/2012/10/clarification-in-my-mind-as-to-how.html.