ColdFusion parsing JSON - json

I have followed this Adobe Help/DeserializeJSON documentation but it's giving me errors like Error in custom script module or Element COLUMNS is undefined in CFDATA. Any help is much appreciated. The error comes from cfData. Doing anything with cfData will cause some kind of errors. Dumping cfData works fine though. It shows all the correct data. Below is my code:
<cfhttp url="http://api.openweathermap.org/data/2.5/weather?zip=55101,us&appid=44db6a862fba0b067b1930da0d769e98" method="get" >
<!--- JSON data is sometimes distributed as a JavaScript function.
The following REReplace functions strip the function wrapper. --->
<cfset theData=REReplace(cfhttp.FileContent, "^\s*[[:word:]]*\s*\(\s*","")>
<cfset theData=REReplace(theData, "\s*\)\s*$", "")>
<!---<cfdump var="#theData#" >--->
<!--- Test to make sure you have JSON data. --->
<cfif !IsJSON(theData)>
<h3>The URL you requested does not provide valid JSON</h3>
<!--- If the data is in JSON format, deserialize it. --->
<cfelse>
<cfset cfData=DeserializeJSON(theData)>
<cfdump var=#cfData# >
<cfset colList=ArrayToList(cfData.COLUMNS)>
<cfset weatherIdx=ListFind(colList, "weather")>
<cfset descriptionIdx=ListFind(colList, "description")>
<!--- Now iterate through the DATA array and display the data. --->
<cfoutput>
<cfloop index="i" from="1" to="#Arraylen(cfData.DATA)#">
<h3>Weather: #cfData[i][weatherIdx]#</h3>
<h4>Discription: #cfData[i][descriptionIdx]#</h4>
</cfloop>
</cfoutput>
</cfif>

Looking at your dump, the result of deserializeJSON is a struct, not a query. You can test to see if weather exists in cfData using the structKeyExists() function. The code below runs for me without error:
<cfhttp url="http://api.openweathermap.org/data/2.5/weather?zip=55101,us&appid=44db6a862fba0b067b1930da0d769e98" method="get" >
<!--- JSON data is sometimes distributed as a JavaScript function.
The following REReplace functions strip the function wrapper. --->
<cfset theData=REReplace(cfhttp.FileContent, "^\s*[[:word:]]*\s*\(\s*","")>
<cfset theData=REReplace(theData, "\s*\)\s*$", "")>
<!---<cfdump var="#theData#" >--->
<!--- Test to make sure you have JSON data. --->
<cfif !IsJSON(theData)>
<h3>The URL you requested does not provide valid JSON</h3>
<!--- If the data is in JSON format, deserialize it. --->
<cfelse>
<cfset cfData=DeserializeJSON(theData)>
<cfdump var=#cfData# >
<cfif structKeyExists( cfData, 'weather' ) AND isArray(cfData.weather)>
<cfoutput>
<cfloop index="i" from="1" to="#arrayLen(cfData.weather)#">
<h3>Weather: #cfData.weather[i].main#</h3>
<h4>Description: #cfData.weather[i].description#</h4>
</cfloop>
</cfoutput>
</cfif>
</cfif>

Related

Coldfusion check if function exists

Hello is there a way to check if a function exists in coldfusion. It is throwing an error function is not defined
I believe isDefined('functionname') works. So you can do:
<cfif isDefined('functionname')>
<cfset functionname() />
</cfif>
If it's a potential method of an object named obj for example you can also do:
<cfif structKeyExists(obj,'functionname')>
<cfset obj.functionname() />
</cfif>
Or
<cfif isDefined('obj.functionname')>
<cfset obj.functionname() />
</cfif>

Getting the classes of HTML input tags when form submitted using ColdFusion

Let me start off with stating that what I'm trying to do might not be the best way to achieve my goal so any suggestions on a better way to do it would be appreciated as well!
My Overall Goal:
Using ColdFusion, validate (and sanitize, if needed) user input when a form is submitted. I'm planning on using jQuery to do the same so that the user can see if there's any issues with their input before submitting but I'm hesitant to rely solely on jQuery as the user might have JavaScript disabled.
My Current Strategy: When a user submits a form, have ColdFusion loop through all the inputs and grab each input's "class" attribute. Depending on its class (e.g., "text", "int", etc.) have the user's input validated accordingly.
My Question: Is there an "easy" way of grabbing HTML attributes using ColdFusion?
Notes:
I'm running ColdFusion 9
While doing some searching, I've read several people talking about jTidy and jsoup but they seem like that might be overkill for what I'm trying to do?
Again, I'm all ears for any alternate methods that will achieve what I'm trying to do as well!
There is no way for Cold Fusion to directly* get an input's class.
Indirect methods (these have nothing to do with Cold Fusion, just loading the form with more data to submit)
You could use a hidden field and do things like "fname=string,age=int,lname=string" or separate hidden fields like name="data_fname" value="string".
(Of course, you could use javascript/jquery to build such a hidden form variable, or set of variables, but that negates the whole point of a second, trusted, line of defense.
But all that is silly, because if you're going to do that, it's more reasonable just to do some cfsets on the processing page if this is how you want to setup your validation. Less data being submitted, and 0 chance of interference on the values of these variables.
<cfset dtypes = StructNew()>
<cfset dtypes["fname"] = "string">
<cfset dtypes["lname"] = "string">
<cfset dtypes["age"] = "int">
And then, if you name your form variables identically, that's easy matching, if you want to do it that way.
<cfloop list="#form.fieldnames#" index="fn">
<cfif StructKeyExists(dtypes,fn)>
Data type for this form field found, do some matching.
<cfelseif listfind("field,names,you,don't,want,to,validate,in,comma,delimited,list",fn)>
Found a field that doesn't need to be validated, like the submit button.
<cfelse>
Field #fn# is not exempt from validation, but no datatype was found.
</cfif>
</cfloop>
Or you can just say..
<cfloop list="#form.fieldnames#" index="fn">
<cfif StructKeyExists(dtypes,fn)>
Data type for this form field found, do some matching.
<cfelse>
Field #fn# has no datatype listed, so no validation is performed.
</cfif>
</cfloop>
You can take this a step further and say
<cfoutput><cfset dtypes = StructNew()>
<cfset dtypes["fname"] = "string">
<cfset dtypes["lname"] = "string">
<cfset dtypes["age"] = "int">
<cfset ErrStruct = StructNew()>
<cfset ErrCount = 0>
<cfloop list="#form.fieldnames#" index="fn">
<cfif StructKeyExists(dtypes,fn)>
<cfif dtypes[fn] is "string">
<cfif if_string_validation_fails>
<cfset ErrStruct[fn] = "#fn# has a bad value.">
<cfset ErrCount = ErrCount + 1>
</cfif>
<cfelseif dtypes[fn] is "int">
<cfif if_int_validation_fails>
<cfset ErrStruct[fn] = "#fn# has a bad value for an int.">
<cfset ErrCount = ErrCount + 1>
</cfif>
</cfif>
<cfelseif listfind("field,names,you,don't,want,to,validate,in,comma,delimited,list",fn)>
Found a field that doesn't need to be validated, like the submit button.
<cfelse>
Field #fn# is not exempt from validation, but no datatype was found.
</cfif>
</cfloop>
<cfif ErrCount gt 0>
<cfloop list="#StructKeyList(ErrStruct)#" index="en">
Field "#en#" failed: #ErrStruct[en]#.
</cfloop>
<cfelse>
Can input form submission stuff here.
</cfif></cfoutput>
The if_string_validation_fails and if_int_validation_fails are completely made up and mean nothing, here's where you might put your validation rules.
I think worrying about the small percentage of users on a browser with JS disabled is likely least of concern, but of course without knowing the purpose of the site, I will not make that assumption. Here's an article to read to determine if that's something you really need to be concerned about: https://webmasters.stackexchange.com/questions/4733/should-i-worry-about-people-disabling-javascript
Once you determine if it's something you really need to worry about, you can move forward and rely on jQuery, optionally using some method for graceful degradation (require them to enable JS for your feature for example).
<cfif listLen(structKeyList(form)) GT 0>
<cfset stcMetadata = deserializeJSON(form._metadata) />
<cfdump var="#stcMetadata#" />
</cfif>
<html>
<body>
<form id="myform" method="post">
name: <input name="name" type="text" class="string" /><br />
options:
<select name="options" class="int">
<option value="1">1</option>
<option value="2">2</option>
</select><br />
big text: <textarea name="bigtext" class="string"></textarea><br />
<input type="submit" />
</form>
</body>
<script type="text/javascript" src="/path/to/jquery-1.8.3.min.js"></script>
<script>
$("#myform").submit(function (e) {
var $this = $(this),
fields, metadata = {};
// Get all the form fields
fields = $this.find(":input");
// Loop the fields and compile metadata
fields.each(function () {
if (this.type !== "submit") {
metadata[this.name] = $(this).attr("class") || "";
}
});
// Append the field to your form
$this.append(
$("<input type='hidden' name='_metadata' />").val(JSON.stringify(metadata))
);
});
</script>
</html>
Your result:
struct
bigtext: string
name: string
options: int

How do I param a dynamic number of form fields in object-oriented Coldfusion setup?

I've used this example to set up a small object oriented project
The page I'm redoing had some order features with a dynamic number of input fields (say sizes of an article, some size S-XL, some size S-5XL.
In Coldfusion fields where labelled dynamically like so:
<input type="button" name="qty#counter#" id="qty-field#counter#"
In my CFC, I'm listing all form fields like so:
<cfcomponent output="false" hint="">
<cfscript>
VARIABLES.Instance.Validation = {
field_a="validation_criteria",
field_b="validation_criteria",
...
}
</cfscript>
<cffunction name="Defaults" access="public" returntype="struct" output="false" hint="">
<cfscript>
// form defaults
var formDefaults = {
field_a="",
field_b="",
...
}
</cfscript>
<cfreturn formDefaults />
</cffunction>
<cffunction name="Commit" ... do something with db
<cfscript>
var LOCAL = {};
structAppend(defaultValues, VARIABLES.Instance.FormData);
LOCAL.xxx = defaultValues;
</cfscript>
... do stuff with LOCAL
So I'm creating an object with all passed form fields and do my stuff inside the CFC. Sort of like cfparam in non-object-oriented pages.
My question:
If I have 200 inputs on a page (easy...), I can't possibly param them each 1-200 or extend my default by 200 inputs. So, my question: Is there an easier way to "param" dynamic number of form fields than to add dummy fields 1-500 and hope this is enough for all cases (which is the worst possible option...)
AND:
If I wanted to port this into MySQL... In Coldfusion I have a query like:
<cfoutput query="s">
<cfquery datasource="db">
UPDATE pos
SET qty = "#evaluate("qty#id#")#"
WHERE id = "#client_id#" and id = "#id#"
</cfquery>
</cfoutput>
Is there any alternative than to loop in Coldfusion and call a storedProc i-numbers of times?
Thanks for input!
EDIT:
So my current solution looks like this:
, ean1="", ean2="", ean3="", ean4="", ean5="", ean6="", ean7="", ean8="", ean9="", ean10=""
, ean11="", ean12="", ean13="", ean14="", ean15="", ean16="", ean17="", ean18="", ean19="", ean20=""
, ean21="", ean22="", ean23="", ean24="", ean25="", ean26="", ean27="", ean28="", ean29="", ean30=""
, ean31="", ean32="", ean33="", ean34="", ean35="", ean36="", ean37="", ean38="", ean39="", ean40=""
, ean41="", ean42="", ean43="", ean44="", ean45="", ean46="", ean47="", ean48="", ean49="", ean50=""
, ean51="", ean52="", ean53="", ean54="", ean55="", ean56="", ean57="", ean58="", ean59="", ean60=""
, ean61="", ean62="", ean63="", ean64="", ean65="", ean66="", ean67="", ean68="", ean69="", ean70=""
, ean71="", ean72="", ean73="", ean74="", ean75="", ean76="", ean77="", ean78="", ean79="", ean80=""
, ean81="", ean82="", ean83="", ean84="", ean85="", ean86="", ean87="", ean88="", ean89="", ean90=""
, ean91="", ean92="", ean93="", ean94="", ean95="", ean96="", ean97="", ean98="", ean99="", ean100=""
// more
, menge1="", menge2="", menge3="", menge4="", menge5="", menge6="", menge7="", menge8="", menge9="", menge10=""
, menge11="", menge12="", menge13="", menge14="", menge15="", menge16="", menge17="", menge18="", menge19="", menge20=""
, menge21="", menge22="", menge23="", menge24="", menge25="", menge26="", menge27="", menge28="", menge29="", menge30=""
, menge31="", menge32="", menge33="", menge34="", menge35="", menge36="", menge37="", menge38="", menge39="", menge40=""
, menge41="", menge42="", menge43="", menge44="", menge45="", menge46="", menge47="", menge48="", menge49="", menge50=""
, menge51="", menge52="", menge53="", menge54="", menge55="", menge56="", menge57="", menge58="", menge59="", menge60=""
, menge61="", menge62="", menge63="", menge64="", menge65="", menge66="", menge67="", menge68="", menge69="", menge70=""
, menge71="", menge72="", menge73="", menge74="", menge75="", menge76="", menge77="", menge78="", menge79="", menge80=""
, menge81="", menge82="", menge83="", menge84="", menge85="", menge86="", menge87="", menge88="", menge89="", menge90=""
, menge91="", menge92="", menge93="", menge94="", menge95="", menge96="", menge97="", menge98="", menge99="", menge100=""
};
So I'm unhappy but safe until my dynamic form spits out 100+ fields... If anyone know a less code-intensive way of setting up "param"/empty vars than the above, please chip in. Thx!
,
What's wrong with doing the cfparam in a loop?
<cfset numOfFields = 200>
<cfset fields = "ean,menge">
<cfloop list="#fields#" index="field">
<cfloop from="1" to="#numOfFields#" index="i">
<cfparam name="Form.#field##i#" default="">
</cfloop>
</cfloop>
Loop through the form scope and parse through the fieldnames to find the related inputs.
Here is a sample form to generate a random number of inputs with random values:
<form name="dynamicForm" action="update.cfm" method="post">
<cfoutput>
<cfloop from="1" to="#randrange(1,100)#" index="i">
<br>#i#:<input type="text" name="qty#i#" value="#randrange(1,100)#">
</cfloop>
<br><input type="submit" name="submit" value="submit">
</cfoutput>
</form>
Then, in update.cfm, do the following:
<cfloop list="#form.fieldnames#" index="fieldname">
<cfif lcase(left(fieldname,3)) EQ "qty">
<cfset id = mid(fieldname,4,len(fieldname)) >
<cfstoredproc
procedure="schema.updQTY"
datasource="myDSN">
<cfprocparam type="IN" cfsqltype="CF_SQL_INTEGER" value="#id#">
<cfprocparam type="IN" cfsqltype="CF_SQL_INTEGER" value="#form[fieldname]#">
</cfstoredproc>
</cfif>
</cfloop>
Personally, I'd name the input fields something more like "qty_XXX" and use listfirst and listlast to parse the fieldnames, but that's pure preference.

How to format a json feed from coldfusion to jquery fullcalendar?

I am trying to get a json feed working for the jquery Full Calendar plugin but I am not having much luck. The data I get back from coldfusion is not formatted correctly (at least that is my guess). Here is what I am getting back:
{"COLUMNS":["TITLE","START","END","REQUEST_TYPE_ID"],"DATA":[["duration of a VTC ","2012-03-15T12:00:00Z","2012-03-15T15:00:00Z",1],["a new vtc overlap","2012-03-15T11:45:00Z","2012-03-15T14:15:00Z",1]]}
I am pretty sure full calendar does not know how to read this data type. So the question is, can I get CF to pass back the data in a format that full calendar will accept? Is there something else going on here?
Here is my component:
<cfquery datasource="#arguments.dsn#" name="eventlist">
select title, to_char(start_time,'YYYY-MM-DD')||'T'||to_char(start_time,'HH24:MI:SS')||'Z' as "start",
to_char(start_time,'YYYY-MM-DD')||'T'||to_char(start_time + (duration/1440),'HH24:MI:SS')||'Z' as "end", request_type_id
from ((request r join event_schedule es on es.request_id = r.id)left join location_ref loc on loc.location_id = r.location_id)
where site_id = <cfqueryparam value="#arguments.site_id#" cfsqltype="cf_sql_varchar" />
and request_type_id = <cfqueryparam value="#arguments.evnttype#" cfsqltype="cf_sql_varchar" />
and start_time between to_date('#sdate#', 'mon dd yyyy') and to_date('#edate#', 'mon dd yyyy')
</cfquery>
and the return format is json.
returnformat="json"
Any ideas?
Thanks!
You need to format your data for the plugin to work. It needs to be an array of Event Objects http://arshaw.com/fullcalendar/docs/event_data/Event_Object/
So you need to loop through your query and create an array of structs with the keys specified on the documentation in the above link.
You will need to use array notation as JavaScript is case-sensitive for variable names and CF likes to make the keys in structs uppercase by default.
So do this:-
<cfset myStruct["id"] = 1>
Instead of:-
<cfset myStruct.id = 1>
I hope that helps.
I created a CFC that returns jSon based on the date passed;
My CFC broken down:
<cfcomponent>
<cffunction name="LoadCalendarData" access="remote" output="false" returntype="Any" returnformat="JSON">
<cfargument name="DEPTID" type="any" required="false" />
<cfargument name="STAFFCLASSID" type="any" required="false" />
<cfargument name="start" type="date" required="false" />
<cfargument name="end" type="date" required="false" />
<cfset arguments.start=#DateFormat(DateAdd("s", arguments.start, "January 1 1970 00:00:00"), "mmmm dd, yyyy")#/>
<cfset arguments.end=#DateFormat(DateAdd("s", arguments.end, "January 1 1970 00:00:00"), "mmmm dd, yyyy")#/>
<cfinvoke component="DataStore" method="Calendar_LU" returnvariable="qGetCalendar" DEPTID="#arguments.deptid#" STAFFCLASSID="#arguments.STAFFCLASSID#" START="#arguments.start#" END="#arguments.end#" />
I tranlate the arguments of the start date and end date in to something fullcalendar can use then I start to build by looping the data based on the information returned. next I break down the dates and times so I can pass it back to fullCalendar.
<cfset CalendarData=[] />
<cfset CalData = arraynew(2) />
<cfloop query="qGetCalendar">
<cfset sd = #dateformat(LEAVESTARTDATE,'dd')# />
<cfset sm = #dateformat(LEAVESTARTDATE,'mm')# />
<cfset sy = #dateformat(LEAVESTARTDATE,'yyyy')# />
<cfset sh = #timeformat(LEAVESTARTDATE,'HH')# />
<cfset si = #timeformat(LEAVESTARTDATE,'mm')# />
<cfset ed = #dateformat(LEAVEENDDATE,'dd')# />
<cfset em = #dateformat(LEAVEENDDATE,'MM')# />
<cfset ey = #dateformat(LEAVEENDDATE,'yyyy')# />
<cfset eh = #timeformat(LEAVEENDDATE,'HH')# />
<cfset ei = #timeformat(LEAVEENDDATE,'mm')# />
<cfset event=structNew() />
<cfset event['title']='#Left(ListLast(FULLNAME,","),1)#. #REReplace(ListFirst(FULLNAME,","),"'","")#' />
<cfset event['start']='#sy#-#sm#-#sd# #sh#:#si#' />
<cfset event['end']='#ey#-#em#-#ed# #eh#:#si#' />
<cfset event['textColor']='##330000'/>
<cfset event['backgroundColor']=STATUSColor />
<cfset event['url']=clickablePath />
<cfset arrayAppend(CalendarData,event)/>
</cfloop>
<cfreturn calendarData />
</cffunction>
in the script where fullCalendar is called I use this to call the CFC
eventSources: [{url:'CFCs/Holidays.cfc?Method=LoadCalendarHolidays'},{url:'CFCs/CalendarDataRemote.cfc?Method=LoadCalendarData&DEPTID=<cfoutput>#deptid#</cfoutput>&STAFFCLASSID=<cfoutput>#STAFFCLASSID#</cfoutput>'}],
As you can see I added another CFC call to pull in the our holidays so you can load more than one calendars data. Hope this helps
You also need to make sure the keywords are lowercase... fullCalendar like javascript is case sensitive so you need start, end etc.
OOOPS just read the end of baynezy answer mentions this please disregard.

Writing an optimised and efficient search engine with mySQL and ColdFusion

I have a search page with the following scenarios listed below. I was told using so many conditional statements degrades performance, but I don't know if any other way to achieve the same objective.
Search.cfm will processes a search made from a search bar present on all pages, with one search input (titleName).
If search.cfm is accessed manually (through URL not through using the simple search bar on all pages) it displays an advanced search form with three inputs (titleName, genreID, platformID (dynamically generated, but not in this example code) or it evaluates searchResponse variable and decides what part of the page to show.
If simple search/advanced search query is blank, has no results, or less than 3 characters it displays an error
If any successful search returns results, they come back normally. There is one function handling the queries.
The top-of-page logic is as follows:
<!---DEFINE DEFAULT STATE--->
<cfparam name="variables.searchResponse" default="">
<!---CHECK TO SEE IF SEARCH A FORM WAS SUBMITTED AND EXECUTE SEARCH IF IT WAS--->
<cfif IsDefined("Form.simpleSearch") AND Len(Trim(Form.titleName)) LTE 2>
<cfset variables.searchResponse = "invalidString">
<cfelseif IsDefined("Form.simpleSearch") AND Len(Trim(Form.titleName)) GTE 3>
<cfinvoke component="gz.cfcomp.search" method="searchTitles" titleName="#Form.titleName#" genreID="" platformID="" returnvariable="searchResult">
<cfset variables.searchResponse = "hasResult">
</cfif>
<!---CHECK IF ADVANCED SEARCH FORM WAS SUBMITTED--->
<cfif IsDefined("Form.advancedSearch") AND (Len(Trim(Form.titleName)) LTE 2 AND Len(Form.genreID) IS 0 AND Len(Form.platformID) IS 0)>
<cfset variables.searchResponse = "invalidString">
<cfelseif IsDefined("Form.advancedSearch") AND variables.searchResponse IS NOT "invalidString">
<cfinvoke component="gz.cfcomp.search" method="searchTitles" returnvariable="searchResult" titleName="#Form.titleName#" genreID="#Form.genreID#" platformID="#Form.platformID#">
<cfset variables.searchResponse = "hasResult">
</cfif>
<!---CHECK IF ANY RECORDS WERE FOUND--->
<cfif IsDefined("variables.searchResult") AND searchResult.RecordCount IS 0>
<cfset variables.searchResponse = "noResult">
</cfif>
I'm using the searchResponse variable to decide what the the page displays, based on the following scenarios:
<!---ALWAYS DISPLAY SIMPLE SEARCH BAR AS IT'S PART OF THE HEADER--->
<form name="simpleSearch" action="search.cfm" method="post">
<input type="hidden" name="simpleSearch" />
<input type="text" name="titleName" />
<input type="button" value="Search" onclick="form.submit()" />
</form>
<!---IF NO SEARCH WAS SUBMITTED DISPLAY DEFAULT FORM--->
<cfif searchResponse IS "">
<h1>Advanced Search</h1>
<!---DISPLAY FORM--->
<form name="advancedSearch" action="search.cfm" method="post">
<input type="hidden" name="advancedSearch" />
<input type="text" name="titleName" />
<input type="text" name="genreID" />
<input type="text" name="platformID" />
<input type="button" value="Search" onclick="form.submit()" />
</form>
</cfif>
<!---IF SEARCH IS BLANK OR LESS THAN 3 CHARACTERS DISPLAY ERROR MESSAGE--->
<cfif searchResponse IS "invalidString">
<cfoutput>
<h1>INVALID SEARCH</h1>
</cfoutput>
</cfif>
<!---IF SEARCH WAS MADE BUT NO RESULTS WERE FOUND--->
<cfif searchResponse IS "noResult">
<cfoutput>
<h1>NO RESULT FOUND</h1>
</cfoutput>
</cfif>
<!---IF SEARCH MADE AND RESULT WAS FOUND--->
<cfif searchResponse IS "hasResult">
<cfoutput>
<h1>Search Results</h1>
</cfoutput>
<cfoutput query="earchResult">
<!---DISPLAY QUERY DATA--->
</cfoutput>
</cfif>
Is my logic a) not efficient because my if statements/is there a better way to do this? And b) Can you see any scenarios where my code can break? I've tested it but I have not been able to find any issues with it. And I have no way of measuring performance. Any thoughts and ideas would be greatly appreciated.
Here is my function, for reference:
<!---SEARCH--->
<cffunction name="searchTitles" hint="This functions searches for a title based on multiple categories" access="public" output="false">
<cfargument name="titleName" required="no" type="string" hint="Search by title">
<cfargument name="genreID" required="no" type="string" hint="Search by genre">
<cfargument name="platformID" required="no" type="string" hint="Search by platform">
<!--- DEFINE LOCAL VARIABLES - NOTE VARIABLE NAME IS QUERY NAME --->
<cfset var searchResult = "">
<!---GET RESULTS--->
<cfquery name="searchResult" datasource="myDSN">
SELECT
games.gameID,
games.gameReleaseDate AS rDate,
titles.titleName AS tName,
titles.titleShortDescription AS sDesc,
platforms.platformName AS pName,
genres.genreName AS gName
FROM
games
INNER JOIN titles ON titles.titleID = games.titleID
INNER JOIN platforms ON games.platformID = platforms.platformID
INNER JOIN genres ON games.genreID = genres.genreID
WHERE
0=0
<cfif ARGUMENTS.titleName IS NOT "">
AND titles.titleName LIKE <cfqueryparam cfsqltype="cf_sql_varchar" value="%#ARGUMENTS.titleName#%">
</cfif>
<cfif ARGUMENTS.genreID IS NOT "">
AND games.genreID = <cfqueryparam cfsqltype="cf_sql_varchar" value="#ARGUMENTS.genreID#">
</cfif>
<cfif ARGUMENTS.platformID IS NOT "">
AND games.platformID = <cfqueryparam cfsqltype="cf_sql_varchar" value="#ARGUMENTS.platformID#">
</cfif>
ORDER BY
rDate DESC,
tName;
</cfquery>
<cfreturn searchResult>
</cffunction>
Many thanks
If you add default values of "" to your genreID and platformID arguments, then I think you can refactor your top code to:
<cfif StructKeyExists(url, "titleName") and Len(Trim(url.titleName)) lte 2>
<cfset variables.searchResponse = "invalidString">
<cfelseif StructKeyExists(url, "titleName")>
<cfinvoke component="gz.cfcomp.search" method="searchTitles" returnvariable="searchResult" argumentcollection="#url#">
<cfset variables.searchResponse = "hasResult">
</cfif>
<cfif searchResponse eq "hasResult" and searchResult.recordCount eq 0>
<cfset variables.searchResponse = "noResult">
</cfif>
Note: I recommend switching your form methods to "get" for a search like this to improve the user's experience navigating your site. I've switched all the form references to url in the code.
Have you thought about using a Full-Text search rather than just specific things like title, genre and platform? Full text is much faster that a normal table query at the cost of a bit more disk space usage, and allows very flexible Google style searches on all your data. Have a look at the link below.
http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html
Perhaps if you did this, you could just implement a pass though user input to the Full text search, since it provides all the advanced search expressions through it's own syntax. By doing this you probably would not need to branch your front end logic based on what they are searching for, this would make the solution cleaner and faster.
Just a preference I suppose, but since the advent of Google, people expect the one search box to do everything. You could provide some advanced search capabilities directly from the simple box by testing the input and conditionally running separate queries and returning the combined results. To make the results clearer you can highlight the matching portion with Coldfusion or javascript.
I would also init the component rather than using cfinvoke multiple times, but I'm not sure if that's a requirement or a preference.
<!--- By setting a default, we don't have to test for this var's existence repeatedly --->
<cfparam name="form.simpleSearch" default="" />
<cfscript>
// Using cfscript for compactness in this little box, could use tag based cf also
// Init myComponent so that it's ready to receive searches
searcher = createObject('component','myComponent');
// init query results
simpleResult = queryNew("id");
genres = queryNew("id");
platforms = queryNew("id");
titles = queryNew("id");
// q: shorthand 'Query' var that's trimmed and prechecked for length
q = "";
if (len(trim(form.simpleSearch)) GTE 3) {
q = trim(form.simpleSearch);
}
// Run simple search - Should returns a query object regardless of result count
if (len(q) {
simpleResult = searcher.simpleSearch(q);
/* Not sure what the difference between this and simpleSearch() is unless
this only looks for exact matches, and simpleSearch() uses LIKE and %
wildcards for looser results. */
titles = searcher.titleSearch(q);
/* Conditionally run advanced searches
- assumes that genreID and platformID are numeric
- IF genreID and platformID are not numeric, you could use regex to test for their signature or just run them anyway knowing they wouldn't match in many cases. (BUT you must, must MUST use cfqueryparam if you attempt that)
- Requires that advancedSearch is split into three separate functions
- Alternately, the simpleSearch(q) or advancedSearch() could do this all internally
- All of these functions should return an empty query if no results are found
if (isNumeric(q)) {
genres = searcher.genreSearch(q);
platforms = searcher.platformSearch(q);
}
}
</cfscript>
...
<!---ALWAYS DISPLAY SIMPLE SEARCH BAR AS IT'S PART OF THE HEADER--->
<cfoutput>
<form action="#cgi.script_name#" method="post">
<input type="text" name="simpleSearch" value="#form.q#" />
<input type="submit" value="Search" name="submit" />
</form>
<cfif titles.recordcount>
<h3>Matching Titles</h3>
<cfloop query="titles">
..html to output the titles
</cfloop>
</cfif>
<cfif genres.recordcount>
<h3>Matching Genres</h3>
<cfloop query="genres">
..html to output the genres
</cfloop>
</cfif>
<cfif platforms.recordcount>
<h3>Matching Platforms</h3>
<cfloop query="platforms">
..html to output the platforms
</cfloop>
</cfif>
<cfif simpleResult.recordcount>
<h3>Results</h3>
<cfloop query="simpleResult">
..html to output the simpleResult
</cfloop>
</cfif>
</cfouput>
Granted, there are many, many different ways to do this. This is simply one method to reduce the complexity of a simple search vs an advanced search. The advanced results would only be shown if they had a match and you can tune those results with your query inside your myComponent function - just remember to return a blank query if there is no result.
My coworkers told me, that we should simplify the CFIF-Statements:
From 'CFIF something EQ ""' we moved to 'CFIF Not Len(something)' and we work more with booleans for NoResult and HasResult we would use a boolean.
Anyway, the other answers are some great inspiration, I need to check my own search page ;)