views:

95

answers:

2

I have a table that records the name of uploaded documents, up to 14 per record. The columns are named thus:

TABLE tblDocuments
COLUMNS documentID (int, not null, pk)
        document1 (varchar(250), null)
        document2 (varchar(250), null)
        /* and this continues through */
        document14 (varchar(250), null)

So I query for any documents for a particular record:

<cfquery name="qryGetDocs" datasource="#dsn#">
     SELECT document1, ...document14
     FROM   tblDocuments
     WHERE  documentID = <cfqueryparam name="SESSION.documentID" cfsqltype="cf_sql_integer">
</cfquery>

The form looks something like this:

<form name="frmUploadDocs" method="post" action="documentsPage.cfm">

<input type="file" name="document1" size="50" >
<cfif qryGetDocs.document1 IS NOT ''>
   (current file name: <a href="#vars.file_path#/#qryGetDocs.document1#">#qryGetDocs.document1#</a>)</cfif>

<input type="file" name="document2" size="50" >
<cfif qryGetDocs.document2 IS NOT ''>
   (current file name: <a href="#vars.file_path#/#qryGetDocs.document2#">#qryGetDocs.document2#</a>)</cfif>

<!--- list all documents --->

<input type="file" name="document14" size="50" >
<cfif qryGetDocs.document14 IS NOT ''>
   (current file name: <a href="#vars.file_path#/#qryGetDocs.document14#">#qryGetDocs.document14#</a>)</cfif>

<input type="submit" name="submit" value="Upload Documents">
</form>

I want to loop from 1 to 14, so that I only have one <input> and <cfif> statement, like so:

<cfloop from="1" to="14" index="i">
   <input type="fiile" name="document#i#" size="30">
   <cfif qryGetDocs.document#i# IS NOT ''>
     (current file name: <a href="#vars.file_path#/#qryGetDocs.document[#i#]#">#qryGetDocs.document[#i#]#</a>)
   </cfif>
</cfloop>

However, I cannot get the syntax correct no matter what I've tried. Can someone please help me with this? Thank you!

+2  A: 

Queries can be accessed (like structs) with a string index and square brackets, but only if you also include the desired row number (!). This works like a two-dimenisonal array.

<cfloop from="1" to="14" index="i">
   <input type="file" name="document#i#" size="30">
   <cfif qryGetDocs["document#i#"][qryGetDocs.CurrentRow] IS NOT ''>
     (current file name: <a href="HTMLEditFormat("#vars.file_path#/#qryGetDocs["document#i#"][qryGetDocs.CurrentRow]#")#">#HTMLEditFormat(qryGetDocs["document#i#"][qryGetDocs.CurrentRow])#</a>)
   </cfif>
</cfloop>

Note the HTMLEditFormat() to protect yourself against cross site scripting attacks. This is important! Never ever output data to HTML without properly escaping it. (I admit that filenames are an improbable attack vector because they usually cannot contain pointy brackets, but a) you can't be too cautious, b) it's a good habit to get into, and c) no one knows what security holes will pop up when the code gets re-factored at some point in the future. Not HTML-escaping data is inexcusable sloppiness.)

A more idiomatic and much more readable version would be:

<cfloop from="1" to="14" index="i">
   <cfset RowNum  = qryGetDocs.CurrentRow>
   <cfset ColName = "document#i#">
   <cfset DocName = qryGetDocs[ColName][RowNum]> 
   <cfset DocPath = "#vars.file_path#/#DocName#">
   <input type="file" name="#ColName#" size="30">
   <cfif FileExists(ExpandPath(DocPath))>
     (current file name: <a href="#HTMLEditFormat(DocPath)#">#HTMLEditFormat(DocName)#</a>)
   </cfif>
</cfloop>
Tomalak
Just do not forgot to add the row number ie #queryName["colName"][rowNumber]#
Leigh
@Leigh: Yeah, sorry posted too early. See edit.
Tomalak
@Tomalak - Cool. Better readability too.
Leigh
Thanks so much for the answers!
BlondeMoment
@Blonde: You're welcome. Don't forget to accept if it works for you! :)
Tomalak
I did not know about the "HTMLEditFormat" protection so that is a bonus. And, I'm looking at changing the structure of my table based on your comments. So, I should have something like tblDocuments with columns documentID, documentName, documentNumber?
BlondeMoment
@BlondeMoment - What are the 14 documents related to ..? For example, are they 14 documents related to a specific "User", a record ID in another table, ...?
Leigh
Yes, they are - they are associated with a user, and I see I forgot to add that to the columns a minute ago
BlondeMoment
@BlondeMoment - Thanks .. but I did not mean to steal the answer. There was just too much code to fit inside a comment.
Leigh
@Leigh: Never mind! ;-)
Tomalak
@Tomalak - Alright, alright. (You know, either these comment boxes are *really* small or I am really wordy ;)
Leigh
+3  A: 

(The original question was already answered. But just to illustrate ...)

A more flexible structure is to store the documents as rows. So basic table might be:

TABLE:    tblDocuments
COLUMNS:  DocumentID  (unique record id)
          UserID
          DocumentName

Using this structure, you could retrieve all existing documents for a single user with a simple query

<cfquery name="qryGetDocs" datasource="#dsn#">
     SELECT documentID, documentName
     FROM   tblDocuments
     WHERE  userID = <cfqueryparam name="#SomeUserIDVariable#" cfsqltype="cf_sql_integer">
</cfquery>

.. and display them with a simple output loop. (Note, I added "documentID" as hidden field to identify existing documents ..)

<cfoutput query="qryGetDocs">
   ...
   <input type="file" name="document#CurrentRow#" size="50" >
   <input type="hidden" name="documentID#CurrentRow#" value="#documentID#" >
   (current file name: <a href="#vars.file_path#/#documentName#">#documentName#</a>)
</cfoutput>

If the query contains less than 14 files (or whatever your maximum is..), you can use the query.recordCount to determine how many additional file inputs to display.

<cfset nextInputNumber = qryGetDocs.recordCount + 1>
<cfoutput>
<cfloop from="#nextInputNumber#" to="#MaximumNumberOfDocs#" index="counter">
   <input type="file" name="document#counter#" size="50" >
   <input type="hidden" name="documentID#counter#" value="0" >
</cfloop>
</cfoutput>
Leigh