Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

How to Create a jQuery Code-Snippet Box

by
Gift

Want a free year on Tuts+ (worth $180)? Start an InMotion Hosting plan for $3.49/mo.

In this tutorial we're going to be creating a code-snippet box that displays example code on web pages in an attractively formatted and easy to read way, much like what you see here on Nettuts. Code-snippet preview boxes like this are often made as Wordpress plugins, but we'll see how we can do the same thing with just a little jQuery and CSS.

We can add all of the expected features such as line-numbering and line striping, and all of the usual tools such as viewing the code as plain text and printing it in a printer-friendly manner. The following screenshot shows the kind of thing that we'll end up with by the end of the tutorial:



Getting Started

The code-snippet box will be built from an underlying <pre> element; this is the perfect element to use because it renders whatever you put into it as plain text in a fixed-width font such as monospace, which is ideal for showing code samples in a clear and concise manner. It also preserves line-breaks which is a feature we can exploit in order to add the line numbering and striping features.

Another reason we use the <pre> tag is because it makes the ideal foundation for our code box; in browsers with JavaScript disabled, or those that simply don't support it, the code will still be rendered acceptably. It'll stand out from the rest of the text on the page and it won't degenerate into something completely useless and ridiculous.

Start off with the following page in your text editor:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> 
<html> 
  <head> 
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 
    <title>jQuery Code-snippet Box</title> 
    <link rel="stylesheet" type="text/css" href="code-snippet.css"> 
  </head> 
  <body> 
    <hr /><h2>CSS Code:</h2> 
    <pre class="snippet">#selector { color:#000000; } 
.another-selector { font-size:20px; border:1px solid #000000; padding:20px; } 
tagname { width:100%; }</pre> 
    <script type="text/javascript" src="jquery-1.3.2.js"></script> 
    <script type="text/javascript"> 
    </script> 
  </body> 
</html>

Save the page as code-snippet.html in a directory that already has the current version of jQuery residing within it. With no styling and no script intervention the page should look like this:


As a default fall-back it's acceptable; if the page had other body text on it, the <pre> element would stand out as a separate block of text and would still appear a little code-like.


Beginning the scripting

Let's make a start by adding the striping effect that will make each line stand out from those above and below it, and the line numbering which will show when a broken line (which overflows on to two or more lines) is part of one line of example code.

In the empty <script> element at the bottom of the <body> add the following code:

$(function() { 
	     
  //process contents of snippets 
  $("pre.snippet").each(function(){ 
				   
  //get contents of <pre> and create list and container 
  var currentItem = $(this), 
    currentContents = currentItem.text(), 
    contents = (currentItem.text().indexOf("\n") != -1) ? currentItem.text().split("\n") : currentItem.text().split("\r"),		   
    list = $("<ol>"), 
    container = $("<div>").addClass("snippet-container").insertBefore(currentItem.prev()); 
					   
  //remove original contents and wrap in container 
  currentItem.text("").prev().andSelf().appendTo(container); 
		  		   
  //normalize height 
  (currentItem.height() > 0) ? list.css("marginTop", -16) : null; 
				   
  //create list item 
  $.each(contents, function(i, val) { 
		     
  //create list item and inner p 
  var li = $("<li>").appendTo(list), 
    p = $("<p>").text(val).appendTo(li); 
 
    //add stripe class 
    (i%2 === 0) ? li.addClass("stripe") : li.addClass("unstripe");		   
    }); 
		   
    list.appendTo(currentItem); 
  }); 
});

Let's step through each part of the code to see what it does; first we select each <pre> element that has the snippet class name and for each one that is found we execute an anonymous function. Within this function we first set five variables; the first variable is the selector for the current <pre> element, which we cache for performance reasons. We'll be using the $(this) reference repeatedly in our code so it makes sense to store it in a variable for quick access.

The next variable will store the whole text contents of the current <pre> and the next will store an array where each item in the array contains a single line of text from the <pre> element. The length of the array will match the number of lines of text in the <pre> element. Because line-breaks within <pre> elements are preserved, we can easily detect these and perform a split based on them.

Most browsers detect line-breaks in JavaScript using the \n newline character so we first see whether the inner text of the <pre> element contains any of these and then use them if it does. IE detects line-breaks differently from other browsers so if no \n characters are found we perform the split using the \r carriage-return character instead.

The next variable contains a new ordered-list element; using an <ol> element is perfect because it will handle the line numbering of the code snippet for us automatically. The final variable points to a new container <div> which we give a class name to and then append to the page directly before the <h2> element that precedes the snippet box.

Creating the array of the <pre> element's contents is not a destructive operation, so we have to remove the original contents to avoid duplicating it, which we can do by using an empty string with jQuery's text() method. We also move the <h2> and the <pre> element into our newly created container element.

Firefox seems to insert a line-break into the <pre> element, which gives the element height when it shouldn't have any height at all (as we've removed the contents). To counter this we detect whether the empty <pre> element has height greater than 0, and if so we set the margin-top of our new <ol> element to pull it up to the top of the element. It's only Firefox that does this, but it's better to check for height instead of checking for Firefox. Feature detection over browser sniffing wins every time.


Striping

The next section of code deals with adding the classes we can use for the striping effect; as we have put the contents of the <pre> element into an array and not a jQuery object we can't use the standard each method like we did with the <pre> elements on the page.

Instead we can use the jQuery each utility method which is called directly on the jQuery $ object. The method accepts 2 arguments, the first is the object to iterate over, in this case our contents array, and the second is an anonymous function to execute for each item in the array. Our function is automatically passed two arguments; i is the index of the item currently being iterated and val is the contents of the array item.

Within our function we first create a new list item for our <ol> and insert a new paragraph element containing the contents of the current array item. The new item is append to the <ol> element that we created earlier. We then add the striping classes; to do this we use the JavaScript modulus operator to determine whether the current array index is odd or even; for each even index we add the class stripe and for each odd index we add the class unstripe. Using 2 classes for striping means that we can easily set both stripe colours instead of insisting that one of them is white.

At this stage our example page should now appear like this:



Snippet Styling

Now we can add the styling for the code-snippet widget and the striping; in a new file in your text editor add the following code:

.snippet-container { 
  width:630px; padding:10px 10px 30px; border:1px solid #999999; 
  background-color:#e1e1e1; margin-bottom:10px; position:relative; 
} 
.snippet-container h2 { 
  margin:0 0 10px; padding-left:10px; font-family:Georgia; 
  font-weight:normal; font-style:italic; 
} 
.snippet { border:1px solid #999999; margin:0; } 
.snippet ol { 
  margin:0; padding-left:34px; background-color:#8dd397; color:#ffffff; 
  font-weight:bold; font-style:italic; 
} 
.snippet ol li { padding-left:10px; } 
.snippet ol p { 
  margin:0; color:#000000; font-weight:normal; font-style:normal; 
  white-space:pre-wrap; padding:3px 0; 
} 
.snippet .stripe { background-color:#f5f5f5; } 
.snippet .unstripe { background-color:#ffffff; }

Save this as code-snippet.css in the same directory as the example page. Most of this CSS is purely for decoration and has been arbitrarily chosen according to my own preferences. You can change any of the fonts, colours, the width or anything at all. The most important functional CSS we set is the relative positioning on each snippet box and the white-space rule on the inner <p> elements in each list item; if we don't set this to pre-wrap the text for each line will overflow the snippet container when it is too long instead of breaking to the next line.

With the addition of our style sheet, the snippet box now appears like this:



Other types of code

In this example so far we've used some very simple CSS as the example code shown in the snippet box. Can we add any other type of code that we want? Let's take a look. Directly after the first <pre> element on the page add the following code:[/sourcecode]

<pre>[sourcecode language="snippet" originalClass="snippet"]<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> 
<html> 
  <head> 
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 
    <title>An Example Web Page</title> 
  </head> 
  <body> 
    <p>This is a basic web page</p> 
  <body> 
<html>

Everything is pretty much what we had before with one major difference; we can't use the < character when displaying HTML tags within our example code. If we were to put <html> in our <pre> tag, it would be interpreted as an <html> element and cause an HTML warning. To overcome this we can specify the HTML entity

<

which will be changed into a < character by the browser without the browser thinking it is an actual element. It's probably best to show any code that uses < characters in this way to prevent accidental interpretation by the browser. Here's how the second snippet box should appear:


I've stuck to the indentation convention that I normally use, which is 2 standard spaces for nested elements; this is a habit I've gotten into as a result of needing to showing lots of potentially wide lines of code in fixed-width documents. It's not the standard convention, which is four single spaces or one tab space. You can use whatever convention you prefer when deploying the snippet box on your own site as both single spaces and tab spaces are preserved by the pre tag.


Snippet Tools

Another feature of code snippet boxes that we can easily add are a series of tools that help the visitor to use the code; if you try copy and pasting the code from one of the snippet boxes into a text editor, you'll see that the line numbers from the ordered list are also copied to the new page. This is unfortunate as leaving these numbers in will probably stop the example code from working. One thing we can do is provide a mechanism where the code can be shown without the line numbers.

Directly after the line of code that reads list.appendTo(currentItem); add the following code:

//create tools 
var toolContainer = $("<div>").addClass("tools-container"), 
  toolList = $("<ul>").addClass("tool-list").appendTo(toolContainer), 
  listItem1 = $("<li>").addClass("tool-item").text("|").appendTo(toolList), 
  listItem2 = $("<li>").addClass("tool-item").appendTo(toolList), 
  plain = $("<a>").addClass("tool").attr({ id: "plain", href: "#", title: "Show code as plain text" }).text("Show Plain").prependTo(listItem1).click(function(e) { 
  //stop event 
  e.preventDefault(); 
			   
    //create container for plain code 
    if ($(this).closest(".snippet-container").find(".snippet").children(".display-box").length === 0) { 
      var displayBox = $("<div>").addClass("display-box").height($(this).closest(".snippet-container").find(".snippet").height()).text(currentContents).appendTo(currentItem); 
    } 
			   
    //show or hide the plain code 
    $(this).closest(".snippet-container").find(".snippet").children(".display-box").slideToggle(); 
 
    //change text on the link 
    ($(this).text() == "Show Plain") ? $(this).text("Show Pretty") : $(this).text("Show Plain"); 
  }), 
  printBox = $("<a>").addClass("tool").attr({ id: "print", href: "#", title: "Print Code" }).text("Print").prependTo(listItem2).click(function(e) { 
  //stop event 
  e.preventDefault(); 
 
  //open new page to print from 		   
  var title = currentItem.prev().text(), 
    currentSnippet = $(this).closest(".snippet-container").find(".snippet"), 
    printWindow = window.open("", "printWindow", "width=" + (currentSnippet.width() + 25) + ", height=" + (currentSnippet.height() + 50)); 
 
  printWindow.document.write("<hr /><h2>" + title + "<\/h2><pre id='code'><\/pre>"); 
  $("#code", printWindow.document).text(currentContents); 
  printWindow.print();			   
}); 
		   
toolContainer.appendTo(container);

All we do is create a whole bunch of variables, much like what we did at the start of the first chunk of code earlier; remember that we're still in the context of the each() method called on all of the <pre> elements on the page. We first create a container for the new tools and give it a class name for styling purposes, but we don't append it to the snippet straight away.

We then create a new unordered-list element and two list items, which are class names and appended to their respective containers. The first list item is given an innerText of the pipe character to act as a separator.


Creating the tools

We then create the first tool, which will be used to show a plain-text version of the code; we add a class name for styling and the required attributes. The link element we create also has an event handler attached in the form of an anonymous callback function for when it is clicked.

Within this function we first create a new container for the plain text, but only after checking that the container doesn't already exist; this will act as a basic lazy-load type mechanism which won't create the plain code elements until the anchor is clicked for the first time. The callback is executed each time the anchor is clicked so we need to check whether the container already exists to prevent a build-up of multiple containers.

We then use jQuery's slideToggle() effect to show or hide the plain code depending on whether it is already visible. We also change the text of the link each time it is clicked which gives the visitor an easy way of switching between the plain and pretty code views.

The next and last variable we create is another anchor element which will be used at the print tool; this element also has an event handler attached which will be executed each time it is clicked. Within the event handler we start off by creating some variables again, caching the text contents of the code snippet's heading element and the current code snippet <pre> element.

We also create a new window using traditional JavaScript's open() method; the first argument this method takes is the URL of the page to open; we don't have a pre-existing page , we're going to construct it from scratch, so we set this argument to an empty string. The second argument is the name of the new window, which is very important as we refer to it several times in a moment. Lastly we set the dimensions of the new window so that it is a little bigger than the original code box.

Once the window is created we then use document.write on the window addressing the name we gave to it, and write a heading containing the contents of the heading element we cached, and an empty pre element, which we give an id to. Using document.write feels so wrong after using jQuery, but unfortunately it's the only method that works.

Once the new window has the <pre> element we can then use jQuery to select it using it's id by supplying a reference to the new window as the context of the selector. We then add the text contents of the original <pre> element which we cached way back at the start of the script and finally call the print() JavaScript method to print the new window.

The JavaScript print() method is very basic, it just prints out the entire page; we can't tell it to only print selected items which is why we create the new page containing just the relevant heading and the plain code and print that instead. There are ways in which we could do it without creating the new page; we could add a print class name to every element on the page and use that to hide them with CSS. On large pages with lots of elements though, this would be massively inefficient. Also, we can't determine when the printing has finished so showing the elements again after it has been printed would also prove troublesome.

Now all of the tools have been created we can finally add them to the page by appending them to each of the snippet containers.


Tool Styling

The new tools will also need some CSS so tidy up their appearance and position them; at the bottom of code-snippet.css add the following new code:

.tools-container { position:absolute; right:10px; } 
.tool-list { margin:6px 0 0; } 
.tool-list li { 
  list-style-type:none; float:left; font-weight:bold; font-size:13px; 
} 
.tool-list li a { 
  text-decoration:none; font-family:Georgia; font-weight:normal; 
  font-style:italic; font-size:11px; color:#000000; padding:0 5px; 
  outline:1px dotted #e1e1e1; 
} 
.tool-list li a:hover { text-decoration:underline; } 
.snippet .display-box { 
  position:absolute; top:0; left:0; width:100%; background-color:#ffffff; 
  display:none; white-space:pre-wrap; 
}

Again, it's mostly arbitrary styles which personally I think look good. Feel free to change them, although I think the bottom of the code box is a good place for them, and is somewhere that we have intentionally left space for, so if you do want to relocate them, you might want to adjust the bottom padding of the snippet container. The new tools should appear like this:


Whenever the plain link is clicked, the plain code container should slide into view and the label of the link will change:


When the print tool is selected, the new window containing the code, and the print dialog box should both appear:


Note that this is relatively slow in IE8 and a little temperamental, sometimes the print box will open and sometimes it won't. Additionally, the print box does not appear in Opera.


Summary

With just a little bit of semantic HTML, some presentational CSS and jQuery, we can easily present code examples attractively and clearly on our web pages. We looked at the initial semantic HTML that remains distinct and readable, and how to show code that uses the < character.

We looked at how easy it is to add effective line numbering and striping to make the code examples more readable. We also saw how we can easily add a couple of tools to the code boxes that make copying the code accurately or printing the code easier for our visitors.

Advertisement