Dynamic Checkboxes not updating correctly - html

I have dynamic checkboxes that are generated as such
<cfset counter = 0>
<form name="setPermissions" class="setPermissions" action="" method="post">
<cfoutput>
<cfloop query="getUserAccess">
<input name="Meetings_#Counter#" type="checkbox" />
<cfset counter = counter + 1>
</cfloop>
</cfoutput>
</form>
The getUserAccess query has 6 rows, which means that 6 of these checkboxes will be outputted.
To read these and update the table respectively, I'm running the following query
<cfloop from="0" to="#getUserAccess.RecordCount#" index="i">
<cfquery datasource="#Request.dsn#">
UPDATE table SET
<cfif structKeyExists(FORM, 'Meetings_#i#')>
Meetings = <cfif FORM['Meetings_#i#'] EQ "on">1,<cfelse>0,</cfif>
</cfif>
WHERE ID = '#an_ID_that_is_specified#'
</cfquery>
</cfloop>
Apologies if this code isn't correct, it works when I'm running it on my page, but there are about 20 of these checkboxes being generated, so I've snipped my code so you can see only one.
Now, this code works great, when I check a box and submit the form, this code will update the database to set the value to 1.
The only problem is, when I uncheck the box, it won't change the value to a 0. This is where I am stuck. Why is it not changing the value to 0 when there is no on specified as a form value?
I've done a <cfdump> to see if anything was still being passed, but there isn't.
With all of the boxes ticked I get this dump
MEETINGS_0 on
MEETINGS_1 on
MEETINGS_2 on
MEETINGS_3 on
MEETINGS_4 on
MEETINGS_5 on
With some ticked, the ones which I haven't ticked are not showing up.
However the value is still not updating from a 1 to a 0.

Checkbox simply wont submit any value when unchecked (that's how it works). So your query should look closer to this:
UPDATE table SET
Meetings = <cfif structKeyExists(FORM, 'Meetings_#i#')>1,<cfelse>0,</cfif>
WHERE ID = '#an_ID_that_is_specified#'

Sergii already answered the original question. But just to throw another approach out there ...
You could also use lists. Assuming your id's are numeric, give all checkboxes the same name and store the original id's in a hidden field.
<cfoutput query="getUserAccess">
#meetingID#
<input name="SelectedMeetings" type="checkbox" value="#meetingID#" />
</cfoutput>
<cfoutput>
<input name="OriginalMeetings" type="hidden" value="#valueList(getUserAccess.meetingID)#" />
</cfoutput>
On the action page FORM.SelectedMeetings will contain a list of all ID's that were selected. Simply take the difference of the two lists to find ID's that were "de-selected".
<cfparam name="FORM.SelectedMeetings" default="" / >
<cfset FORM.DeSelectedMeetings = replaceList( FORM.OriginalMeetings
, FORM.SelectedMeetings
, "") />
Then you only need two queries to updates the database instead of six (or however many checkboxes you have). Obviously whichever method you choose, you should wrap all queries in a transaction to ensure they all succeed or fail together.
<cfif listLen(trim(FORM.selectedMeetings))>
<cfquery datasource="#Request.dsn#">
UPDATE table
SET Meetings = 1
WHERE ID IN (
<cfqueryparam value="#FORM.selectedMeetings#" list="true" cfsqltype="cf_sql_integer">
)
</cfquery>
</cfif>
<cfif listLen(trim(FORM.DeSelectedMeetings))>
<cfquery datasource="#Request.dsn#">
UPDATE table
SET Meetings = 0
WHERE ID IN (
<cfqueryparam value="#FORM.DeSelectedMeetings#" list="true" cfsqltype="cf_sql_integer">
)
</cfquery>
</cfif>

Related

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

Simple pagination

Lately I've been messing around with a ColdFusion forum script, and I'm trying to add pagination to it so that it only displays 10 comments per page instead of all of them. Sadly, I've only found complicated solutions to do this and no simple solution like I was used to in PHP. Currently, this is my code for getting the comments out of my database:
<cfquery name = "comments" datasource = "#DSN#">
SELECT *
FROM `forum_comments`
WHERE topicid = #id#
</cfquery>
Because I don't want to overload my processor while fetching 1000+ comments per topic, I want to seperate all the comments into pages of 10 comments per page. Is this possible with just some little modifications to my page? My best guess to do this is by using the URL.page statement and the LIMIT function in MySQL, but I have no idea how.
Update
All of my current code:
<cfif IsDefined('URL.page')> // Pagination
<cfset page = URL.page>
<cfelse>
<cfset page = 1>
</cfif>
<cfset howManyRecsToShow = 10>
<cfset startRec = page*howManyRecsToShow>
<cfquery name = "comments" datasource = "#DSN#"> // Get all comments
SELECT *
FROM `forum_comments`
WHERE topicid = #id#
LIMIT #startRec#, #howManyRecsToShow#
</cfquery>
<cfset colour ="post_uneven"> // Required for CSS
<cfloop query="comments"> // Loop over comments
<div id="post_text">
<div id="post_text_edit" title="Edit"></div>
<div id="post_text_delete" title="Delete"></div>
<div id="post_text_title">
RE: #gettopic.title#
</div>
<div id="post_text_date">
#DateFormat(dateAdd("s", comments.timestamp, "01/01/1970"))# #TimeFormat(dateAdd("s", comments.timestamp, "01/01/1970"))#
</div>
<div id="post_text_text">
#comments.text#
</div>
</div>
</div>
</cfloop>
</cfoutput>
<cfif colour is "post_uneven"><cfset colour="post_even"><cfelse><cfset colour="post_uneven"></cfif> // Required for CSS
<div id="topic_info_balk">
<div id="forum_paginas">
<cfif page gt 1>
PREVIOUS 10 |
</cfif>
NEXT 10
</div>
You said you want a SIMPLE example so here it is. By SIMPLE I mean this will just show NEXT and PREVIOUS links. If you want slightly more complicated pagination that shows the number of pages then you have to get the total record count before hand. (I quickly free-hand typed this, not on my CF server right now, so not sure if this is 100% accurate)...
(pageTest.cfm)
<cfif IsDefined("url.page")>
<cfset page = url.page>
<cfelse>
<cfset page = 1>
</cfif>
<cfset howManyRecsToShow = 10>
<cfset startRec = page*howManyRecsToShow>
<cfquery name="q1">
select id, username, email
from users
order by id limit #startRec# , #howManyRecsToShow#
</cfquery>
<cfoutput>
<table border="1">
<cfloop query="q1">
<tr>
<td>#q1.id#</td>
<td>#q1.username#</td>
<td>#q1.email#</td>
</tr>
</cfloop>
</table>
<cfif page gt 1>
PREVIOUS 10 |
</cfif>
NEXT 10
</cfoutput>

how to display child of child?

i have one table called content where in I am adding top level pages and child pages. The top level page will have parent__id as the id of page title home and child pages will have their respective parent__id. when i add pages it something look like this.
id parent__id title and so on.
1 0 Home
2 1 About
3 2 services
4 1 contact us
here I want to display it like
Home
>About
>>services
>contact
i have tried this
<cfquery name="d" datasource="mydata">
SELECT p.id as parentid,
p.title as parent,
p.nav_depth,
c.id as childid,
c.title as child,
c.parent__id as child_parentid
from content p
left join content as c on c.parent__id = p.id
where p.site__id = "8432381492061036983"
group by p.id, c.id
order by p.id, c.id, c.nav_order
</cfquery>
<cfdump var="#d#">
<cfoutput query="d" group="parentid">
<cfif d.nav_depth EQ 0>
#d.parent#<br />
<cfif d.parentid EQ d.child_parentid>
<cfoutput>> #d.child#<br /></cfoutput>
</cfif>
</cfif>
<cfif d.nav_depth GT 0>
<cfif d.childid EQ d.child_parentid>
<cfoutput> >> #d.child<br /></cfoutput>
</cfif>
</cfif>
</cfoutput>
I got the output like this. when i look into the dump it shows correct result.
Home
> About US
> Services
can any help me out what should i do in the query or cfoutput to mark the child of child differently and to display it just below the parent?
here is my complete table structure with data http://sqlfiddle.com/#!2/a29fc/1/0. Thanks
I don't see where child is coming from, but what you need is a nested group output. This should get you on the right track. The biggest thing to watch is that your query is ordered in the order they should display on the screen. Your current SQL Fiddle isn't doing that part correctly.
<cfoutput query="d" group="parentid">
#d.parent#<br />
<cfoutput group="child"><!--- group based on child --->
> #d.child#<br /><!--- output child header --->
<cfoutput>
>> #d.child<br /><!--- all matching children for current d.child --->
</cfoutput>
</cfoutput>
</cfoutput>
This would output
Home
> About Us
>> Our Works
> Contact Us

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.

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 ;)