ColdFusion: Common Update Pattern - coldfusion

I usually create update statements like this. But I know there is a better way. How can I approve upon this? *Note the sample below is pseudo demo, may not run.
<cffunction name="updateEmp" returntype="void">
<cfargument name="empId" required="yes" hint="empId">
<cfargument name="firstName" required="yes" hint="firstName">
<cfargument name="lastName" required="yes" hint="lastName">
<!--- Get emp details in db --->
<cfquery datasource="#ds#" name="getEmployee">
SELECT *
FROM Employee
WHERE emp_id = <cfqueryparam
value="#arguments.empId#"
CFSQLType="CF_SQL_INTEGER">
</cfquery>
<!--- If employee is in db or if emp db details are different --->
<cfif getEmployee.recordCount eq 1
and getEmployee.firstName neq trim(arguments.firstName)
or getEmployee.lastName neq trim(arguments.lastName)>
<cfquery name="UpdateExistingEmployee" datasource="#ds#">
UPDATE Employee
SET 1 = 1
<cfif getEmployee.firstName neq trim(arguments.firstName)>
,firstName = <cfqueryparam
value="#arguments.firstName#"
CFSQLType="CF_SQL_VARCHAR" >
</cfif>
<cfif getEmployee.lastName neq trim(arguments.lastName)>
,lastName = <cfqueryparam
value="#arguments.lastName#"
CFSQLType="CF_SQL_VARCHAR" >
</cfif>
WHERE emp_id=<cfqueryparam
value="#emp_id#"
CFSQLType="CF_SQL_INTEGER">
</cfquery>
</cfif>
<!--- maybe return success? --->
</cffunction>
Edited:
<cffunction name="updateEmp" returntype="void">
<cfargument name="empId" required="yes" hint="empId">
<cfargument name="firstName" required="yes" hint="firstName">
<cfargument name="lastName" required="yes" hint="lastName">
<cfquery name="UpdateExistingEmployee" datasource="#ds#">
UPDATE Employee
SET firstName = <cfqueryparam
value="#arguments.firstName#"
CFSQLType="CF_SQL_VARCHAR" >
,lastName = <cfqueryparam
value="#arguments.lastName#"
CFSQLType="CF_SQL_VARCHAR" >
WHERE emp_id=<cfqueryparam
value="#emp_id#"
CFSQLType="CF_SQL_INTEGER">
</cfquery>
<!--- maybe return success? --->
</cffunction>

Not sure what you are trying to improve. If you just want to write less code you might just do an update:
<cffunction name="updateEmp" returntype="void">
<cfargument name="empId" required="yes" hint="empId">
<cfargument name="firstName" required="yes" hint="firstName">
<cfargument name="lastName" required="yes" hint="lastName">
<cfquery name="UpdateExistingEmployee" datasource="#ds#">
UPDATE Employee
SET firstName = <cfqueryparam value="#arguments.firstName#" CFSQLType="CF_SQL_VARCHAR">
,lastName = <cfqueryparam value="#arguments.lastName#" CFSQLType="CF_SQL_VARCHAR" >
WHERE emp_id=<cfqueryparam value="#emp_id#" CFSQLType="CF_SQL_INTEGER">
</cfquery>
</cffunction>
The check for an existing record is pretty much superfluous since the where clause will prevent any action if no emp_id matches, and why bother checking if the names match? If they do, then you just updated them to be the same, if they don't you're going to update them anyway. There's no logical reason to be doing all that checking.

Maybe check out Coldfusion ORM? Or, for something that would require less drastic changes to your codebase, you could use DataMgr (which really helps cut down on CRUD): http://datamgr.riaforge.org/

Related

Coldfusion function within conditional logic

I am having an issue with functions inside my cfc. They are acting funny when I try to introduce conditional logic to assign a query to the grid. Basically in the URL I will have ?GRIDID=x and it will tell the cfc which function to run, but when I nest the closing cffunction tag inside of the if statements, it throws an error. Here is the code.
<cffunction name="grabInfo" access="remote" output="false" returntype="any">
<cfargument name="page" required="yes">
<cfargument name="pageSize" required="yes">
<cfargument name="gridsortcolumn" required="yes">
<cfargument name="gridsortdirection" required="yes">
<cfargument name="filtercolumn" required="no" default="">
<cfargument name="filter" required="no" default="">
<cfargument name="gridID" required="yes">
<cfif arguments.gridsortcolumn eq "">
<cfset arguments.gridsortcolumn = "PatientsName" />
<cfset arguments.gridsortdirection = "asc" />
</cfif>
<cfif ARGUMENTS.gridID EQ "1">
<cfquery name="x" datasource="#dsn#">
<!--- .... --->
</cfquery>
<cfreturn QueryConvertForGrid(qGrabInfo, Arguments.page, Arguments.pagesize)>
</cffunction>
</cfif>
<cfif ARGUMENTS.gridID EQ "2">
<cfquery name="x" datasource="#dsn#">
<!--- .... --->
</cfquery>
<cfreturn QueryConvertForGrid(qGrabInfo, Arguments.page, Arguments.pagesize)>
</cffunction>
</cfif>
This will give me the error Context validation error for the cfif tag. but as you can see, all the cfif statements are closed. If I take the first argument and place it with the closing cffunction tag outside of the if statement it will work, like so
<cfif ARGUMENTS.gridID EQ "1">
<cfquery name="x" datasource="#dsn#">
<!--- .... --->
</cfquery>
<cfreturn QueryConvertForGrid(qGrabInfo, Arguments.page, Arguments.pagesize)>
</cfif>
<cfif ARGUMENTS.gridID EQ "2">
<cfquery name="x" datasource="#dsn#">
<!--- .... --->
</cfquery>
<cfreturn QueryConvertForGrid(qGrabInfo, Arguments.page, Arguments.pagesize)>
</cfif>
</cffunction>
The reason I need to do this is because I need several other functions run when GridID EQ 2 as well, so I need to close the function and open another as follows
<cfif ARGUMENTS.gridID EQ "2">
<cfquery name="x" datasource="#dsn#">
<!--- .... --->
</cfquery>
<cfreturn QueryConvertForGrid(qGrabInfo, Arguments.page, Arguments.pagesize)>
</cffunction>
<cffunction name="otherFunction">
<!--- .... --->
</cffunction>
</cfif>
Add additional functions in your component.
<cffunction name="grabInfo" access="remote" output="false" returntype="any">
<cfargument name="page" required="yes">
<cfargument name="pageSize" required="yes">
<cfargument name="gridsortcolumn" required="yes">
<cfargument name="gridsortdirection" required="yes">
<cfargument name="filtercolumn" required="no" default="">
<cfargument name="filter" required="no" default="">
<cfargument name="gridID" required="yes">
<cfif arguments.gridsortcolumn eq "">
<cfset arguments.gridsortcolumn = "PatientsName" />
<cfset arguments.gridsortdirection = "asc" />
</cfif>
<cfif ARGUMENTS.gridID EQ "1">
<cfquery name="x" datasource="#dsn#">
<!--- .... --->
</cfquery>
<cfreturn QueryConvertForGrid(qGrabInfo, Arguments.page, Arguments.pagesize)>
</cfif>
<cfif ARGUMENTS.gridID EQ "2">
<cfquery name="x" datasource="#dsn#">
<!--- .... --->
</cfquery>
<!--- call your other functions --->
<cfset otherFunction(arg1, arg2)>
<cfset anotherFunction(arg1, arg2)>
<cfreturn QueryConvertForGrid(qGrabInfo, Arguments.page, Arguments.pagesize)>
</cfif>
</cffunction>
New functions in same component
<cffunction name="otherFunction" access="remote" output="false" returntype="any">
<cfargument name="arg1">
<cfargument name="arg2">
<!--- do things --->
</cffunction>
<cffunction name="anotherFunction" access="remote" output="false" returntype="any">
<cfargument name="arg1">
<cfargument name="arg2">
<!--- do things --->
</cffunction>
The chief problem you have is a lack of understanding how code compiles. Your code is not executed at runtime, which is when stuff like conditions are evaluated, it needs to be compiled first. And to be compiled, the code needs to be syntactically valid. Which yours is not.
Functions are discrete units of processing, and need to be self contained. What you're trying to do makes absolutely no sense at all from a code perspective. It also demonstrates a lack of understanding of how functions work. They do not execute when they are declared (ie: the <cffunction>/</cffunction> block, they run when they're called.
Matt has got you on the right track, but to reiterate, you don't do this:
<cffunction name="mainFunction">
<!--- some stuff --->
<cfif someCondition>
<!--- some other stuff --->
<!--- finish off --->
</cffunction>
<cfelse>
<!--- different stuff --->
<cffunction name="theOtherFunction">
<!--- different function --->
</cffunction>
</cffunction><!--- this is for the outer function --->
</cfif>
That's... well it's not right.
What you want is this:
<cffunction name="mainFunction">
<!--- some stuff --->
<cfif someCondition>
<!--- some other stuff --->
<!--- different function --->
<cfset something = theOtherFunction()>
</cfif>
<!--- finish off --->
</cffunction>
<cffunction nname="theOtherFuction">
<!--- different stuff --->
</cffunction>
Note how each coding structure is self-contained.
I think you could benefit from reading through the CFML docs, and also some basic programming tutorials (any language) before you go too much further into it.
Also note: try to avoid using tag-based code for business logic: tags are really better suited for views, and it's a throw-back to earlier, ill-informed times that one can even define a function with tags. As a rule of thumb: tags for views; script for logic.
It's also important to read and understand the comments below. Even if your code was syntactically correct and could compile, it still wouldn't do what you want it to do because functions are compiled separately to the rest of the code, so your conditionality still wouldn't work.

Finding images not referenced in a DB table

I get a query of all the files (images) in a directory
<cfset local.teamBase = "#GetDirectoryFromPath(GetBaseTemplatePath())#teamlogo\">
<cfdirectory action="LIST" directory="#local.teamBase#" name="rc.qryTeamLogo">
I later loop over all these filenames, skipping those that are in use
<select name="Logo" <cfif deleted>disabled="disabled"</cfif>>
<option></option>
<cfloop query="rc.qryTeamLogo">
<cfif name EQ mylogo>
<option value="#name#" selected>#name#</option>
<cfelseif request.objTeam.isUsed(name) EQ 1 OR name EQ "thumbs.db">
<!--- Do not show --->
<cfelse>
<option value="#name#">#name#</option>
</cfif>
</cfloop>
</select>
The isUsed() function looks like
<cffunction name="isUsed" returntype="boolean" output="false">
<cfargument name="logo" required="true" type="string">
<cfquery name="qryUsed" datasource="#application.DSN#">
SELECT logo
FROM CCF.team
WHERE logo = <cfqueryparam cfsqltype="CF_SQL_VARCHAR" value="#arguments.logo#">
AND Deleted = 0
</cfquery>
<cfif qryUsed.recordcount EQ 0>
<cfreturn false>
</cfif>
<cfreturn true>
</cffunction>
The problem with this is as more and more files get added, this gets slower and slower.
What I would really like a single query that can assemble the whole thing
You can run one query before your select box
<cfquery name="qryLogos" datasource="#application.DSN#">
SELECT logo
FROM CCF.team
WHERE logo IN (<cfqueryparam cfsqltype="CF_SQL_VARCHAR" value="#valueList(rc.qryTeamLogo.name)#" list="true">)
AND Deleted = 0
</cfquery>
Then set all those logo's into an array and add thumbs.db too, so you have one less check to do.
<cfset allLogs = listToArray(valueList(qryLogos.logo))>
<cfset arrayAppend(allLogs,'thumbs.db')>
and then change your elseif to this
<cfelseif arrayFind(allLogs,name)>
<!--- don't display --->
Load the file information into XML. Shread the XML into a table and JOIN on that.
<cfset local.teamBase = "#GetDirectoryFromPath(GetBaseTemplatePath())#teamlogo\">
<cfset local.qryTeamLogo = DirectoryList(local.teamBase, false, "query")>
<cfsavecontent variable="local.xmlTeamLogo">
<ul class="xoxo">
<cfoutput query="local.qryTeamLogo">
<li><code>#xmlformat(name)#</code></li>
</cfoutput>
</ul>
</cfsavecontent>
<cfquery name="local.qryResult">
DECLARE #xmlTeamLogo xml = <cfqueryparam cfsqltype="CF_SQL_varchar" value="#local.xmlTeamLogo#">
SELECT Value AS Name
FROM dbo.udfXOXORead(#xmlTeamLogo)
WHERE NOT Value IN (
SELECT Logo
FROM CCF.Team WITH (NOLOCK)
WHERE Deleted = 0
)
</cfquery>
The Table Valued Function
create function [dbo].[udfXOXORead](#xmlData xml)
returns #tblmessage TABLE (
[value] nvarchar(max) NULL
)
as
begin
INSERT INTO #tblMessage
SELECT Tbl.Col.value('(code)[1]', 'nvarchar(max)') AS [value]
FROM #xmlData.nodes('/ul/li') Tbl(Col)
RETURN
end

using autocomplete with commas in a field

I'm trying to use autocomplete with a field with commas in it. When I type the comma it will ignore it, and won't return anything. So far I have this:
index.cfm
<!--- A simple form for auto suggest --->
<cfform action="autosuggest.cfm" method="post">
Artist:
<cfinput type="text" name="artist" size="50" autosuggest="cfc:autosuggest.findartist({cfautosuggestvalue})" autosuggestminlength="4" maxresultsdisplayed="5" /><br /><br />
</cfform>
autosuggest.cfc
<cfcomponent output="false">
<!--- Lookup used for auto suggest --->
<cffunction name="findartist" access="remote" returntype="string">
<cfargument name="search" type="any" required="false" default="">
<!--- Define variables --->
<cfset var local = {} />
<!--- Query Location Table --->
<cfquery name="local.query" datasource="#application.datasource#" >
select DISTINCT artist
from items
where artist like <cfqueryparam cfsqltype="cf_sql_varchar" value="#ucase(arguments.search)#%" />
order by artist
</cfquery>
<!--- And return it as a List --->
<cfreturn valueList(local.query.artist)>
</cffunction>
</cfcomponent>
When I try to search for example Brown,James it doesn't return anything. What do I need to put in it to return results with commas.
Thanks
One option is to have your function return an array, instead of a string. Then delimiters are not an issue.
<cffunction name="findartist" access="remote" returntype="array">
...
<cfreturn listToArray(valueList(local.query.artist, chr(30)), chr(30))>
</cffunction>
Update:
As Raymond pointed out, the only sure-fire way to avoid delimiter issues on the CF side is not to use them. Instead loop through the query to build the array, ie:
<cffunction name="findartist" access="remote" returntype="array">
...
<cfset local.arr = []>
<cfloop query="local.query">
<cfset arrayAppend(local.arr, local.query.artist)>
</cfloop>
<cfreturn local.arr>
</cffunction>

How to have autocomplete ignore multiples?

Hello I have autocomplete set up like this:
index.cfm
<!--- A simple form for auto suggest --->
<cfform action="autosuggest.cfm" method="post">
Park Name:<br />
<cfinput type="text" name="parkname" size="50" autosuggest="cfc:autosuggest.findPark({cfautosuggestvalue})" autosuggestminlength="1" maxresultsdisplayed="10" /><br /><br />
</cfform>
and autosuggest.cfc
<cfcomponent output="false">
<!--- Lookup used for auto suggest --->
<cffunction name="findPark" access="remote" returntype="string">
<cfargument name="search" type="any" required="false" default="">
<!--- Define variables --->
<cfset var local = {} />
<!--- Query Location Table --->
<cfquery name="local.query" datasource="#application.datasource#" >
select FirstName
from Users
where FirstName like <cfqueryparam cfsqltype="cf_sql_varchar" value="#ucase(arguments.search)#%" />
order by FirstName
</cfquery>
<!--- And return it as a List --->
<cfreturn valueList(local.query.FirstName)>
</cffunction>
</cfcomponent>
What I need it to do is ignore multiples. Instead of having this: Dave, Jack, Jack, Jack, Chet. It should only do this: Dave, Jack, Chet. How would I go about doing that?
You need to use SELECT DISTINCT in your query.
<cfquery name="local.query" datasource="#application.datasource#" >
select DISTINCT FirstName
from Users
where FirstName like <cfqueryparam cfsqltype="cf_sql_varchar" value="#ucase(arguments.search)#%" />
order by FirstName
</cfquery>

ColdFusion autosuggest in text field is not responsive

I have a text field that I want to autosuggest values based on a query. I have a main file along with a separate file (getdata.cfc) that holds my query.
Here is the text field portion of my main file:
<cfinput name="search_query" autosuggest="url:getdata.cfc?suggestvalue={cfautosuggestvalue}" maxResultsDisplay="10" showAutoSuggestLoadingIcon="true" size="10" />
Here is the code in getdata.cfc:
<cfcomponent>
<cffunction name="get_data" access="remote" output="false">
<cfargument name="suggestvalue" required="true">
<cfquery name="get_data" datasource="#application.DSN#">
SELECT DISTINCT myItem
FROM myTable
WHERE myItem LIKE <cfqueryparam value="#suggestvalue#%"
cfsqltype="cf_sql_varchar">
ORDER BY myItem
</cfquery>
<cfif get_data.recordCount eq 1>
<cfreturn ",#get_data.myItem#">
<cfelse>
<cfreturn ValueList(get_data.myItem)>
</cfif>
</cffunction>
</cfcomponent>
The text field shows up fine, but when I type a word no autosuggest values show up. Nothing happens. The text is just displayed as I type it.
Any suggestions? Thank you!
I switched away to using jquery plugins from a lot of CF stuff, but here is an example I have that works in some old production code
<cfinput type="text" name="email" id="email" autosuggest="cfc:cfc.users.lookupEmail({cfautosuggestvalue})" maxresultsdisplayed = "25">
<cffunction name="lookupEmail" access="remote" returntype="array">
<cfargument name="search" type="any" required="false" default="">
<!--- Define variables --->
<cfset var data="">
<cfset var result=ArrayNew(1)>
<!--- Do search --->
<cfquery name="data" datasource="datasource" maxrows="25" cachedwithin="#CreateTimeSpan(0,0,30,0)#">
SELECT distinct email
FROM users
WHERE email LIKE <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.search#%">
ORDER BY email
</cfquery>
<!--- Build result array --->
<cfloop query="data">
<cfset ArrayAppend(result, email)>
</cfloop>
<!--- And return it --->
<cfreturn result>
</cffunction>
maybe this helps
also make sure you have your cfform tags around your form, and make sure that your /cfide folder is mapped to your website.
looking at your code and comparing it... it may be the way your calling the cfc (filename)
try: autosuggest="cfc:getdata.get_data.({cfautosuggestvalue})"