Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Universal Package Tracking in ColdFusion

DZone's Guide to

Universal Package Tracking in ColdFusion

·
Free Resource
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 DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

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

X

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

{{ parent.tldr }}

{{ parent.urlSource.name }}