Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with: Jaana Gilbert and Brenda Priest

Learning ColdFusion 8: CFZip Part II - Zipping Files And Directories With CFZipParam

By Ben Nadel on
Tags: ColdFusion

In the first part of this tutorial, we explored the ways in which the ColdFusion 8 CFZip tag, when used alone, can zip both files and entire directories into archives. Now, let's take a look at the ColdFusion 8 CFZipParam tag in the context of archive creation. The CFZipParam tag is a child of the CFZip tag and gives us even more power over what kind of data is added to the archive and how those entries are stored.

In Part I, we started off zipping a single file with CFZip; now, let's start off Part II zipping a single file with CFZipParam:

  • <!---
  • Create a zip archive that contains a single
  • file. We are going to overwrite any previously
  • existing archive of the same name.
  • --->
  • <cfzip
  • action="zip"
  • file="#ExpandPath( './data.zip' )#"
  • overwrite="true">
  •  
  • <!---
  • Add a single file. The file will be stored in
  • the archive as the root directory with the same
  • file name it already has (readme.txt).
  • --->
  • <cfzipparam
  • source="#ExpandPath( './data/documents/readme.txt' )#"
  • />
  •  
  • </cfzip>

Here, the CFZipParam tag is the only child tag of the CFZip tag. Notice that, instead of providing the source attribute of the CFZip tag, as we would have done previously, we are providing the source attribute through the CFZipParam tag. By default, ColdFusion will store this file in the root directory of the archive with the same name as the file as it exists. This will result in a zip directory structure that looks like this:

./readme.txt

Both the CFZip and the CFZipParam tags have a source attribute. And, when it comes to using both tags in conjunction, it's not one or the other - the Source attributes of the two tags also work in conjunction. If the CFZip tag defines a source directory, then the Source attributes of the nested CFZipParam tags are relative to that parent directory. Running the following code:

  • <!---
  • Create a zip archive that contains a single
  • file. We are going to overwrite any previously
  • existing archive of the same name.
  • --->
  • <cfzip
  • action="zip"
  • file="#ExpandPath( './data.zip' )#"
  • source="#ExpandPath( './data/documents/' )#"
  • overwrite="true">
  •  
  • <!---
  • Add a single file. The file will be stored in
  • the archive as the root directory with the same
  • file name it already has (readme.txt). Since the
  • source attribute was defined in the CFZip tag,
  • the source attribute of the CFZipParam tag is
  • relative the previous source value.
  • --->
  • <cfzipparam
  • source="readme.txt"
  • />
  •  
  • </cfzip>

... will result in a zip directory structure that looks like this:

./readme.txt

In the above example, ColdFusion took the Source attribute of the CFZip tag, "./data/documents/", and the Source attribute of the CFZip tag, "readme.txt", and created a combined path of "./data/documents/readme.txt." Now, in Part I of our tutorial, we demonstrated that if you provide a directory as the Source attribute, ColdFusion will archive the entire directory (by default). Notice, that in the above example, ColdFusion did not archive the directory, but rather just the CFZipParam file.

If you choose to use the Source attribute of the CFZip tag, all of your CFZipParam tags must use relative source attributes. You cannot mix and match. If you try to use a CFZip source and an absolute path for the CFZipParam tag, ColdFusion will throw an error. Either you use both, or you use just the CFZipParam source attribute.

We don't have to let the file be archived with the same name. We can rename it using the EntryPath attribute of the CFZipParam tag. Running this code:

  • <!---
  • Create a zip archive that contains a single
  • file. We are going to overwrite any previously
  • existing archive of the same name.
  • --->
  • <cfzip
  • action="zip"
  • file="#ExpandPath( './data.zip' )#"
  • overwrite="true">
  •  
  • <!---
  • Add a single file. The file will be stored in
  • the archive as the root directory but, instead of
  • using the existing file name, the file will be
  • stored as read_me_first.txt.
  • --->
  • <cfzipparam
  • source="#ExpandPath( './data/documents/readme.txt' )#"
  • entrypath="read_me_first.txt"
  • />
  •  
  • </cfzip>

... will result in a zip directory structure that looks like this:

./read_me_first.txt

In the above, all we defined was the new file name; however, like the prefix attribute of the CFZip tag, the EntryPath attribute can also define a directory structure. By adding slashes to the attribute value, ColdFusion will create directories in which to save the file. Running this code:

  • <!---
  • Create a zip archive that contains a single
  • file. We are going to overwrite any previously
  • existing archive of the same name.
  • --->
  • <cfzip
  • action="zip"
  • file="#ExpandPath( './data.zip' )#"
  • overwrite="true">
  •  
  • <!---
  • Add a single file. Instead of storing it in the
  • root directory, we are going to store it in a
  • new, nested directory.
  • --->
  • <cfzipparam
  • source="#ExpandPath( './data/documents/readme.txt' )#"
  • entrypath="manuals/_LOOK_HERE_FIRST_/readme.txt"
  • />
  •  
  • </cfzip>

... will result in a zip directory structure that looks like this:

./manuals/_LOOK_HERE_FIRST_/readme.txt

Notice that we are not only setting the file name, we are also defining which directory that file is stored in.

I mentioned above that the ColdFusion 8 CFZip tag has a prefix attribute; well, the CFZipParam tag also has a Prefix attribute. And, just like the CFZip tag, the Prefix attribute of this CFZipParam tag defines the directory in which the file is stored. We can reproduce the above structure using the Prefix attribute. Running the code:

  • <!---
  • Create a zip archive that contains a single
  • file. We are going to overwrite any previously
  • existing archive of the same name.
  • --->
  • <cfzip
  • action="zip"
  • file="#ExpandPath( './data.zip' )#"
  • overwrite="true">
  •  
  • <!---
  • Add a single file. Instead of storing it in the
  • root directory, we are going to store it in a
  • new, nested directory at the given prefix.
  • --->
  • <cfzipparam
  • source="#ExpandPath( './data/documents/readme.txt' )#"
  • prefix="manuals/_LOOK_HERE_FIRST_"
  • />
  •  
  • </cfzip>

... will result in a zip directory structure that looks like this:

./manuals/_LOOK_HERE_FIRST_/readme.txt

Notice that since we did not include any EntryPath information, the file is stored using its original file name. You might be tempted to try and use the EntryPath to define the file name in conjunction with the Prefix to define the storage directory, as in this example:

  • <!---
  • Create a zip archive that contains a single
  • file. We are going to overwrite any previously
  • existing archive of the same name.
  • --->
  • <cfzip
  • action="zip"
  • file="#ExpandPath( './data.zip' )#"
  • overwrite="true">
  •  
  • <!---
  • Add a single file. Instead of storing it in the
  • root directory, we are going to store it in a
  • new, nested directory at the given prefix stored
  • at the given entry path file name.
  • --->
  • <cfzipparam
  • source="#ExpandPath( './data/documents/readme.txt' )#"
  • prefix="manuals/_LOOK_HERE_FIRST_"
  • entrypath="read_me_text.txt"
  • />
  •  
  • </cfzip>

ColdFusion will not throw any sort of error here, but it will not work the way you expect it to. EntryPath takes precedence over the Prefix attribute. In fact, when the EntryPath is present, the Prefix value is completely ignored. Therefore, running the code above will result in a zip directory structure that looks like this:

./read_me_text.txt

Notice that since the EntryPath did not include any sub directory information, the file entry is stored in the root directory of the zip archive (even though the Prefix defined some directory nesting). The EntryPath could have, just as we demonstrated earlier, included the sub directory definition.

One of the coolest things about the CFZipParam tag is that it allows us to archive more than just files off of the server; we can also archive text and binary data using the Content attribute. In this next example, we are going to store a string directly into a text entry of the archive:

  • <!---
  • Create a zip archive that contains a single
  • entry. This entry will be created from text
  • data, not from a file on the server. We are
  • going to overwrite any previously existing
  • archive of the same name.
  • --->
  • <cfzip
  • action="zip"
  • file="#ExpandPath( './data.zip' )#"
  • overwrite="true">
  •  
  • <!---
  • Add a single entry using the given text
  • data into the given entry path.
  • --->
  • <cfzipparam
  • entrypath="text_entry.txt"
  • content="This is my text entry!"
  • />
  •  
  • </cfzip>

Running that code will result in a zip directory structure that looks like this:

./text_entry.txt

... and that text file contains the text data from the Content attribute above. When storing text data, ColdFusion uses the CharSet attribute the string into binary data. This attribute defaults to whatever the default encoding of the machine is. I don't full understand this, but not including the attribute seems to work nicely.

Binary data can be stored in exactly the same way. Running this code:

  • <!--- Read in the image file as binary data. --->
  • <cffile
  • action="readbinary"
  • file="#ExpandPath( './data/images/mud_monster.jpg' )#"
  • variable="binImage"
  • />
  •  
  • <!---
  • Create a zip archive that contains a single
  • entry. This entry will be created from binary
  • image data, not from a file on the server. We
  • are going to overwrite any previously existing
  • archive of the same name.
  • --->
  • <cfzip
  • action="zip"
  • file="#ExpandPath( './data.zip' )#"
  • overwrite="true">
  •  
  • <!---
  • Add a single entry using the given image binary
  • data into the given entry path.
  • --->
  • <cfzipparam
  • entrypath="mud_monster.jpg"
  • content="#binImage#"
  • />
  •  
  • </cfzip>

... will result in a zip directory structure that looks like this:

./mud_monster.jpg

When you use the Content attribute, there is no original file on which to base the entry's file name. Therefore, if you do store string or binary data, you must use the EntryPath attribute so that ColdFusion knows how to store the target file.

Ok, so now let's talk about directories. CFZipParam can archive full or partial directories as well as single files. In the following examples, we are going to assume we have the following data directory:

./data/documents/manual.txt
./data/documents/readme.txt
./data/images/funny.jpg
./data/images/mud_monster.jpg
./data/images/red_face.jpg
./data/images/smile.jpg

Now, we can archive the directory using CFZipParam:

  • <!---
  • Create a zip archive that contains a multiple
  • entries. We are going to overwrite any previously
  • existing archive of the same name.
  • --->
  • <cfzip
  • action="zip"
  • file="#ExpandPath( './data.zip' )#"
  • overwrite="true">
  •  
  • <!---
  • Zip the entire images directory at the given
  • source path.
  • --->
  • <cfzipparam
  • source="#ExpandPath( './data/images/' )#"
  • />
  •  
  • </cfzip>

Running the above will result in a zip directory structure that looks like this:

./funny.jpg
./mud_monster.jpg
./red_face.jpg
./smile.jpg

In this case, we were using an expanded path to point the directory. However, just as with the single file example way above, the Source attribute of the CFZip and CFZipParams work together when zipping directories. In the next example, we will define the CFZipParam's Source directory as being relative to the CFZip's Source attribute:

  • <!---
  • Create a zip archive that contains a multiple
  • entries. We are going to overwrite any previously
  • existing archive of the same name.
  • --->
  • <cfzip
  • action="zip"
  • file="#ExpandPath( './data.zip' )#"
  • source="#ExpandPath( './data/images/' )#"
  • overwrite="true">
  •  
  • <!---
  • Zip the entire directory. Since the CFZip tag defined
  • a source attribute, that means the source attribute
  • of the CFZipParam tag will relative to it. Since we
  • want to zip the entire directory, just leave the
  • source attribute empty.
  • --->
  • <cfzipparam
  • source=""
  • />
  •  
  • </cfzip>

Notice that since the CFZip tag defines the actual directory we are trying to archive, the CFZipParam tag's Source attribute doesn't need a value at all. Running the above will result in a zip directory structure that looks like this:

./funny.jpg
./mud_monster.jpg
./red_face.jpg
./smile.jpg

Because the Source attribute of the CFZipParam tag is relative, we could have also used the value:

"./"

This would put us into the same directory. If you are familiar with this type of path notation, then you would also know that:

"../"

... moves you up a directory. The CFZipParam tag recognizes this as a valid relative path directive. In fact, if we re-ran the above code example using "../" instead of "./", we would end up with an archive that contained both the documents and images directories. The "../" would have taken us out of the images directory and into the root Data directory. Then, since CFZipParam recurses through directories by default, it would have zipped both the nested images and documents sub directories.

Just like the CFZip tag, when dealing with directories, the CFZipParam tag has the Filter, Recurse, and Prefix attributes. These act in exactly the same way as they did in the CFZip tag. I am not going to into detail here since the filter and recurse features were covered extensively in Part I of this tutorial and the Prefix attribute was covered in both Part I as well as above.

ColdFusion 8's new CFZipParam works with both files and directories, but this doesn't mean you have to use one or the other. The CFZip tag can have multiple CFZipParam child tags, each of which may deal with a file or a directory. In our next example, we will zip up the entire Data directory, but using multiple CFZipParam tags:

  • <!---
  • Create a zip archive that contains the entire Data
  • directory. We are going to use the CFZip's source
  • attribute to put us in the correct base directory
  • and then each child CFZipParam tag will use a source
  • relative to that. We are going to overwrite any
  • previously existing archive of the same name.
  • --->
  • <cfzip
  • action="zip"
  • file="#ExpandPath( './data.zip' )#"
  • source="#ExpandPath( './data/' )#"
  • overwrite="true">
  •  
  • <!---
  • Zip the entire documents directory. This
  • will create a documents directory in the
  • root of the archive.
  • --->
  • <cfzipparam
  • source="./documents/"
  • />
  •  
  • <!--- Zip one of the images into the archive root. --->
  • <cfzipparam
  • source="./images/funny.jpg"
  • />
  •  
  • <!--- Zip one of the images into the archive root. --->
  • <cfzipparam
  • source="./images/mud_monster.jpg"
  • />
  •  
  • <!--- Zip one of the images into the archive root. --->
  • <cfzipparam
  • source="./images/red_face.jpg"
  • />
  •  
  • <!--- Zip one of the images into the archive root. --->
  • <cfzipparam
  • source="./images/smile.jpg"
  • />
  •  
  • </cfzip>

Running the above code will result in a zip directory structure that looks like this:

./documents/manual.txt
./documents/readme.txt
./funny.jpg
./mud_monster.jpg
./red_face.jpg
./smile.jpg

Notice that the documents directory gets created as a root-level directory - it's relative source path was maintained within the archive - but the images were zipped directly into the root of the archive - their relative source paths were not maintained. This is slightly different behavior than the zipping of a directory using just the CFZip tag. If all we used was the CFZip tag with a source directory, the files of the source directory would be zipped directly into the archive root.

Since the CFZipParam tag doesn't have a StorePath attribute (like the CFZip tag), if we wanted to put all of the files into the archive root, we have two options (not including the one that doesn't use the CFZipParam tag at all): we could change the source attribute of the CFZip tag to start in the documents folder:

  • <!---
  • Create a zip archive that contains the entire Data
  • directory. We are going to use the CFZip's source
  • attribute to put us in the documents base directory
  • and then each child CFZipParam tag will use a source
  • relative to that. We are going to overwrite any
  • previously existing archive of the same name.
  • --->
  • <cfzip
  • action="zip"
  • file="#ExpandPath( './data.zip' )#"
  • source="#ExpandPath( './data/documents/' )#"
  • overwrite="true">
  •  
  • <!---
  • Zip the entire documents directory.
  • --->
  • <cfzipparam
  • source="./"
  • />
  •  
  • <!--- Zip one of the images into the archive root. --->
  • <cfzipparam
  • source="../images/funny.jpg"
  • />
  •  
  • <!--- Zip one of the images into the archive root. --->
  • <cfzipparam
  • source="../images/mud_monster.jpg"
  • />
  •  
  • <!--- Zip one of the images into the archive root. --->
  • <cfzipparam
  • source="../images/red_face.jpg"
  • />
  •  
  • <!--- Zip one of the images into the archive root. --->
  • <cfzipparam
  • source="../images/smile.jpg"
  • />
  •  
  • </cfzip>

Notice that this time, the Source attribute of the CFZip tag puts us into the Documents sub directory. The CFZipParam tag that zips the documents directory now just has "./" to denote the current directory. The CFZipParam tags that zip the images now just have to go up a directory to be able to access the images folder. This will put all files into the root archive since the file-only CFZipParam tags do that by default and the directory CFZipParam tag doesn't really have a path, so its contents get put in the archive root.

Our other option is to break the zip archive creation into two CFZip tags:

  • <!---
  • Create a zip archive that contains the entire Data
  • directory. We are going to use the CFZip's source
  • attribute to put us in the images base directory
  • and then each child CFZipParam tag will use a source
  • relative to that. We are going to overwrite any
  • previously existing archive of the same name.
  • --->
  • <cfzip
  • action="zip"
  • file="#ExpandPath( './data.zip' )#"
  • source="#ExpandPath( './data/images/' )#"
  • overwrite="true">
  •  
  • <!--- Zip one of the images into the archive root. --->
  • <cfzipparam
  • source="./funny.jpg"
  • />
  •  
  • <!--- Zip one of the images into the archive root. --->
  • <cfzipparam
  • source="./mud_monster.jpg"
  • />
  •  
  • <!--- Zip one of the images into the archive root. --->
  • <cfzipparam
  • source="./red_face.jpg"
  • />
  •  
  • <!--- Zip one of the images into the archive root. --->
  • <cfzipparam
  • source="./smile.jpg"
  • />
  •  
  • </cfzip>
  •  
  •  
  • <!---
  • Now that we have a zip archive that has the images,
  • let's add the contents of the documents folder to the
  • exisitng zip archive root. This time, we have no need
  • to overwrite the existing archive.
  • --->
  • <cfzip
  • action="zip"
  • file="#ExpandPath( './data.zip' )#"
  • source="#ExpandPath( './data/documents/' )#"
  • />

Notice that this time, the first CFZip tag's Source attribute puts us into the Images directory. Each of the child CFZipParam tags then provides a directory-relative path to the image. The second CFZip tag then zips the documents directory into the existing archive created by the first CFZip tag. Pretty cool stuff. Running either code examples will result in a zip directory structure that looks like this:

./funny.jpg
./manual.txt
./mud_monster.jpg
./readme.txt
./red_face.jpg
./smile.jpg

The ColdFusion 8 CFZip tag gives us a lot of functionality. The CFZipParam tag gives us even more functionality with the ability to store text entries and binary entries and run multiple actions on a single archive. Used together, you can really see how easy ColdFusion 8 is making it for us developers to create zip archives.




Reader Comments

Hello Ben, good post!!!!
Now, is it possible to manually select the files I want to zip? Let's say I list all the files in a directory. So I need to zip, from that directory file1.eps, file2.eps and file.ai

Can I do that by using cfselect multiple and then passing the values to CFZipParam with the selection from my form?

Thank you much!

Reply to this Comment

how do i know it is zipped?
like what if i zip more 10mg then it takes time...
so is there any way to find out it's zipping or zipped?

Reply to this Comment

@Sangyong,

CFZip doesn't execute asynchronously; when you call the CFZip tag, your thread of execution will halt at that command until the files are zipped. You don't have to add any additional logic to detect when something is done zipping.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.