Saying Goodbye to ActiveX

browser icons - Firefox, Edge, Chrome, and Safari

I’ve never had much reason to use ActiveX controls, though one major web application I support has a function where users are able to send log entries via Outlook email. This function was built using an ActiveX control many years ago, when Internet Explorer was the only browser we had to support. If a user wanted to run Chrome, they had to do without this function.

Now that Internet Explorer 11 is being sunsetted this year, and Microsoft Edge will only have IE 11 mode through 2025, it is high time to update any apps that rely on technologies like ActiveX that only work in IE or Edge’s IE 11 mode.

The original code that created the ActiveX control is below.

function(oContent,sSubject,sTitleHTML)
		{
			 try
			 {
				 var sContent = buildPrintHTML(oContent,sSubject,sTitleHTML,"email");
				 var outlookApp = new ActiveXObject("Outlook.Application");
				 var mailItem = outlookApp.CreateItem(0);
				 mailItem.Subject=sSubject;
				 mailItem.HTMLBody = sContent;
				 mailItem.Display (0);
			 }
			 catch(er)
			 {
				 alert(er.message);
			 }
			 finally
			 {
				mailItem = null;
				mailFolder = null;
				nameSpace = null;
				outlookApp = null;
			 }
		};

When a user clicked the link that called this function, the Outlook.Application ActiveX control would open a new HTML email in Outlook containing all the text, including URLs, from the shared log entry.

Initially, I thought about building an anchor tag that would store all the email data in the URL in the query string and use an HTTP GET request to send it to the server. I discovered quickly that this wouldn’t work, as there are limitations on how long a query string can be, and that limit is around 2000 characters if the URL is to be compatible with most browsers.

An article on Stack Overflow pointed me in the right direction: creating an anchor tag with all of the data in the href attribute, but not to be used in a GET request. The link would use the download attribute, which would cause the data stored in the href attribute to be downloaded as a file. Though any file format can be placed here, only one that would cause an Outlook email to be generated would work for this purpose. A MIME file type that can be used for this is EML.

The JavaScript code below would, after building the anchor tag and adding it programmatically to the form as a hidden link, automatically “click” the hidden link, which would download the contents of the href attribute as an EML file, which would open in Outlook just as the ActiveX control did.

function(oContent,sSubject,sTitleHTML,sEmailLogEntryID)
		{
			 try
			 {
				var sContent = buildPrintHTML(oContent,sSubject,sTitleHTML,"email");
				
				if(browserUtility.isIE()||browserUtility.isIE11()) {
					var outlookApp = new ActiveXObject("Outlook.Application");
					var mailItem = outlookApp.CreateItem(0);
					mailItem.Subject=sSubject;
					mailItem.HTMLBody = sContent;
					mailItem.Display (0);
				} else {					
					sContent = sContent.replaceAll('#','%23');
					sSubject = sSubject.replaceAll('#','%23');
								 
					var emlContent = "data:message/rfc822 eml;charset=utf-8,";
					emlContent += 'To: \n';
					emlContent += 'Subject: '+sSubject+'\n';
					emlContent += 'X-Unsent: 1'+'\n';
					emlContent += 'Content-Type: text/html'+'\n';
					emlContent += ''+'\n';
					emlContent += sContent;
					
					var d = new Date();
					var sDate = d.getUTCFullYear() + ("0"+(d.getUTCMonth()+1)).slice(-2) + ("0" + d.getUTCDate()).slice(-2)   + ("0" + d.getUTCHours()).slice(-2) + ("0" + d.getUTCMinutes()).slice(-2) + ("0" + d.getUTCSeconds()).slice(-2) + ("00" + d.getUTCMilliseconds()).slice(-3);
					
					var encodedUri = encodeURI(emlContent); //encode spaces etc like a url
					var a = document.createElement('a'); //make a link in document
					var linkText = document.createTextNode("fileLink");
					a.appendChild(linkText);
					a.href = encodedUri;
					a.id = 'fileLink';
					a.download = 'logEntryEmail_' + sEmailLogEntryID + '_' + sDate + '.eml';
					a.style = "display:none;"; //hidden link
					
					document.body.appendChild(a);
					document.getElementById('fileLink').click(); //click the link
				};					
			 }
			 catch(er)
			 {
				 alert(er.message);
			 }
			 finally
			 {
				outlookApp = null;
				mailItem = null;
				sContent = null;
				emlContent = null;
				encodedUri = null;
				a = null;
				linkText = null;				
				d = null;
				dateString = null;
			 }

There were a few pitfalls that had to be overcome in completing this. If the HTML code includes any octothorpes (a.k.a, number sign, pound sign, hashtag), these must be escaped by replacing them with the hex code “%23” (no quotes). Otherwise, they will cause the URL to be processed incorrectly, causing errors.

In some cases, escaping the number signs won’t work. For instances where they are used as CSS colors, either the common name should be used when one exists (i.e., #FFFFFF is “white”), or RGB should be subbed in instead.

For the filename, I concatenated both a unique log entry ID, and the UTC time the file was created to prevent duplicate filenames in the user’s downloads folder.

 

Element is Undefined Java Error on Adobe ColdFusion

ColdFusion logo

An error that I’ve run into a few times when implementing new components in a ColdFusion website is “Element [COMPONENT NAME] is undefined in a Java object of type class…”

This error, when found in a development environment, is likely due to a typo in declaring locations for components in the Application.cfc file. (Or perhaps you just forgot to do it!) But what happens when your development environment works fine, but the error arises after migrating code to production?

In all likelihood, the application needs to be restarted (or you may restart the ColdFusion service, restarting all applications). This has fixed the issue for me every time.

Documenting Filenames in the Source Code with Python

Python logo

Troubleshooting a web application page that is made from a crazy quilt of source code files is not always easy. Whether your application is built using ASP, PHP, ColdFusion, or something completely different, the challenge here is the same. How do you find the line of code in the source that is causing the problem if you don’t know which file to look in?

If you’re lucky, and a particular piece of error handling text or other identifying information is only found in a couple of places, using a PowerShell script like the one below will find what you’re looking for.

Get-ChildItem -Path "C:\Root folder" -Recurse | Select-String -Pattern "text to search for" | group path | select name

However, if the error text itself is not embedded in the code, but you can find the offending line of code in the rendered web page using Chrome Dev Tools or the like, you may still have a problem locating the actual line of code in source. That’s where this technique comes in. Putting the source code filenames in comments at the beginning and end of each appropriate file can make it easier to determine the origin of the problem. 

I’ve visited this topic before, and used PowerShell to do this task then. Now, I’m using Python. I was able to cobble this script together from one that traverses folders and documents their contents and one that filters the file type since, for this exercise, I only need to alter the ColdFusion (.cfm) files. 

import os
import sys

walk_dir = sys.argv[1]

print('walk_dir = ' + walk_dir)

# If your current working directory may change during script execution, it's recommended to
# immediately convert program arguments to an absolute path. Then the variable root below will
# be an absolute path as well. Example:
# walk_dir = os.path.abspath(walk_dir)
print('walk_dir (absolute) = ' + os.path.abspath(walk_dir))

for root, subdirs, files in os.walk(walk_dir):
    files = [ fi for fi in files if fi.endswith(".cfm") ]    

    for filename in files:
        file_path = os.path.join(root, filename)
        adj_file_path = file_path.replace(walk_dir,'').replace('\\','/')
        print('\t- file %s (full path: %s)' % (filename, adj_file_path))
        
        with open(file_path, 'rb') as f:
            f_content = f.read().decode("utf-8")
            f_content = '<!-- file: ' + adj_file_path + ' -->\n' + f_content + '\n<!-- end of file: ' + adj_file_path + ' -->\n'                       
            
        with open(file_path, 'wb') as f:
            f.write(f_content.encode("utf-8"))