homedownloadpurchaseregisterproductsservicesdeveloperpartnerscompanyinternationalsupport homedownloadpurchaseregisterproductsservicesdeveloperpartnerscompanyinternationalsupport searchindexcontact




 
   

Roll Your Own Features in HomeSite and Studio

By Dan Hulse
HomeSite/Studio Development
Macromedia, Inc.

There's something about the anticipation of that shiny new release of your favorite software package. The mystery surrounding the infamous "Beta" testing, the rumor of the latest features in the newsgroups, the final announcement on the Web site, the FedEx truck turning the corner of your street...

Okay, so maybe that's a bit dramatic for most of us. Still, it's satisfying to find that must-have feature finally implemented in the latest and greatest version.

HomeSite (and ColdFusion and JRun Studios) includes a built-in scripting language based on Microsoft's ActiveScripting engine. This scripting engine allows you to use either JScript (a JavaScript-derived language) or VBScript as a language base and utilize all of the native statements, functions, and objects of these languages. On top of this base-language functionality, the HomeSite and Studio products add additional objects which allow you to control features specific to these applications. These additional objects comprise the Visual Tools Object Model (VTOM) and allow the advanced user to manipulate and extend the product beyond its core feature set. So, instead of waiting for that next release, we can begin to evolve and tailor the software right away.

Introduction

In this article, I'll offer some examples of adding two new features to HomeSite through VTOM. I'll be using JScript, although VBScript can be used just as well. Although this article isn't meant to be a tutorial on JScript, I'll explain the major points of the sample scripts, and the end of the article lists some additional resources for more information on scripting. The intent here is to give advanced developers some code fragments to generate ideas for their own customizations. Novice users can still use the sample scripts to add real functionality to HomeSite, and hopefully get a taste for the power of VTOM. The samples will work equally well in HomeSite, ColdFusion Studio, and JRun Studio.

Converting Text to Special Character Entities

When writing documents destined for the Web, I often need to insert HTML coding samples which need to be viewed in their "source" form, with all the tags intact and displayed in the browser. This involves the use of special character entities in the HTML source, such as &lt; for the "<" symbol. While HomeSite offers the Special Character tool window for entering these entities, it can be awkward to use if you dislike mixing keyboard and mouse usage. Our first script (ConvSpecChars.js) allows you to type in the most common characters directly, and then convert them automatically to their character entity equivalents.

/*
	ConvSpecChars.js

	Takes the current selection, and replaces special characters 
	with their general entity equivalent
	
	E.g., 
	
	<html> ..becomes.. &gt;html&lt;
	
	
*/

function Main() {

	var aChars = new Array(4);
	var aRepls = new Array(4);
	var SelStr = '';
	var NewStr = '';
	var s      = '';
	var i;
	
	// Initialize search/replace pairs
	// Note that order is important (e.g., replace '&' first!!)
	aChars[0] = '&';
	aRepls[0] = '&amp;';
	
	aChars[1] = '<';
	aRepls[1] = '&lt;';
	
	aChars[2] = '>';
	aRepls[2] = '&gt;';
	
	aChars[3] = '"';
	aRepls[3] = "&quot;";

	with (Application){
		with (ActiveDocument){
			if (SelLength == 0){
				MessageBox('One or more characters must be selected.', 'Error', 0);
				return;
			}

			SelStr = SelText;
			
			for (i = 0; i < SelStr.length; i++){
				s = SelStr.substr(i, 1);

				iItem = ArrFind(aChars, s);
				
				if (iItem > -1)
					NewStr = NewStr + aRepls[iItem]
				else 
					NewStr = NewStr + s;
			}
			
			SelText = NewStr;
		}
	}
}


function ArrFind(aArray, sSearch){
	var i;
	var result = -1;
	var s;

	for (i = 0; i < aArray.length; i++){
		s = aArray[i];
		if (s == sSearch){
			result = i;
			break;
		}
	}

	return(result);
}

The single character symbols and their equivalent character entities are stored in two arrays (JScript does not support multidimensional arrays). Application and ActiveDocument are objects which are provided through VTOM. Application is the HomeSite or Studio application itself, and ActiveDocument is the currently displayed document in the code editor. The with () statement establishes a default object, so instead of having to write Application.ActiveDocument.SelText, we can nest the Application and ActiveDocument objects in two with statements and just use the property or method names without the object identifiers.

This script simply iterates through all the characters of the current text selection, and performs a lookup of the character (via the ArrFind function). We build up a new string with any replacement entities, and set the selected text to this new string.

The best way to utilize the above script is through a keystroke combination. HomeSite versions 4.5 and up allow you to assign a script to a keyboard shortcut. To do this, perform the following steps:

  1. Select the Options ->Customize menu item.
  2. In the Customize window, select the Script Shortcuts tab.
  3. Click the Add button and select your script from the file dialog.
  4. Back in the Customize window, make sure the script is highlighted in the list, and then click in the shortcut box in the bottom left of the dialog.
  5. Type in the key combination you want to assign to this script and click Apply.
  6. Click Close to exit the Customize dialog.

The next time you want to convert characters, simply select the desired text and press the key combination you specified.



Saving and Loading Editor Bookmarks

The code editor in HomeSite allows you to create bookmarks for quickly moving to specific lines in your code. CTRL+K toggles a bookmark for the current line on and off, and SHIFT+CTRL+K moves the cursor to each bookmarked line in sequence. One feature that has been requested by users is the ability to save and reload bookmarks for individual files. This is not a trivial matter using VTOM, but the next set of scripts will show you how this can be done.

We'll accomplish this by writing out the bookmarks to a simple text file. Each line of the file will contain the bookmarks for a single document, with the filename first, followed by '=' and then the list of bookmarks in comma-delimited format (e.g., 'C:\InetPub\wwwroot\Content\Index.htm=12,27,102'). Since we're dealing with external files, the next set of scripts give some insight on using the FileSystemObject. This object is provided by the Microsoft scripting engine, and provides access to the Windows file system.

We'll start with the SaveBookMarks.js script:

/*
SaveBookMarks.js

Save current bookmarks to a file...
	
File is an "INI" style format, with a file name 
matched to bookmarks in a comma-delimited format. E.g.:

C:\Inetpub\wwwroot\catalog.htm=12,20,55

*/


function Main() {
		var iSelStart;
var iSelLen;
	var sMarkFile 	= 'C:\\Program Files\\Allaire\\ColdFusion Studio 4.5\\UserData\\BookMarks.dat';
	
	with (Application.ActiveDocument){

		// Save current selection
		iSelStart = SelStart;
		iSelLen   = SelLength;
		
		try{
			// Prevent editor updates
			BeginUpdate();

			SaveBookMarks(sMarkFile);
		}
		finally{
			// Restore selection			
			SelStart  = iSelStart;
			SelLength = iSelLen;

			// Restore editor updates
			EndUpdate();
		}
	}
}


function SaveBookMarks(sFileName){
	var aMarkSets;
	var aMarks = new Array();
	var sMarkLine;
	var iPos;
	var i;
	var iStartPos;
	var iLine;
	var sLine;

	with (Application.ActiveDocument){
	
		sMarkLine	= Filename + '=';
		aMarkSets	= FileToArray(sFileName);

		// Check if this file is currently in the bookmarks file and delete it
		for (iPos = 0; iPos < aMarkSets.length; iPos++){
			sLine = aMarkSets[iPos];

			if (sLine.indexOf(sMarkLine) >= 0){
				
				// Shift remaining entries up one element
				for (i = iPos; i < aMarkSets.length - 1; i++){
					aMarkSets[i] = aMarkSets[i + 1];
				}
				
				// Adjust array size so the last element is deleted 
				aMarkSets.length = aMarkSets.length - 1;
			
				break;
			}
		}

		try{
			CursorDocStart(false);
			CursorLineEnd(false);
			iStartPos = SelStart;
			
			// Go to next bookmark
			Application.ExecCommand(60, 0);
			
			// If the cursor hasn't moved, then no bookmarks present?
			if (SelStart == iStartPos)
				return;

			iLine = LineNo(Text, SelStart);

			while (true){
				aMarks.length = aMarks.length + 1;
				aMarks[aMarks.length - 1] = iLine;
				
				// Go to next bookmark
				Application.ExecCommand(60, 0);
				
				iLine = LineNo(Text, SelStart);

				// if we are back at the first bookmark, then we are done
				if (aMarks[0] == iLine)
					break;
			}

			// append the comma-delimited line numbers
			sMarkLine = sMarkLine + aMarks.join(',');
			
			// add current bookmark set to existing ones
			aMarkSets.length = aMarkSets.length + 1;
			aMarkSets[aMarkSets.length - 1] = sMarkLine;
		}
		finally{
			ArrayToFile(sFileName, aMarkSets);
		}
	}
}


function ArrayToFile(sFileName, aArray){
	var aFileObj	= new ActiveXObject("Scripting.FileSystemObject");
	var ForWriting	= 2;
	var aTextStream;
	
	if (!aFileObj.FileExists(sFileName))
		aFileObj.CreateTextFile(sFileName, true);

	aTextStream = aFileObj.OpenTextFile(sFileName, ForWriting);
	aTextStream.Write(aArray.join('\r\n'));
	aTextStream.Close();
}


function FileToArray(sFileName){
	var aFileObj	= new ActiveXObject("Scripting.FileSystemObject");
	var ForReading	= 1;
	var aTextStream;
	var aFile;	
	var sText;
	var aArray;
	
	if (!aFileObj.FileExists(sFileName))
		return(new Array());
		
	aFile = aFileObj.GetFile(sFileName);
	if (aFile.Size == 0)
		return(new Array());
			
	aTextStream = aFileObj.OpenTextFile(sFileName, ForReading);
	
	try{
		sText	= aTextStream.ReadAll();
		aArray 	= sText.split('\r\n');
	}
	finally{
		aTextStream.Close()
	}
	
	return(aArray);
}


function LineNo(sText, iOffSet){
	var iPos;
	var iLine;
	
	iPos  = 0;
	iLine = 1;

	while (iPos < iOffSet){
		if (sText.substr(iPos, 2) == '\r\n'){
			iLine++;
			
			// Increment the offset to account for line feeds
			iOffSet++;

			// skip past the \n 
			iPos++;
		}
		
		iPos++;
	}
	
	return iLine;
}

Before running this script, ensure that the variable sMarkFile points to a valid location for your system. Note that in JScript, the backslash used for the path separator needs to be "escaped" out in a JScript string by preceding it with another backslash character.

Let's look at the support functions first. The LineNo function is actually a workaround for an error in the JScript implementation of the ActiveDocument.GetCaretPos method, which would normally be used to get the current line containing the cursor. In VBScript, this method takes two parameters which are passed by reference (the variables passed in will be modified by the method). JScript only allows passing of variables by value, not by reference, so this method will not work correctly in this language. Our LineNo function takes a string and an offset, and calculates which line the offset is located on based on the count of carriage returns and line feeds ('\r\n') preceding the offset. As used in the above script, we pass it the text of the document (ActiveDocument.Text) and the start of the selection in the editor (ActiveDocument.SelStart), and the function's return value tells us which line the cursor is on.

The ArrayToFile and FileToArray functions provide a way to store JScript arrays to an external file and to load the lines of a text file back into an array. The FileSystemObject provides numerous file access functions, such as testing if a file exists, getting a file's size on disk, etc. This object also allows us to read and write files by creating a FileStream object. Check the Microsoft scripting links at the end of this article to get more information on the features of these objects.

The SaveBookMarks function is the core routine for this script. We first use the FileToArray function to load in the current bookmark file to an array variable and delete any item which references the current document. We position the editor cursor at the top of the document, and then call the Application.ExecCommand method to move to each consecutive bookmark in the document, and store the line number into an array. ExecCommand is a generic method used for performing many tasks inside HomeSite. The first parameter of this method is an integer value which corresponds to a specific application command. These values are listed in the help documentation, under the Scripting the Visual Tools Object Model topic section. The second parameter determines if text is selected during any cursor movement operations.

Once we find a bookmarked line that we have already detected, we know that we have looped through all of the bookmarks. We then add the current document's name and comma-delimited bookmarked line numbers to the bookmark file array, and make a call to ArrayToFile to save it back to disk.

One point worth noting is the use of the BeginUpdate and EndUpdate methods, which are part of the ActiveDocument object. BeginUpdate will suspend any updates to the code editor in HomeSite. Its used in these scripts so that the user doesn't see text being selected or the cursor scrolling through the document as the script is executing. EndUpdate will resume proper refreshing of the editor. While this provides a visually cleaner running script, be aware that if the script halts on an error before the EndUpdate is called, the editor will be left in a suspended state, and you will need to restart HomeSite (or create a script with just an Application.ActiveDocument.EndUpdate within the Main() procedure). It's a good idea to comment out the editor update commands until you are satisfied that the script is working as expected.

Now that we can successfully save a document's bookmarks, we need to be able to load them back in. The LoadBookMarks.js script accomplishes this:

/*
	LoadBookMarks.js

	Load current bookmarks from a file...
	
	File is an "INI" style format, with a file name 
	matched to bookmarks in a comma-delimited format. E.g.:

		C:\Inetpub\wwwroot\catalog.htm=12,20,55

*/


function Main() {
	var iSelStart;
	var iSelLen;
	var sMarkFile 	= 'C:\\Program Files\\Allaire\\ColdFusion Studio 4.5\\UserData\\BookMarks.dat';
	
	with (Application.ActiveDocument){

		// Save current selection
		iSelStart = SelStart;
		iSelLen   = SelLength;
		
		try{
			// Prevent editor updates
			BeginUpdate();

			LoadBookMarks(sMarkFile);
		}
		finally{
			// Restore selection			
			SelStart  = iSelStart;
			SelLength = iSelLen;

			// Restore editor updates
			EndUpdate();
		}
	}
}



function LoadBookMarks(sFileName){
	var aMarkSets;
	var aMarks;
	var iCount;
	var sMarkLine;
	var iPos;
	var sCurrDoc = Application.ActiveDocument.Filename;

	aMarkSets = FileToArray(sFileName);

	if (aMarkSets.length == 0)
		return;
	
	for (iCount = 0; iCount < aMarkSets.length; iCount++){
		sMarkLine 	= aMarkSets[iCount];
		iPos 		= sMarkLine.indexOf(sCurrDoc + '=');
		
		if (iPos >= 0)
			break;
	}
	if (iPos < 0)
		return;
		
	iPos 		= (sMarkLine.indexOf('=') + 1);
	sMarkLine 	= sMarkLine.substring(iPos, sMarkLine.length);
	aMarks 		= sMarkLine.split(',');
		
	with (Application.ActiveDocument){
		for (iCount = 0; iCount < aMarks.length; iCount++){
			SelectLine(aMarks[iCount]);
			Application.ExecCommand(59, 0);
		}
	}
}

function FileToArray(sFileName){
	var aFileObj	= new ActiveXObject("Scripting.FileSystemObject");
	var ForReading	= 1;
	var aTextStream;
	var aFile;	
	var sText;
	var aArray;
	
	if (!aFileObj.FileExists(sFileName))
		return(new Array());
		
	aFile = aFileObj.GetFile(sFileName);
	if (aFile.Size == 0)
		return(new Array());
			
	aTextStream = aFileObj.OpenTextFile(sFileName, ForReading);
	
	try{
		sText	= aTextStream.ReadAll();
		aArray 	= sText.split('\r\n');
	}
	finally{
		aTextStream.Close()
	}
	
	return(aArray);
}

Again, before running the script, check that the sMarkFile variable points to a valid path on your system.

This script uses the same FileToArray procedure as the SaveBookMarks.js script. The LoadBookMarks procedure uses this function to read in the current bookmark file. We iterate through the resulting array to see if the current document is listed. If it is, we convert its comma-delimited bookmark string to an array and loop through the array to send the cursor to the appropriate line (ActiveDocument.SelectLine), We then issue an ExecCommand to set a bookmark.

You might decide that you want to include the saving or loading of the document along with its bookmarks. In SaveBookMarks.js, you would just add the following line just before the SaveBookMarks() call in the Main() method:

  Save();

To load a file together with its bookmarks, add the following line before the LoadBookBookMarks() call in the LoadBookMarks.js script:

  if (OpenFile(''));

You can tie the bookmark scripts to a keyboard shortcut as we did for the ConvSpecChars.js script, or you might find it more convenient to access them via a custom toolbar button. To create a custom toolbar icon, do the following:

  1. Select Options -> Customize from the main menu.
  2. From the Visible Toolbars list, select the destination toolbar for the new buttons.
  3. Click Add Custom Button to display the Custom Toolbutton window.
  4. Click the Execute an ActiveScript file radio button, and enter the appropriate script filename in the Script File edit box.
  5. Enter a caption for the button (you can also set a bitmap image and a hint as well).
  6. Click Ok to close the Custom Toolbutton window.
  7. Click Close to exit the Customize dialog.


Hopefully you've seen that the VTOM scripting features in HomeSite can do a lot more than just execute repetitive tasks. You can add major functionality to the software in order to customize it to your own needs and extend it beyond its out-of-the-box capabilities.

Resources

Scripting the Visual Tools Object Model
HomeSite/ColdFusion Studio/JRun Studio help file.

Microsoft Scripting Technologies
msdn.microsoft.com/scripting/default.htm?/scripting/start.htm

Microsoft Scripting Clinic
msdn.microsoft.com/voices/scripting05142001.asp

Microsoft JScript Documentation (HTML help file)
msdn.microsoft.com/scripting/jscript/download/jsdoc.exe

HomeSite Dev Center
www.allaire.com/developer/hsreferencedesk/






< a l l a i r e >

Macromedia & Allaire have merged.
Copyright © 1995-2001 Allaire Corp., All rights reserved.
Year 2000 (Y2K)    Site problems?    Customer Service?     Privacy Policy     Feedback