3

Our code base has quite a bit of the following example as we allow a lot of our base pages to be customized to our customers' individual needs.

<cfif fileExists("/custom/someFile.cfm")>
    <cfinclude template="/custom/someFile.cfm" />
<cfelse>
    <cfinclude template="someFile.cfm" />
</cfif>

I wanted to create a custom CF tag to boilerplate this as a simple <cf_custominclude template="someFile.cfm" />, however I ran into the fact that custom tags are effectively blackboxes, so they aren't pulling in local variables that exist prior to the start of the tag, and I can't reference any variable that was created as a result of the tag from importing the file.

E.G.

<!--- This is able to use someVar --->
<!--- Pulls in some variable named "steve" --->
<cfinclude template="someFile.cfm" />
<cfdump var="#steve#" /> <!--- This is valid, however... --->

<!--- someVar is undefined for this --->
<!--- Pulls in steve2 --->
<cf_custominclude template="someFile.cfm" />
<cfdump var="#steve2#" /> <!--- This isn't valid as steve2 is undefined. --->

Is there a means around this, or should I utilize some other language feature to accomplish my goal?

AntiTcb
  • 609
  • 4
  • 15
  • You could add a path to a "custom tags" folder in your application.cfc, and then just call out the tag normally. ColdFusion will search for a matching tag in that folder first, as long as it's first in your list of folders. Just add: `this.customTagPaths = "#fsRoot#/tags/custom/,#fsRoot#/tags/library/";` – Redtopia Dec 18 '18 at 16:22
  • @Redtopia My issue is not with that it can't find the custom tag or that I don't have a custom folder for my tags that my application searches through. – AntiTcb Dec 18 '18 at 16:28
  • 3
    I thought you needed a way to call a custom tag without having to do the test to see if it exists. Sorry... cfinclude is different from cfmodule (a custom tag) because it has it's own scope (thisTag). There are ways to pass data in/out of custom tags, but it's kinda crappy... you could pass in a struct, and put the retun values in the struct, or you could call `setVariable("caller.variableName")` from inside the tag. – Redtopia Dec 18 '18 at 16:38
  • I agree with @Redtopia, there are ways to pass data to custom tags and there are ways for the custom tag to access it's callers variable scope - but they are kinda crappy. Read all about it in the documentation - [Passing data to custom tags](https://helpx.adobe.com/coldfusion/developing-applications/building-blocks-of-coldfusion-applications/creating-and-using-custom-cfml-tags/passing-data-to-custom-tags.html) – Miguel-F Dec 18 '18 at 17:03
  • My question is similar to @Redtopia. Do you included files need variables and/or do they change variables? – James A Mohler Dec 18 '18 at 17:03
  • 1
    Works for narrow cases where I know all incoming and outgoing variable names, but we have cases where that's not necessarily know/should logically be assumed within code. Appreciate the affirmation that this *could* work but would be ugly either way. – AntiTcb Dec 18 '18 at 17:14
  • Refactor where you can... cfinclude can cause problems with code maintenance because it obfuscates what is happening under the hood. Use it sparingly if ever. In my opinion, cfinclude should be avoided. – Redtopia Dec 18 '18 at 18:08
  • 3
    I just noticed your tag is ColdFusion 7. Is that really the version? I do understand that it may be out of your control, but if it really is that old, you should press _VERY_ hard to upgrade to a much more recent version. And if cost is an issue, you can always go with Lucee. But you are setting yourself up for some serious issues running a version of CF (and especially the underlying Java) that is that old. Not to mention the fact that a newer version will allow you to use the features of CF that actually make it a good language. But CFMX7 is just begging for a severe breach. Or worse. – Shawn Dec 18 '18 at 23:39
  • @shawn I'm aware, and it's in the pipeline for us to move on to ColdFusion 9. If I had my way, we'd be doing away with it all and using ASP.NET Core. – AntiTcb Dec 19 '18 at 15:15
  • @AntiTcb Unfortunately, "doing away with it and move to x" is a result of the poor perception of CF based on older versions. CFML has evolved into a very good language that's a good bit more powerful and useful than anything – Shawn Dec 19 '18 at 15:39
  • Not trying to sound alarmist or fanboi-ish, but I do like CFML, and it concerns me to see sites still using an easily-exploitable version of CF. I think it would definitely be beneficial for Adobe to offer some serious upgrade incentives to shops running unsupported versions of CF. – Shawn Dec 19 '18 at 15:40

1 Answers1

5

Well, I question doing this at all but I know we all get handed code at times we have to deal with and the struggle it is to get people to refactor.

This should do what you are wanting. One important thing to note is that you will need to ensure your custom tag has a closing or it won't work! Just use the simplified closing, so like you had it above:

<cf_custominclude template="someFile.cfm" />

This should do the trick, called it has you had it : custominclude.cfm

<!--- executes at start of tag --->
<cfif thisTag.executionMode eq 'Start'>
    <!--- store a list of keys we don't want to copy, prior to including template --->
    <cfset thisTag.currentKeys = structKeyList(variables)>
    <!--- control var to see if we even should bother copying scopes --->
    <cfset thisTag.includedTemplate = false>
    <!--- standard include here --->
    <cfif fileExists(expandPath(attributes.template))>
        <cfinclude template="#attributes.template#">
        <!--- set control var / flag to copy scopes at close of tag --->
        <cfset thisTag.includedTemplate = true>
    </cfif>
 </cfif>
 <!--- executes at closing of tag --->
 <cfif thisTag.executionMode eq 'End'>
    <!--- if control var / flag set to copy scopes --->
    <cfif thisTag.includedTemplate>
        <!--- only copy vars created in the included page --->
        <cfloop list="#structKeyList(variables)#" index="var">
            <cfif not listFindNoCase(thisTag.currentKeys, var)>
                <!--- copy from include into caller scope --->
                <cfset caller[var] = variables[var]>
            </cfif>
        </cfloop>
    </cfif>
 </cfif>

I tested it and it works fine, should work fine being nested as well. Good luck!

<!--- Pulls in steve2 var from include --->
<cf_custominclude template="someFile.cfm" />
<cfdump var="#steve2#" /> <!--- works! --->
Nate
  • 2,881
  • 1
  • 14
  • 14
  • 2
    Should also note that closing the tag causes it to execute twice, so if you don't use `thisTag.executionMode`, anything outside of those `cfif` blocks will be duplicated. – Shawn Dec 18 '18 at 23:41