<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" 
      xmlns:thr="http://purl.org/syndication/thread/1.0">
  <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/07/creating-a-simple-multi-file-u.html" />
  <link rel="self" type="application/atom+xml" href="http://www.insideria.com/atom.xml" />
  <id>tag:www.insideria.com,2009://34/tag:www.insideria.com,2009://34.37476-</id>
  <updated>2009-11-16T14:51:38Z</updated>
  <title>Comments for Creating a simple multi-file uploader with additional fields (http://www.insideria.com/2009/07/creating-a-simple-multi-file-u.html)</title>
  <generator uri="http://www.sixapart.com/movabletype/">Movable Type 4.21-en</generator>
  <entry>
    <id>tag:www.insideria.com,2009://34.37476</id>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/07/creating-a-simple-multi-file-u.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://blogs.oreilly.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=34/entry_id=37476" title="Creating a simple multi-file uploader with additional fields" />
    <published>2009-07-10T01:11:23Z</published>
    <updated>2009-07-10T01:48:15Z</updated>
    <title>Creating a simple multi-file uploader with additional fields</title>
    <summary>Simple example of using jQuery clone to allow for multiple file uploads and metadata.</summary>
    <author>
      <name>Raymond Camden</name>
      <uri>http://www.coldfusionjedi.com</uri>
    </author>
    
    <category term="Blogs" />
    
    <content type="html" xml:lang="en" xml:base="http://www.insideria.com/">
      <![CDATA[Please forgive the title - I wasn't quite sure what to name this. It may help if I describe the problem first and hopefully things will make a bit more sense. I was asked by a client to add a simple multi-file uploader for their site. I've done this a few times before and there are a lot of nice options out there. Most recently I've used Uploadify (<a href="http://www.uploadify.com/">http://www.uploadify.com/</a>), a jQuery plugin, and it worked very well. 
<br/><br/>
However - all of these plugins/scripts/etc, all suffer from one problem - none of them support the addition of extra fields along with the file. Imagine that every file uploaded to your server has to include a description and a set of keywords. A typical multi-file uploader will only let you specify files to upload. They won't let you provide additional details. 
<br/><br/>
To be fair - this really isn't something folks typically do. Normally you add a multi-file uploader because you quickly want to send up a large number of files. If you are going to need to type in a bunch of junk for each file, why bother? Well, I can (and the client could) still see some merit in providing a way to upload multiple files while still providing additional fields for each file. Here is a quick demo of what I came up with.
<br/><br/>
I knew that it was easy to add form fields to an existing form with jQuery. I blogged on this earlier this year (<a href="http://www.coldfusionjedi.com/index.cfm/2009/2/19/Using-jQuery-to-add-form-fields">Using jQuery to add form field</a>). At that time, I simply created a string of HTML and added it to the end of my form. When I did, a few readers pointed out a more elegant way - using the jQuery clone() method. As you can probably guess, this will take a block of elements and create a copy of them. It's pretty perfect for what I have in mind. Let me show the initial form and then I'll add the JavaScript on top.
<br/><br/>

<div class="acode" style="overflow: auto; padding: 10px; height: 240px;" ><div style="overflow-x: visible;">
<code language="perl">
<pre>
&lt;form action="<span class="quote">multiupload.cfm</span>" <span class="category1">method</span>="<span class="quote">post</span>" enctype="<span class="quote">multipart/form-data</span>" id="<span class="quote">mainForm</span>"&gt;
&lt;<span class="category2">input</span> <span class="category2">type</span>="<span class="quote">button</span>" id="<span class="quote">adder</span>" value="<span class="quote">Add Another File</span>"&gt;

&lt;div id="<span class="quote">dynamicArea</span>"&gt;
&lt;fieldset id="<span class="quote">file0</span>" style="<span class="quote">margin-bottom:10px</span>"&gt;
	&lt;table&gt;
		&lt;tr&gt;
			&lt;td&gt;File:&lt;/td&gt;
			&lt;td&gt;&lt;<span class="category2">input</span> <span class="category2">type</span>="<span class="quote">file</span>" <span class="category2">name</span>="<span class="quote">file1</span>"&gt;&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;Keywords:&lt;/td&gt;
			&lt;td&gt;&lt;<span class="category2">input</span> <span class="category2">type</span>="<span class="quote">text</span>" <span class="category2">name</span>="<span class="quote">keywords1</span>"&gt;&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr valign="<span class="quote">top</span>"&gt;
			&lt;td&gt;Description:&lt;/td&gt;
			&lt;td&gt;&lt;textarea <span class="category2">name</span>="<span class="quote">description1</span>"&gt;&lt;/textarea&gt;&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/table&gt;
&lt;/fieldset&gt;
&lt;/div&gt;

&lt;<span class="category2">input</span> <span class="category2">type</span>="<span class="quote">submit</span>"&gt;
&lt;/form&gt;</pre>
</code>

</div></div>
<br/><br/>
The form consists of three fields - file1, keywords1, and description1. The numbers will make more sense later. There is a submit button at the end, and on top I added a button with the label, "Add Another File". This is what will drive the ability to add other files. Now let's look at the code.

<br/><br/>

<div class="acode" style="overflow: auto; padding: 10px; height: 240px;" ><div style="overflow-x: visible;">
<code language="perl">
<pre>
&lt;script <span class="category2">type</span>="<span class="quote">text/javascript</span>" src="<span class="quote">http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js</span>"&gt;&lt;/script&gt;
&lt;script&gt;
<span class="category1">var</span> current = 1

$(document).ready(<span class="category1">function</span>() {
 
 	$("<span class="quote">#adder</span>").click(<span class="category1">function</span>() {
  		current++
  		<span class="category1">var</span> newset = $("<span class="quote">#file0</span>").clone(<span class="category1">true</span>)
  
  		newset.find("<span class="quote">input,textarea</span>").each(<span class="category1">function</span>(i) { 
   				<span class="category1">var</span> $currentElem= $(<span class="category1">this</span>)
   				<span class="linecomment">//remove the number from the name and id</span>
   				<span class="category1">var</span> curname = $currentElem.attr("<span class="quote">name</span>")
   				curname = curname.replace(/[0-9]+$/,'<span class="quote"></span>')
   				<span class="category1">var</span> curid = $currentElem.attr("<span class="quote">id</span>")
   				curid = curid.replace(/[0-9]+$/,'<span class="quote"></span>')
   
   				$currentElem.attr("<span class="quote">name</span>",curname+current)
   				$currentElem.attr("<span class="quote">id</span>",curid+current)
   		})
  
  		newset.appendTo("<span class="quote">#dynamicArea</span>")
  
  	})
 	
})
&lt;/script&gt;</pre>
</code>

</div></div>
<br/><br/>
The majority of the code here resides within the "ready" block so I'll focus on that. I begin by creating an event handler for the click action on my button. Looking back at the HTML, note I had used a fieldSet to wrap my 3 sets of form fields. This lets me then simply do a clone. Now in theory, I could be done right then and there. However, the cloned items end up with the same names and IDs. In ColdFusion, this will result in values stored as lists, which can be a problem if your values contain commas. 
<br/><br/>
At this point I use a technique that was demonstrated by one of the commenters on my blog, Brian Swartzfager. He begins by doing a find on all input and textareas within the new cloned block: 	newset.find("input,textarea").each(function(i) { 
<br/><br/>
For each of these, we can take the name value, use a regex to remove the number, and then add a new number back to the end. I'm using a global variable, current, to keep store of the values. 
<br/><br/>
After we modify the clioned entity, the last step is to then simply insert it into the dom:  newset.appendTo("#dynamicArea")
<br/><br/>
And that's it... at least for the client side code. Obviously what you do on the server side is up to you. My ColdFusion code simply used a conditional loop. It looked for the existence of fileN, and N continued to increment. As soon as it did not exist, the loop ended. I used a string to return one block of results back to the user. For example: File x.jpg uploaded. File y.jpg was not processed because you forgot the description. File y.pdf was not processed because it was not an image. I've included the complete code below, with the file upload,management, and database insertion commented out. Hopefully it will help others.

<br/><br/>
<div class="acode" style="overflow: auto; padding: 10px; height: 240px;" ><div style="overflow-x: visible;">
<code language="perl">
<pre>

&lt;cfif <span class="category1">not</span> structIsEmpty(form)&gt;

	&lt;!--- A simple string that stores results we can report back to the user. ---&gt;
	&lt;cfset results = "<span class="quote"></span>"&gt;
	
	&lt;!--- loop, looking <span class="category1">for</span> fileX---&gt;
	&lt;cfset counter = 1&gt;
	&lt;cfset turnedOn = structKeyExists(form, "<span class="quote">file1</span>")&gt;
	&lt;cfloop condition="<span class="quote">#turnedOn#</span>"&gt;
		&lt;!--- Based <span class="category1">on</span> my testing, the file, keywords, <span class="category1">and</span> description are required, everything <span class="category1">else</span> is cake. ---&gt;
		&lt;!--- <span class="category1">in</span> all cases though, <span class="category2">check</span> first <span class="category1">for</span> the file. no file - no processing ---&gt;
		
		&lt;cfif len(form["<span class="quote">file#counter#</span>"])&gt;
		
			&lt;cfset keywords = form["<span class="quote">keywords#counter#</span>"]&gt;
			&lt;cfset description = form["<span class="quote">description#counter#</span>"]&gt;
			
			&lt;cfif len(keywords)&gt;

					&lt;!--- file upload ---&gt;			
					&lt;!--- db insert ---&gt;
														
			&lt;cfelse&gt;			
				&lt;cfset results = results &amp; "<span class="quote">Upload ###counter# was not processed since you forgot a required value.&lt;br/&gt;</span>"&gt;
			&lt;/cfif&gt;
			
		&lt;/cfif&gt;

		&lt;cfset counter = counter + 1&gt;
		&lt;cfset turnedOn = structKeyExists(form, "<span class="quote">file#counter#</span>")&gt;
	&lt;/cfloop&gt;
&lt;/cfif&gt;

&lt;!DOCTYPE HTML PUBLIC "<span class="quote">-//W3C//DTD HTML 4.01 Transitional//EN</span>" "<span class="quote">http://www.w3.org/TR/html4/loose.dtd</span>"&gt;
&lt;<span class="category2">html</span>&gt;
&lt;head&gt;
&lt;meta http-equiv="<span class="quote">Content-Type</span>" content="<span class="quote">text/html; charset=iso-8859-1</span>"&gt;
&lt;script <span class="category2">type</span>="<span class="quote">text/javascript</span>" src="<span class="quote">http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js</span>"&gt;&lt;/script&gt;
&lt;script&gt;
<span class="category1">var</span> current = 1

$(document).ready(<span class="category1">function</span>() {
 
 	$("<span class="quote">#adder</span>").click(<span class="category1">function</span>() {
  		current++
  		<span class="category1">var</span> newset = $("<span class="quote">#file0</span>").clone(<span class="category1">true</span>)
  
  		newset.find("<span class="quote">input,textarea</span>").each(<span class="category1">function</span>(i) { 
   				<span class="category1">var</span> $currentElem= $(<span class="category1">this</span>)
   				<span class="linecomment">//remove the number from the name and id</span>
   				<span class="category1">var</span> curname = $currentElem.attr("<span class="quote">name</span>")
   				curname = curname.replace(/[0-9]+$/,'<span class="quote"></span>')
   				<span class="category1">var</span> curid = $currentElem.attr("<span class="quote">id</span>")
   				curid = curid.replace(/[0-9]+$/,'<span class="quote"></span>')
   
   				$currentElem.attr("<span class="quote">name</span>",curname+current)
   				$currentElem.attr("<span class="quote">id</span>",curid+current)
   		})
  
  		newset.appendTo("<span class="quote">#dynamicArea</span>")
  
  	})
 	
})
&lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
	
&lt;cfif structKeyExists(variables, "<span class="quote">results</span>")&gt;

	&lt;p&gt;
	&lt;b&gt;Results of your upload:&lt;/b&gt;&lt;br/&gt;
	&lt;cfoutput&gt;#results#&lt;/cfoutput&gt;
	&lt;/p&gt;

&lt;/cfif&gt;

&lt;form action="<span class="quote">multiupload.cfm</span>" <span class="category1">method</span>="<span class="quote">post</span>" enctype="<span class="quote">multipart/form-data</span>" id="<span class="quote">mainForm</span>"&gt;
&lt;<span class="category2">input</span> <span class="category2">type</span>="<span class="quote">button</span>" id="<span class="quote">adder</span>" value="<span class="quote">Add Another File</span>"&gt;

&lt;div id="<span class="quote">dynamicArea</span>"&gt;
&lt;fieldset id="<span class="quote">file0</span>" style="<span class="quote">margin-bottom:10px</span>"&gt;
	&lt;table&gt;
		&lt;tr&gt;
			&lt;td&gt;File:&lt;/td&gt;
			&lt;td&gt;&lt;<span class="category2">input</span> <span class="category2">type</span>="<span class="quote">file</span>" <span class="category2">name</span>="<span class="quote">file1</span>"&gt;&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;Keywords:&lt;/td&gt;
			&lt;td&gt;&lt;<span class="category2">input</span> <span class="category2">type</span>="<span class="quote">text</span>" <span class="category2">name</span>="<span class="quote">keywords1</span>"&gt;&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr valign="<span class="quote">top</span>"&gt;
			&lt;td&gt;Description:&lt;/td&gt;
			&lt;td&gt;&lt;textarea <span class="category2">name</span>="<span class="quote">description1</span>"&gt;&lt;/textarea&gt;&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/table&gt;
&lt;/fieldset&gt;
&lt;/div&gt;

&lt;<span class="category2">input</span> <span class="category2">type</span>="<span class="quote">submit</span>"&gt;
&lt;/form&gt;

&lt;/body&gt;
&lt;/<span class="category2">html</span>&gt;</pre>
</code>

</div></div>


 
]]>
      
    </content>
  </entry>

  <entry>
    <id>tag:www.insideria.com,2009://34.37476-comment:2068046</id>
    <thr:in-reply-to ref="tag:www.insideria.com,2009://34.37476" type="text/html" href="http://www.insideria.com/2009/07/creating-a-simple-multi-file-u.html"/>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/07/creating-a-simple-multi-file-u.html#comment-2068046" />
    <title>Comment from Brad on 2009-07-09</title>
    <author>
        <name>Brad</name>
        <uri>http://www.infinitewebdesign.com</uri>
    </author>
    <content type="html" xml:lang="en" xml:base="http://www.infinitewebdesign.com">
        <![CDATA[<p>Thank You! It seems any time I am looking for an elegant way to handle a new problem, you have just dealt with said problem and posted the solution. Each time you are about a week or two ahead of me. </p>

<p>It is truly appreciated and I hope one day i will have learned enough to be able to share something of value with you.</p>

<p>Thanks again! </p>]]>
    </content>
    <published>2009-07-10T04:06:32Z</published>
  </entry>

</feed
