How to add attributes ColdFusion tags in bulk - coldfusion

I was curious if there is a way to force a ColdFusion tag to hold an attribute as default, such as the datasource in cfquery.
For example instead of writing
<cfquery datasource="mydatasource">
I can write
<cfquery>
and the system automatically knows that the datasource is "mydatasource".
Would be really cool if this was possible.

It is actually possible for datasource, but not for everything.
You may set a this.datasource="mydatasource" as the default datasource in your Application.cfc
https://wikidocs.adobe.com/wiki/display/coldfusionen/Application+variables

The practical answer to your question are the custom tags. You can extend the features of ColdFusion tags to match your needs.
Taking into example the cfquery tag and wrapping a custom tag around it. Provide all the default values you want for the parameters of the cfquery into the tag's attribute default.
So essentially your custom tag page would be something like:
flexiquery.cfm
<cfif THISTAG.ExecutionMode EQ 'end'>
<cfparam name="Attributes.datasource" default="someDSN">
<cfparam name="Attributes.cacheWithin" default="#CreateTimeSpan(0,6,0,0)#">
<cfparam name="Attributes.maxRows" default="25">
<cfparam name="Attributes.timeOut" default="600">
<!--- some logic you want to perform --->
<cfquery datasource="#Attributes.datasource#"
cacheWithin="#Attributes.cacheWithin#"
maxRow="#Attributes.maxRows#"
timeOut="#Attributes.timeOut#"
<cfoutput>#THISTAG.GeneratedContent#</cfoutput>
</cfquery>
<!--- Caller assignment and other processing --->
</cfif>
And now you can use it and re-use it across your project, the way you wanted and even overriding the value you want to be different:
<cf_flexiquery>
<!--- you query here --->
</cf_flexiquery>
or
<cf_flexiquery maxRows="100" timeOut="1200">
<!--- you query here --->
</cf_flexiquery>
It gives you a fair idea of how to go with it. I have extended the custom tags features to leverage the features of cfhttp, cfpdf, cffile etc.
This is only way you can adopt the flexibility you want with ColdFusion tags and it works perfectly.

Related

Pass queried parameters to ColdFusion file multiple times?

I have an cfmail function set-up in a particular file, email_output.cfm, which requires an ID passed to it to work properly, like email_output.cfm?ID=1. I want to set up a cron job that runs through a query returning the various needed IDs to pass. In testing, I can do the following:
<cflocation url="email_output.cfm?ID=10" >
But, since cflocation stops all other execution and opens another page, I can't loop through it. How would I pass parameters from a query to a single CF page multiple times?
Thanks - Joe
A custom tag sample implementation of this...
If this is your first time using a custom tag, it's easiest to put it in the same folder as the page calling it. There are a few options for putting it in a different directory, but let's start simple.
EmailMembers.cfm
<cfquery name="GetUIDs">
select userid from users
</cfquery>
<cfoutput query="GetUIDs">
<cf_maileach uid="#userID#">
</cfoutput>
Notice how I called my tag cf_maileach?
In the same directory, place maileach.cfm, see how the names match?
maileach.cfm
<cfif StructKeyExists(attributes,"uid") and val(attributes.uid) gt 0>
<cfquery name="getinfo">
select fname,lname,email
from users
where userID = <cfqueryparam cfsqltype="cf_sql_integer" value="#attributes.uid#">
</cfquery>
<cfmail to="#getinfo.email#" subject="Hi #getinfo.fname#">...</cfmail>
</cfif>
Notes
Depending on your version of cf, and whether you're using application.cfc or not, there are several ways to place a custom tag in an outside directory. There is also <cfmodule>
This is a sample only, something this basic is redundant, I was just trying to mimic what asker outlined. In this sample, I'm calling a query that could get all the data, only to use it to query row by row.
If you're not familiar with <cfqueryparam>, look it up, use it, love it.
Edit: While a CFHTTP method can serve this purpose, it suffers a few problems
Sessions are not automatically passed (even if the requesting server and destination server are the same.).
The page is accessed like a browser request. Application/OnRequestEnd are processed (and since session info is passed as well, this can cause problems trying to access files in secured areas.
Because of the above, the page would need to be in a folder with its own Application file to negate any application files above it in the directory hierarchy.
To combat 1, 2, and 3, You'd need to code in a layer of security, similar to your application's own security, so that the file is not vulnerable should the url be found.
Each call to the file via cfhttp would need to invoke some extra security checking.
It is significantly slower. In a very simple test with a zero-content application.cfc, the custom tag method was executing in literally <= 1/100th of the time. As actual function is added to the method, the difference in results would change.
Here is some sample code to test this yourself.
Contents of folder "safe":
Application.cfc
[ blank file, to negate my testing site's actual application.cfc ]
Testrun.cfm
<cfoutput><cfset starttick = GetTickCount()>
<cfloop from="1" to="20" index="i">
<cfhttp url="http://mysamesite.com/safe/http.cfm?u=#i#" method="get" result="test">
#test.filecontent#<br>
</cfloop>
CFHTTP Execution Time: #(GetTickCount() - starttick)#<br><br>
<cfset starttick = GetTickCount()>
<cfloop from="1" to="20" index="i">
<cf_testtag u="#i#"><br>
</cfloop>
CustomTag Execution Time: #(GetTickCount() - starttick)#<br><br>
</cfoutput>
testtag.cfm
<cfoutput>The ID entered was #attributes.u#</cfoutput>
http.cfm
<cfoutput>The ID entered was #url.u#</cfoutput>
Results (in milliseconds)
Each test was 20 passes at HTTP and 20 Passes at the custom tag.
CFHTTP Tag
661ms 6ms
1624 5
616 5
460 4
522 6
816 4
You can do this by using cfhttp also
<cfquery name="GetUIDs">
select userid from users
</cfquery>
<cfloop query="GetUIDs">
<cfhttp url="http://localhost:8500/cf10/test.cfm?id=#userid#" method="get" result="test">
</cfloop>

CFSelect not binding but text box is

I am using CF10. I have a Select:
<cfselect name="company" id="company" query="qcompany" display="placename" value="placeid" queryposition="below">
<option value="0">--Select--
</cfselect>
I have another cfselect that is bound to the first:
<cfselect name="People" bind="cfc:schedule.GetPeopleArray({company})" ></cfselect>
I cannot get the second cfselect to display any results. To test whether or not I am receiving data from my component (which I will display at the bottom), I bound a text box:
<cfinput name="test" bind="cfc:schedule.GetPeopleArray({company})" bindonload="false"/>
This text box is displaying the results of the call to my component every time, but the cfselect never displays anything.
What could I possibly be doing wrong?
I have tried returning arrays and queries from my component. No help. I have tried adding display and value attributes to the second cfselect. No help.
Here is my component:
<cfcomponent output="false">
<cffunction name="GetPeopleArray" access="remote" returnType="array" output="false">
<cfargument name="company" type="string" >
<!--- Define variables --->
<cfset var data="">
<cfset var result=ArrayNew(2)>
<cfset var i=0>
<cfquery name="qEmployee" datasource="texas" >
SELECT 0 as personid,'Select Person' as fullname,0 as sortorder
UNION
SELECT p.personid ,concat(firstname,' ',lastname) as fullname,3 as sortorder
FROM person p
INNER JOIN placeperson pp
ON p.personid=pp.personid
where personstatus='ACT'
and pp.placeid=#arguments.company#
order by sortorder,fullname
</cfquery>
<!--- Convert results to array --->
<cfloop index="i" from="1" to="#qEmployee.RecordCount#">
<cfset result[i][1]=qEmployee.personid[i]>
<cfset result[i][2]=qEmployee.fullname[i]>
</cfloop>
<!--- And return it --->
<cfreturn result>
</cffunction>
</cfcomponent>
Ultimately, you may want use jQuery anyway, but FWIW your existing code worked fine with CF10. (The only change was removing the JOIN for simplicity) So either you are using different code, or there is something else going on we are unaware of ..
Truthfully the Ajax functionality does have some "quirks". However, you should not have any problem with a simple case like this. Aside from adding a text field, what other debugging or troubleshooting steps did you perform? What I usually recommend is:
Test the CFC independently first. Access it directly in your browser with a variety of sample values:
http://localhost/path/to/schedule.cfc?method=GetPeopleArray&company=someValue
I did this with the original code and discovered an error occurred when the company value is not numeric, like an empty string. (I suspect that might have been the problem) You can prevent that error by substituting an invalid ID like 0 instead. Note, be sure to use cfqueryparam to prevent sql injection.
AND pp.placeid = <cfqueryparam value="#val(arguments.company)#"
cfsqltype="cf_sql_integer">
Enable the CF AJAX debugger in the CF Administrator. Then append ?cfdebug to your test script so you can view the console and check for problems/errors.
http://localhost/path/to/yourTestForm.cfm?cfdebug
Again, I did this after tweaking the query. But there were no errors. Your existing cfform code worked perfectly.
Usually those two steps are enough to pinpoint any problems. If not, make sure your Application.cfc file (if you are using one) is not interfering with the Ajax request. That is a common gotcha. Test the code in a separate directory that is outside any Application files.
EDIT: Also, you may as well set bindonload="false" for the select list too. Since you do not want to call the function when the page first loads. Only when the user selects something from the list.

if viewing a certain page then exclude content

I was wondering if its possible to exclude data from an wraparound page footer based on which content is loaded into the index page in ColdFusion
Roughly it would be something like.
IF (not = pageToBeExcluded)
THEN {
Show content
}
There are any number of ways to do that.
The simplest is probably to key on the page address.
<cfif CGI.SCRIPT_NAME DOES NOT CONTAIN "someArbitraryPage.cfm">
<!--- show this content --->
...
</cfif>
As mentioned, you might want to use getCurrentTemplatePath() or getBaseTemplatePath().
Personally, I would probably set a variable in the template where I want this to happen. The main benefit of this is that I don't need to keep changing my footer code every time I add (or remove) a file from my list of files where I want this.
<!--- in the template itself --->
<cfset request.suppressFooterContent = true>
Then, in the footer:
<cfparam name="request.suppressFooterContent" default="false">
<cfif NOT request.suppressFooterContent>
<!--- display content here --->
...
</cfif>

ColdFusion: do i need to use structKeyExists for every element of a deep struct?

Let's say i've just parsed someone else's XML document which is a response to an API request. I want to know if a value nested deep inside exists. If my API request worked, it will be in the same place every time. If my API request fails, the root of the XML is very different.
If I try <cfif structKeyExists(myStruct.level1.level2.level3, 'myTarget')> on a failed api request, I get the fatal error: Element LEVEL1.LEVEL2 is undefined in MYSTRUCT.
Of course, I could try to depend on the root level of the XML telling me of success or failure, and not looking for the result if it failed, but... barring that solution, what should i do?
Do i need to check for the existence of each level of the struct? As in:
<cfif structKeyExists(myStruct, 'level1')
and structKeyExists(myStruct.level1, 'level2')
and structKeyExists(myStruct.level1.level2, 'level3')
and structKeyExists(myStruct.level1.level2.level3, 'myTarget')>
<!--- ... --->
</cfif>
This is not a real-world problem, this is just something i've faced too many times. Please don't tell me solutions that involve changing the API or solutions like those in the third paragraph.
Thanks!
edit: i should have mentioned why i can't use isDefined() - some of the keys do not have syntactically valid names, so isDefined() throws an error, eg myStruct.level1[42].level3
XMLSearch
I would use the parsed XML document (i.e. xmlDoc) and XMLSearch:
<cfset xmlDoc = xmlParse(responseData)>
<cfset nodes = XmlSearch(xmlDoc, '/level1/level2/level3/myTarget')>
<cfif arrayLen(nodes)>
<!--- do something, you have the "nodes" array to work with too --->
</cfif>
xpath for XMLSearch() assumes the structure keys are nodes. You would need to modify accordingly if, for instance, 'myTarget' is an attribute of a node.
StructFindKey
Another way of doing this would be StructFindKey.
<cfset result = structFindKey(myStruct, "myTarget")>
<cfif arrayLen(result) AND result.path EQ "level1.level2.level3">
<!--- do something --->
</cfif>
Conclusion
Haven't tested, but I believe either will be faster than using IsDefined() or a try-catch block. Has the advantage over XMLValidate() of not needing a DTD. And, even with a DTD, the node you want may be defined as optional, so it could still validate.
You could validate the XML against a DTD to make sure the document was in the right format. XmlParse() and XmlValidate() both take a DTD as a parameter.
<cfset validateResult = XmlValidate(myXmlDocument, myDTD)>
<cfif validateResult.status>
<!--- xml is valid continue processing --->
<cfelse>
<!--- xml did not validate handle the error --->
</cfif>
Personally I wouldn't go crazy checking for every level of a 'deep' structure like this. I would presume that if the top level exists the rest of the document will be as you expect, and I'd just address the document from there.
If you wanted you could perhaps try to address the value in your struct and wrap it in a try/catch. That way you can handle any errors at any 'level' in the same way.
<cftry>
<cfset myVar = myStruct.level1.level2.level3 />
<cfcatch type="any">
<!--- Handle error --->
</cfcatch>
</cftry>
Hope that helps some.
I know I'm going to get booed off the stage here, but this is where isDefined() can save you a lot of typing:
<cfif isDefined(structKeyExists(myStruct.level1.level2.level3)>
<!--- do something --->
</cfif>
I know this is a year old, but I'm going to put in an answer here. I struggled for a good long time with this one, till I found a simple solution. If I know the structure of the XML already, a simple IsDefined works to test if the node or node attribute exists. I don't think most people know you can do this, or have tried and failed because they didn't include single quotes in the IsDefined function.
So say I grab some user xml from a web service somewhere and want to display the user's ID.
<cfhttp url="https://mycompany.com/mywebservices/getusers" username="me" password="mysecret">
<cfset userXMLDoc = XMLParse(ToString(cfhttp.FileContent).trim())>
<cfif IsDefined('userXMLDoc.Data.Record.User.XmlAttributes.id')>
<cfdump var="#userXMLDoc.Data.Record.User.XmlAttributes.id#">
<cfelse>
<cfoutput>Failed: No User ID found</cfoutput>
</cfif>

Is onApplicationStart is good Idea in ColdFusion?

I have to use a Variable(Query Resultset) in ColdFusion, which will get the results from Other Application DB, and stores in Coldfusion Application.
The main idea is that I need to call the other Application DB only at Server startup time and cache the results in local. And I need to read the variable in other pages in my Application. I won't overwrite that variable in any page.
On googling I found that 'onApplicationStart' is useful to assign the variables at Application Startup time.
Is using the onApplicationStart fine or is there any other way? We can assign a variable at startup time(one time).
If onApplicationStart is fine: how to use? Maybe any link where it is explained clearly is helpful.
Well, it depends. How often will this query data be updated? If it really is unchanging, then onApplicationStart() is a fine place to put it. However, if it will change every so often, you can just tell Coldfusion to cache the query for a certain period of time, then you don't need to mess with onApplicationStart(), but rather when you call the query it will return the cached result automatically (within your specified time period).
Regardless, I would write a custom function to retrieve the data. Then it will be trivial to call it from onApplicationStart() or elsewhere.
Startup.cfc: (Named whatever you like)
<!--- Replace the datasource name with your db name --->
<cffunction name="getStartupQuery" hint="Returns a query recordset for startup">
<cfargument name="datasource" required="no" type="string" default="OtherAppDB">
<!--- Init the query variable --->
<cfset var result = queryNew("id")>
<!-- Get the query dataset --->
<cfquery name="result" datasource="#arguments.datasource#">
YOUR QUERY HERE
</cfquery>
<cfreturn result>
</cffunction>
Application.cfc: (Just the important parts)
<cffunction name="onApplicationStart">
<!--- init the startup.cfc, then retrieve the data
and save it to the application scope. Remember the component name must match
your component above --->
<cfset var startup = createObject("component", "startup")>
<cfset application.varFromOtherDB = startup.getStartupQuery()>
<cfreturn true>
</cffunction>
Now, you should be able to access this variable from any CFM or CFC in your application using:
<cfset myNewVar = application.varFromOtherDB>
or
#application.varFromOtherDB#
IF you use the onApplicationStart() method, I highly recommend implementing a method to reinit the application. For an example, see this other discussion.