Trimming Trailing Whitespace in Adobe DreamWeaver

Bronze Option, Search and Replace

My co-worker Brian Hall and I have long wanted to have a “delete trailing whitespace” feature for Dreamweaver. I’ve Googled, used the Adobe forums, looked in the Adobe Dreamweaver Marketplace, and nothing. We thought we had the answer in using regular expression search and replace, but my experiments with that were failures. Until today! Although it seems that the following search:

\s+$

to be replace with nothing would work, Dreamweaver (on Windows, at least), treats $ as end of file, not end of line. No good. We got closer with looking for carriage returns, but still didn’t have it. The breakthrough came today when Brian had the “aha” moment with capturing. Here’s the regex you want to use for your find:

[ \t]+([\r\n]+)

and what you want to replace that with is:

$1

Make sure that you have the checkbox for “Use regular expression” turned ON. Just to provide explanation, what we’re doing is looking for one or more spaces or tabs (in any order), following by one or more newlines or carriage returns (in any order). We use parentheses to capture the string of newlines / carriage returns, and then put that captured string (the $1) back.

Et voila, you retain the newlines (whether UNIX style or DOS style) that previously existed, don’t delete wanted blank lines in your code, but do delete all spaces and tabs at the end of lines.

Here’s a screenshot to help (it’s linked to larger version, follow link to embiggen):

Silver Option, Dreamweaver Command

But wait, there’s more. Brian also discovered the mechanism to add commands to Dreamweaver (those things that appear in the “Commands” menu). Take the following HTML and put it in your Commands directory with the file name “Remove Trailing Whitespace.html” (remove quotes, leave spaces):

<!DOCTYPE HTML SYSTEM "-//Macromedia//DWExtension layout-engine 5.0//dialog">
<html>
<head>
<title>Remove Trailing Whitespace</title>
<script type="text/javascript">
<!--
	function canAcceptCommand() {
		// Only enable this command if a document is active
		var theDOM = dw.getDocumentDOM();
		if (theDOM) return true;
		else return false;
	}

	function runCommand() {
	// Get the DOM again
	var theDOM = dw.getDocumentDOM();

	// Get the outerHTML of the HTML tag (the
	// entire contents of the document)
	var theDocEl = theDOM.documentElement;
	var theWholeDoc = theDocEl.outerHTML;

	// Get tabs and spaces preceding newlines and carriage returns
	// Use submatch for the newlines and carriage returns, and put those back into document
	var fixedString = theWholeDoc.replace(/[ \t]+([\r\n]+)/g, "$1");
	theDocEl.outerHTML = fixedString;
	}
// -->
</script>
</head>
<body onLoad="runCommand()"></body>
</html>

In my installation of Dreamweaver 5.5 on Windows, that’s:

C:\Program Files (x86)\Adobe\Adobe Dreamweaver CS5.5\configuration\Commands

If you find the top level of your Adobe installation, look for the Dreamweaver directory, then configuration, then Commands (which might be commands). Create the file as “Replace Trailing Whitespace.htm” (including spaces in the file name). If you have Dreamweaver running, close it and restart. Your Commands menu should now contain a new entry of “Remove Trailing Whitespace”. If you have no open files, the option will be grayed out. Open any file. The menu option should now be enabled. Add some trailing whitespace such as spaces or tabs. Verify you have them in your file by using View -> Code View Options -> Hidden Characters. Go to Commands -> Remove Trailing Whitespace. Your file should show it is edited, and the trailing whitespace should be removed.

Gold Option, Dreamweaver Extension

But wait, there’s even more!

We investigated the possibility of packaging this as a bona-fide Dreamweaver extension. To do so, you need an MXI file that describes the properties of your extension. The best resource we could find is an Adobe PDF describing the format. Using that, plus information from Dreamweaver Fever on creating Dreamweaver extensions, we were able to create this XML. Create a new directory, named anything you want. This will be used to hold the HTML, the MXI, and store the rsulting MXP file. Save the following XML as RemoveTrailingWhitespace.mxi:

<macromedia-extension id="" 
	name="Remove Trailing Whitespace" 
	version="1" 
	type="object"> 
<!-- List the required/compatible products --> 
 <products> 
 <product name="Dreamweaver" 
	version="4" primary="true" /> 
 </products> 

<!-- Describe the author --> 
 <author name="Brian Hall and Tony Miller" /> 

<!-- Describe the extension --> 
<description> 
 <![CDATA[Takes the entire current document and removes trailing spaces and tabs, preserving new lines and carriage returns.]]> 
</description> 

<ui-access> 
 <![CDATA[Click on Remove Trailing Whitespace from Commands menu.]]> 
</ui-access> 

<files> 
 <file name="RemoveTrailingWhitespace.htm" 
    destination="$dreamweaver/configuration/Commands" /> 
</files> 

<!-- Describe the changes to the configuration --> 
<configuration-changes>
	<menu-insert insertAfter="DWMenu_Commands_SortTable" skipSeparator="true">
		<menuitem id="com_carolinamantis_Menu_Commands_Remove_Trailing_Whitespace" name="_Remove Trailing Whitespace" dynamic="true" file="Commands/RemoveTrailingWhitespace.htm" enabled="canAcceptCommand()">
	</menu-insert>
</configuration-changes>
</macromedia-extension>

You will also need the HTML/JavaScript that defines the command. Save the following HTML as RemoveTrailingSpace.htm:

<!-- MENU-LOCATION=NONE -->
<!DOCTYPE HTML SYSTEM "-//Macromedia//DWExtension layout-engine 5.0//dialog">
<html>
<head>
<title>Remove Trailing Whitespace</title>
<script type="text/javascript">
<!--
	function canAcceptCommand() {
		// Only enable this command if a document is active
		var theDOM = dw.getDocumentDOM();
		if (theDOM) {
			return true;
		} else {
			return false;
		}
	}

	function runCommand() {
	// Get the DOM again
	var theDOM = dw.getDocumentDOM();

	// Get the outerHTML of the HTML tag (the
	// entire contents of the document)
	var theDocEl = theDOM.documentElement;
	var theWholeDoc = theDocEl.outerHTML;

	// Get tabs and spaces preceding newlines and carriage returns
	// Use submatch for the newlines and carriage returns, and put those back into document
	var fixedString = theWholeDoc.replace(/[ \t]+([\r\n]+)/g, "$1");
	theDocEl.outerHTML = fixedString;
	}
// -->
</script>
</head>
<body onLoad="runCommand()"></body>
</html>

You’ll note that this is exactly the same HTML as the Silver option, just using a file name without space (personal preference) and with an explicit comment that Dreamweaver uses to not automatically create a menu option. We want to suppress the automatic menu because we’re explicitly defining the menu information in our MXI file.

Start your Adobe Extension manager. Choose File -> Package Extension (which seems to now be File -> Package MXP Extension in 5.5). Go to the directory with your RemoveTrailingWhitespace.mxi file. Choose it and click “Open”. You will then be prompted to save your Extension Package as. Call it RemoveTrailingWhitespace.mxp (you don’t have to call it the same thing as the MXI or HTML, but we’re doing so to be consistent). Once it’s successfully created, use File -> Install Extension. Choose your RemoveTrailingWhitespace.mxp. Accept the boilerplate disclaimer, and what it be included in your Dreamweaver extensions list. If you have DW open, close it and restart. You should have a grayed out menu option as described in the for the Silver Option. Open a file, add whitespace at the end of some lines, choose Commands -> Remove Trailing Whitespace, and your document should show that it’s modified and the trailing whitespace should be gone.

If you don’t want to roll your own, then keep reading.

Files and References

ZIP file containing HTML, MXI, MXP
RemoveTrailingWhitespace.zip
MXI Reference from Adobe
http://download.macromedia.com/pub/exchange/mxi_file_format.pdf
Dreamweaver Fever Extension Information
http://dreamweaverfever.com/extensions/
Macromedia Dreamweaver Support Center – Extending Dreamweaver
http://www.adobe.com/support/dreamweaver/extend/creating_simple_cmmd_ext/index.html
Extending Dreamweaver CS5 and CS5.5
http://help.adobe.com/en_US/dreamweaver/cs/extend/dreamweaver_cs5_extending.pdf
Extending Dreamweaver – Types of Extensions
http://help.adobe.com/en_US/Dreamweaver/10.0_Extending/WS5b3ccc516d4fbf351e63e3d117f508c8de-8000.html

Linking to HTML Elements via id Attribute

My coworker Brian Hall and I came across a posting saying “everyone knows this”, and the this was that you can use a hash to link to an id.  Here’s a snippet to demonstrate:

My Page With Internal Links

Navigation

A

Some content here that if relevant to 'A'

B

Some content here that is relevant to 'B'

C

Some content here that is relevant to 'C'

Previously, the way you would do this is to use an a tag with a name value around the h2 elements:

My Page With Internal Links Using Name Attribute

Navigation

A

Some content here that if relevant to 'A'

B

Some content here that is relevant to 'B'

C

Some content here that is relevant to 'C'

The “everyone knows this” technique has many advantages:

  • No extraneous a tags that have to be unstyled, because they’re not really links
  • Better cooperation with jQuery code because you are now using id attributes
  • You can do some nifty styling using the target pseudo class selector

Google Deprecates Google Maps API for Flash

Well, I guess the decision that was made at ECU to use the JavaScript API, not the Flash API for Google Maps was ultimately the correct one.  According to the Google Geo Developers Blog, the Flash API deprecation announcement has been made.  At the time, the Flash API was not chosen due to lack of experience with ActionScript, plus the benefits of using HTML, CSS, and JavaScript for maps development.  Many advanced Google Maps I’ve seen have used the Flash API, so it will be interesting to see if the projects are converted to the JavaScript API, if they will be considered feature complete and not updated (Google will make bug fixes, but not add new features to the Flash API), or if work will continue with the knowledge that Google won’t add new features to the Flash API.

UNC CAUSE 2011 Proposal Accepted

My coworker Brian Hall and I submitted a proposal for UNC CAUSE 2011.  I’m really excited that we were accepted!  Our talk is titled “A Pirate Looks at Forty (Weeks of Mobile Development)“.  We will be talking about our experiences implementing ECU’s mobile web site as well as working with Blackboard Mobile to implement the ECU Mobile App (currently available for iOS and Android, BlackberryOS and WebOS coming soon).

New Theme: Busby

I’ve been saving links to interesting WordPress themes in my Delicious account, and the most recent is a rather impressive theme indeed.  It’s called Busby, and it’s got many advanced features.  I’ve set the basic options, but will want to explore the nifty image slider that’s a default part of the theme.  I also need to come up with a 200x50px logo image.  I’m a tech person, not a graphics designer, so the initial ones are no doubt going to be of rather poor quality.

ColdFusion, DDX, and PDFs Part 1: Gotchas

Before getting into the details of styling PDF document components using DDX, I wanted to share two very big “gotchas” that Brian and I experienced:

  1. When declaring the DDX element, make sure you copy it exactly, including the (seemingly) incorrect space in the schemaLocation.
  2. When testing your DDX using the IsDDX() function, make sure that your server isn’t locking the file

Here’s the correct DDX element to use whenever you’re trying to create a valid DDX document instance:

<DDX xmlns="http://ns.adobe.com/DDX/1.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ns.adobe.com/DDX/1.0/ coldfusion_ddx.xsd">

If you remove the space, IsDDX() will return false, and you’ll pull your hair out trying to create a minimal DDX that will work.

As for 2, we again discovered the file locking propensity of Windows 7 and Adobe’s Acrobat reader. Even if your PDF is downloaded from a web server, it’s quite possible to remain locked if it’s open. Whether testing with your dev server (especially a local machine) or a production server, make sure that when doing the tweak code/rerun PDF generation/view result cycle that you absolutely positively are not viewing, previewing, or have any possibility of keeping an open handle on the output PDF. If you do, once again IsDDX() will return false. Again you’ll want to pull out your hair in frustration as code that worked just a moment ago now refuses to regenerate a PDF.

ColdFusion, DDX, and PDFs Part 2: Styling a Table of Contents

As promised, experiences from working with Brian Hall on the ECU printable telephone directory.  We knew we want Adobe Acrobat (aka PDF) files to provide a reasonable platform-agnostic method of producing quality printed output.  One of the features that we wanted is a table of contents.  That’s reasonably easy using DDX, and you can see the DDX-101 version mentioned by Adobe and by the ever-popular ColdFusion Jedi .  Beyond the ability to create a table of contents, there’s not much out there about modifying the look and feel.  Hence the excitement when Brian was able to dig up the DDX reference manual.  It turns out that you actually do have control over the style of the table of contents even with the modified LiveCycle support within ColdFusion 9.0.1.

<cfsavecontent variable="myDDX">
<DDX xmlns="http://ns.adobe.com/DDX/1.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ns.adobe.com/DDX/1.0/ coldfusion_ddx.xsd">
	<PDF result="Out1">
	<TableOfContents includeInTOC="false" bookmarkTitle="Table of Contents">
		<TableOfContentsEntryPattern applicableLevel="all" >
			<StyledText>
				<p font-family="Times New Roman" font-size="12pt">
					<_BookmarkTitle/>
					<Space/>
					<Space/>
					<leader leader-pattern="dotted"/>
					<Space/>
					<Space/>
					<_BookmarkPageCitation/>
				</p>
			</StyledText>
		</TableOfContentsEntryPattern>
	</TableOfContents>
	<PDFGroup>
		<PDF source="Doc1" />
		<PDF source="Doc2" />
	</PDFGroup>
	</PDF>
</DDX>
</cfsavecontent>
<cfif IsDDX(#myDDX#)>
	<cfset inputStruct = StructNew()>
	<cfset inputStruct.Doc1 = "FirstDocument.pdf">
	<cfset inputStruct.Doc2 = "SecondDocument.pdf">
	<cfset outputStruct = StructNew()>
	<cfset outputStruct.Out1 = "CombinedDocument.pdf">
	<cfpdf action="processddx" ddxfile="#myddx#" inputfiles="#inputStruct#" outputfiles="#outputStruct#" name="ddxVar">
	<cfdump var="#ddxVar#">
<cfelse>
	<cfoutput><p>NO, DDX IS NOT OK</p></cfoutput>
</cfif>

Above is a complete snippet, suitable for saving to a CFM file that will run completely. For it to function on your own ColdFusion instance, you’ll need the following:

  • A PDF file with the file name FirstDocument.pdf
  • A PDF file with the file name SecondDocument.pdf

Given these two files and the above code saved to a ColdFusion cfm file, you’ll get a new file generated called CombinedFile.pdf.  If you open this file with a PDF reader, you’ll see a table of contents page followed by the contents of your two PDFs.  You’ll also see a bookmark called “Table of Contents”.  The table of contents will have entries for your two PDFs; these entries can be clicked and you’ll go directly to that sub-document.

The reason for this blog post is not the part that takes individual PDFs and combines them (you can find that in the Adobe documentation), but the fact that you can actually style the ToC, not just accept the defaults (which for us was a plain-jane Courier font).  The key is discovering the TableOfContentsEntryPattern, leader, _BookmarkTitle, and _BookmarkPageCitation elements.  The TableOfContentsEntryPattern defines how each line in your ToC will look.  Within that is a StyledText element, which is necessary whenever you are using styled text within DDX.  Further, since you’re defining a single instance of an element that gets repeated by the LiveCycle engine, you need markers for the title and page number.  The title is the _BookmarkTitle and the page number is _BookmarkPageCitation.

The leader element is used for producing the stream of periods between the title and page number.  The leader element has a leader-pattern attribute, which can be:

  • space (default)
  • dashed
  • double-dashed
  • triple-dashed
  • solid
  • double
  • triple
  • dotted
  • double-dotted
  • triple-dotted

This valuable information exists only in the DDX reference, without which you’re not going to be able to produce complex formats.

With the font size we were using, we needed a little space after the document title and before the page number.  The oh-so-useful Space element fit the bill exactly.  Finally, we added a couple of optional attributes to the TableOfContents: includeInToc (which means that the table of contents page itself is not included in the ToC) and bookmarkTitle (which is the text used for the bookmark that references the ToC).

Oh, and one final thing: almost all of the DDX examples I found used a file system file containing the DDX.  Big thanks to Raymond Camden for providing examples where cfsavecontext is used to have an inline version.  Make sure that you use the trim function on your context saved variable, or you’ll have (yet another!) instance of where IsDDX() will return false.

In Part 3, there will be additional formatting options we discovered through a combination of trial and error and combing the DDX documentation.  Until then, get some PDFs and have fun playing with these options.

UNC CAUSE 2010 Speaker

I’m delighted that my abstract submission to UNC CAUSE 2010 has been accepted. That means I now get to use the lovely graphic they’ve provided

UNC CAUSE Speaker

My talk is “What’s that Building? Using Google’s Map API to Create a Campus Map.

I got to attend UNC CAUSE 2008 (the last held one; last year’s was canceled due to NC’s budget issues) as a substitute attendee. The experience was fantastic, meeting tech people from all over the UNC System.  The keynotes, presentations, and BoF sessions were excellent, and the between-session socializing let me meet fantastic folks with tons of great things to share.   UNC CAUSE 2010 is shaping up to be even better.