Over a million developers have joined DZone.

Universal Package Tracking in ColdFusion

·
A while back, I was in need of a universal package tracking tool that is not specific to the carrier. Almost every carrier provides some kind of an API but it can be a pain to set up each one separately. Fortunately, I found a nifty tracking tool which is universal and can be leveraged through RSS. It works with UPS, FedEx, USPS, or DHL/AirBorne without having to specify which carrier you need. Instead, it determines the carrier from the tracking number.

You can check it out at http://isnoop.net/tracking/ and the some details can be found at http://isnoop.net/blog/?p=19.

While this might not be good commercial solution, it is still usable for personal or a small site. The code below shows how to leverage this with ColdFusion. The Ajax/JavaScript implementation relies on Prototype.js. You can see it in action at http://blog.tech-cats.net/examples/universalPackageTracking.cfm and download it from http://blog.tech-cats.net/examples/universalPackageTracking.txt

The code is well documented and should be easy to read/understand:



<cfsetting enablecfoutputonly="yes">
<!--- Setup default parameters and constants --->

<!--- Is this call to the page from javascript (Ajax) --->
<cfparam name="url.isAjaxCall" default="false" />

<!--- The message to display while loading --->
<cfset loadingMessage = "Loading..." />

<!--- Default tracking number as a url variable (trackingNumber) --->
<cfparam name="url.trackingNumber" default="" />
<!---
	Tracking page url: very nice free tracking for all carriers that you can
	call to get an rss feed generated based on your tracking number as in
	'http://isnoop.net/tracking/index.php?t=85642012466&rss=1'.

	If you call it without setting the 'rss' variable as in:
	'http://isnoop.net/tracking/index.php?t=85642012466' you can see a nice
	google map of where in route your package is.
	
	This service works for UPS, FedEx, USPS, or DHL/AirBorne without having
	to specify the carrier as it determins it from the tracking number.
	--->
<cfparam name="trackingPageUrl" default="http://isnoop.net/tracking/index.php" />

<!---
Function:		parseRss

Arguments:

rssData		string (The string of rss xml retrieved with cfhttp)
debugMode	boolean

Return Value:
An array of structures containing the parsed rss feed. Example:
array[1]
 link - the link from the rss item
 title - the title from the rss feed
 description - the description from the rss feed

Description:
Parses the RSS feed passed in --->
<cffunction name="parseRss" returntype="array" output="true" hint="Parses the RSS feed passed in">
	<cfargument name="rssData" type="string" required="true">
	<cfargument name="debugMode" type="string" required="false">

	<!--- Set default variables --->
	<cfset var xmlData = "">
	<cfset var result = arrayNew(1)>
	<cfset var x = "">
	<cfset var items = "">
	<cfset var xPath = "">
	<cfset var node = "">

	<cftry>
		<!--- Parse the data as xml --->
		<cfset xmlData = xmlParse(arguments.rssData)>

		<!--- Create xpath search string based on the xml root name --->
		<cfif xmlData.xmlRoot.xmlName is "rss">
			<cfset xPath = "//item">
		<cfelse>
			<cfset xPath = "//:item">
		</cfif>

		<!--- Get all the xml nodes matching the xpath search string --->
		<cfset items = xmlSearch(xmlData, xPath)>

		<!--- Loop through the found xml nodes and build an array of structures --->
		<cfloop index="i" from="1" to="#arrayLen(items)#">
			<cfset node = structNew()>
			<cfset node.link = items[i].link.xmlText>
			<cfset node.title = items[i].title.xmlText>
			<cfset node.description = items[i].description.xmlText>

			<cfset result[arrayLen(result) + 1] = duplicate(node)>
		</cfloop>
		<cfcatch>
		</cfcatch>
	</cftry>

	<cfreturn result>
</cffunction>

<cfoutput>
<!---
If this is an ajax call, get the tracking results trackingPageUrl specified above --->
<cfif url.isAjaxCall and url.trackingNumber neq ''>
	<cfhttp method="get" url="#trackingPageUrl#" result="test" charset="windows-1252">
		<!--- Set the 'rss' url variable --->
		<cfhttpparam name="rss" type="url" value="1" />
		<!--- Set the tracking url variable --->
		<cfhttpparam name="t" type="url" value="#url.trackingNumber#" />
	</cfhttp>

	<!--- Parse the rss feed from the contents returned by cfhttp --->
	<cfset rssFeed = parseRss(test.filecontent) />

	<cfif arraylen(rssFeed) gt 0>
		<div id="rssItem">
			<span id="description">Tracking data for tracking number
			'<span id="trackingNumber">#url.trackingNumber#</span>'</span>
		</div>
		<br />
		<!--- Loop through the contents of the rss feed and display them --->
		<cfloop index="i" from="1" to="#arrayLen(rssFeed)#">
		<div id="rssItem">
			<span id="description">#rssFeed[i].description.replaceall("Package update on ", "")#</span>
			
		</div>
		</cfloop>
	<cfelse>
		<div id="rssItem">
			<span id="description">No tracking data found for tracking number
			'<span id="trackingNumber">#url.trackingNumber#</span>'</span>
		</div>
	</cfif>
<cfelse>
	<!--- This is not an ajax call, so display a form for the user to enter a tracking number --->
	<!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>
	<script type="text/javascript" src="/js/prototype.js" mce_src="/js/prototype.js"></script>
	<style type="text/css">
	span##trackingNumber { font-weight: 700; }
	div##statusContainer { position:absolute;left:0px;top:0px;width:100%;height:10px; }
	div##statusMessageContainer { position:absolute;background-color:##000000;color:white;width:70px;font-family:Arial, Helvetica, sans-serif;padding:2px;left:0px; }
	</style>
	</head>
	<body>
	Browser Bookmarklet:
	<a href="javascript:(function(){var url='http://#cgi.http_host##cgi.script_name#';searchString=prompt('Enter your tracking number:','');searchString=((searchString==null)?'':searchString.replace(/^(\s+)?(.*?)(\s+)?$/gi,'$2'));var params='?isAjaxCall=true&t=';if(searchString!=''){if(location.href.indexOf(url)==-1){var win=window.open(url+params+escape(searchString));}else{location.href=url+params+escape(searchString);}}})();">
	Track Packages
	</a>
	<br /><br />
	<form id="trackingForm" name="trackingForm" method="get" action="#cgi.script_name#" class="ajaxForm">
	<input type="hidden" id="isAjaxCall" name="isAjaxCall" value="true" />
	<input type="text" id="trackingNumber" name="trackingNumber" value="1Z04WF350314328154" />
	<input type="submit" id="getTrackingResults" name="getTrackingResults" value="Track" />
	</form>
	<br />
	<!-- Results container that will be updated with the results of the request --->
	<div id="resultsContainer" class="ajaxContent"></div>
	<br />
	<!-- Status container that will be display durring processing --->
	<div id="statusContainer" style="display: none;" class="ajaxStatus">
		<div id="statusMessageContainer">Loading...</div>
	</div>
	<script language="javascript" type="text/javascript">
	var trackingForm = Class.create();
	trackingForm.prototype = {
		ajaxContainerElement: 'div',
		formID: '',
		ajaxUrl: '',
		resultsContainer: '',
		/*
		Function:	initialize
		Description: Performs various intiliazion tasks for the form
		*/
		initialize: function() {
			var ajaxFormsList = $$('form.ajaxForm');
			var ajaxContainersList = $$(this.ajaxContainerElement + '.ajaxContent');

			if (ajaxFormsList.length > 0 && ajaxContainersList.length > 0) {
				this.formID = ajaxFormsList[0].id;
				this.ajaxUrl = $(this.formID).action;
				this.resultsContainer = ajaxContainersList[0].id;

				// Tie the submit event to the submitForm function
				$(this.formID).observe('submit', this.submitForm.bind(this));

				// Reset the form
				$(this.formID).reset();

				// Activate the first element on the form
				$(this.formID).findFirstElement().activate();
			}
		},
		/*
		Function:	submitForm
		Description: Submits the form
		*/
		submitForm: function(event) {
			// Serialize the form parameters to pass them along as part of the form submission
			var params = $(this.formID).serialize(true);

			// Check if the tracking number is empty
			if (!params.trackingNumber.empty()) {
				// Disable the form
				$(this.formID).disable();

				// Make an ajax request passing it the serialized form				
				new Ajax.Updater(
					$(this.resultsContainer),
					this.ajaxUrl,
					{
					method: 'get',
					parameters: params,
					onFailure: this.reportError.bindAsEventListener(this),
					onSuccess: this.processResults.bindAsEventListener(this),
					evalScripts: true
					}
				);
			}

			// Prevent the form from being submitted
			Event.stop(event);
		},
		/*
		Function: processResults
		Description: Processes the server results
		*/
		processResults: function() {
			// Enable the form
			$(this.formID).enable();
	
			// Reset the form
			$(this.formID).reset();

			// Activate the first element on the form
			$(this.formID).findFirstElement().activate();
		},
		reportError: function(request){}
	};

	Event.observe(window, 'load', function() {
		var ajaxStatusContainersList = $$('div.ajaxStatus');
		var ajaxStatusContainer = '';

		// Create an instance of the form object defined above
		trackingFormInstance = new trackingForm();

		if (ajaxStatusContainersList.length > 0) {
			ajaxStatusContainer = $$('div.ajaxStatus')[0].id;

			Ajax.Responders.register({
				onCreate: function() {
					$(ajaxStatusContainer).show();
				},
				onComplete: function() {
					$(ajaxStatusContainer).hide();
				}
			});
		}
	});
	</script>
	</body>
	</html>
</cfif>
</cfoutput>
Topics:

Published at DZone with permission of Boyan Kostadinov, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}