It's been a while since I did a "ColdFusion Sample", so if you've forgotten, the idea of this is blog series to demonstrate a ColdFusion tag or feature in a complete, if simple, application. In this entry, I'm going to demonstrate CFZIP. This tag allows for creating, reading, and extracting zip files. In my example I'll be making use of reading and extracting.
I've built a small web application that allows you to upload image.
When you upload a file, the logic is pretty simple. Check to see if it's an image first. If it is, we copy the image to the photos directory, then read in the image and scale it for a thumbnail directory. Checking a file to see if it a valid image as easy as using isImageFile. Resizing is also pretty easy with imageScaleToFit. You can also resize with imageResize, but imageScaleToFit allows you to scale images and keep their proportions.
So that portion isn't too complex. But let's ramp it up a notch. What if we allowed folks to upload both images and zips of images. We need to modify our code to check zips for images and extract them as well. Here's the entire template.
<!--- used to flag if we uploaded crap --->
<cfset successFlag = false> <cfif structKeyExists(form, "upload") and len(form.upload)>
<cfset tempDir = getTempDirectory()>
<cffile action="upload" filefield="upload" destination="#tempDir#" nameconflict="overwrite"> <cfset theFile = file.serverdirectory & "/" & file.serverfile> <cfset images = []> <cfif file.filewassaved>
<cfif isImageFile(theFile)>
<cfset arrayAppend(images,theFile)>
</cfif> <!--- check for zip --->
<cfif file.serverfileext is "zip">
<cftry>
<cfzip action="list" filter=".jpg,.png,*.gif" file="#theFile#" name="files">
<cfloop query="files">
<cfzip action="unzip" entryPath="#name#" destination="#tempDir#" file="#theFile#" overwrite="true">
<cfif isImageFile(tempdir & "/" & name)>
<cfset arrayAppend(images, tempdir & "/" & name)>
</cfif>
</cfloop>
<cfcatch>
<cfdump var="#cfcatch#">
</cfcatch>
</cftry>
</cfif>
</cfif> <cfif arrayLen(images)>
<cfloop index="theFile" array="#images#">
<!--- create a UUID based name. Helps ensure we don't conflict --->
<cfset newName = createUUID() & "." & listLast(theFile, ".")>
<!--- copy original to image dir --->
<cfset fileCopy(theFile, imageDir & newName)>
<!--- now make a thumb version --->
<cfset imgOb = imageRead(theFile)>
<cfset imageScaleToFit(imgOb, 200,200)>
<cfset imageWrite(imgOb, thumbDir & newName)>
</cfloop>
<cfset successFlag = true>
</cfif> </cfif> <cfset thumbs = directoryList(thumbDir,true,"name",".jpg|.png|*.gif" )> <!DOCTYPE html>
<html>
<head>
<title>Zip Demo</title>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1" /> <link rel="stylesheet" href="http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css">
<link rel="stylesheet" href="jquery.lightbox-0.5.css" type="text/css" />
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script type="text/javascript" src="jquery.lightbox-0.5.min.js"></script>
<script type="text/javascript">
$(function() {
$(".imageList").lightBox();
});
</script>
</head>
<body> <div class="container"> <h2>Images</h2>
<ul class="media-grid">
<cfloop index="image" array="#thumbs#">
<cfoutput>
<li>
<a href="photos/#getFileFromPath(image)#" class="imageList">
<img class="thumbnail" src="thumbs/#getFileFromPath(image)#">
</a>
</li>
</cfoutput>
</cfloop>
</ul> <h2>Upload New Image</h2>
<form enctype="multipart/form-data" method="post"> <cfif successFlag>
<p>
Image(s) have been uploaded. Thanks!
</p>
</cfif> <cfif structKeyExists(variables, "errors")>
<cfoutput><p>#variables.errors#</p></cfoutput>
</cfif> <p>
Select image, or zip file of images:
<input type="file" name="upload">
</p> <p>
<input type="submit" value="Upload" class="btn primary">
</p> </form> </div> </body>
</html>
<!--- images and thumbs dir are relative --->
<cfset imageDir = expandPath("./photos") & "/">
<cfset thumbDir = expandPath("./thumbs") & "/">
Consider the template as two halves. The bottom half simply handles outputting the images and providing a form. This is just regular HTML although I made use of a jQuery LightBox plugin to make it sexier.
Let's focus more on the top portion. First, we wrap our main handling code in a check for an actual file upload. If the form was submitted and nothing was uploaded, we could provide an error. (In fact, you can see where I made use of an error display in the bottom half, but I ended up not bothering creating any errors.) I send the upload to a temp directory outside of web root. Hopefully we all know why.
I've created an array, images, that will store all the files I'll be copying and creating thumbs with. My code then branches into two sections. If the file was an image, I just add it to the array. If the file was a zip, and note we check the extension, there is no "isZipFile" in ColdFusion, I use the list operation of cfzip to get all the images contained within it. For each one, I extract it, check it again to ensure it really is an image, and then add it to array.
At this point I've got an array of images in a temporary directory. I can then simply loop over it and perform my copy/scale operations. Note the use of createUUID(). This provides a new name for the image and allows me to not worry about overwriting an existing image.
That's it. I'm not going to post a demo for this as I know some jerk will abuse the upload. I did include a zip of the code base though. You should be able to extract this locally under your web root and just play with it.
Archived Comments
Nice. The one thing I think I haven't really nailed down is uploading of images. I made it work for the most part using <cffileupload> but I know it is far from perfect.
I was tasked with creating a photo gallery of sorts and what I did was use <cffileupload> to allow the administrator to upload all the photos needed via the flash uploader which I know isn't ideal, but it worked.
I have been trying to figure out a very user-friendly way of allowing 10-20 images to be uploaded at once and also allowing the ability to put captions in for each photo. I broke down and just let them upload all the images first then come back and show a page with the images and allow them to put captions after the upload.
I think Photo Galleries are one thing that programmers always tend to have an issue with or at least from the standpoint of a very user-friendly one on the backend. CF made it easier to an extent with image resizing and the cffileupload.
Not to mention the picture names were uploaded to a DB, then the captions were placed into the DB after the upload.
I think your approach (upload first, then edit captions) makes sense. You don't want too much going on at once probably anyway.
Ray,
I've been playing around with this a bit and noticing on page refresh the files are re-written. I guess they are staying in memory. If my suspicion is true, what do you suggest to remove from memory?
Thanks
Henry
Are you reloading the page after you did a form submit? If so, it's resubmitting, which is normal behavior. You can prevent that if you did a cflocation after the form uploaded.
Yep, good to know. Thanks.
Great Work Ray as usual.
Why Coldfusion does not alert developers in development about the deprecated tag or attributes? :(
You mean like via a console message? I don't know - we just don't do that.
Yes, some message (as Java does) when CF compiles the CFML into .CLASS, I believe it can make the developer to use or interact more with the new functions, tags or attributes.
~SanooP
<cfset thumbs = directoryList(thumbDir,true,"name","*.jpg|*.png|*.gif" )>
hi,,,when i run this application.i got an error variable directorylist is undefined.....pls help me...
directoryList was added to ColdFusion 9. It is a function version of <cfdirectory action="list">. You can replace it with that.