Source code line numbers and JavaScript

A few months ago, I started adding line numbers to some of the programs/scripts I post here. The idea was to make it easier for me to describe the code; I could say things like “look at lines 15-19” instead of “look at the if statement that checks the first line of the file.” I wrote a Python script called “anl” to add line numbers to my code, a JavaScript function called “styleLN” that put the line numbers between <span></span> tags, and some CSS to style those <span>s to make the line numbers easily visible but unobtrusive.

I was happy with the aesthetics of the results. The scripts looked good, and the descriptions of them became both clearer and less verbose. But some usability was lost, because readers could no longer simply copy the code out of my posts and past them into their editors—the line numbers had to be cut out before the scripts would run. My usual text editor, TextMate, has a column selection feature that makes this very easy, but TextMate isn’t universal, and even I don’t use it for AppleScripts.

What I really wanted was a way to toggle the display of the line numbers—they could be on when you’re reading the description of the code and off when you want to select and copy it. I knew I had seen buttons that would do this on other web sites, but I couldn’t remember where they were. So I wrote my own.

The differences between the new version of “styleLN” and the previous version are:

Here it is:

 1:  function styleLN() {
 2:    var isIE = navigator.appName.indexOf('Microsoft') != -1;
 3:    if (isIE) return;
 4:    var preElems = document.getElementsByTagName('pre');
 5:    if (0 == preElems.length) {   // no pre elements; stop
 6:       return;
 7:    }
 8:    for (var i = 0; i < preElems.length; i++) {
 9:      var pre = preElems[i];
10:      var code = pre.getElementsByTagName('code')[0];
11:      if (null == code) {        // no code; move on
12:        continue;
13:      }
14:      var oldContent = code.innerHTML;
15:      var newContent = oldContent.replace(/^( *)(\d+):(  )/mg, 
16:                 '<span class="ln">$1$2$3<' + '/span>');
17:      if (oldContent.match(/^( *)(\d+):(  )/mg)) {
18:        newContent += "\n" + '<button onclick="toggleLN(this.parentNode)">Toggle line numbers</button>';
19:      }
20:      code.innerHTML = newContent;
21:    }
22:  }

You can see on line 18 that the onclick handler for the toggle button is a new JavaScript function called “toggleLN.” Here it is:

1:  function toggleLN(code) {
2:    for (var i=0; i<code.childNodes.length; i++){
3:      node = code.childNodes[i];
4:      if (node.nodeName == 'SPAN'){
5:        if (node.style.display == 'none') node.style.display = '';
6:        else node.style.display = 'none';
7:      }
8:    }
9:  }

I don’t have much JavaScript DOM experience, so there may be a nicer way to write this function. It seems to work, but I’m open to any suggestions for improvement.

The purpose of “toggleLN” is to go through the <code> element associated with the clicked button and turn off the display of all the <span> elements within it (the line numbers are inside the <span>s). You should note that “styleLN” put the <button> element between the <code></code> tags. I don’t know if that’s considered good HTML style, but it has the advantage of making the <code> element the parent of the <button> and easy to pass into “toggleLN” via this.parentNode.

All the line-numbered code in this blog should now have a “Toggle line numbers” button at the bottom. It works for me and I hope it works for you.

Update
Late night coding always gets you in trouble. The toggle button doesn’t work in Firefox 2 or Camino 1.5. (Now fixed! It works for both Mac and Windows Firefox.) From Jamie Phelps, I hear that the layout on IE 6 is a disaster, which should be a CSS thing and unchanged from before, so I don’t know what that’s about. I’ll make inquiries into IE7’s behavior today and try to get things fixed.

Later update
My tests with IE7 suggest that it wasn’t doing the replace command correctly; some of the line numbers were being surrounded by <span></span> and some weren’t. This was buggering up the entire block and making the whole thing render as one long line. Since my scripts aren’t particularly useful for Windows users anyway, I’m not going to spend any more time trying to fix this. I just added a test to lines 2 and 3 of “styleLN” that exit the script immediately if the browser is IE.

The upshot is: if you’re using IE, you’ll see the my line-numbered scripts in the raw—the line number, a colon, two spaces, and then the code itself. All monospaced and the same size and color. It’s readable, but not nice looking, which is probably what you’re used to with IE.

Tags: