At the request of a fellow EasyCFM Forum user, we’re going to discuss how to use ColdFusion to manage remote files on the server. While the original request was only for .txt files specifically, we’re going to look at handling .txt, .cfm, .cfml, .htm, and .html. This can actually come in handy if you need to do a quick edit on a page on your Web site, but have no access to FTP clients…only a Web browser.In order for the code shown in this tutorial to work, all files to be edited must reside on the Web server, in the web root directory, in a directory called myfiles’. The tutorial will assume the following path:
c:inetpubwwwrootmyfiles
This application will consist of 5 templates:
remote_file_list.cfm – displays a list of all available files
remote_file_add.cfm – allows the user to create a new file
remote_file_edit.cfm – allows the user to edit an existing file
remote_file_save.cfm – saves the file to the server, either after an add or an edit
remote_file_delete.cfm – deletes a file
NOTE: usually I like to include a link to a working sample of code with my tutorials. However, due to the nature of this tutorial (the ability to write files to the server), I’ve decided that an ounce of prevention is worth a pound of cure…and chickened out. I do, however, include all 5 files in a zip located at http://charlie.griefer.com/code/tutorials/rfm.zip.
On to the tutorial…
remote_file_list.cfm:
<cfscript>
  if (NOT structKeyExists(form, 'fileExt')) form.fileExt = "*";
</cfscript>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>File List</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<style type="text/css">
    body, td { font-family:verdana; font-size:11px; }
    a { color:#0000ff; text-decoration:none; }
  a:hover { color:#ff0000; text-decoration:underline; }
</style>
</head>
<body>
<!--- get the list of text files. this assumes they will all be in a directory
      called 'textfiles' under the web root --->
<cfdirectory action="list"
      directory="c:Inetpubwwwrootmyfiles"
      name="fileList"
      filter="#form.fileExt#">
<table style="width:500px;" cellpadding="1" cellspacing="1">
    <form action="remote_file_list.cfm" method="post">
    <tr>
      <td style="vertical-align:bottom; padding-bottom:2px;">Select a File to Edit:</td>
      <td style="vertical-align:bottom; text-align:right;">File Type:
      <select name="fileExt" style="font-family:verdana; font-size:11px;" onchange="this.form.submit();">
      <option value="*"<cfif form.fileExt IS "*"> selected</cfif>>all files</option>
      <option value="*.cfm"<cfif form.fileExt IS "*.cfm"> selected</cfif>>cfm</option>
      <option value="*.htm"<cfif form.fileExt IS "*.htm"> selected</cfif>>html</option>
      <option value="*.txt"<cfif form.fileExt IS "*.txt"> selected</cfif>>txt</option>
      </select>
    </td>
  </tr>
  </form>
</table>
<table style="width:500px; border:1px #000000 solid;" cellpadding="1" cellspacing="1">
    <tr style="background-color:#cccccc;">
      <td style="font-weight:bold;">File Name </td>
      <td style="font-weight:bold; width:80px; text-align:right;">File Size </td>
      <td style="font-weight:bold; width:150px;">Last Modified </td>
      <td style="font-weight:bold; width:60px;"> </td>
      </tr>
    <cfoutput query="fileList">
    <cfif left(fileList.name, 1) IS NOT ".">
    <!--- this condition does NOT need to be included in CF MX!! --->
    <tr style="background-color:<cfif currentRow MOD 2>##ffffff<cfelse>##ececec</cfif>;">
<td><a href="remote_file_edit.cfm?file=#URLEncodedFormat(name)#">#name#</a> </td>
    <td style="text-align:right;">#int(evaluate(size/1024))# KB </td>
    <td>#dateFormat(dateLastModified, 'mm/dd/yyyy')# #timeFormat(dateLastModified, 'h:mm tt')# </td>
    <td style="text-align:center;"><a href="remote_file_delete.cfm?file=#URLEncodedFormat(name)#"
      onclick="return confirm('Are You Sure You Wish to Delete This File?');">delete</a></td>
    </tr>
    </cfif>
    </cfoutput>
</table>
<br />
<a href="remote_file_add.cfm">Create a new file</a>
</body>
</html>
Here we use a <cfdirectory>
with a LIST action attribute. By default, there is no filter applied (all files are returned). In include a <select>
form input allowing the user to determine the type of file he/she wishes to see (options being *.txt, *.html, and *.cfml). This is reflected in the FILTER attribute of the <cfdirectory>
tag.
Looking at the docs for <cfdirectory>,
you can see that it actually returns a named recordset (very similar to a cfquery), with specific columns’. I display a table which shows the values of those columns. Name, file size, and date last modified are displayed within the <cfoutput query="fileList"></cfoutput>
tags.
The Name column contains a link to remote_file_edit.cfm, and passed the file name in the URL (wrapped in a URLEncodedFormat() function to handle any non-URL-friendly characters). To make this more of a full’ application, I also include a link with each record to remote_file_delete.cfm
, also passing the name of the file in the URL. The links to remote_file_delete.cfm
contain a JavaScript confirm() method, prompting the user as to whether or not he/she wishes to proceed with the delete. This is something I try to do standard on links that will delete any files or data.
Finally, a link exists underneath the existing files, allowing the user to create a new file.
remote_file_edit.cfm:
<cfif NOT structKeyExists(URL, 'file')>
  <cflocation url="remote_file_list.cfm" />
  <cfabort />
</cfif>
<cfscript>
  variables.validFile = 1;
  if (NOT reFindNoCase('.txt$|.htm$|.html$|.cfm$|.cfml$', URL.file)) {
    variables.validFile = 0;
  }
</cfscript>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Editing <cfoutput>#URL.file#</cfoutput></title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<script language="JavaScript" type="text/javascript">
  function sTrim(sVariable) {
    return sVariable.replace(/^s+|s+$/g,"");
  }
  function validateFields(form) {
    if (sTrim(form.fileContent.value) == "") {
      alert('The file must have content!');
      form.fileContent.focus();
      return false;
  }
  return true;
  }
</script>
<style type="text/css">
  body, td { font-family:verdana; font-size:11px; }
  a { color:#0000ff; text-decoration:none; }
  a:hover { color:#ff0000; text-decoration:underline; }
</style>
</head>
<body>
<cfif NOT variables.validFile>
  <cfoutput>
    <span style="font-weight:bold;">#URL.file#</span> is not an acceptable file type to edit.
    <br /><br />
    <a href="remote_file_list.cfm">click here to continue</a>
  </cfoutput>
<cfelse>
  <cffile action="read"
      file="c:inetpubwwwrootmyfiles#URL.file#"
      variable="thisFile">
  <cfoutput>
  <form action="remote_file_save.cfm" method="post" onsubmit="return validateFields(this);">
  <input type="hidden" name="fileName" value="#URL.file#" />
  <input type="hidden" name="action_type" value="edit" />
  <table border="0" style="width:600px;">
    <tr>
      <td style="font-weight:bold; width:120px;">Currently Editing: </td>
      <td style="width:480px;">#URL.file#</td>
    </tr>
    <tr>
      <td colspan="2"><textarea name="fileContent"
      style="font-family:verdana; font-size:11px; height:250px; width:600px;">#thisFile#</textarea>
      </td>
    </tr>
    <tr>
      <td colspan="2" style="text-align:right;">
<input type="button" value="cancel" style="font-family:verdana; font-size:11px;"
      onclick="location.href='remote_file_list.cfm';" />
<input type="submit" value="edit file >" style="font-family:verdana; font-size:11px;" />
      </td>
      </tr>
  </table>
  </form>
  </cfoutput>
</cfif>
</body>
</html>
Before doing anything on the remote_file_edit.cfm page, we need to make sure a URL variable named file’ was passed. If not, we send the user back to the main page. We are then going to use <cffile>
with an ACTION="read"
attribute. However, before reading the file, we need to make sure it’s a valid’ file (eg a text-based file). I use an reFindNoCase()
function to ensure that the file extension of the specified file is either .txt, .cfm, .cfml, .htm, or .html. If this condition is not met, the user is informed that he or she picked a file type that can not be edited, and a link to the first page is displayed.
Assuming a valid file type, the <cffile action="read">
is run on the specified file, and the content saved into a variable called thisFile'
.
The rest of the code is a simple HTML form, with a couple of hidden form fields (one to tell the action page that the user was editing a file, not adding one…and one with the file name), and one textarea which will contain the contents of the file.
There is a JavaScript function to validate the form prior to submission, ensuring that there is content inside of the textarea (a zero-length file cannot be saved).
remote_file_save.cfm:
<cfif NOT structKeyExists(form, 'fileName')>
  <cflocation url="remote_file_list.cfm" />
  <cfabort />
</cfif>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>File Saved</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<style type="text/css">
  body, td { font-family:verdana; font-size:11px; }
  a { color:#0000ff; text-decoration:none; }
  a:hover { color:#ff0000; text-decoration:underline; }
</style>
</head>
<body>
<cffile action="write"
    file="c:inetpubwwwrootmyfiles#form.fileName#"
    output="#form.fileContent#"
    addnewline="no">
<span style="font-weight:bold;"><cfoutput>#form.fileName#</cfoutput></span>
has been <cfif form.action_type IS "edit">updated<cfelse>written</cfif> successfully.
<br /><br />
<a href="remote_file_list.cfm">click here to continue</a>
</body>
</html>
In remote_file_save.cfm, we first check for the existence of the form field fileName’. If it does not exist, we send the user back to the main page. Once we’re passed that, we run a <cffile>
with an ACTION="write"
attribute to create the specified file. Since we’re specifying a filename that already exists, we will essentially be overwriting the original file.
Because remote_file_save.cfm
is the action page for both editing and adding a file, we need to run a quick condition to check the value of form.action_type
before displaying the confirmation message to the user.
A link is provided to return the user to the main page.
remote_file_add.cfm:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Untitled</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<cfdirectory action="list"
      directory="c:Inetpubwwwroottextfiles"
      name="fileList"
      filter="*.txt">
<script language="JavaScript" type="text/javascript">
  var fileArray = new Array(<cfoutput>#quotedValueList(fileList.name)#</cfoutput>);
  function sTrim(sVariable) {
    return sVariable.replace(/^s+|s+$/g,"");
  }
  function validateFields(form) {
    var fileCount = 0;
    var re = /.txt$|.cfm$|.cfml$|.htm|.html$/;
    // has the user entered a file name?
    if (sTrim(form.fileName.value) == "") {
      alert('You Must Enter a File Name');
      form.fileName.focus();
      return false;
    }
    // make sure the file name ends in .txt
    if (form.fileName.value.search(re) < 0) {
    alert('Unacceptable File Extension!nn.cfm, .cfml, .htm, .html, and .txt Only!');
    form.fileName.focus();
    form.fileName.select();
    return false;
    }
    for (var i=0; i<fileArray.length; i++) {
    if (sTrim(form.fileName.value) == fileArray[i]) {
    fileCount++;
    }
    }
    // is the file name going to conflict with an existing file name?
    if (fileCount > 0) {
    alert('The File Name You Selected Already Exists! Please Choose Another Name');
    form.fileName.focus();
    form.fileName.select();
    return false;
    }
    // has the user entered content for the file?
    if (sTrim(form.fileContent.value) == "") {
    alert('The file must have content!');
    form.fileContent.focus();
    return false;
    }
  return true;
  }
</script>
<style type="text/css">
  body, td { font-family:verdana; font-size:11px; }
</style>
</head>
<body>
<cfoutput>
<form action="remote_file_save.cfm" method="post" onsubmit="return validateFields(this);">
<input type="hidden" name="action_type" value="add" />
<table border="0" style="width:400px;">
  <tr>
    <td style="font-weight:bold;" nowrap="nowrap">File Name: </td>
    <td><input type="text" name="fileName"
style="font-family:verdana; font-size:11px; width:316px;" /></td>
  </tr>
  <tr>
    <td colspan="2">
    <textarea name="fileContent" style="font-family:verdana;
      font-size:11px; height:250px; width:600px;"></textarea>
    </td>
  </tr>
  <tr>
    <td colspan="2" style="text-align:right;">
    <input type="button" value="cancel" style="font-family:verdana; font-size:11px;"
      onclick="location.href='textfile_reader.cfm';" />
    <input type="submit" value="add file >" style="font-family:verdana; font-size:11px;" />
    </td>
  </tr>
</table>
</form>
</cfoutput>
</body>
</html>
remote_file_add.cfm
is very similar to remote_file_edit.cfm
. One major difference is the user is specifying the new file name, so a JavaScript function runs (on form submit
) to ensure a valid file extension (again, .cfm, .cfml, .htm, .html, and .txt).
remote_file_delete.cfm:
<cfif NOT structKeyExists(URL, 'file')>
  <cflocation url="textfile_reader.cfm" />
  <cfabort />
</cfif>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Delete <cfoutput>#URL.file#</cfoutput></title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<style type="text/css">
  body, td { font-family:verdana; font-size:11px; }
  a { color:#0000ff; text-decoration:none; }
  a:hover { color:#ff0000; text-decoration:underline; }
</style>
</head>
<body>
<cffile action="delete"
    file="c:inetpubwwwrootmyfiles#URL.file#">
<span style="font-weight:bold;"><cfoutput>#URL.file#</cfoutput></span> has been deleted.
<br /><br />
<a href="remote_file_list.cfm">click here to continue</a>
</body>
</html>
remote_file_delete.cfm is one of the simpler templates. After checking for the existence of a URL variable named file’, it simply runs a <cffile>
with an <ACTION=”delete”> attribute. The user is then presented with a message indicating a successful deletion, with a link back to the main page.
Questions? I spend way too much time in the Easycfm.com forums. Meet me there and I’ll see if I can answer em for ya 🙂
* Originally Published at EasyCFM.com
Click here to sign up for FREE tech newsletters from Murdok!
Charlie lives in Phoenix, AZ with his lovely wife and daughter, two dogs,
and a pig. He has been using ColdFusion since version 1.5, and likes to
think that one day he’ll be good at it. A frequent contributor to Easycfm, Charlie enjoys writing ColdFusion tutorials, and
helping others in the forums. He invites you to view his own site at
http://charlie.griefer.com or drop him an email at charlie@griefer.com.