I am trying to write a custom tag that will iterate over a cfquery object in a special way. I found this page: http://www.zrinity.com/developers/mx/undocumentation/query.cfm outlining how to use the underlying java methods to navigate the result set, but it doesn't seem to be working in CF9.
I can call .next(), .previous(), .first(), and .last() just fine, and each method updates query.currentRow, but referencing query.columnName always returns the value from the first row, not currentRow.
Example:
<cfquery name="testQuery" datasource="source">
SELECT FooName FROM NumberedFoos
</cfquery>
<cfloop from="1" to="3" index="i">
<cfoutput>#testQuery.currentRow# => #testQuery.fooName#</cfoutput><br />
<cfset testQuery.next()>
</cfloop>
Produces:
1 => Foo 1
2 => Foo 1
3 => Foo 1
I know i could use something like testQuery.fooName[testQuery.currentRow], but that is pretty undesirable for the people I am making the custom tag for. Was the functionality described in the above link removed from CF9? If so is there an alternative?
EDIT
To expand on the why, the client wants a custom tag that allows them to "assert" certain things about a query. The client has a pretty low level understanding of CF, but are pretty solid writing SQL. Their desired end result is something akin to:
<cfquery name="purchaseTotals">
SELECT PurchaseId, Total FROM Purchases
</cfquery>
<CF_ASSERT query="purchaseTotals">
purchaseTotals.Total gte 0
</CF_ASSERT>
The desired output would be a html table with each row being the row from the query that fails the assertion. So to me, the CF_ASSERT tag need to be able to update the current row.
Edit 2:
The main challenge is to allow html in the body of the tag, while still having query values substituted from the appropriate row:
<CF_ASSERT query="purchaseTotals">
<CF_CONDITION expression="purchaseTotals.Total gte 0">
<!---error message when expression is false--->
<cfoutput>
Purchase #purchaseTotals.purchaseId# has a negative total!
</cfoutput>
</CF_CONDITION>
<CF_CONDITION expression="purchaseTotals.Total eq ''">
#PurchaseTotals.purchaseId# has a null total, this may be caused by:
<ul>
<li>Edge Case 1</li>
<li>Edge Case 2</li>
</ul>
</CF_CONDITION>
<CF_ASSERT>
The output here would be something like:
Purchase 120 has a negative total!
Purchase 157 has a negative total!
Purchase 157 has a null total, this may be caused by:
Edge Case 1Edge Case 2
Was the functionality described in the above link removed from CF9?
The internal stuff has definitely changed since the article was written in 2006. But I suspect the exact functionality you are describing may not have existed in any mx version. A key difference between your code and the linked examples is the usage of <cfoutput query=".."> (not just a plain <cfoutput>). The query attribute obviously provides some extra context when evaluating the variables. Remove it (like in your example) and the results are "the value from the first row, not currentRow.". Even under MX6, which does not bode well for subsequent versions. That exact functionality probably was not removed. It just never worked to begin with.
If so is there an alternative?
Like I said earlier, the cleanest approach would be to use array notion ie #query.column[row]#. Given that you seem to have rejected that option, you are basically left with evaluate(). You would need to loop through the query within the parent tag. Then use evaluate to process the subtag expression and content. It is not particularly elegant or simple IMO. But I think that may be good as it gets without array notation, or a ritual sacrifice of some kind.
ASSERT.cfm
<cfparam name="attributes.query" type="string">
<cfif thisTag.ExecutionMode is 'start'>
<!--- validate attributes.query is a query object --->
<cfif not ( structKeyExists(caller, attributes.query) AND IsQuery(caller[attributes.query]) )>
<cfthrow message="Attributes.query [#attributes.query#] is undefined or not a query object">
</cfif>
</cfif>
<cfif thisTag.ExecutionMode is 'end'>
<cfset variables[attributes.query] = caller[attributes.query]>
<cfloop query="variables.#attributes.query#">
<cfloop array="#thisTag.assocAttribs#" index="subTag">
<cfset variables.matchFound = evaluate(subTag.expression)>
<cfif variables.matchFound>
<cfoutput>[#currentRow#] #evaluate(DE(subTag.Content))#</cfoutput><hr>
</cfif>
</cfloop>
</cfloop>
</cfif>
CONDITION.cfm
Note: Do NOT use <cfoutput> tags within the tag content.
<cfparam name="attributes.expression" type="string">
<cfif thisTag.ExecutionMode is "start">
<cfassociate baseTag="CF_ASSERT">
</cfif>
<cfif thisTag.ExecutionMode is "end">
<cfset attributes.content = thisTag.GeneratedContent>
<cfset thisTag.GeneratedContent = "">
</cfif>
client has a pretty low level understanding of CF, but are pretty
solid writing SQL
Having said all that, are things being implemented this way because it is the best approach or because it is most similar to writing SQL ie comfortable ?
Classic example of the inner platform effect.
I would advise you not to do this as you are trying to create a system which mimics built in functionality of the base or running system which ultimately becomes a poorly implemented version of the system in which it is running on / implemented in.
Sounds confusing, I know - but this is a well known anti-pattern to avoid.
See here for more information : http://en.wikipedia.org/wiki/Inner-platform_effect
P.s. what you are looking for (although I disagree with the implementation) is itteration of a query outside of a cfloop, simply use array syntax :
#queryName.fieldName[rowNumber]#
Using this you can itterate the query however you wish, certainly no need for underlying java. Notice we aren't using queryName.currentRow. For previous() next() functionality, you just change the rowNumber up or down.
Related
I am trying to construct a coldfusion conditional statement that looks for incremental form ID checkboxes to have been selected. All checkboxes are defined as Component[component number]. I have established a loop that is looking for a URL variable that is different for every form that calls on the condition as seen below. The issue I am having is that I recieve an error when executing that tells me "Complex constructs are not supported with function parameterexists."
Clearly it has to do with the dynamic nature of the parameterexists statement, but I do not fully know what this means. Can anyone explain this and also offer a solution? I am fairly new to coldfusion and coding, so take it easy on me.
<cfloop from="1" to="#URL.loopcounter#" index="loopvar">
<cfif parameterexists(Form.Component#loopvar#)>
INSERT INTO Results (MP_Barcode, Reworked, Reworked_By)
VALUES ('#Form.MontaplastBarcode#', 'YES', '#URL.BadgeNumber#')
</cfloop>
<cfoutput>
<p class="success">YOUR REWORK HAS BEEN SUBMITTED SUCCESSFULLY.</p>
</cfoutput>
<cfelse>
<p class="error">NO REWORK WAS SUBMITTED. NO COMPONENTS SELECTED.</p>
</cfif>
Depending on the form that calls on this action, the URL loopcounter variable could range from 1 to 50.
To answer the question, there are several ColdFusion functions that won't allow you to create a dynamic name before the function evaluates it. parameterExists() was one of those. Both isDefined() and structKeyExists() will allow dynamic variables. So will the member function of structKeyExists() > structName.keyExists("theKey").
Again, if you are new to ColdFusion, I'd simply pretend you never saw parameterExists(). I believe it has been listed as "deprecated" since CF 4.5 or somewhere around there. That's almost 20 years ago. That function has actually become somewhat of a joke about how Adobe never really throws away their trash.
As I pointed out above, I'd get rid of it completely and go with structKeyExists(). I also don't know what your whole page is doing, but with the code you provided, I'd change it to something like this:
<cfloop from="1" to="#url.loopcounter#" index="loopvar">
<cfoutput>
<cfif structKeyExists(form,"Component#loopvar#")>
<!--- SANITIZE INPUTS --->
<cfset inMontplastBarcode = sanitizingFunction(FORM.MontaplastBarcode)>
<cfset inBadgeNumber = sanitizingFunction(URL.BadgeNumber)>
<!--- Now use sanitized inputs in query with queryparams --->
<cfquery name="InsertStuff" datasource="myds">
INSERT INTO Results (MP_Barcode, Reworked, Reworked_By)
VALUES (
<cfqueryparam value="#inMontaplastBarcode#" cfsqltype="cf_sql_varchar" maxlength="50">
, 'YES'
, <cfqueryparam value="#inBadgeNumber#" cfsqltype="cf_sql_varchar" maxlength="20">
)
</cfquery>
</cfif>
</cfoutput>
</cfloop>
In your database, Reworked should be a boolean datatype. It appears that it may be a 'Yes' or 'No' string. A true boolean will be a) smaller and b) easier to validate. In the cfqueryparams, if you are using a cf_sql_varchar datatype, make sure you set an appropriate max length. You'll need to look at the available CF datatypes and see how they match up to your database datatypes. (Also see https://cfdocs.org/cfqueryparam)
For your sanitizingFunction() that you'll use to sanitize your input variables, you'll want to write a function that will follow through the steps to clean up your variables to strip out unsafe characters or other things you don't want. That is an entirely different, extremely large topic all on its own.
In your form, name your checkboxes simpler. Like reworked01 through reworked50.
On the action page use cfparam to default them to zero (since html forms don't post unchecked boxes):
<cfloop from="1" to="#url.loopCounter#" index="i">
<cfparam name="form.reworked#numberFormat(i, 00)#" default="0">
</cfloop>
Then instead of fumbling with whether or not a variable exists, you can instead look for the value:
<cfloop from="1" to="#url.loopCounter#" index="i">
<cfif evaluate("form.reworked"&i) eq 1>
<!--- some logic here --->
<cfelse>
<!--- some other logic here --->
</cfif>
</cfloop>
This seems like it should be an easy one, but CF seems to have different ways of accomplishing the same thing when it comes to Arrays, so I want to be sure I'm doing it the best way;
I have a simple cfoutput;
<cfoutput query="getusers">
Username: #username# ID:#fnid#
</cfoutput>
I'm also using cfwebsocket elsewhere on the page, and the value of the 'fnid' (which is a session.auth variable) is part of the subscription. A dump of wsGetSubscribers gives me the following;
What I'd like to do is during the output of the 'getusers' query, check to see if the FNID under subscriberinfo.fndid is there - the goal being to add a note to say that user is subscribed at that point.
I know I can do this using a cfloop, but that seems longwinded!
What I'm trying to avoid is having to use cfloop (on the array) within the cfoutput query
So you want a kind of look-up for fnId? How about:
<cfset subscribersById = StructNew()>
<cfloop array="#wsGetSubscribers()#" index="subsciption">
<cfset subscribersById[subsciption.subscriberInfo.fnId] = subsciption>
</cfloop>
<cfoutput query="getusers">
Username: #username# ID: #fnid# Subscribed: #StructKeyExists(subscribersById, fnid)#
</cfoutput>
CF10+ has arrayEach() as well, if you prefer this syntax over the <cfloop>.
I am moving one of our applications from ColdFusion 9.01 to ColdFusion 11 and encountered a situation where I cannot get the date formatted the way I want it using "DateFormat". I read through the docs since things have changed in CF versions, but I honestly can't figure out why this isn't working. It worked beautifully in CF 9. I know it's probably something very easy, but I am just not seeing it.
The query (Oracle DB) provides me a list of the last 30 days and the loop is simply to reformat the date output from "2014-07-01 00:00:00.0" to a more friendly looking display of 01-Jul-2014 except that I cannot get it to format as "dd-mmm-yyyy" it just spits back the original output from the query. I hard coded the date where normally there would be a cfquerparam. Any ideas?
<cfquery name="qryDateArray" datasource="#request.db#">
select trunc(to_date('07/01/2014', 'mm/dd/yyyy') + 1 - rownum) as ref_date
from dual connect by rownum <= 30
</cfquery>
<cfloop from="1" to="#qryDateArray.recordcount#" index="j">
<cfset qryDateArray.ref_date[j] = DateFormat(qryDateArray.ref_date[j], "dd-mmm-yyyy")>
</cfloop>
<cfoutput>
<cfdump var="#qryDateArray#">
</cfoutput>
I could not test this on CF11 since I do not have it handy. I did verify that your code though returns results as you explained when I ran it on my CF10 environment here. So what you can do is add a column to the query object and define it as a varchar and add your formatted data to that. This in turn dumped out the formatted dates.
<cfquery name="qryDateArray" datasource="#request.db#">
select trunc(to_date('07/01/2014', 'mm/dd/yyyy') + 1 - rownum) as ref_date
from dual connect by rownum <= 30
</cfquery>
<cfset aryData = [] />
<cfloop from="1" to="#qryDateArray.recordcount#" index="j">
<cfset ArrayAppend(aryData, DateFormat(qryDateArray.ref_date[j], "dd-mmm-yyyy")) />
</cfloop>
<cfset QueryAddColumn(qryDateArray, "STRDATE", "VarChar", aryData) />
<cfoutput>
<cfdump var="#qryDateArray#">
</cfoutput>
If dependent on the query column names then could use something like Ben's method explained here to do some renaming of the columns: http://www.bennadel.com/blog/357-ask-ben-changing-coldfusion-query-column-names.htm
It'd be great if you'd given us a portable test case rather than one that relies on your database, but I suspect it is because ColdFusion has become more rigid with its type management of query columns.
So CF considers your ref_date column to be of type date, so when you try to put the formatted string back into the query column, CF tries (and succeeds) to convert the string back into a date.
Aside:
I have to wonder why you don't format the data string in the DB from the outset, and just return it the way you need it, rather than returning something else, then looping over the thing to adjust it..?
I want to build a framework that hides the URL's and am just learning CFSWITCH. Navigation links are queried in datatable. I have tried to query around cfswitch and keep getting error
Example:
?category=5&page=21 ( where category=page_category and 5= page_categoryid
and page is page_id is 21 in datatable )
<cfoutput query="pagecategories" group="page_categoryid">
<cfswitch expression="#URL.category#">
<cfcase value="21">
<cfinclude template="../templates/index_welcome.cfm">
</cfcase>
<cfcase value="#page_categoryid#">
<cfinclude template="../templates/page_bycategory.cfm?page_categoryid=#page_categoryid#">
</cfcase>
<cfcase value="22">
<cfinclude template="/modules/blog/">
</cfcase>
</cfswitch>
</cfoutput>
First, welcome to ColdFusion, it's a great language, I think you'll like it. :)
What you're attempting here is a really bad idea for a number of reasons, read on for why and some alternatives.
1) it needlessly obfuscates it from you, the developer. Trust me you will learn to hate it 6 months from now when you go back in for some arbitrary changes and can't remember what "14" means.
2) it is bad for search engines - google SES urls for more info (search engine safe).
3) numeric incrementing keys are less secure than descriptive texts. I can just loop over numbers and see all your pages, skipping your navigation and seeing everything.
4) you get no perceivable advantages that can't be gained in other ways (which I'll show you one next).
Instead of going about it the way you have listed, arbitrary numbers that link to different elements- why not instead key them off of actual strings that have meanings? If what you are trying to accomplish is hiding the actual page names that are processing the request, why not instead use something like this:
http://www.domain.com/?/category/blog/page/links
instead of:
http://www.domain.com/page.cfm?category_id=21&page=5
In my example, i'm not pointing to an actual directory, I'm going to take the cgi.querystring param (which will contain the string "/category/blog/page/links") and parse it and match it to key values. (see coldfusion list functions: http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions-pt0_13.html, use "/" as delimiter). Then I can pull up whatever logic i need for category "blog" and page "links" - which can be stored in a database just the same as "21" and "14". :)
Now, on to your code...
As for the switch statement, it simply works sort of like a set of if statements :
<cfswitch expression="value_to_check">
<cfcase value="possible_value_1">
<!--- do something --->
</cfcase>
<cfcase value="possible_value_2,another_possible_value">
<!--- do something different --->
</cfcase>
<cfdefaultcase>
<!--- if none of the above, do this --->
</cfdefaultcase>
</cfswitch>
You also have some weirdness in your include statement. You can't specify any url parameters in a <cfinclude> statement. Think of it as literally grabbing the code from the page you specify and pasting it into the document. It does exactly that, no more, no less. Therefore, you can't specify url parameters. This is invalid :
<cfinclude template="../templates/page_bycategory.cfm?page_categoryid=#page_categoryid#">
Additionally, it's pretty abnormal for the case statement to have a dynamic value such as this:
<cfcase value="#page_categoryid#">
Let me know if you have any questions / need clarification
Why does this not work? My welcome message, it just doesn't show up:
<p>Welcome <cfoutput>#Recordset1.UserID#</cfoutput>.</p>
The session variable on the login page I created is:
<cflock timeout=999 scope="Session" type="Exclusive">
<cfset Session.IDUsers =''>
</cflock>
is this incorrect? On the index page where I'm trying to display my welcome message I have:
<cfquery name="Recordset1" datasource="cfGossip">
SELECT *
FROM users
WHERE users.IDUsers = <cfqueryparam value="#Session.IDUsers#">
</cfquery>
I'm not sure if this works, or is necessary?
If you set the userid stored in the session to be the empty string, when you query on it, you will only get users for whom the id is the empty string, which shouldn't be any of them. Therefore, the query is returning an empty set, and your page is (correctly) not displaying a user id.
How are you initially identifying the user? Are you querying against a database when they log in? Are you storing a cookie? Reading Tarot cards? For this to work, at some point, you have to store the correct userid, probably in the session. To do that, you need to first identify who the user is.
Also, if you are using CF6+, you probably do not need the cflock. It is now used to prevent race conditions, as CF is now thread-safe.
Looks like you're just starting with CF, welcome to the community.
My understanding of your code makes the structure look like the following, if I'm understanding you correctly:
<cfset session.idUsers = '' />
<cfquery datasource = "cfgossip" name = "recordset1">
SELECT * FROM USERS WHERE USERS.ID_USERS = <cfqueryparam cfsqltype = "cf_sql_integer" value = "#session.idUsers# />
</cfquery>
<cfoutput>Welcome #recordset1.userID#</cfoutput>
The reason this doesn't work is because your session.idUsers value is blank. Assuming you have a user in your database with an ID_USERS value of 1, you could change the CFSET to look like and it should return results.
Additionally, while it's great to see you using CFQUERYPARAM, I'd recommend including a CFSQLTYPE attribute in the tag whenever possible to provide an added line of defense against injection attacks. You can check out http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=Tags_p-q_18.html to see the list of available types.
Is there anywhere in your code where you set your session.IDUsers? You initialize it as a blank ''. Coldfusion does not populate it for you. The session scope is a place that will remember things for that user that you put there for a specified period of time inactivity, usually 20 minutes. So hopefully, somewhere before you run your query you have additional logic that fills that in, otherwise you are asking the database for a user named, ''.
This is just a point of style, but the following may work better for you:
<cfset Session.IDUsers =''>
<!--- Do something here to populate Session.IDUsers --->
<!--- Creates a blank query - not necessary, but can reduce errors later --->
<cfset Recordset1 = queryNew("UserID")>
<!--- Populate the query from the database --->
<cfquery name="Recordset1" datasource="cfGossip">
SELECT *
FROM users
WHERE users.IDUsers = <cfqueryparam value="#Session.IDUsers#">
</cfquery>
<!--- If the query has data, use it, otherwise show generic message --->
<cfoutput>
<cfif Recordset1.recordcount>
<p>Welcome #Recordset1.UserID#.</p>
<cfelse>
<p>Welcome new user!</p>
</cfif>
</cfoutput>
<!--- OR since we used queryNew("userID"), we can simplify without throwing an error. ---->
<cfoutput>
<p>Welcome <cfif len(Recordset1.userID)>#Recordset1.userID#.<cfelse>new user!</cfif></p>
</cfoutput>
Putting the cfoutput outside the paragraph block will make it easier if you have additional variables to insert into the text. (but will work either way)
Regardless of all that, unless you forgot to share a bit more of the code, I think the issue is that the session.IDUsers is blank and needs to be populated before the query. I hope this helps!