Edit: Check out the comment from Parker for a better solution to the problem we discuss here. The post still applies for problems that require more complex behavior on a hashchange, though.
Sticking with the theme of posting about stuff learned from coworkers, Mike recently introduced me to the hashchange javascript event. I was putting together some functionality for highlighting posts in a thread and generating permalinks to them. A simple version of this page is as shown:
<div class="subject">Fan Death</div>
<div id="0" class="message">
<span class="from">Dan Gottlieb</span><a class="permalink" href="#0">Permalink</a>
<div class="body">http://en.wikipedia.org/wiki/Fan_death</div>
<div id="1" class="message reply">
<span class="from">Mike Dirolf</span><a class="permalink" href="#1">Permalink</a>
<div class="body">Fan death is real, don't believe everything you read on wikipedia
<div id="2" class="message reply">
<span class="from">Greg Rubin</span><a class="permalink" href="#2">Permalink</a>
<div class="body">Oh man I have to ask my friend about this one!</div>
.message-highlight {border: 1px solid #000000; background: #00DDEE;}
<script type="text/javascript" charset="utf-8"
src="https://fiesta.cc/static/33baa53a62712f40a707b94f473250928cf37567/j/jquery.min.js">
<script type="text/javascript">
function updateMessageHighlights() {
jQuery(".message-highlight").removeClass("message-highlight");
var hash = this.href.split("#")[1];
jQuery("#" + hash).addClass("message-highlight");
jQuery("A.permalink").click(updateMessageHighlights)
So what makes this javascript ugly? For one, the function that gets called has to reference "this" (the node that was clicked) instead of window.location.hash. When a JS click event is triggered, the click handler (updateMessageHighlights) is executed before the window location changes. So, if we were to use window.location.hash, we'd always be a step behind. On the first permalink click, no message would be highlighted. Every subsequent click on a permalink would highlight the previously clicked message. To get around that, we have to reference the currently clicked on node with the "this" keyword, and then manually find the hash it references.
Manually finding the hash from a link is a bit tricky as well, since the href attribute returns the full path (e.g. "file:///home/dgottlieb/blog.html#2") instead of just the hash ("#2"). We need to parse out the piece after the octothorpe (to be more correct we should probably ensure the string properly split into exactly 2 pieces). After parsing out the hash, we can reference the proper message (but we can't forget to add the octothorpe back in!) and highlight it. All of this extra work is needed because the click event gets fired before window.location updates.
Instead let's try binding the updateMessageHighlights function to the hashchange event:
<script type="text/javascript">
function updateMessageHighlights() {
jQuery(".message-highlight").removeClass("message-highlight");
var hash = window.location.hash;
jQuery(hash).addClass("message-highlight");
updateMessageHighlights();
jQuery(window).bind("hashchange", updateMessageHighlights);
There is roughly the same amount of code, but the function is much more robust. window.location.hash returns the empty string if the hash doesn't exist so we can now safely call this function on page load. Thus if a page is loaded directly with a message referenced (e.g file:///home/dgottlieb/blog.html#0), the proper message gets highlighted. The hashchange event also fires when a user hits the back button on the browser (the onclick event obviously wouldn't in that case). Debugging why the click event was working strangely also took a bit of time and a stroke of insight to understand, and this method is much simpler.
We use jQuery to help deal with cross-browser development. Unfortunately, jQuery itself does not support the hashchange event in a cross-browser way (window.onhashchange is only implemented in modern browsers). In this instance it didn't make sense for us to go out of the way to support older browsers, but if you need to there's a jQuery plugin that does just that.