Having trouble grouping CFQuery results - html

I have a CFQuery to pull data from a table which I would like to output to the screen in a format that groups the data according to one of the columns, 'Company Name'. I can't seem to wrap my head around the logic for this.
Currently I'm just looping through the data to output it to the screen and separating it by a horizontal rule tag. Not the best looking way to do it and it generates a really long list of results that the user needs to scroll through. I am hoping that by grouping the data, it will be more readable.
Here is my code:
<!--- Feedback Query --->
<cfquery name="getFeedback" datasource="#datasource#">
select ticket_id, service_satisfaction, customer_notes, response_date, company_name
from service_verification
order by company_name
</cfquery>
<br />
<cfoutput query="getFeedback" group="company_name"><strong>Company Name: #getFeedback.company_name#</strong><br />
<cfquery dbtype="query" name="parsed">
select company_name
from getFeedback
where company_name = '#getFeedback.company_name#'
</cfquery>
(#parsed.recordcount# Responses)<br />
<cfoutput>
Ticket Number: <cfif #getFeedback.ticket_id# eq 0>No ticket associated with this feedback. This was solicited feedback.<cfelse>#getFeedback.ticket_id#</cfif><br />
Date: #DateFormat(getFeedback.response_date, 'mm/dd/yyyy')# at #TimeFormat(getFeedback.response_date, 'hh:mm:ss')#<br /><br />
Rating: <cfif #getFeedback.service_satisfaction# eq 'thumbs-up'><img src="images/thumbs-up-small.png" /><cfelse><img src="images/thumbs-down-small.png" /></cfif><br />
Customer Notes: <cfif #getFeedback.customer_notes# eq ''>No additional comments provided.<cfelse>#getFeedback.customer_notes#</cfif><br /><br />
<hr style="border-top: 1px dashed ##8c8c8c;" />
</cfoutput>
<br />
</cfoutput>
Here is a sample of my CFDUMP from the above query:
company_name service_satisfaction response_date ticket_id customer_notes
1 AmerTech thumbs-up {ts '2014-10-22 10:25:14'} 22667 Jeff was great. thanks
2 AmerTech thumbs-up {ts '2015-01-20 12:02:34'} 23795 Rich was good. Thanks. He needs to send out a another drive that we would like as backup to take home at night. Also, he missed one machine for backups that I need to discuss. Have someone please call . Thanks
3 AmerTech, Inc thumbs-up {ts '2015-04-16 13:56:44'} 25066
4 AmerTech, Inc thumbs-down {ts '2015-10-22 11:23:40'} 27293 Brian, I understand from Dave that you could not solve the problem and that he had to call the OEM to solve the problem. This is what I was informed. I do not know any of the details surrounding the issue. but it shouldn't take that long to install a printer on a new laptop. Why did this occur and how do I make sure it doesn't happen again. thanks mark
5 AMIB thumbs-down {ts '2014-10-02 12:18:27'} 22463 Representative did not call me upon arrival at group home as instructed and implemented changes without approval from HR
6 AMIB thumbs-up {ts '2015-06-08 09:58:03'} 25599
7 AMIB thumbs-up {ts '2016-03-10 14:10:01'} 28777
8 AMIB thumbs-up {ts '2016-03-28 09:10:37'} 29193 Michael is a great tech! Extremely helpful and responsive to our needs!
9 AMIB thumbs-up {ts '2016-03-28 10:19:19'} 28777
Update:
When I add the group attribute to the cfoutput tag, it only shows the first result from the group
<cfloop query="getFeedback">
<cfoutput><cfoutput query="getFeedback" group="company_name">
Company Name: #getFeedback.company_name# <cfif #getFeedback.service_satisfaction# eq 'thumbs-up'><img src="images/thumbs-up-small.png" /><cfelse><img src="images/thumbs-down-small.png" /></cfif><br />
Ticket Number: <cfif #getFeedback.ticket_id# eq 0>No ticket associated with this feedback. This was solicited feedback.<cfelse>#getFeedback.ticket_id#</cfif><br />
Date: #DateFormat(getFeedback.response_date, 'mm/dd/yyyy')# at #TimeFormat(getFeedback.response_date, 'hh:mm:ss')#<br /><br />
Customer Notes: #getFeedback.customer_notes# <br /><br />
<br />
<hr></cfoutput>
</cfloop>

Get rid of the loop tag. Then use this:
<cfoutput query="getFeedback" group="company_name">
<!--- OUTPUT EACH GROUP --->
Company Name: #getFeedback.company_name# <cfif #getFeedback.service_satisfaction# eq 'thumbs-up'><img src="images/thumbs-up-small.png" /><cfelse><img src="images/thumbs-down-small.png" /></cfif><br />
<cfoutput>
<!--- OUTPUT EACH RECORD --->
Ticket Number: <cfif #getFeedback.ticket_id# eq 0>No ticket associated with this feedback. This was solicited feedback.<cfelse>#getFeedback.ticket_id#</cfif><br />
Date: #DateFormat(getFeedback.response_date, 'mm/dd/yyyy')# at #TimeFormat(getFeedback.response_date, 'hh:mm:ss')#<br /><br />
Customer Notes: #getFeedback.customer_notes# <br /><br />
<br />
</cfoutput>
<hr>
</cfoutput>
It's the second/nested CFOUTPUT tag that goes through each record.
Note the HR is within the GROUP, not each record.
If you want to get fancy, you can make this an accordion with jQuery. Each GROUP is the head, and each record is the contents.

Here is my final verson of the working code. Thanks #Leigh
<!--- Feedback Query --->
<cfquery name="getFeedback" datasource="#datasource#">
select ticket_id, service_satisfaction, customer_notes, response_date, company_name
from service_verification
order by company_name
</cfquery>
<br />
<cfoutput query="getFeedback" group="company_name">
<strong>Company Name: #getFeedback.company_name#</strong><br />
<cfquery dbtype="query" name="parsed">
select company_name
from getFeedback
where company_name = '#getFeedback.company_name#'
</cfquery>
(#parsed.recordcount# Responses)<br />
<cfoutput>
Ticket Number: <cfif getFeedback.ticket_id eq 0>No ticket associated with this feedback. This was solicited feedback.<cfelse>#getFeedback.ticket_id#</cfif><br />
Date: #DateFormat(getFeedback.response_date, 'mm/dd/yyyy')# at #TimeFormat(getFeedback.response_date, 'hh:mm:ss')#<br /><br />
Rating: <cfif getFeedback.service_satisfaction eq 'thumbs-up'><img src="images/thumbs-up-small.png" /><cfelse><img src="images/thumbs-down-small.png" /></cfif><br />
Customer Notes: <cfif getFeedback.customer_notes eq ''>No additional comments provided.<cfelse>#getFeedback.customer_notes#</cfif><br /><br />
<hr style="border-top: 1px dashed ##8c8c8c;" />
</cfoutput>
<br />
</cfoutput>

Related

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

Dynamic Checkboxes not updating correctly

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>

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