<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">

<channel>
	<title>Steven Benner's Blog</title>
	
	<link>http://stevenbenner.com</link>
	<description>Random articles about programming, computing and the internet.</description>
	<lastBuildDate>Tue, 09 Mar 2010 08:00:45 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/stevenbenner" /><feedburner:info uri="stevenbenner" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><item>
		<title>Custom link click tracking using Omniture</title>
		<link>http://feedproxy.google.com/~r/stevenbenner/~3/DClZT54xC_Y/</link>
		<comments>http://stevenbenner.com/2010/03/custom-link-click-tracking-using-omniture/#comments</comments>
		<pubDate>Sun, 07 Mar 2010 20:55:28 +0000</pubDate>
		<dc:creator>Steven Benner</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Analytics]]></category>
		<category><![CDATA[How-To]]></category>
		<category><![CDATA[JavaScript]]></category>

		<guid isPermaLink="false">http://stevenbenner.com/?p=633</guid>
		<description><![CDATA[Omniture is the de facto standard tracking and analytics  system that most online retailers use. It has a suite of reporting metrics and allows for custom reporting variables. It is primarily designed for online stores to track usage, conversions and sales.
Admittedly, I am not that big a fan of Omniture. Their SiteCatalyst reporting application [...]]]></description>
			<content:encoded><![CDATA[<p>Omniture is the de facto standard tracking and analytics  system that most online retailers use. It has a suite of reporting metrics and allows for custom reporting variables. It is primarily designed for online stores to track usage, conversions and sales.</p>
<p>Admittedly, I am not that big a fan of Omniture. Their <em>SiteCatalyst</em> reporting application is slower than Google Analytics and not as flexible as it could be. My biggest gripe is their tracking JavaScript code, it&#8217;s just plain terrible. It&#8217;s slow, obfuscated (really? why do this?), bloated, impossible to debug and not built using modern practices. Oh, and they&#8217;re also insanely expensive if you want to get at all fancy.</p>
<p>This article is simply a straight how-to for building a custom link tracking JavaScript in the hopes that I can save some other JavaScript developer out there the headaches and tears usually associated with learning advanced Omniture implementations.</p>
<p><span id="more-633"></span></p>
<h3>Omniture basics</h3>
<p>The Omniture JavaScript does all of its work through the <code>s</code> object. This object has many different methods and properties that are all ingeniously designed to confuse and torment would-be implementers.</p>
<p>For custom tracking metrics you will be using <em>prop codes</em>, which are called <em>&#8220;Custom Insights&#8221;</em> in the SiteCatalyst reporting software. You associate a specific prop code to a specific piece of data. So you may associate <code>s.prop1</code> to be the category of a product and <code>s.prop2</code> to be the name of the product.</p>
<p>The way we track is by loading up prop variables with strings then telling the Omniture code to run. This is done by calling <code>s.t()</code> for a pageview track or <code>s.tl()</code> for a link click track.</p>
<h3>How to track a click action with custom prop data</h3>
<p>The code to track an action is actually quite simple, loading prop values and calling the tracking method. Here is what a tracking script looks like. This example is the basic custom click tracking code that you would expect to see in an <em>onclick</em> event attached to an anchor tag.</p>
<pre class="brush: jscript;">
s.linkTrackVars = 'prop1,prop2';
s.linkTrackEvents = 'None';
s.prop1 = 'Televisions';
s.prop2 = 'Samsung - LN40B530 - 40&quot; LCD TV - 1080p (FullHD)';
s.tl(this, 'o');
</pre>
<p>Now, this is fairly self-explanatory, but I&#8217;ll break this down line for line so you know exactly what&#8217;s happening and what these statement mean.</p>
<ul>
<li><strong>s.linkTrackVars</strong> declares which prop codes you will be using in this event. This property is a comma-separated list of each prop code that you want tracked.</li>
<li><strong>s.linkTrackEvents</strong> are the &#8220;events&#8221; that an action may represent. Usually you will use events like &#8220;purchase&#8221; or &#8220;save&#8221; but you can put just about anything you want in here. It&#8217;s just a different way of looking at the same information. I typically set it to none and use custom insights for reports.</li>
<li><strong>s.prop1</strong> and <strong>s.prop2</strong> are the custom metrics that will be attached to this click event. In this case prop1 is the product category and prop2 is the product name.</li>
<li><strong>s.tl(this, &#8216;o&#8217;)</strong> calls the tracking action. The <code>tl()</code> method (a.k.a. the <em>trackLinks method</em>) requires two parameters, this anchor object to be tracked (should always be this) and the type of link we are tracking. There are three types of basic links that is method understands: o is the &#8220;Other/General&#8221; link type, d is a &#8220;File Download&#8221; link, and e which is an &#8220;Exit Link&#8221;. Anything that isn&#8217;t a download or exit link should be given the o tag.</li>
</ul>
<p>There you have it, not incredibly complex, but not obviously simple either. There are a bunch of other properties that you can attach to a trackLinks event, but this is what 99% of them look like.</p>
<h3>Building a script to attach link tracking events automatically</h3>
<p>There are two standard ways to attach these custom link tracking events to the anchors that you want to track: <em>the stupid method</em>, and <em>the smart method</em>.</p>
<ul>
<li><strong>The stupid method</strong> is the most common. This involves adding an onclick tag to every anchor and filling it with the appropriate code.
<ul>
<li>Pros: Stupid, simple, always works</li>
<li>Cons: Stupid, unmaintainable, ugly, increases markup, adds non-semantic markup</li>
</ul>
</li>
<li><strong>The smart method</strong> is to build a single script that will attach the code to every link you want to track automatically on page load.
<ul>
<li>Pros: Smart, not-stupid, simple, maintainable</li>
<li>Cons: Links don&#8217;t get the tracking events attached until the whole page has finished loading.</li>
</ul>
</li>
</ul>
<p>For this rest of this article I will assume that you want to use <em>the smart method</em>. Basically, we will build a script that will look at every single anchor tag in the <acronym title="Document Object Model">DOM</acronym> and attach the click tracking events to any links matching whatever criteria you choose. This script will automatically run when the page loads.</p>
<h3>Defining the criteria and search pattern</h3>
<p>First off, you must be able to identify the links that you want to track. I have used two different systems for this: Searching href tags and adding rel tags.</p>
<p>Searching href tags is generally the best option. This means matching the link href and attaching the event based on that data. However I have found situations where I didn&#8217;t want to track every link that had the string &#8220;products.aspx&#8221; in it, so I chose to add rel tags to those links that I did want to track. This way I could just search for links that had rel=&#8221;product&#8221; in them.</p>
<p>The technique is still basically the same, we will gather all of the anchors in the document into an array, then iterate through the array, attaching the appropriate events and data to those anchor which meet our criteria for each type of page.</p>
<h3>The basic script structure</h3>
<p>No matter what you choose as your search criteria and trackable data you will need a script that has at least the following features:</p>
<ul>
<li>Iterates through all anchors on a page</li>
<li>Allows you to attach tracking events <em>without overriding</em> any existing events</li>
<li>Automatically runs as soon as the page loads</li>
<li>Highly <em>optimized</em> for minimum execution time</li>
<li>Cannot throw an error that will break the page</li>
<li><em>Unobtrusive design</em> that doesn&#8217;t have any dependencies (other than Omniture)</li>
<li>Namespaced in <em>object literal notation</em> so that it can never have any confilcts</li>
</ul>
<h4>The basic JavaScript object that I use for this</h4>
<pre class="brush: jscript;">
// Omniture click tracking
// http://stevenbenner.com/2010/03/custom-link-click-tracking-using-omniture/

if(!sb_trackLinks) { var sb_trackLinks = new Object(); };

sb_trackLinks = {
	// initialize the click-tracking system and attach events
	init: function() {
		// verify that we are running in a modern browser, if not give up silently
		if (!document.getElementsByTagName) return false;

		// iterate through all of the links in the document
		var anchors = document.getElementsByTagName('a');
		for (var i=anchors.length-1; i&gt;=0; i--) {

			// SEARCH STATEMENTS AND TRACKING VARIABLE BUILDING GOES HERE

		};
	},

	// tracking function
	track: function(obj, propCodes, linkType = 'o') {
		s = s_gi(s_account);
		// reset tracking properties
		s.linkTrackVars = '';
		s.linkTrackEvents = 'None';
		// iterate through each property in propCodes
		for (code in propCodes) {
			s.linkTrackVars += code + ','; // build the CSV list
			s[code] = propCodes[code];     // attach the prop code
		}
		// track it
		s.tl(obj, linkType);
	};

	// non-breaking/non-overriding/cross-browser event attachment
	addEvent: function(obj, evType, fn, useCapture) {
		if (obj.addEventListener) {
			obj.addEventListener(evType, fn, useCapture);
			return true;
		} else if (obj.attachEvent) {
			var r = obj.attachEvent('on' + evType, fn);
			return r;
		} else {
			obj['on' + evType] = fn;
		};
	}
};

// auto-init on load
sb_trackLinks.addEvent(window, 'load', sb_trackLinks.init);
</pre>
<p>This is the basic outline of my JavaScript click tracking object. It has three methods and automatically runs itself in the window.onload event.</p>
<p>I've put a lot of thought into optimizing this script. For instance, notice that I reverse iterate through the anchors array. This technique has been proven to be faster because the array length is only calculated once when the for statement begins. If you do the <code>while i&lt;array.length</code> style then the length is calculated once for every iteration in the loop.</p>
<p>When you build any kind of tracking or analytics JavaScript your highest priority should be to shave every nanosecond from it's operations. It is acceptable to sacrifice readability for performance here. After all, as a web developer your primary job should be to improve <em>user experience</em>, tracking scripts are a necessity but do not benefit users in any way, so you do your best to reduce their impact on user experience.</p>
<h3>The sb_trackLinks methods</h3>
<p>There are three methods in the sb_trackLinks object:</p>
<dl>
<dt>init()</dt>
<dd>The init() method is where the startup, initialization and event attachment will occur. We still need to build the search criteria, tracking variables and event attachment code before this will do anything.</dd>
<dt>track(obj, propCodes, linkType)</dt>
<dd>The track() method is the code that will be attached to the onclick events for the anchors that we want to track. It requires two parameters and will accept a third.</p>
<ul>
<li><strong>obj:</strong> The first parameter is the anchor object that we will be tracking, in the context of our for statement in the <code>init()</code> method this will always be <code>anchors[i]</code>.</li>
<li><strong>propCodes:</strong> The second parameter is an object with the prop codes that we want to use. This should be a standard JavaScript <em>object</em> with the conventions as the <code>s.propXX</code> codes. So if you want to track prop1 you should set <code>propCodes['prop1']</code> to the value that you want and pass it through to the <code>track()</code> method.</li>
<li><strong>linkType:</strong> The third parameter is the link type that will be passed to s.tl() and is optional. It will default to <code>'o'</code>. As I said earlier, it will accept o, d and e as valid values.</li>
</ul>
<p>You may have to modify this function if your Omniture code has been customized.</p>
</dd>
<dt>addEvent(obj, evType, fn, useCapture)</dt>
<dd>The addEvent() method is the standard addEvent that you see everywhere on the internet.</dd>
</dl>
<h3>Crafting the search and tracking variables</h3>
<p>Now that we have a basic structure that we can use, all that we need to build is the anchor search statements, tracking variable and event attachments.</p>
<p>This needs to be custom built for your specific needs. The application, URL structure and tracking needs are all critical parts of this equation. If you are using <a href="http://stevenbenner.com/2010/02/friendly-urls-in-an-asp-net-app/">friendly URLs</a> and need to parse data from them then you will may have to deconstruct the URI to grab the specific data that you want to track in prop codes. If you need data from somewhere else on the page you will have to figure out out to grab it. Etcetera.</p>
<p>I'll provide you with an example based on our previous examples. In this case we are tracking clicks in an online store application. We are using standard query string parameters in our URLs and do not need any other special data.</p>
<p>This is what our <code>for</code> statement will look like for this site:</p>
<pre class="brush: jscript;">
for (var i=anchors.length-1; i&gt;=0; i--) {
	// parse the query string into an object
	var queryString = {};
	anchors[i].href.replace(
		new RegExp(&quot;([^?=&amp;]+)(=([^&amp;]*))?&quot;, &quot;g&quot;),
		function($0, $1, $2, $3) { queryString[$1] = $3; }
	);

	// conditionals to match link href targets
	if (anchors[i].href == 'http://your.domain/' || anchors[i].href.indexOf('home.aspx') &gt; -1) {
		// home page
		sb_trackLinks.addEvent(anchors[i], 'click', (function() {
				return function() {
					try {
						sb_trackLinks.track(
							this,
							{
								'prop1': 'home'
							}
						);
					} catch(e) {}
					return true;
				};
			})()
		);
	} else if (anchors[i].href.indexOf('category.aspx') &gt; -1) {
		// category pages
		sb_trackLinks.addEvent(anchors[i], 'click', (function(catId) {
				return function() {
					try {
						sb_trackLinks.track(
							this,
							{
								'prop1': 'category',
								'prop2': catId
							}
						);
					} catch(e) {}
					return true;
				};
			})(queryString['category_id'])
		);
	} else if (anchors[i].href.indexOf('product.aspx') &gt; -1) {
		// product pages
		sb_trackLinks.addEvent(anchors[i], 'click', (function(catId, productId, productName) {
				return function() {
					try {
						sb_trackLinks.track(
							this,
							{
								'prop1': 'product',
								'prop2': catId,
								'prop3': productId,
								'prop4': productName
							}
						);
					} catch(e) {}
					return true;
				};
			})(queryString['category_id'], queryString['product_id'], queryString['product_name'])
		);
	};
};
</pre>
<p>This is a bit technical, and uses some advanced JavaScript techniques. However, it should be fairly understandable if you know your way around JavaScript.</p>
<p>Since we need to grab query string values for most of our tracked pages I start off by <a title="JavaScript regex trick: Parse a query string into an object" href="http://stevenbenner.com/2010/03/javascript-regex-trick-parse-a-query-string-into-an-object/">parsing the query string into an object</a>.</p>
<p>I then proceed down a list of <code>if</code> statements that try to match a specific page. Inside each <code>if</code> statement I attach an onclick event to the current anchor. That event passes our current data down through the JavaScript scope via a <code>(function(v){})(data)</code> statement. Inside that event I call the <code>track()</code> method with the data and return true so that the browser follows the link once the code as been executed.</p>
<p>This can get very long indeed if you have a lot of different  types of pages in your web application that need click tracking. As  always, order your if statements by commonality, putting the most  commonly matched statements first. The script engine will have to fail  every if check to get to the last statement. This minor optimization may  save you a millisecond or two.</p>
<p>It is very important that you <strong>wrap all of the code inside the event in a <code>try{}catch(e){}</code></strong> just in case something somewhere breaks. If you do not do this then the links on your site will stop working if anything goes wrong. There is nothing quite as asinine as links not working because the tracking script is broken.</p>
<h3>Putting it all together</h3>
<p>Now that we've built the search and attach code all we have to do is insert it into our base script. The result looks like this:</p>
<pre class="brush: jscript;">
// Omniture click tracking
// http://stevenbenner.com/2010/03/custom-link-click-tracking-using-omniture/

if(!sb_trackLinks) { var sb_trackLinks = new Object(); };

sb_trackLinks = {
	// initialize the click-tracking system and attach events
	init: function() {
		// verify that we are running in a modern browser, if not give up silently
		if (!document.getElementsByTagName) return false;

		// iterate through all of the links in the document
		var anchors = document.getElementsByTagName('a');
		for (var i=anchors.length-1; i&gt;=0; i--) {
			// parse the query string into an object
			var queryString = {};
			anchors[i].href.replace(
				new RegExp(&quot;([^?=&amp;]+)(=([^&amp;]*))?&quot;, &quot;g&quot;),
				function($0, $1, $2, $3) { queryString[$1] = $3; }
			);

			// conditionals to match link href targets
			if (anchors[i].href == 'http://your.domain/' || anchors[i].href.indexOf('home.aspx') &gt; -1) {
				// home page
				sb_trackLinks.addEvent(anchors[i], 'click', (function() {
						return function() {
							try {
								sb_trackLinks.track(
									this,
									{
										'prop1': 'home'
									}
								);
							} catch(e) {}
							return true;
						};
					})()
				);
			} else if (anchors[i].href.indexOf('category.aspx') &gt; -1) {
				// category pages
				sb_trackLinks.addEvent(anchors[i], 'click', (function(catId) {
						return function() {
							try {
								sb_trackLinks.track(
									this,
									{
										'prop1': 'category',
										'prop2': catId
									}
								);
							} catch(e) {}
							return true;
						};
					})(queryString['category_id'])
				);
			} else if (anchors[i].href.indexOf('product.aspx') &gt; -1) {
				// product pages
				sb_trackLinks.addEvent(anchors[i], 'click', (function(catId, productId, productName) {
						return function() {
							try {
								sb_trackLinks.track(
									this,
									{
										'prop1': 'product',
										'prop2': catId,
										'prop3': productId,
										'prop4': productName
									}
								);
							} catch(e) {}
							return true;
						};
					})(queryString['category_id'], queryString['product_id'], queryString['product_name'])
				);
			};
		};
	},

	// tracking function
	track: function(obj, propCodes, linkType = 'o') {
		s = s_gi(s_account);
		// reset tracking properties
		s.linkTrackVars = '';
		s.linkTrackEvents = 'None';
		// iterate through each property in propCodes
		for (code in propCodes) {
			s.linkTrackVars += code + ','; // build the CSV list
			s[code] = propCodes[code];     // attach the prop code
		}
		// track it
		s.tl(obj, linkType);
	};

	// non-breaking/non-overriding/cross-browser event attachment
	addEvent: function(obj, evType, fn, useCapture) {
		if (obj.addEventListener) {
			obj.addEventListener(evType, fn, useCapture);
			return true;
		} else if (obj.attachEvent) {
			var r = obj.attachEvent('on' + evType, fn);
			return r;
		} else {
			obj['on' + evType] = fn;
		};
	}
};

// auto-init on load
sb_trackLinks.addEvent(window, 'load', sb_trackLinks.init);
</pre>
<p>Save this code in the s_code.js (or whatever you named it) script for your Omniture-enabled web site and it will start sending click data back to your Omniture account.</p>
<h3>Gotchas</h3>
<p>There are some possible problems that may crop up using this system.</p>
<ul>
<li><strong>Page never stops loading</strong><br />
I have seen pages that simple never stop loading because of poor use of AJAX, slow servers, or broken dependencies. The click tracking events will not be attached to links until the loading icon in the browser stops.</li>
<li><strong>Links added after page load</strong><br />
If there are any links that are created after the page load event is fired, such as AJAX widgets or dynamic links, then they will not exist when this script is run and therefor will not have any tracking events attached to them.</li>
<li><strong>Omniture conflicts</strong><br />
There have been several occasions where I've seen people try to use two (or more) instances of Omniture on the same page. If you have to deal with this then you need to modify the <code>track()</code> method to use your account name.</li>
<li><strong>500ms link delay</strong><br />
The <code>s.tl()</code> function has a half-second delay built into it. This is because the tracking data is sent by requesting a 1 pixel web beacon from the Omniture servers and the script needs to give the browser enough time to send the request. Unfortunately, this also adds an annoying delay to every link that you attach tracking code to.</li>
</ul>
<h3>Advanced tracking (more than just links)</h3>
<p>Omniture link tracking can be used for more than just links. You can use the <code>s.tl()</code> method to send any data at any time for any reason. Do you want to track when someone puts their mouse over an image, or how long they waited before scrolling, or even what text they selected? You can do all of that, and more if you <em>really</em> want to.</p>
<p>If you ask me those fine-grain <em>viewer eyeball focus</em> type of reports are not only invasive but genuinely useless. However, I've been asked to do even worse invasive and pointless tracking before. If you need to you can send any data you want on any DOM event, or even with timers.</p>
<p>The only thing that you must overcome is that the object you pass to <code>s.tl()</code> must be an anchor, or more specifically, <em>something</em> with an href attribute. There is nothing about this in the documentation, but it simply doesn't seem to work unless it has an anchor with an href passed to it. You can get around this by creating an element or simply passing <code>true</code>.</p>
<p>This is, however, the subject of a different article.</p>
<h3>Conclusion</h3>
<p>As you can see it's not <em>that</em> difficult to implement custom link tracking with Omniture and you can get some very powerful reports without that much work. I still cannot give my glowing stamp of approval for Omniture as a reporting solution, for various reasons, but if you are using it then hopefully this tutorial will save you some time and headaches.</p>
<p>If you have any questions or comments please leave them in the form below and I will do my best to answer them. You can also take a look at the <a href="http://blogs.omniture.com/2009/03/12/custom-link-tracking-capturing-user-actions/" target="_blank">Custom Link Tracking</a> post in the Omniture blog. It's a year old, but the author is still responding to questions.</p>
<h3  class="related_post_title">Related articles</h3><ul class="related_post"><li><a href="http://stevenbenner.com/2010/02/addthis-friendly-widget-and-brilliant-viral-tracking-scheme/" title="AddThis: friendly widget and brilliant viral tracking scheme">AddThis: friendly widget and brilliant viral tracking scheme</a></li><li><a href="http://stevenbenner.com/2010/01/external-templates-for-the-dataview-control-microsoft-ajax-library-4-0/" title="External templates for the DataView control: Microsoft Ajax Library 4.0">External templates for the DataView control: Microsoft Ajax Library 4.0</a></li><li><a href="http://stevenbenner.com/2010/03/javascript-regex-trick-parse-a-query-string-into-an-object/" title="JavaScript regex trick: Parse a query string into an object">JavaScript regex trick: Parse a query string into an object</a></li><li><a href="http://stevenbenner.com/2010/02/exslt-set-distinct-in-msxml-for-internet-explorer/" title="EXSLT set:distinct in MSXML for Internet Explorer">EXSLT set:distinct in MSXML for Internet Explorer</a></li><li><a href="http://stevenbenner.com/2010/02/friendly-urls-in-an-asp-net-app/" title="Friendly URLs in an ASP.NET app">Friendly URLs in an ASP.NET app</a></li></ul><img src="http://feeds.feedburner.com/~r/stevenbenner/~4/DClZT54xC_Y" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://stevenbenner.com/2010/03/custom-link-click-tracking-using-omniture/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		<feedburner:origLink>http://stevenbenner.com/2010/03/custom-link-click-tracking-using-omniture/</feedburner:origLink></item>
		<item>
		<title>JavaScript regex trick: Parse a query string into an object</title>
		<link>http://feedproxy.google.com/~r/stevenbenner/~3/W187TXwhFUQ/</link>
		<comments>http://stevenbenner.com/2010/03/javascript-regex-trick-parse-a-query-string-into-an-object/#comments</comments>
		<pubDate>Wed, 03 Mar 2010 18:42:02 +0000</pubDate>
		<dc:creator>Steven Benner</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Tips and Tricks]]></category>

		<guid isPermaLink="false">http://stevenbenner.com/?p=634</guid>
		<description><![CDATA[Have you ever needed to get query string values using JavaScript? This task is usually a painful split, split, split, iterate, indexOf hack that is really slow and terribly ugly to look at. It also tends to pile up lines of code really fast.
Here is a really sweet way to parse the query string into [...]]]></description>
			<content:encoded><![CDATA[<p>Have you ever needed to get query string values using JavaScript? This task is usually a painful split, split, split, iterate, indexOf hack that is really slow and terribly ugly to look at. It also tends to pile up lines of code really fast.</p>
<p>Here is a really sweet way to parse the query string into a JavaScript object with two lines of code using regular expressions to populate an object. I discovered this trick a few years ago and filed it away in my code snippets folder.</p>
<p><span id="more-634"></span></p>
<h3>The Code</h3>
<p>The JavaScript code itself uses the <code>replace()</code> method with a regular expression to target the name/value pairs in the URI string that you are working with. The replace value is actually a function which will be executed for each pair. This little function simply pushes the name/value pair into the <code>queryString</code> object.</p>
<pre class="brush: jscript;">
var queryString = {};
anchor.href.replace(
	new RegExp(&quot;([^?=&amp;]+)(=([^&amp;]*))?&quot;, &quot;g&quot;),
	function($0, $1, $2, $3) { queryString[$1] = $3; }
);
</pre>
<p>Isn&#8217;t it beautiful? In this example <em>anchor</em> is some anchor tag on a page that I have already defined with a <code>document.getElementById</code>. However it works just as well with <code>window.location.href</code>.</p>
<h3>Using it</h3>
<p>Using this simple trick you can access query string parameters by saying <code>queryString['name']</code>. Let&#8217;s say that your URI looks like this:</p>
<pre>http://your.domain/product.aspx?category=4&amp;product_id=2140&amp;query=lcd+tv</pre>
<p>Want that product ID? Simple, <code>queryString['product_id']</code> will return &#8220;2140&#8243;. Nice and simple.</p>
<p>If the query string parameter that you are searching for does not exist then it will return <em>undefined</em> without throwing an error.</p>
<h3>Full example</h3>
<p>Copy and paste this into your Firebug console and run it to see it in action:</p>
<pre class="brush: jscript;">
var uri = 'http://your.domain/product.aspx?category=4&amp;product_id=2140&amp;query=lcd+tv';
var queryString = {};
uri.replace(
	new RegExp(&quot;([^?=&amp;]+)(=([^&amp;]*))?&quot;, &quot;g&quot;),
	function($0, $1, $2, $3) { queryString[$1] = $3; }
);
console.log('Product ID: ' + queryString['product_id']);     // Product ID: 2140
console.log('Product Name: ' + queryString['product_name']); // Product Name: undefined
console.log('Product Category: ' + queryString['category']); // Product Category: 4
</pre>
<h3  class="related_post_title">Related articles</h3><ul class="related_post"><li><a href="http://stevenbenner.com/2010/03/custom-link-click-tracking-using-omniture/" title="Custom link click tracking using Omniture">Custom link click tracking using Omniture</a></li><li><a href="http://stevenbenner.com/2010/02/optional-parameters-and-named-parameters-in-c-4-0/" title="Optional parameters and named parameters in C# 4.0">Optional parameters and named parameters in C# 4.0</a></li><li><a href="http://stevenbenner.com/2010/02/addthis-friendly-widget-and-brilliant-viral-tracking-scheme/" title="AddThis: friendly widget and brilliant viral tracking scheme">AddThis: friendly widget and brilliant viral tracking scheme</a></li><li><a href="http://stevenbenner.com/2010/01/external-templates-for-the-dataview-control-microsoft-ajax-library-4-0/" title="External templates for the DataView control: Microsoft Ajax Library 4.0">External templates for the DataView control: Microsoft Ajax Library 4.0</a></li><li><a href="http://stevenbenner.com/2010/01/must-have-firefox-add-ons-for-web-developers-designers/" title="Must-have Firefox Add-ons for Web Developers &#038; Designers">Must-have Firefox Add-ons for Web Developers &#038; Designers</a></li></ul><img src="http://feeds.feedburner.com/~r/stevenbenner/~4/W187TXwhFUQ" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://stevenbenner.com/2010/03/javascript-regex-trick-parse-a-query-string-into-an-object/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://stevenbenner.com/2010/03/javascript-regex-trick-parse-a-query-string-into-an-object/</feedburner:origLink></item>
		<item>
		<title>Optional parameters and named parameters in C# 4.0</title>
		<link>http://feedproxy.google.com/~r/stevenbenner/~3/qohG9skUQeM/</link>
		<comments>http://stevenbenner.com/2010/02/optional-parameters-and-named-parameters-in-c-4-0/#comments</comments>
		<pubDate>Wed, 24 Feb 2010 22:12:19 +0000</pubDate>
		<dc:creator>Steven Benner</dc:creator>
				<category><![CDATA[Desktop Development]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Tips and Tricks]]></category>

		<guid isPermaLink="false">http://stevenbenner.com/?p=619</guid>
		<description><![CDATA[One little quirk of C#, which has pretty much become a defining characteristic of the language, is the default parameters system, or lack thereof.
If you want to have a default set of arguments for a function or constructor then you have to create several overloaded versions of the function for each possible set of parameter [...]]]></description>
			<content:encoded><![CDATA[<p>One little quirk of <em>C#</em>, which has pretty much become a defining characteristic of the language, is the default parameters system, or lack thereof.</p>
<p>If you want to have a default set of arguments for a function or constructor then you have to create several overloaded versions of the function for each possible set of parameter that you want to be able to accept.</p>
<p>This will no longer be the case in C# 4.0 with the addition of <em>optional parameters</em> and <em>named parameters</em> features. This is one little change I am really looking forward to in a big way.</p>
<p><span id="more-619"></span></p>
<h3>Traditional overloads</h3>
<p>The use of good old fashioned overload functions isn&#8217;t going to be completely replaced by the new optional parameters system, but you will almost never need them again.</p>
<p>Here is an example of how we currently do default parameters:</p>
<pre class="brush: csharp;">
static string test(int foo)
{
	return test(foo, &quot;The value is {0}&quot;, false);
}

static string test(bool bar)
{
	return test(1, &quot;The value is {0}&quot;, bar);
}

static string test(int foo, string blah)
{
	return test(foo, blah, false);
}

static string test(string blah, bool bar)
{
	return test(1, blah, bar);
}

static string test(int foo, string blah, bool bar)
{
	if (bar)
	{
		return String.Format(blah, foo);
	}
	else
	{
		return String.Format(blah, foo + 5);
	}
}
</pre>
<p>This is a lot of code for very little work. In fact, this doesn&#8217;t even cover all of the options, but I shortened it for this article. However this is how us C# programmers have been doing it forever.</p>
<h3>C# 4.0 default parameters</h3>
<p>With the new default parameters feature in C# 4.0 you can set the default value for any argument. The syntax for this is identical to JavaScript and couldn&#8217;t be simpler, <code>arg = default</code>.</p>
<p>Using this you can refactor the above functions into one short and readable function:</p>
<pre class="brush: csharp;">
static string test(int foo = 1, string blah = &quot;The value is {0}&quot;, bool bar = false)
{
	if (bar)
	{
		return String.Format(blah, foo);
	}
	else
	{
		return String.Format(blah, foo + 5);
	}
}
</pre>
<p>I absolutely adore this syntax. Like I said it is identical to JavaScript and is so much leaner and meaner. Improved readability always means improved maintainability.</p>
<h3>C# 4.0 named parameters</h3>
<p>This is all well and great, but the default parameters feature alone cannot replace the overload system. What if you want to skip an argument in the parameters list?</p>
<p>Named parameters to the rescue! This feature allows to you specify names and values when calling a function. Again, the syntax couldn&#8217;t be simpler, <code>name: value</code>.</p>
<p>This means that we can call that function with any parameters we want, in any order. For example:</p>
<pre class="brush: csharp;">
test(foo: 3, bar: true);
</pre>
<p>Parameter order does not matter when using this syntax, the parameter name is all the information needed to attach the value. You can even mix named parameters with regular arguments.</p>
<pre class="brush: csharp;">
test(15, bar: true, blah: &quot;{0} is the value&quot;);
</pre>
<p>However, when mixing named parameters with regular parameters the named parameters must come after all of the regular parameters, and the regular parameters must be in the correct order.</p>
<h3>Conclusion</h3>
<p>This tiny, almost insignificant change will probably deprecate <em>hundreds</em> of lines of code in your average C# project. It will make functions and constructors much more readable and will make whole projects much more maintainable. It will reduce unneeded code and generally make your cs files shorter.</p>
<p>This single, tiny change is my single favorite upgrade to C# in 4.0.</p>
<h3  class="related_post_title">Related articles</h3><ul class="related_post"><li><a href="http://stevenbenner.com/2010/01/reporting-of-unhandled-exceptions-in-your-distributable-net-application/" title="Reporting of unhandled exceptions in your distributable .NET desktop application">Reporting of unhandled exceptions in your distributable .NET desktop application</a></li><li><a href="http://stevenbenner.com/2010/03/javascript-regex-trick-parse-a-query-string-into-an-object/" title="JavaScript regex trick: Parse a query string into an object">JavaScript regex trick: Parse a query string into an object</a></li><li><a href="http://stevenbenner.com/2010/02/friendly-urls-in-an-asp-net-app/" title="Friendly URLs in an ASP.NET app">Friendly URLs in an ASP.NET app</a></li><li><a href="http://stevenbenner.com/2010/01/external-templates-for-the-dataview-control-microsoft-ajax-library-4-0/" title="External templates for the DataView control: Microsoft Ajax Library 4.0">External templates for the DataView control: Microsoft Ajax Library 4.0</a></li><li><a href="http://stevenbenner.com/2009/09/firewall-script-tool-for-iptables/" title="Firewall script tool for iptables">Firewall script tool for iptables</a></li></ul><img src="http://feeds.feedburner.com/~r/stevenbenner/~4/qohG9skUQeM" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://stevenbenner.com/2010/02/optional-parameters-and-named-parameters-in-c-4-0/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://stevenbenner.com/2010/02/optional-parameters-and-named-parameters-in-c-4-0/</feedburner:origLink></item>
		<item>
		<title>Reset cascading style sheets are bad practice</title>
		<link>http://feedproxy.google.com/~r/stevenbenner/~3/cbAfxMl9CF8/</link>
		<comments>http://stevenbenner.com/2010/02/reset-cascading-style-sheets-are-bad-practice/#comments</comments>
		<pubDate>Sat, 20 Feb 2010 23:29:31 +0000</pubDate>
		<dc:creator>Steven Benner</dc:creator>
				<category><![CDATA[Web Design]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Accessibility]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[Rants]]></category>

		<guid isPermaLink="false">http://stevenbenner.com/?p=582</guid>
		<description><![CDATA[We&#8217;ve all seen them, most of us have used them, almost every CSS framework is built on one and you instantly know one is present when you open Firebug and see a hundred levels of CSS inheritance declarations. Reset cascading style sheets (a.k.a. reset.css).
The idea of the reset style sheet is to globally target every [...]]]></description>
			<content:encoded><![CDATA[<p>We&#8217;ve all seen them, most of us have used them, almost every CSS framework is built on one and you instantly know one is present when you open <em>Firebug</em> and see a hundred levels of CSS inheritance declarations. <em>Reset cascading style sheets (a.k.a. reset.css)</em>.</p>
<p>The idea of the reset style sheet is to globally target every markup element and set all of its possible attributes to zero, default or inherit. This gives the developer a &#8220;clean slate&#8221; with no browser default styles. Many say that this is the first thing you should add when you start building a web page, but I firmly disagree.</p>
<p>I&#8217;m sure that I am one of the small minority of web developers who&#8217;s saying this, but reset style sheets are bad practice, and it&#8217;s time to stop using them.</p>
<p><span id="more-582"></span></p>
<h3>Why are reset style sheets so popular?</h3>
<p>The trend of using a reset CSS wasn&#8217;t always so prevalent, not long ago they were practically unheard of. But in recent years the reset trend has absolutely exploded. Now every CSS framework and many large web sites use them as the base for everything they put on the web.</p>
<p>This happened because designers and developers were tired of dealing with the quirks of the different browsers. Just about every browser has a default set of styles that it applies to pages. 99% of these quirks could be remedied by globally setting margin and padding to zero.</p>
<p>However, this wasn&#8217;t enough for some. Designers working with forms and other browser/OS specific elements were tired of having to specifically reset every form element in CSS every time they started a new web site. So someone decided to make a single CSS file to specifically target and reset the CSS on every HTML element. Now all they have to do is link the reset CSS and never worry about it again.</p>
<p>This is quite a time saver, especially when working with forms. The technique gained popularity and eventually became a regular part of web development for many designers.</p>
<h3>Why is using a reset CSS bad?</h3>
<p>I am not saying that the technique is bad, I&#8217;m saying it&#8217;s <em>bad practice</em>, for numerous reasons.</p>
<h4>Accessibility</h4>
<p>Many reset style sheets set <em>outline</em> to zero. Outline is an important feature for usability and accessibility. It is used for keyboard navigation.</p>
<p>The outline property sets an overlay border on an active/selected element that is actionable and/or has a tabindex. This gives users a visual cue as to where there active cursor is (note: tab cursor, not mouse cusor). Without the outline users who cannot use a mouse or who choose to use the tab function to navigate pages can&#8217;t see where their cursor is. This effectively makes the page unusable for some disabled people, and less-usable for tabbing power-users.</p>
<h4>Unnecessarily large style sheets</h4>
<p>There are certain display standards and conventions that all browsers agree on by default. Headings are bold, links are underlined, lists have bullets, etcetera. When using a reset style sheet you have to manually set every single display property for every single element.</p>
<p>Why would you want to repeat the same CSS that every browser gives you by default? It only clutters your style sheet, increases download footprint and slows the browser rendering engine.</p>
<h4>Inefficient inheritance and slow rendering</h4>
<p>Styles are not simple text properties that are built into your operating system, they are rendering statements that are passed to a large and complicated rendering engine in the browser. This rendering engine must read all of the CSS and build the display rules for the entire page before it will even show you the content.</p>
<p>The browser default styles that reset style sheets are trying to kill will be the foundation of the inheritance tree regardless of what styles you put on top of it. So the rendering engine must add your rules on top of the reset rules, on top the default display rules. As a CSS coder it is your job to optimize your CSS to make this as fast and easy on the rendering engine as possible.</p>
<p>For example: you want h1 tags to be bold.</p>
<ul>
<li>Without a reset CSS you do not have to add any statements to accomplish this effect, it is part of the browser default. So the rendering engine only has to process the already optimized and compiled default to give you this effect.
<pre>h1 { font-weight: bold; } /* Browser Default */</pre>
</li>
<li>With a reset CSS you will be sending not only the h1 reset, but probably the div, body and html reset along with your override, <code>h1{font-weight:bold;}</code>. So the rendering engine sees an inheritance tree that looks something like this:
<pre>html {
	<span style="text-decoration: line-through;">font-weight: normal;</span>
	body {
		<span style="text-decoration: line-through;">font-weight: normal;</span>
		h1 {
			font-weight: bold;
		}
	}
}
h1 { <span style="text-decoration: line-through;">font-weight: bold;</span> }  /* Browser Default */</pre>
</li>
</ul>
<p>You can actually see a view of this when you are looking at the CSS information in Firebug. This is a whole lot of extra work for the browser to have to work through to create your styles. Even if you didn&#8217;t want the default bold h1 then it would still be far more efficient to put <code>h1{font-weight:normal;}</code> in your CSS without the reset. After all, the default target is specifically overridden and the engine does not have to process the extra inheritance information.</p>
<div>
<dl id="attachment_604">
<div id="attachment_604" class="wp-caption alignnone" style="width: 310px"><a href="http://stevenbenner.com/wp-content/uploads/firebug_reset_css.png" target="_blank"><img class="size-medium wp-image-604 " title="Firebug Reset CSS" src="http://stevenbenner.com/wp-content/uploads/firebug_reset_css-300x187.png" alt="Firebug Reset CSS" width="300" height="187" /></a><p class="wp-caption-text">Worst-case scenario with reset CSS.</p></div>
</dl>
</div>
<p>The image above is an excellent example of what I&#8217;m talking about.  It&#8217;s a worst-case situation where bad markup, bad styles and a reset CSS  have all come together to produce a monstrosity. This is a real site  that I&#8217;ve had to work on. Notice the scroll bar on the style list,  because of the outrageous inheritance tree this one element has 30+  self-overridden reset selectors applied to it.</p>
<p>Granted, this example is an extreme case, however the effect is the same even when the site has excellent markup and well designed CSS.</p>
<h4>Maintainability and debugging</h4>
<p>I am a dedicated Firebug user. I love the tool and I simply cannot live without it for CSS development. But I hate working on pages that have reset styles because they clutter up the interface and I have to scroll through countless overridden selectors to find the one that is applied in each case.</p>
<h4>Transmission footprint</h4>
<p>A reset style sheet is yet another CSS file and a couple hundred more bytes that the browser has to download. The single most important aspect of web page and server optimization is reducing download size and reducing the number of connections needed to send a single page.</p>
<p>Page optimization is why we use image sprites and minified/combined include files now. Every connection and byte of data that you can remove will make your site faster and scale better. A large majority of the time spent downloading a web page is spent on the latency and over-the-wire transmit time for page dependencies and resources.</p>
<p>Take this very seriously, development on a high traffic web sites is all about shaving every millisecond and every byte you can because you will be able to support that many more simultaneous users.</p>
<h3>That was a very long-winded rant, so how do you deal with it?</h3>
<p>Simple, the way we&#8217;ve been doing it since we stopped using tables and 1px separator gifs.</p>
<pre class="brush: css;">
* {
	margin: 0;
	padding: 0;
	/* possibly border: 0 */
}
</pre>
<p>This single statement at the top of your style sheet will give you almost the same effect as a reset style sheet with almost none of the drawbacks or performance degradation.</p>
<p>Asterisk is the global selector, it will target everything, regardless of type, standards or validity. It is also an efficient rule (when used on its own). Most rendering engines will understand that an asterisk select means <em>everything</em>, regardless of inheritance. So it doesn&#8217;t process it as an inheritance, it is simply attached to every element.</p>
<p>It is worth noting that asterisk when joined with another selector (e.g. #navigation *) is actually an inefficient selector because it does have to process an inheritance tree.</p>
<h3>Conclusion</h3>
<p>I understand that there are times when a reset CSS can be an asset, such as rapid prototyping and very extreme/artsy designs where you would have to override everything anyway. However for the sake of performance optimization and general best practices you should avoid reset style sheets.</p>
<h3  class="related_post_title">Related articles</h3><ul class="related_post"><li><a href="http://stevenbenner.com/2010/01/over-use-of-javascript-and-ajax-bad-practice-and-broken-pages/" title="Excessive JavaScript and AJAX: bad practice &#038; broken pages">Excessive JavaScript and AJAX: bad practice &#038; broken pages</a></li><li><a href="http://stevenbenner.com/2010/02/addthis-friendly-widget-and-brilliant-viral-tracking-scheme/" title="AddThis: friendly widget and brilliant viral tracking scheme">AddThis: friendly widget and brilliant viral tracking scheme</a></li><li><a href="http://stevenbenner.com/2010/01/must-have-firefox-add-ons-for-web-developers-designers/" title="Must-have Firefox Add-ons for Web Developers &#038; Designers">Must-have Firefox Add-ons for Web Developers &#038; Designers</a></li><li><a href="http://stevenbenner.com/2010/01/graceful-degradation-theory-ie-and-css3/" title="Graceful degradation theory, IE  and CSS3">Graceful degradation theory, IE  and CSS3</a></li><li><a href="http://stevenbenner.com/2009/09/php-needs-an-ide-and-a-membership-provider/" title="PHP needs an IDE and a membership provider">PHP needs an IDE and a membership provider</a></li></ul><img src="http://feeds.feedburner.com/~r/stevenbenner/~4/cbAfxMl9CF8" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://stevenbenner.com/2010/02/reset-cascading-style-sheets-are-bad-practice/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		<feedburner:origLink>http://stevenbenner.com/2010/02/reset-cascading-style-sheets-are-bad-practice/</feedburner:origLink></item>
		<item>
		<title>EXSLT set:distinct in MSXML for Internet Explorer</title>
		<link>http://feedproxy.google.com/~r/stevenbenner/~3/NcYD0ZIpyCI/</link>
		<comments>http://stevenbenner.com/2010/02/exslt-set-distinct-in-msxml-for-internet-explorer/#comments</comments>
		<pubDate>Wed, 17 Feb 2010 00:01:21 +0000</pubDate>
		<dc:creator>Steven Benner</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[How-To]]></category>
		<category><![CDATA[Internet Explorer]]></category>
		<category><![CDATA[XML]]></category>
		<category><![CDATA[XSLT]]></category>

		<guid isPermaLink="false">http://stevenbenner.com/?p=526</guid>
		<description><![CDATA[I&#8217;ve recently talked about how XSLT is pretty good as a presentation layer for web applications. XSLT is a powerful template language by itself but it is woefully missing several functions and interfaces which you will eventually find absolutely necessary for some purposes. EXSLT is the solution. EXSLT is a collection of extensions for the [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve recently talked about how <a href="http://stevenbenner.com/2010/02/publishing-xml-to-the-web-with-xslt-a-replacement-for-the-presentation-layer/">XSLT is pretty good as a presentation layer for web applications</a>. XSLT is a powerful template language by itself but it is woefully missing several functions and interfaces which you will eventually find absolutely necessary for some purposes. <em><a href="http://www.exslt.org/" target="_blank">EXSLT</a></em> is the solution. EXSLT is a collection of extensions for the XSLT. It has numerous functions and features that XSLT is missing.</p>
<p>EXSLT functions are widely used and extensively tested. Not only is it available for every server-side XSLT engine, but it is also available in Mozilla Firefox, Safari, Chrome and Opera.</p>
<p>Unfortunately, as usual <em>Internet Explorer</em> throws a wrench in the whole works. Microsoft in their infinite wisdom decided not to implement the EXSLT functions and specs in their browser. Instead they implemented their own <em>MSXSL</em> extensions, which are frankly terrible. It doesn&#8217;t have anywhere near as much functionality as EXSLT and will never work with anything other than Internet Explorer.</p>
<p>The most common (and difficult to replicate) EXSLT function that I use is <a href="http://www.exslt.org/set/functions/distinct/" target="_blank"><em>set:distinct</em></a>. It is an extremely powerful function that lets you grab a distinct list of nodes, attributes, or values without impacting your current context in the style sheet.</p>
<p>This function is not available in any form in MSXML, so I decided to build it.</p>
<p><span id="more-526"></span></p>
<h3>Building a custom function is MSXML</h3>
<p>The MSXSL spec in MSXML does have one powerful feature that can help us rectify some of these problems, <a href="http://msdn.microsoft.com/en-us/library/ms256042.aspx" target="_blank"><em>msxsl:script</em></a>. This feature allows you to build script extensions inside your XSLT style sheet. Of course it only exists in the Microsoft spec, so really it&#8217;s only obvious use is spending weeks hand-crafting the standard extension functions that Microsoft decided that they didn&#8217;t need to support.</p>
<p>In this case I built a <em>JScript</em> hack to implement the set:distinct function. It isn&#8217;t nearly as powerful or flexible as the real EXSLT function, but it will grab a distinct list of nodes.</p>
<p>We have to use native MSXML <em>XSLDOM</em> scripting to do all of this and we have to return the results in the form of a NodeList so the the XSLT processor can process the output.</p>
<p>This is the script that I came up with:</p>
<pre class="brush: jscript;">
this['distinct'] =  function (values) {
	// create an MSXML DOMDocument
	var xmlDoc = new ActiveXObject('Msxml2.DOMDocument.3.0');
	xmlDoc.async = false;
	xmlDoc.loadXML('&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;\n&lt;distinctRoot&gt;&lt;/distinctRoot&gt;');

	var dedupe = [];
	// iteratre through the nodes passed to this function
	var node = values.nextNode();
	while (node) {
		var isDuplicate = false;
		// does this node already exist in our dedupe array?
		for (var i=0; i&lt;dedupe.length; i++) {
			if (dedupe[i] == node.xml) {
				isDuplicate = true;
				break;
			}
		}
		if (!isDuplicate) {
			dedupe.push(node.xml);
			// append this node to the DOMDocument
			xmlDoc.documentElement.appendChild(node.cloneNode(true));
		}
		node = values.nextNode();
	}
	var tagName = xmlDoc.documentElement.childNodes[0].nodeName;
	return xmlDoc.documentElement.getElementsByTagName(tagName);
}
</pre>
<p>We will run this script inside a <code>&lt;msxsl:script language="JScript" implements-prefix="set"&gt;</code> statement. Notice the <code>implements-prefix</code> attribute, this tells the MSXML engine that this block of code will define the &#8220;set&#8221; namespace. In the code we attach a function to this['distinct'], so when we call &#8220;set:distinct&#8221; in the XSLT it will call this function.</p>
<p>This script is not perfect, and it is no where near a full implementation of the EXSLT set:distinct function. Here are the major limitations:</p>
<ul>
<li><strong>Only works on NodeLists.</strong> It will not be able to return a distinct list of attributes, values, text nodes, etc. There might be a way to do those in JScript, but I couldn&#8217;t find one.</li>
<li><strong>Only one kind of node.</strong> If you want a single distinct list of &lt;authors&gt;  it will work, however if you want a single distinct list of &lt;authors&gt;  and &lt;writers&gt;  it will <strong>not</strong> work.</li>
</ul>
<p><strong>Important note:</strong> Do not use msxsl:script in server-side XSLTs in .NET. There have been <a href="http://www.tkachenko.com/blog/archives/000620.html" target="_blank">numerous problems reported in .NET 2.0</a> that will case fatal memory leaks. It works fine in web browsers, just don&#8217;t use this tag in the XslTransform or XslCompiledTransform classes.</p>
<h3>Implementing the set:distinct hack</h3>
<p>Getting this code to work is actually quite simple once we&#8217;ve built the function. All you have to do is paste the code into the XSLT and tell the style sheet to exclude the <code>set</code> and <code>msxsl</code> prefixes. But I&#8217;ll provide a demonstration so you can see a real world working example.</p>
<p>First off: a sample XML file for us to show the use and purpose of the set:distinct function.</p>
<pre class="brush: xml;">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;?xml-stylesheet type=&quot;text/xsl&quot; href=&quot;layout.xsl&quot;?&gt;
&lt;searchresults&gt;
	&lt;book id=&quot;21&quot;&gt;
		&lt;title&gt;Advanced XSLT&lt;/title&gt;
		&lt;author id=&quot;2&quot;&gt;Steven Benner&lt;/author&gt;
		&lt;releasedate&gt;February 2010&lt;/releasedate&gt;
	&lt;/book&gt;
	&lt;book id=&quot;142&quot;&gt;
		&lt;title&gt;XSLT&lt;/title&gt;
		&lt;author id=&quot;5&quot;&gt;Doug Tidwell&lt;/author&gt;
		&lt;releasedate&gt;August 2001&lt;/releasedate&gt;
	&lt;/book&gt;
	&lt;book id=&quot;15&quot;&gt;
		&lt;title&gt;XSLT and XPath On The Edge (Professional Mindware)&lt;/title&gt;
		&lt;author id=&quot;3&quot;&gt;Jeni Tennison&lt;/author&gt;
		&lt;releasedate&gt;July 2005&lt;/releasedate&gt;
	&lt;/book&gt;
	&lt;book id=&quot;92&quot;&gt;
		&lt;title&gt;Blood, sweat and tears. The XSLT experience.&lt;/title&gt;
		&lt;author id=&quot;2&quot;&gt;Steven Benner&lt;/author&gt;
		&lt;releasedate&gt;January 2010&lt;/releasedate&gt;
	&lt;/book&gt;
	&lt;book id=&quot;103&quot;&gt;
		&lt;title&gt;Beginning XSLT 2.0: From Novice to Professional (Beginning: from Novice to Professional)&lt;/title&gt;
		&lt;author id=&quot;3&quot;&gt;Jeni Tennison&lt;/author&gt;
		&lt;releasedate&gt;October 2001&lt;/releasedate&gt;
	&lt;/book&gt;
&lt;/searchresults&gt;
</pre>
<p>I want to build a list of books grouped by author. Each author should have his or her section with their books displayed under their name.</p>
<p>Now we&#8217;ll create the full XSLT style sheet to implement this functionality.</p>
<pre class="brush: xml;">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;xsl:stylesheet version=&quot;1.0&quot;
	xmlns:xsl=&quot;http://www.w3.org/1999/XSL/Transform&quot;
	xmlns:set=&quot;http://exslt.org/sets&quot;
	xmlns:msxsl=&quot;urn:schemas-microsoft-com:xslt&quot;
	exclude-result-prefixes=&quot;set msxsl&quot;&gt;

	&lt;!-- the msxsl script for our custom set:distinct --&gt;
	&lt;msxsl:script language=&quot;JScript&quot; implements-prefix=&quot;set&quot;&gt;
	&lt;![CDATA[
		this['distinct'] =  function (values) {
			// create an MSXML DOMDocument
			var xmlDoc = new ActiveXObject('Msxml2.DOMDocument.3.0');
			xmlDoc.async = false;
			xmlDoc.loadXML('&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;\n&lt;distinctRoot&gt;&lt;/distinctRoot&gt;');

			var dedupe = [];
			// iteratre through the nodes passed to this function
			var node = values.nextNode();
			while (node) {
				var isDuplicate = false;
				// does this node already exist in our dedupe array?
				for (var i=0; i&lt;dedupe.length; i++) {
					if (dedupe[i] == node.xml) {
						isDuplicate = true;
						break;
					}
				}
				if (!isDuplicate) {
					dedupe.push(node.xml);
					// append this node to the DOMDocument
					xmlDoc.documentElement.appendChild(node.cloneNode(true));
				}
				node = values.nextNode();
			}
			var tagName = xmlDoc.documentElement.childNodes[0].nodeName;
			return xmlDoc.documentElement.getElementsByTagName(tagName);
		}
	]]&gt;
	&lt;/msxsl:script&gt;

	&lt;!-- set output mode as html --&gt;
	&lt;xsl:output method=&quot;html&quot;/&gt;

	&lt;!-- this key is used to sort the results, if you don't need
		to sort then you don't need a key --&gt;
	&lt;xsl:key name=&quot;authorIds&quot; match=&quot;author&quot; use=&quot;@id&quot; /&gt;

	&lt;!-- store the books in a variable because we loose
		context in the foreach --&gt;
	&lt;xsl:variable name=&quot;books&quot; select=&quot;/searchresults/book&quot; /&gt;

	&lt;!-- template that matches the root node --&gt;
	&lt;xsl:template match=&quot;searchresults&quot;&gt;
		&lt;html&gt;
		&lt;head&gt;
			&lt;title&gt;Demo&lt;/title&gt;
		&lt;/head&gt;
		&lt;body&gt;
			&lt;h1&gt;Results by author&lt;/h1&gt;

			&lt;!-- for each unique author id --&gt;
			&lt;xsl:for-each select=&quot;set:distinct(book/author)&quot;&gt;

				&lt;!-- sort by author name --&gt;
				&lt;xsl:sort select=&quot;key('authorIds', @id)&quot; order=&quot;ascending&quot; /&gt;

				&lt;!-- store the current id in a variable so we can use
					it for selects --&gt;
				&lt;xsl:variable name=&quot;authorId&quot;  select=&quot;@id&quot; /&gt;

				&lt;h2&gt;&lt;xsl:value-of select=&quot;key('authorIds', @id)&quot; /&gt;&lt;/h2&gt;
				&lt;dl&gt;
					&lt;!-- run the book template on every book
						with this author --&gt;
					&lt;xsl:apply-templates select=&quot;$books[author/@id=$authorId]&quot;&gt;
						&lt;!-- sort by book name --&gt;
						&lt;xsl:sort select=&quot;title&quot; order=&quot;ascending&quot; /&gt;
					&lt;/xsl:apply-templates&gt;
				&lt;/dl&gt;

			&lt;/xsl:for-each&gt;

		&lt;/body&gt;
		&lt;/html&gt;
	&lt;/xsl:template&gt;

	&lt;!-- template that matches books --&gt;
	&lt;xsl:template match=&quot;book&quot;&gt;
		&lt;dt&gt;&lt;xsl:value-of select=&quot;title&quot; /&gt;&lt;/dt&gt;
		&lt;dd&gt;
			&lt;xsl:value-of select=&quot;author&quot; /&gt; / &lt;xsl:value-of select=&quot;releasedate&quot; /&gt;
		&lt;/dd&gt;
	&lt;/xsl:template&gt;

&lt;/xsl:stylesheet&gt;
</pre>
<p>Note the <code>exclude-result-prefixes</code> statement. This tells the XSLT engine that it should not render anything with these namespaces. So Firefox, Safari, Chrome, etc. will completely ignore the msxsl blocks.</p>
<p>This XML and XSL pair works in all modern browsers including the not-so-modern IE6.</p>
<p>Check out the <a href="http://stevenbenner.com/misc/xslt_demo2/index.xml" target="_blank">working demo</a> for a proof of concept.</p>
<h3>Conclusion</h3>
<p>As usual, Internet Explorer sucks. Microsoft&#8217;s unwillingness to adopt the same standards that every other browser lives by is hurting the internet. However, it is possible to work around some of the missing functionality. It&#8217;s just really painful and requires you to learn Microsoft specific technologies and language implementations that will never see the light of day for 60% of your users.</p>
<p>This particular solution is completely cross-browser compatible. Every EXSLT enabled browser will ignore the msxsl namespace and use it&#8217;s built-in set:distinct function. Internet Explorer will evaluate the script and run our custom set:distinct function.</p>
<h3  class="related_post_title">Related articles</h3><ul class="related_post"><li><a href="http://stevenbenner.com/2010/02/publishing-xml-to-the-web-with-xslt-a-replacement-for-the-presentation-layer/" title="Publishing XML to the web with XSLT: a replacement for the presentation layer">Publishing XML to the web with XSLT: a replacement for the presentation layer</a></li><li><a href="http://stevenbenner.com/2010/03/custom-link-click-tracking-using-omniture/" title="Custom link click tracking using Omniture">Custom link click tracking using Omniture</a></li><li><a href="http://stevenbenner.com/2010/02/friendly-urls-in-an-asp-net-app/" title="Friendly URLs in an ASP.NET app">Friendly URLs in an ASP.NET app</a></li><li><a href="http://stevenbenner.com/2010/01/external-templates-for-the-dataview-control-microsoft-ajax-library-4-0/" title="External templates for the DataView control: Microsoft Ajax Library 4.0">External templates for the DataView control: Microsoft Ajax Library 4.0</a></li><li><a href="http://stevenbenner.com/2010/01/reporting-of-unhandled-exceptions-in-your-distributable-net-application/" title="Reporting of unhandled exceptions in your distributable .NET desktop application">Reporting of unhandled exceptions in your distributable .NET desktop application</a></li></ul><img src="http://feeds.feedburner.com/~r/stevenbenner/~4/NcYD0ZIpyCI" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://stevenbenner.com/2010/02/exslt-set-distinct-in-msxml-for-internet-explorer/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://stevenbenner.com/2010/02/exslt-set-distinct-in-msxml-for-internet-explorer/</feedburner:origLink></item>
		<item>
		<title>AddThis: friendly widget and brilliant viral tracking scheme</title>
		<link>http://feedproxy.google.com/~r/stevenbenner/~3/T_xQ6rH0lXE/</link>
		<comments>http://stevenbenner.com/2010/02/addthis-friendly-widget-and-brilliant-viral-tracking-scheme/#comments</comments>
		<pubDate>Sun, 14 Feb 2010 08:15:15 +0000</pubDate>
		<dc:creator>Steven Benner</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Analytics]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Privacy]]></category>
		<category><![CDATA[Rants]]></category>

		<guid isPermaLink="false">http://stevenbenner.com/?p=511</guid>
		<description><![CDATA[I was once a huge fan of the AddThis social bookmarking widget, it&#8217;s a simple device for adding the social network buttons that everyone expects to see on every page on the internet (now-a-days). It has excellent browser support and is very simple to implement, modify and configure. As far as social bookmarking widgets go, [...]]]></description>
			<content:encoded><![CDATA[<p>I was once a huge fan of the <em>AddThis</em> social bookmarking widget, it&#8217;s a simple device for adding the social network buttons that everyone expects to see on every page on the internet (now-a-days). It has excellent browser support and is very simple to implement, modify and configure. As far as social bookmarking widgets go, it is probably the best.</p>
<p>However, in the latter half of 2008 <a href="http://www.reuters.com/article/idUSTRE48T1Q820080930" target="_blank">AddThis was acquired by Clearspring</a>, an online media company. They were able to see the value in having a glorified tracking bug on hundreds of thousands of sites across the internet. It&#8217;s grown to such an extent that <a href="http://www.readwriteweb.com/archives/clearspring_now_sees_what_half_of_the_internet_is.php" target="_blank">Clearspring now sees what half of the internet is doing</a>.</p>
<p>I&#8217;m no tinfoil hat paranoid, but I was a bit surprised at the scale and power of this system. Personally I congratulate them on their wild success. It was a brilliant idea and has become an internet phenomenon. However, I have no interest in giving Clearspring information on my visitors (for free).</p>
<p>So lets reduce it&#8217;s tracking ability with a couple lines of JavaScript.</p>
<p><span id="more-511"></span></p>
<h3>Neutering the AddThis widget (a little)</h3>
<p>You can greatly reduce their tracking abilities by adding two simple configuration options to the AddThis settings.</p>
<pre class="brush: jscript;">
var addthis_config = {
	data_use_cookies: false,
	data_use_flash: false
}
</pre>
<p>The first statement, <code>data_use_cookies</code> is fairly obvious, it turns off the AddThis cookies. However many web users who are concerned about their privacy have already taken to limiting or disabling cookies altogether. There is however an ingenious workaround that few people even know exists, the <a href="http://en.wikipedia.org/wiki/Local_Shared_Object" target="_blank"><em>Local Shared Object</em></a> (LSO) feature in <em>Flash</em>.</p>
<h3>Local Shared Objects</h3>
<p>LSOs are basically cookies, but their bigger and better in every way. Flash LSOs do not expire and can hold much more data. Most importantly, they are almost never deleted by users. The only way to control them is through the <a href="http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager03.html" target="_blank">Global Storage Settings</a> control via Adobe&#8217;s Flash configuration on their web site, or with the <a href="https://addons.mozilla.org/en-US/firefox/addon/6623" target="_blank">BetterPrivacy</a> Firefox add on.</p>
<p>If you&#8217;re in the business of analytics and tracking, LSOs are where it&#8217;s at. This is why we are setting <code>data_use_flash</code> to false. The widget itself has no reason to use flash, the only reason it is in there is because Clearspring wants to use LSOs to augment their tracking.</p>
<p>If you&#8217;re a Firefox user you can pick up the BetterPrivacy add on and get in-browser control of your LSOs. Like I said, I&#8217;m not a paranoid, but I do believe in protecting online privacy.</p>
<h3>Does this really change anything?</h3>
<p>No, not really. I don&#8217;t know the internals of their analytics system, but if they have any know-how whatsoever they can grab 95% of their analytics information from the tracking data provided by the widget JavaScript and logs. Removing the cookie tracking does however make it more difficult for their cross-domain tracking system to aggregate and collate user interests.</p>
<h3>Conclusion</h3>
<p>If you are concerned about your privacy online them only use <a href="http://www.mozilla.com/firefox/" target="_blank">Mozilla Firefox</a> and pick up the <a href="https://addons.mozilla.org/en-US/firefox/addon/6623" target="_blank">BetterPrivacy</a> and <a href="https://addons.mozilla.org/en-US/firefox/addon/4703" target="_blank">CookieMoster</a> add-on&#8217;s (you need both). They will stop the simple cookie/LSO based tracking systems.</p>
<p>If you are concerned about the privacy of the visitors on your web site then do not use AddThis bookmarking widget. But if you really can&#8217;t come up with an alternative at least be sure to add the JavaScript code I posted above with your AddThis widget. <a href="http://sharethis.com/" target="_blank">ShareThis</a> is an alternative, but they are hoping to cash in the same way.</p>
<p>In the end, you and your site&#8217;s visitors have little to no privacy when it comes to browsing habits. Google knows that you&#8217;re looking at my site right now. This isn&#8217;t something to be afraid of, but be aware that this is the way it works. As a web site administrator the best you can do is to not use content or tools provided by metrics or advertising companies. If you can you should replace AddThis with some simple icons and links, and if you have the resources, replace Google Analytics with <a href="http://piwik.org/" target="_blank">Piwik</a>. But that is too far even for me.</p>
<h3  class="related_post_title">Related articles</h3><ul class="related_post"><li><a href="http://stevenbenner.com/2010/03/custom-link-click-tracking-using-omniture/" title="Custom link click tracking using Omniture">Custom link click tracking using Omniture</a></li><li><a href="http://stevenbenner.com/2010/01/over-use-of-javascript-and-ajax-bad-practice-and-broken-pages/" title="Excessive JavaScript and AJAX: bad practice &#038; broken pages">Excessive JavaScript and AJAX: bad practice &#038; broken pages</a></li><li><a href="http://stevenbenner.com/2010/03/javascript-regex-trick-parse-a-query-string-into-an-object/" title="JavaScript regex trick: Parse a query string into an object">JavaScript regex trick: Parse a query string into an object</a></li><li><a href="http://stevenbenner.com/2010/02/reset-cascading-style-sheets-are-bad-practice/" title="Reset cascading style sheets are bad practice">Reset cascading style sheets are bad practice</a></li><li><a href="http://stevenbenner.com/2010/01/external-templates-for-the-dataview-control-microsoft-ajax-library-4-0/" title="External templates for the DataView control: Microsoft Ajax Library 4.0">External templates for the DataView control: Microsoft Ajax Library 4.0</a></li></ul><img src="http://feeds.feedburner.com/~r/stevenbenner/~4/T_xQ6rH0lXE" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://stevenbenner.com/2010/02/addthis-friendly-widget-and-brilliant-viral-tracking-scheme/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		<feedburner:origLink>http://stevenbenner.com/2010/02/addthis-friendly-widget-and-brilliant-viral-tracking-scheme/</feedburner:origLink></item>
		<item>
		<title>Poor man’s code repository and versioning with Dropbox</title>
		<link>http://feedproxy.google.com/~r/stevenbenner/~3/fywxKKYP5c4/</link>
		<comments>http://stevenbenner.com/2010/02/poor-mans-source-code-repository-and-versioning-with-dropbox/#comments</comments>
		<pubDate>Mon, 08 Feb 2010 03:38:30 +0000</pubDate>
		<dc:creator>Steven Benner</dc:creator>
				<category><![CDATA[Desktop Development]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Tools]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[Windows]]></category>

		<guid isPermaLink="false">http://stevenbenner.com/?p=185</guid>
		<description><![CDATA[By now just about everyone knows about Dropbox, the simple file syncing/sharing and online backup solution. The software is available for several operating systems and is very fast and intuitive. It even has a good online interface so you can access your dropbox without the software.
But did you think about using it as a poor [...]]]></description>
			<content:encoded><![CDATA[<p>By now just about everyone knows about <em><a href="https://www.dropbox.com/" target="_blank">Dropbox</a></em>, the simple file syncing/sharing and online backup solution. The software is available for several operating systems and is very fast and intuitive. It even has a good online interface so you can access your dropbox without the software.</p>
<p>But did you think about using it as a poor man&#8217;s CVS? It&#8217;s actually a really good solution for personal projects, or for a small group of people working on a project. I have fallen in love with the service for several reasons.</p>
<p><span id="more-185"></span><br />
<strong>It&#8217;s free.</strong> Your free account will give you 2GB of storage to start, if you play their new user game or viral marking game then you can easily get 3GB on your free account, which is far more than you will ever use for source code. If you do end up needing more space, they offer pay subscriptions for 50GB and 100GB, costing $10/month and $20/month. Personally, I&#8217;m sticking with the free plan.</p>
<p><strong>It&#8217;s fast.</strong> When I am actively working on a project my changes are uploaded and synced to my other computers in seconds with no performance impact on the computers. The system works by only sending the delta so it doesn&#8217;t upload an entire file every time it changes. It is even smart enough to sync over the network so it uses the least bandwidth.</p>
<p><strong>It&#8217;s easy.</strong> I don&#8217;t have to worry about proper commits or forgetting to check something back in at 2am, when I save the file it is automatically synced up. Sure it&#8217;s not as advanced as a <em>real</em> repository, but it is just so simple. After all, do you really need branch and merge on your freelance project or web design?</p>
<p><strong><img class="size-full wp-image-474 alignright" title="Dropbox Version Screen" src="http://stevenbenner.com/wp-content/uploads/dropbox_version_thumb.png" alt="Dropbox Version Screen" width="150" height="150" />It has versioning (kinda).</strong> Dropbox even has a versioning system, although it&#8217;s not designed for code. You miss out on nifty diff and blame tools. But on a small project you probably already know who to blame. Unfortunately they only keep versions and deleted files for 30 days unless you have a pay plan. I really wish they&#8217;d give that out for free.</p>
<p>Personally, I use dropbox as a personal VSS and backup for all of my projects. It is really nice to know that my work is backed up with some version info in one central location as well as distributed to all of my computers.</p>
<p><strong>It&#8217;s an off-site backup.</strong> It is very important that you keep your hard work backed up. But just copying it to an external drive isn&#8217;t enough. You need to have an off-site backup in case something terrible happens. This is something else that dropbox does well.</p>
<p><strong>The other option:</strong> If you don&#8217;t mind giving your code to anyone in the world who wants it, <em><a href="http://github.com/" target="_blank">GitHub</a></em> is still the free code repository of choice. You&#8217;ll learn to deal with  the quirks of <em>git</em> after a while, though it does have a rather steep learning curve. However, if you want to keep your code private and want to have a lot of projects, GitHub will get pricey very fast. Since I don&#8217;t want to release the code for my personal stuff, GitHub just isn&#8217;t worth the cost for me. Personal projects are saved on dropbox, work projects are saved in the company&#8217;s VSS.</p>
<p>For big-project code work in a multi-developer environment dropbox will certainly not replace your CVS/VSS/SVN/GIT code repository. Having tools like diffs, branches and merges integrated into the repository is absolutely required in a corporate environment.  But for one man projects or freelance work where you can&#8217;t really justify the cost or the time for managing a <em>real</em> code repository, dropbox is a good choice.</p>
<h3  class="related_post_title">Related articles</h3><ul class="related_post"><li><a href="http://stevenbenner.com/2009/09/firewall-script-tool-for-iptables/" title="Firewall script tool for iptables">Firewall script tool for iptables</a></li><li><a href="http://stevenbenner.com/2010/01/an-ide-for-php-that-doesnt-suck/" title="An IDE for PHP that doesn&#8217;t suck">An IDE for PHP that doesn&#8217;t suck</a></li><li><a href="http://stevenbenner.com/2010/01/must-have-firefox-add-ons-for-web-developers-designers/" title="Must-have Firefox Add-ons for Web Developers &#038; Designers">Must-have Firefox Add-ons for Web Developers &#038; Designers</a></li><li><a href="http://stevenbenner.com/2010/01/building-a-home-network-attached-storage-server-part-2-raid-setup/" title="Building a home Network Attached Storage server. Part 2: RAID Setup">Building a home Network Attached Storage server. Part 2: RAID Setup</a></li><li><a href="http://stevenbenner.com/2010/01/reporting-of-unhandled-exceptions-in-your-distributable-net-application/" title="Reporting of unhandled exceptions in your distributable .NET desktop application">Reporting of unhandled exceptions in your distributable .NET desktop application</a></li></ul><img src="http://feeds.feedburner.com/~r/stevenbenner/~4/fywxKKYP5c4" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://stevenbenner.com/2010/02/poor-mans-source-code-repository-and-versioning-with-dropbox/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://stevenbenner.com/2010/02/poor-mans-source-code-repository-and-versioning-with-dropbox/</feedburner:origLink></item>
		<item>
		<title>Friendly URLs in an ASP.NET app</title>
		<link>http://feedproxy.google.com/~r/stevenbenner/~3/Ar1HxaMXWyI/</link>
		<comments>http://stevenbenner.com/2010/02/friendly-urls-in-an-asp-net-app/#comments</comments>
		<pubDate>Fri, 05 Feb 2010 03:04:13 +0000</pubDate>
		<dc:creator>Steven Benner</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[How-To]]></category>
		<category><![CDATA[SEO]]></category>

		<guid isPermaLink="false">http://stevenbenner.com/?p=420</guid>
		<description><![CDATA[Friendly URLs are a great way to improve SEO, promote linking and generally make your web application look more professional. If you have already hopped on to the MVC bandwagon then you don&#8217;t need to worry about it, friendly URLs are one of the great many benefits to using ASP.NET MVC. However, if you are [...]]]></description>
			<content:encoded><![CDATA[<p>Friendly URLs are a great way to improve SEO, promote linking and generally make your web application look more professional. If you have already hopped on to the MVC bandwagon then you don&#8217;t need to worry about it, friendly URLs are one of the great many benefits to using <em>ASP.NET MVC</em>. However, if you are still using web forms you will have to go through a couple extra steps to get the same effect.</p>
<p>Just in case you don&#8217;t know what a friendly url is, <em>&#8220;friendly URLs&#8221;</em> (aka <em>&#8220;Pretty URLs&#8221;</em>, <em>&#8220;Beautiful URLs&#8221;</em>, <em>&#8220;URL Rewriting&#8221;</em>, <em>&#8220;URL Mapping&#8221;</em>, <em>&#8220;SEO URLs&#8221;</em>, etc.)  I am talking about pages or resources that are identified by keywords in a pseudo-directory structure. For Example, you have a web application with a search page. In a standard web forms application your URI will probably look something like this:</p>
<pre style="text-align: left; padding-left: 30px;">http://your.address/Search.aspx?query=friendly</pre>
<p>In a friendly URL structure, your URI would look something like this:</p>
<pre style="text-align: left; padding-left: 30px;">http://your.address/search/friendly</pre>
<p>This is a much leaner and cleaner looking link. Really better in every way, though if you are still using ASP.NET web forms you will have to go through some trouble to make your links look like that. As well as having to deal with maintaining the rules and code to support it. It may very well not be worth the trouble.</p>
<p>If your web application is consumer facing and relies on search engines for traffic then you should probably implement friendly URLs in some form for your app.</p>
<p><span id="more-420"></span></p>
<p>Using friendly URLs is by no means required. They have been proven to increase relevancy in search engines because the keywords in the URL adds weight to the result for any matching keywords. But this really is the only benefit that can be used to justify the time and maintenance it takes to deal with using friendly URL strings. If your web application is behind a login screen, or doesn&#8217;t need to be ranked in search engines then friendly URLs are probably not worth the trouble.</p>
<h3>Options</h3>
<p>You have a lots of options for implementing friendly URLs in your ASP.NET application. There are dozens of URL rewriting solutions for .NET out there, these are just the few that I have had real experience using.</p>
<h4>Use MVC &#8211; its grrrrrrreat!</h4>
<p>By very definition, ASP.NET MVC gives you great URLs without extra effort or maintenance issues. The MVC system uses the ASP.NET <em>routing engine</em>, a very nice solution which is integrated into the ASP.NET application life cycle. Since it&#8217;s integrated into .NET itself it is very fast and requires zero maintenance.</p>
<p>There are so many other reasons to use MVC that I wouldn&#8217;t even try to go over them in this post. Suffice it to say that I haven&#8217;t built a new web forms application since MVC 1.0 was officially released. As far as I&#8217;m concerned, this is the way ASP.NET should work.</p>
<p>If however, you must use web forms then you still have two other good solutions.</p>
<h4>IIS 7 URL Rewrite Module</h4>
<p>If you are running IIS 7 and have enough access on your server to add and manage IIS modules then the official IIS 7 URL Rewrite module is your best choice. It is fast, since it is directly integrated with IIS and it is official, so it&#8217;s fully supported.</p>
<ul>
<li><a href="http://learn.iis.net/page.aspx/734/url-rewrite-module/" target="_blank">Official URL Rewrite Module Page</a></li>
<li><a href="http://forums.iis.net/1152.aspx" target="_blank">URL Rewrite Module Forum</a></li>
</ul>
<p>Implementing pretty urls with the IIS7 URL Rewrite Module is pretty straight forward. You have to install the module on the server then configure the site, either via the administration panel, or in the web.config.</p>
<h4>Managed Fusion Rewrite library</h4>
<p>This is another good solution. The Rewrite module from Managed Fusion is a DLL that you add to your project. It runs as an HttpModule that you configure in the web.config. It will pull your rewrite rules from a separate file that you specify in the web.config or you can set your rule in the web.config itself. This module uses standard Apache mod_rewrite syntax and commands .</p>
<p>It is a proprietary library, but has a nice open license so you can use it for personal or commercial purposes. It is reasonably well maintained and I have personally used it in very high traffic environments.</p>
<ul>
<li><a href="http://www.managedfusion.com/products/url-rewriter/" target="_blank">Official Managed Fusion URL Rewriter Page</a></li>
<li><a href="http://urlrewriter.codeplex.com/" target="_blank">Managed Fusion Rewriter at Codeplex</a></li>
<li><a href="http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html" target="_blank">Apache mod_rewrite documentation</a> (syntax reference)</li>
</ul>
<p>Implementing the Managed Fusion rewriter is actually very simple, especially if you are already familiar with Apache&#8217;s mod_rewrite syntax. You just need to reference the rewriter DLL in your project and build the rewriter rules. It&#8217;s all in one solution with no external dependencies so it&#8217;s easy to deploy and manage. You don&#8217;t have to mess with any IIS configuration.</p>
<h4>Roll your own</h4>
<p>If you have special needs, or are feeling bored, then sure, you can roll your own solution. I don&#8217;t recommend it. You don&#8217;t want to have to deal with every possible URL in code, nor do you want to build and maintain something hundreds of other people are doing already (maybe you can <em>reinvent the wheel</em>), it will just be a maintenance nightmare.</p>
<h3>Implementation</h3>
<p>Setting up a web forms application to implement a URL rewriting system is fairly straight forward, but will take some time and some serious thought.</p>
<p><strong>Install rewriter.</strong> First off set up whatever rewriter solution you have chosen and verify that it is working. I gave you all of the links you&#8217;ll need to get them working already.</p>
<p><strong>Define URL formats.</strong> Now you need to figure out exactly what format you will follow for urls. This planning step is unique to every application so I can&#8217;t give you much advice, except to say that you should focus on adding the important keywords. Do not pack the URL with too many keywords, you will get punished for keyword stuffing. Keep it simple, like /search/QUERY or /user/NAME.</p>
<p>In this example I will define the search URLs as:</p>
<p style="padding-left: 30px;">/search/SEARCH_QUERY</p>
<p style="padding-left: 30px;">/search/SEARCH_QUERY/PAGE_NUMBER</p>
<p><strong>Build rewriter RegEx.</strong> The next step is to write the actual string statements that will match your new friendly url structure and extract the data that you need to pass the page. Basically all rewriter systems, including the IIS 7 URL Rewrite Module and the Managed Fusion Rewriter, use <a href="http://en.wikipedia.org/wiki/Regular_expression" target="_blank"><em>Regular Expressions</em></a> to do the string matching and evaluation. There is an art to writing good regex statements. If you haven&#8217;t learned regex yet it&#8217;s never too late to start, you will find them useful throughout your career.</p>
<p>In this case I&#8217;ll be using the Managed Fusion Rewriter because I am so used to doing this kind of thing in apache. The MF Rewriter uses the same functions and syntax as apache&#8217;s mod_rewrite. For a good reference check out the <a href="http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html" target="_blank">Apache mod_rewrite documentation</a>.</p>
<p>Here is an example rewriter file for our little search app running Managed Fusion.</p>
<pre class="brush: perl;">
# Initialize the rewriter
RewriteEngine On
RewriteBase /

# Search rules
########################

# Basic search rule
# matches &quot;/search/some_search_query
RewriteRule ^/search/(\w+)$          /Search.aspx?query=$1          [NC,L]

# Search pagination rule
# matches &quot;/search/some_search_query/2&quot; (i.e. page 2)
RewriteRule ^/search/(\w+)/(\d+)     /Search.aspx?query=$1&amp;page=$2  [NC,L]
</pre>
<p>Lets take a closer look at the rule on line 10:</p>
<ul>
<li><strong><code>RewriteRule</code></strong> begins the statement telling the processor that this is a one-line rule. This statement has 3 arguments.</li>
<li><strong><code>^/search/(\w+)$</code></strong> is the regex that we are trying to match in the friendly url. Breaking it down the first character is a carrot ^ which means &#8220;begins with&#8221;. It is followed by the string &#8220;/search/&#8221; which is a literal string match. Next is (\w+) . \w is the &#8220;word&#8221; character, it means any alpha-numeric or basic symbol character. The + means &#8220;one or more&#8221;. That whole statement is wrapped in parentheses, which means &#8220;save this value so I can access it later&#8221;. The last character is the $ symbol, which means &#8220;ends with&#8221;. Putting it all together this regular expression reads <em>&#8220;Begins with /search/, followed by, and ending with, one or more word characters. Save the word characters into $1&#8243;</em>.</li>
<li><strong><code>/search.aspx?query=$1</code></strong> is the <em>rewritten url</em> that we will evaluate the friendly url into. This is the <em>real url</em> that will be executed. The $1 means the first parentheses item from the first statement. They are in left-to-right order and can be nested as deep as you want.</li>
<li><strong><code>[NC,L]</code></strong> are the rule statements. In this case I am using &#8220;NC&#8221; which means &#8220;Not Case-sensitive&#8221; and &#8220;L&#8221; which means &#8220;Last&#8221;. So case has no effect on the match, and when it does match it stops processing the rewriter rules. If you don&#8217;t have the L statement then the rewiter engine will keep testing every rule that follows it, wasting processing power and possibly matching a rule further down that you didn&#8217;t want it to use.</li>
</ul>
<p><strong>Create a class to build your new URLs.</strong> Now that you know what your URLs should look like and have gotten them working, you need need to be able to link to them anywhere in the application. My solution for this is to build a class in the codebehind to handle creating the URLs. No matter what we do we will have to maintain URLs and support these changes in two places, the rewiter rules, and the UrlBuilder class. But this is better (and more maintainable) than custom crafting URLs every time you want to link them.</p>
<p>Here is an example class for our search application:</p>
<pre class="brush: csharp;">
public class BuildUrl
{
	public string Search(string searchQuery)
	{
		return Search(searchQuery, 1);
	}

	public string Search(string searchQuery, int pageNumber)
	{
		if (pageNumber == 1)
		{
			return &quot;/search/&quot; + HttpUtility.UrlEncode(searchQuery);
		}
		else
		{
			return &quot;/search/&quot; + HttpUtility.UrlEncode(searchQuery) + &quot;/&quot; + pageNumber.ToString();
		}
	}
}
</pre>
<p>Now you can use this one central class throughout your project whenever you need to create a link. For example if you want to create a search link for &#8220;cars&#8221; all you have to do is say:</p>
<pre class="brush: plain;">
&lt;a href=&quot;&lt;%= BuildUrl.Search(&quot;cars&quot;) %&gt;&quot;&gt;Search for cars&lt;/a&gt;
</pre>
<p>You will have to build a function for every type of link, but at least it&#8217;s easy to maintain.</p>
<h3>Migration</h3>
<p>If your web app is already running and is indexed by search engines or linked to by other sites then you will need to setup redirects for every page that you will be moving to a friendly URL. If you don&#8217;t then the pages will just 404 or error out. This means that any links to your pages will be broken and the page rank that they&#8217;ve earned will be dropped.</p>
<p>So it is indeed <em>very</em> important to <em>properly</em> redirect all old urls to the new ones, forever. By properly, I mean a HTTP 301 redirect. Status code 301 means &#8220;Moved Permanently&#8221;, this will tell search engines to <em>update</em> the URL, and browsers will cache the updated URL.</p>
<p>The most simple solution is to add a code block at the top of the page that tests for an old url, then builds and redirects to a new url.</p>
<p>Here is a little sample for what our search page redirect would look like:</p>
<pre class="brush: csharp;">
string NewUrl = BuildUrl.Search(Request.QueryString[&quot;query&quot;]);

if (Request.RawUrl.IndexOf(&quot;/search.aspx&quot;, StringComparison.OrdinalIgnoreCase) &gt; -1 ||
	Request.RawUrl.IndexOf(NewUrl, StringComparison.OrdinalIgnoreCase) == -1 )
{
	Response.Status = &quot;301 Moved Permanently&quot;;
	Response.AddHeader(&quot;Location&quot;, NewUrl);
	Response.End();
}
</pre>
<p>Pretty simple right? This needs to be custom made for each aspx page to match the new URL format, but it is a simple block of code.</p>
<h3>Conclusion</h3>
<p>It&#8217;s a bit of extra work and requires some thought to set up url beautification, but if your web app depends on people finding it through search engines then it&#8217;s probably worth the effort. Think very carefully about the URL structure you use. Once you deploy it to the internet it is very difficult to un-do. If you expect people to link to you then you have to support every url that has ever worked indefinitely.</p>
<p>Once you have it all setup it should keep working just as indefinitely. When you add a new page you will have to add some new rules and a new function to the UrlBuilder class but that is fairly easy. Just make sure you get your url structure right, <em>the first time</em>.</p>
<p>Though, if you are still planning your next app and know you want pretty urls, now it the time to take a good hard look at ASP.NET MVC and all of the wonderful tools and processes it brings to the table. I have fallen in love with MVC and will probably never make another web forms app. I will of course continue to support the ones that I have.</p>
<p>ScottGu has an old but probably <a href="http://weblogs.asp.net/scottgu/archive/2007/02/26/tip-trick-url-rewriting-with-asp-net.aspx">better post than mine</a> on his blog. If you&#8217;re thinking about implementing rewriting then you should read that one too.</p>
<h3  class="related_post_title">Related articles</h3><ul class="related_post"><li><a href="http://stevenbenner.com/2010/01/external-templates-for-the-dataview-control-microsoft-ajax-library-4-0/" title="External templates for the DataView control: Microsoft Ajax Library 4.0">External templates for the DataView control: Microsoft Ajax Library 4.0</a></li><li><a href="http://stevenbenner.com/2010/01/reporting-of-unhandled-exceptions-in-your-distributable-net-application/" title="Reporting of unhandled exceptions in your distributable .NET desktop application">Reporting of unhandled exceptions in your distributable .NET desktop application</a></li><li><a href="http://stevenbenner.com/2010/03/custom-link-click-tracking-using-omniture/" title="Custom link click tracking using Omniture">Custom link click tracking using Omniture</a></li><li><a href="http://stevenbenner.com/2010/02/optional-parameters-and-named-parameters-in-c-4-0/" title="Optional parameters and named parameters in C# 4.0">Optional parameters and named parameters in C# 4.0</a></li><li><a href="http://stevenbenner.com/2010/02/exslt-set-distinct-in-msxml-for-internet-explorer/" title="EXSLT set:distinct in MSXML for Internet Explorer">EXSLT set:distinct in MSXML for Internet Explorer</a></li></ul><img src="http://feeds.feedburner.com/~r/stevenbenner/~4/Ar1HxaMXWyI" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://stevenbenner.com/2010/02/friendly-urls-in-an-asp-net-app/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://stevenbenner.com/2010/02/friendly-urls-in-an-asp-net-app/</feedburner:origLink></item>
		<item>
		<title>Publishing XML to the web with XSLT: a replacement for the presentation layer</title>
		<link>http://feedproxy.google.com/~r/stevenbenner/~3/SkFtlKvIllU/</link>
		<comments>http://stevenbenner.com/2010/02/publishing-xml-to-the-web-with-xslt-a-replacement-for-the-presentation-layer/#comments</comments>
		<pubDate>Tue, 02 Feb 2010 06:39:54 +0000</pubDate>
		<dc:creator>Steven Benner</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[How-To]]></category>
		<category><![CDATA[XML]]></category>
		<category><![CDATA[XPath]]></category>
		<category><![CDATA[XSLT]]></category>

		<guid isPermaLink="false">http://stevenbenner.com/?p=184</guid>
		<description><![CDATA[Most web APIs and data feeds are built to consume and produce XML data. RSS, SOAP, REST, ATOM, AJAX, web-services, indeed, even XHTML itself is a form of XML. If you publish anything to the web you&#8217;re probably publishing XML in some form or another. Many web developers work with some flavor of XML every [...]]]></description>
			<content:encoded><![CDATA[<p>Most web APIs and data feeds are built to consume and produce <em>XML</em> data. RSS, SOAP, REST, ATOM, AJAX, web-services, indeed, even XHTML itself is a form of XML. If you publish anything to the web you&#8217;re probably publishing XML in some form or another. Many web developers work with some flavor of XML every day.</p>
<p>However few have heard of <a href="http://www.w3.org/TR/xslt" target="_blank"><em>XSLT</em></a>, a standard language adopted by the <a href="http://www.w3.org/" target="_blank">W3C</a> a decade ago for styling XML data for user consumption. XSLT, or <em>XSL Transform</em>, is a real language, part of XSL, the <em>XML Stylesheet Language</em>. XSLT is a tool for reformatting XML data, literally an XML stylesheet. This powerful language lets you convert XML data into almost any other XML structure you could imagine, including completely valid and functional XHTML.</p>
<p>Simply put, if your web application has been built to produce XML for feeds or APIs, then you do not need to build another set of logic to make a web version. You can let web browsers hit the service, just as you would for feeds or APIs. Just create an XSLT and link it from your XML document. All modern browsers will render the XSLT and display it as a normal web page.</p>
<p><span id="more-184"></span>The most famous example of this technique in action is the <a href="http://www.worldofwarcraft.com/" target="_blank">World of Warcraft</a> home page. Blizzard publishes a minimum amount of data in a XML document which includes a reference to an XSLT stylesheet. The XSLT takes this minimum of data and uses it to build the XHTML document in the users&#8217; web browser. This means that instead of having a complicated and slow CMS solution they can serve the entire (very high-traffic) home page from flat text files. When they change the content, all they need to do is publish the tiny XML file with the new data. When they want to change the look and feel they simply change the XSLT file.</p>
<p>This provides a nice level of abstraction between the data and the layout. Your application can focus on data, leaving the work of presentation to the XSLT.</p>
<h3>Applications</h3>
<p>This technique may not be particularly well suited to your average web page, such as a company web site. Most standard web sites have no need for XML, so there is no structure to build or maintain it. But for a web application where you are going to be dealing with a lot of XML this is a great solution.</p>
<p>I would also argue that all XML feeds and APIs should have an XSLT stylesheet so they can render in a more user friendly view to people who want to open them directly in their browser. So if your API accepts search queries why not use an XSLT to format the results in a user friendly browser view, and add a little search form at the top.</p>
<p>Anywhere you send XML to the internet there is a good reason to attach an XSLT.</p>
<h3>Benefits</h3>
<p>Using XSLT to support and render your XML data can have many huge advantages, especially for a high-traffic web application.</p>
<p><strong>Faster development.</strong> Why build a whole new layer of logic and functionality? You can work on making the best XML data returns and use XSLT as your visual layer.</p>
<p><strong>Less server load.</strong> Scripting a whole web site on top of your current data layer adds overhead. Save the servers, publish your data directly with a flat text file and let the browser handle the heavy lifting.</p>
<p><strong>More maintainable.</strong> XSLT is basically a template. I&#8217;ve mentioned this before, but I love templates. If you want to change the look and feel of your application all you have to do is modify the XSLT files. Once you&#8217;ve gotten proficient at XSLT you will find that in many cases it is easier to modify.</p>
<h3>Browser support</h3>
<p>All modern browsers support XSLT, even <em>updated</em> version of IE6.  The original vanilla version of Internet Explorer 6 used the <em>Microsoft XML Core Services</em> (MSXML)<em> 3.0</em> processor for its XSLT engine. This version of the MSXML  processor didn&#8217;t support the full XSLT 1.0 specification released by the  W3C, it only partially support an antiquated draft. It was also simply terrible in every way. Say &#8220;MSXML 3&#8243; to an old  XML guy and he&#8217;ll probably adopt the   fetal position and begin rambling incoherent nonsense about the horrors he&#8217;s  experienced.</p>
<p>Fortunately, it is exceedingly rare to find a copy of  the original IE6 out in the wild. If the user has updated Windows,  installed a service pack, or installed the Microsoft Office software,  then their version of IE6 has been updated to use the MSXML 4.0  processor. MSXML 4.0 and up have excellent support for XSLT and are very  fast at rendering it.</p>
<p>I don&#8217;t believe that you should ever have  to worry about browser support issues. Original IE6 is the  exception, and like I said, it is very rare to find such a version of  this browser out in the wild today. IE5 and earlier, as well as Opera 8 and earlier have no support for XSLT, but both of those have been deprecated by their creators.</p>
<p>W3 Schools has a good rundown on <a href="http://www.w3schools.com/XSL/xsl_browsers.asp" target="_blank">XSLT Browser</a> support.</p>
<h3>SEO implications</h3>
<p>The impact of the XML/XSLT system on search engine rankings hasn&#8217;t been very well researched. Google seems to be able to process XML/XSLT pages just fine. They see the rendered content in their index and will display results based on that content.</p>
<p>Honestly, I can&#8217;t say for sure if this system will impact your search engine rankings in a positive or negative way. I&#8217;ll do some experimentation the next time I get the chance.</p>
<h3>Implementation</h3>
<p>If you already work with XML then diving into XSLT shouldn&#8217;t be that hard at all. Authoring XSLTs is notably more technical than simple HTML, but if you&#8217;re building a web application that produces XML then you must already have a good amount of technical prowess.</p>
<p>There are plenty of good tutorials for XSLT out there, a quick google search will give you all of the resources you will ever need to become an XSLT master. But I will provide one example XML with and XSLT to show you what it looks like.</p>
<h4>The XML</h4>
<p>Any XML document that is valid can be rendered with an XSLT. The only item that you will need to add to the XML document is a stylesheet declaration pointing to the XSLT file (or files) that it should use for use rendering.</p>
<p>Immediately after the XML declaration you will add a line of code that looks like this:</p>
<pre class="brush: xml;">
&lt;?xml-stylesheet type=&quot;text/xsl&quot; href=&quot;/path/to/your/xslt_file.xsl&quot;?&gt;
</pre>
<p>Let&#8217;s use the following XML data for this example, a search result for the string &#8220;XSLT&#8221;:</p>
<pre class="brush: xml;">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;?xml-stylesheet type=&quot;text/xsl&quot; href=&quot;layout.xsl&quot;?&gt;
&lt;searchdata&gt;
	&lt;query&gt;
		&lt;search&gt;XSLT&lt;/search&gt;
	&lt;/query&gt;
	&lt;results&gt;
		&lt;class id=&quot;12&quot; name=&quot;XSLT 101&quot;&gt;
			&lt;datetime&gt;Feb 1, 2010 8:00pm PST&lt;/datetime&gt;
			&lt;description&gt;A basic introduction to XSLT.&lt;/description&gt;
			&lt;teacher&gt;Steven Benner&lt;/teacher&gt;
		&lt;/class&gt;
		&lt;class id=&quot;18&quot; name=&quot;Advanced XSLT&quot;&gt;
			&lt;datetime&gt;Feb 2, 2010 8:00pm PST&lt;/datetime&gt;
			&lt;description&gt;Advanced XSLT techniques and concepts.&lt;/description&gt;
			&lt;teacher&gt;Steven Benner&lt;/teacher&gt;
		&lt;/class&gt;
		&lt;class id=&quot;35&quot; name=&quot;XSLT History&quot;&gt;
			&lt;datetime&gt;Feb 1, 2010 2:00pm PST&lt;/datetime&gt;
			&lt;description&gt;The history of XSLT.&lt;/description&gt;
			&lt;teacher&gt;Steven Benner&lt;/teacher&gt;
		&lt;/class&gt;
	&lt;/results&gt;
&lt;/searchdata&gt;
</pre>
<h4>The XSLT</h4>
<p>There is a whole language and syntax that powers XSLT, I wont bother trying to write a full guide here. I just want to provide you with a basic example to show you how it works.</p>
<p>One important item to note is that XSLT selectors use <em><a href="http://www.w3.org/TR/xpath/" target="_blank">XPath</a></em> statements to target data. XPath, or <em>XML Path</em> statements treat XML data like a directory structure. Node values are targeted like a directory (e.g. parentNode/childNode), attributes are selected using an &#8220;at&#8221; sign (e.g. parentNote/@id).</p>
<p>This is what the XSLT for rendering it as an XHTML document would look like.</p>
<pre class="brush: xml;">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;xsl:stylesheet version=&quot;1.0&quot; xmlns:xsl=&quot;http://www.w3.org/1999/XSL/Transform&quot;&gt;

	&lt;!-- set output mode as html --&gt;
	&lt;xsl:output method=&quot;html&quot;/&gt;

	&lt;!-- template that matches the root node --&gt;
	&lt;xsl:template match=&quot;searchdata&quot;&gt;
		&lt;html&gt;
		&lt;head&gt;
			&lt;title&gt;Search Results For &lt;xsl:value-of select=&quot;query/search&quot; /&gt;&lt;/title&gt;
		&lt;/head&gt;
		&lt;body&gt;
			&lt;h1&gt;Search results for &lt;xsl:value-of select=&quot;query/search&quot; /&gt;&lt;/h1&gt;

			&lt;!-- results table --&gt;
			&lt;table border=&quot;1&quot; cellpadding=&quot;5&quot;&gt;
				&lt;tr&gt;
					&lt;th&gt;Class ID&lt;/th&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Teacher&lt;/th&gt;
					&lt;th&gt;Description&lt;/th&gt;
					&lt;th&gt;Date &amp;amp; Time&lt;/th&gt;
				&lt;/tr&gt;
				&lt;!-- run the template that renders the classes in table rows --&gt;
				&lt;xsl:apply-templates select=&quot;results/class&quot; /&gt;
			&lt;/table&gt;
		&lt;/body&gt;
		&lt;/html&gt;
	&lt;/xsl:template&gt;

	&lt;!-- template that matches classes --&gt;
	&lt;xsl:template match=&quot;class&quot;&gt;
		&lt;tr&gt;
			&lt;td&gt;&lt;xsl:value-of select=&quot;@id&quot; /&gt;&lt;/td&gt;
			&lt;td&gt;&lt;xsl:value-of select=&quot;@name&quot; /&gt;&lt;/td&gt;
			&lt;td&gt;&lt;xsl:value-of select=&quot;teacher&quot; /&gt;&lt;/td&gt;
			&lt;td&gt;&lt;xsl:value-of select=&quot;description&quot; /&gt;&lt;/td&gt;
			&lt;td&gt;&lt;xsl:value-of select=&quot;datetime&quot; /&gt;&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/xsl:template&gt;

&lt;/xsl:stylesheet&gt;
</pre>
<p>This would render the following view in your browser:</p>
<div style="overflow: hidden;"><img class="size-medium wp-image-371" title="Rendered XML with XSLT" src="http://stevenbenner.com/wp-content/uploads/xslt_demo1-300x84.png" alt="Rendered XML with XSLT" width="300" height="84" /></div>
<p>Don&#8217;t believe me? Take a look at this <a href="http://stevenbenner.com/misc/xslt_demo/index.xml" target="_blank">live demo</a>.</p>
<h3>Alternative implementation</h3>
<p>You can use XSLT on the server side just as easily as in the browser. If you simply cannot publish an XSLT for the web you can still use this technology as a template engine. You can build a separate presentation layer that interacts with your XML web service then run the transforms on the server side. Users will never see the raw XML.</p>
<p>Be warned however, XSLT is not the fastest template engine ever made. You will lose the speed benefits of publishing the XML/XSLT directly to  the browser. For pure speed, a properly optimized PHP or .NET web application will out-perform it.</p>
<p>In .NET you have the <a href="http://msdn.microsoft.com/en-us/library/system.xml.xsl.xslcompiledtransform.aspx" target="_blank">XslCompiledTransform</a> class, and in PHP you have the <a href="http://php.net/manual/en/book.xsl.php" target="_blank">XSL extension</a>.</p>
<h3>Conclusion</h3>
<p>The example I provided above is just the most basic functionality XSLT has to offer. I hope you can at least get an idea powerful this technique is and how much time it can save you on your next (or current) web application.</p>
<p>There is so much value in separating your data layer and presentation layer. Maybe XSLT is the solution to all of your problems, maybe XSLT is completely useless to you. Either way, it is another valuable tool in your toolbox. Remember it is there and perhaps you&#8217;ll find that you couldn&#8217;t have lived without it.</p>
<h3  class="related_post_title">Related articles</h3><ul class="related_post"><li><a href="http://stevenbenner.com/2010/02/exslt-set-distinct-in-msxml-for-internet-explorer/" title="EXSLT set:distinct in MSXML for Internet Explorer">EXSLT set:distinct in MSXML for Internet Explorer</a></li><li><a href="http://stevenbenner.com/2010/03/custom-link-click-tracking-using-omniture/" title="Custom link click tracking using Omniture">Custom link click tracking using Omniture</a></li><li><a href="http://stevenbenner.com/2010/02/friendly-urls-in-an-asp-net-app/" title="Friendly URLs in an ASP.NET app">Friendly URLs in an ASP.NET app</a></li><li><a href="http://stevenbenner.com/2010/01/external-templates-for-the-dataview-control-microsoft-ajax-library-4-0/" title="External templates for the DataView control: Microsoft Ajax Library 4.0">External templates for the DataView control: Microsoft Ajax Library 4.0</a></li><li><a href="http://stevenbenner.com/2010/01/reporting-of-unhandled-exceptions-in-your-distributable-net-application/" title="Reporting of unhandled exceptions in your distributable .NET desktop application">Reporting of unhandled exceptions in your distributable .NET desktop application</a></li></ul><img src="http://feeds.feedburner.com/~r/stevenbenner/~4/SkFtlKvIllU" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://stevenbenner.com/2010/02/publishing-xml-to-the-web-with-xslt-a-replacement-for-the-presentation-layer/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://stevenbenner.com/2010/02/publishing-xml-to-the-web-with-xslt-a-replacement-for-the-presentation-layer/</feedburner:origLink></item>
		<item>
		<title>External templates for the DataView control: Microsoft Ajax Library 4.0</title>
		<link>http://feedproxy.google.com/~r/stevenbenner/~3/i8MQw02Fgds/</link>
		<comments>http://stevenbenner.com/2010/01/external-templates-for-the-dataview-control-microsoft-ajax-library-4-0/#comments</comments>
		<pubDate>Sat, 30 Jan 2010 04:59:20 +0000</pubDate>
		<dc:creator>Steven Benner</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[AJAX]]></category>
		<category><![CDATA[How-To]]></category>
		<category><![CDATA[JavaScript]]></category>

		<guid isPermaLink="false">http://stevenbenner.com/?p=330</guid>
		<description><![CDATA[One of the best things about templates is how portable they are. You can build a template for a particular piece of presentation and then use that template everywhere you need it. In fact you can have several templates to choose from, giving you themes and different ways of displaying the same data.
This is a [...]]]></description>
			<content:encoded><![CDATA[<p>One of the best things about <em>templates</em> is how portable they are. You can build a template for a particular piece of presentation and then use that template everywhere you need it. In fact you can have several templates to choose from, giving you themes and different ways of displaying the same data.</p>
<p>This is a very powerful and very <em>maintainable</em> technique. Portable template files are a great example of the <em>Don&#8217;t Repeat Yourself</em> (DRY) principal and are a good idea for any template that will see reuse.</p>
<p>The new <em>DataView</em> control in the latest beta of the <a href="http://www.asp.net/ajaxlibrary/" target="_blank">Microsoft Ajax Library</a> version 4.0 (aka <em>the ASP.NET AJAX Library</em>) has a fine template engine built into it. Unfortunately, those templates must be embedded in the page markup. If it&#8217;s your project then you can simply use an ascx User Control, or a <code>ContentPlaceHolder</code> or an <code>Html.RenderPartial</code>, but what if you need to be able to use the template on a static file, or a clients site, or even in another framework? You can&#8217;t have portable template files that you can just call anywhere. Well, you can, but you have to use a workaround technique.</p>
<p>This is one technique that I came up with for importing a template file into a page. This lets me have one (or a selection of) template files that I can use everywhere for a portable JavaScript widget. It has no dependencies other than the Microsoft Ajax Library.</p>
<p><span id="more-330"></span></p>
<p>You can save your template as an html or xml file in your project  and then, using an AJAX connection, download the template and insert it into  the page. Once the template has been inserted into the page you can create the DataView control and generate the widget.</p>
<p>First off create the JavaScript file, in this case <em>MyWidget.js</em>:</p>
<pre class="brush: jscript;">
var displayElement;

function WidgetInit() {
	displayElement = $get('MyWidget'); // div that we will insert the template into

	// create a new web request and pull Template.html
	var wRequest = new Sys.Net.WebRequest();
	wRequest.set_url('/Template.html');
	wRequest.set_httpVerb(&quot;GET&quot;);
	wRequest.set_userContext(&quot;user's context&quot;);
	wRequest.add_completed(WidgetWebRequestCompleted);
	wRequest.invoke();
}

function WidgetWebRequestCompleted(executor, eventArgs) {
	if (executor.get_responseAvailable()) {
		// insert the markup from Template.html into the display div
		displayElement.innerHTML += executor.get_responseData();
		// create the DataView using the inserted template
		$create(Sys.UI.DataView, { data: MyWidgetData }, null, null, $get(&quot;MyWidgetTemplate&quot;));
	}
}
</pre>
<p>Next we&#8217;ll create <em>Template.html</em>, which contains our template:</p>
<pre class="brush: xml;">
&lt;table id=&quot;MyWidgetTemplate&quot; class=&quot;sys-template&quot; border=&quot;1&quot;&gt;
	&lt;tr&gt;
		&lt;td&gt;{{Name}}&lt;/td&gt;
		&lt;td&gt;{{WebSite}}&lt;/td&gt;
	&lt;/tr&gt;
&lt;/table&gt;
</pre>
<p>And finally bring it all together in the page:</p>
<pre class="brush: xml;">
&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;

	&lt;head&gt;
		&lt;title&gt;Microsoft Ajax DataView External Template Demo&lt;/title&gt;
		&lt;style type=&quot;text/css&quot;&gt;
			/* make the template invisible until the work is done */
			.sys-template {display:none}
		&lt;/style&gt;
		&lt;script src=&quot;scripts/MicrosoftAjax.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
		&lt;script src=&quot;scripts/MicrosoftAjaxTemplates.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
		&lt;script src=&quot;scripts/MyWidget.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
		&lt;script type=&quot;text/javascript&quot;&gt;
			// this should come from a javascript include tag, but for the
			// sake of example, im embedding it here for you
			var MyWidgetData = [
				{ Name: &quot;Steven Benner&quot;, WebSite: &quot;http://stevenbenner.com/&quot; },
				{ Name: &quot;Bill Gates&quot;, WebSite: &quot;http://www.gatesfoundation.org/&quot; },
				{ Name: &quot;Stephen Hawking&quot;, WebSite: &quot;http://www.hawking.org.uk/&quot; }
			];

			function pageLoad() {
				WidgetInit();
			}
		&lt;/script&gt;
	&lt;/head&gt;

	&lt;body&gt;

		&lt;h1&gt;Microsoft Ajax DataView External Template Demo&lt;/h1&gt;

		&lt;!--
			The MyWidget div is the target where the template will be
			inserted, and the widget content generated.
		--&gt;
		&lt;div id=&quot;MyWidget&quot;&gt;&lt;/div&gt;

	&lt;/body&gt;

&lt;/html&gt;
</pre>
<p>This is kind of a hacky solution, but it works. Often, when I want  to use templates, I want to use external templates. Simple flat files  that I can change, cache and version. After all, half the point of  templates is having the ability to have a <em>selection</em> of different  templates.</p>
<h3  class="related_post_title">Related articles</h3><ul class="related_post"><li><a href="http://stevenbenner.com/2010/03/custom-link-click-tracking-using-omniture/" title="Custom link click tracking using Omniture">Custom link click tracking using Omniture</a></li><li><a href="http://stevenbenner.com/2010/02/friendly-urls-in-an-asp-net-app/" title="Friendly URLs in an ASP.NET app">Friendly URLs in an ASP.NET app</a></li><li><a href="http://stevenbenner.com/2010/01/over-use-of-javascript-and-ajax-bad-practice-and-broken-pages/" title="Excessive JavaScript and AJAX: bad practice &#038; broken pages">Excessive JavaScript and AJAX: bad practice &#038; broken pages</a></li><li><a href="http://stevenbenner.com/2010/01/reporting-of-unhandled-exceptions-in-your-distributable-net-application/" title="Reporting of unhandled exceptions in your distributable .NET desktop application">Reporting of unhandled exceptions in your distributable .NET desktop application</a></li><li><a href="http://stevenbenner.com/2010/03/javascript-regex-trick-parse-a-query-string-into-an-object/" title="JavaScript regex trick: Parse a query string into an object">JavaScript regex trick: Parse a query string into an object</a></li></ul><img src="http://feeds.feedburner.com/~r/stevenbenner/~4/i8MQw02Fgds" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://stevenbenner.com/2010/01/external-templates-for-the-dataview-control-microsoft-ajax-library-4-0/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://stevenbenner.com/2010/01/external-templates-for-the-dataview-control-microsoft-ajax-library-4-0/</feedburner:origLink></item>
	</channel>
</rss>
