We've moved!

TechKnack.blogspot.com has officially moved to TechKnack.net. You should be redirected in 3-5 seconds. Thank you.

February 12, 2008

Progressively Enhance copyable code blocks

Add this post to Del.icio.us. Del.icio.us (0 saved)

I've employed a bit of progressive JS here on the blog to make it easier for readers to copy the blocks of code that I post. Go ahead, try it: click within the code block, and the entire thing is selected. Makes for pretty easy copying, rather than having to carefully select just the text inside the box...
// I'm code!! Copy me!! $ hello world // w00t...

The way it works

Here's how it works: I've defined a function which goes over every single <code> element on the page. For each code element, a textarea element is created and inserted into the DOM tree directly before the corresponding code element. Once a textarea is added to the DOM, it is hidden via CSS. The textarea is given, as child nodes, recursive clones of code's DOM child nodes. Then, the onclick event of the code element and the onblur event of the textarea are set to different functions. The code's onclick function hides the code element, un-hides the textarea element gives the textarea the focus, and selects the textarea's contents. The textarea's onblur function hides the textarea and un-hides the corresponding code element.

It's possible to have the script do this for all code elements on the page, but I've implemented it to only target those code elements to which I've given a class name of "copyable".


The hiding-un-hiding tricks aren't possible without CSS. Well, they're possible, but such an implementation would make the JS much messier. Here I've used CSS to similarly style both code elements and elements with a class name of "codeenhance" (which is used to identify the inserted textareas).
.codeenhance, code { width:430px; color:#000; display:block; background:#eee; padding:0 10px; padding-bottom:1em; margin:0; border-width:3px 1px !important; border:#99f solid; overflow:auto; line-height:1.4em; font-family:monospace; font-size:1.1em; } .codeenhance, .copyable { height:250px; } code { white-space:pre; } .codeenhance { padding:0; } .codeenhance-off { display:none; } The first declaration is the main styling for the code elements. The height specification for .codeenhance and .copyable fix the height of copyable code blocks, so your 159 lines of copyable JS don't take up too much space. I've left the height out of the main declaration, to allow the other code blocks to expand/retract to their contents' size. The white-space declaration for code elements maintains the whitespace as you typed it. The padding declaration for .codeenhance fixes an interesting jumping issue with the textareas. And the display declaration for .codeenhance-off is what hides the appropriate items.

The Javascript

Here's the JS. The explanations are in the code comments ;)
// Enable click-select on code elements function enhanceCode() { // Get all code tags var codes = document.getElementsByTagName("code"); // loop over code tags for (i=0; i<codes.length; i++) { // working only on tags of class "copyable" if (codes[i].className.match('copyable')) { // for each tag, create a new textarea var text = document.createElement("textarea"); // copy the code's children over for (j=0; j<codes[i].childNodes.length; j++) { // special code for Blogger, to take care of extra br elements if (codes[i].childNodes[j].nodeName == "BR") text.appendChild(document.createTextNode('\r\n')); else text.appendChild(codes[i].childNodes[j].cloneNode(true)); } // set the initial classname // (use .className rather than .setAttribute('clas', 'blah') because IE6 // doesn't like the latter text.className = "codeenhance codeenhance-off"; // setup the onblur event text.onblur = decodex; // insert the textarea before the code codes[i].parentNode.insertBefore(text, codes[i]); // setup the onclick event codes[i].onclick = codex; } } } // this = code element; this.previousSibling = textarea element function codex() { // show the textarea this.previousSibling.className = this.previousSibling.className.replace(/ ?codeenhance\-off/, ""); // hide the code this.className = this.className+" codeenhance-off"; // focus and select the textarea this.previousSibling.focus(); this.previousSibling.select(); } // this = textarea; this.nextSibling = code function decodex() { // show the code this.nextSibling.className = this.nextSibling.className.replace(/ ?codeenhance\-off/, ""); // hide the textarea this.className = this.className+" codeenhance-off"; } window.onload = function() { // run the enhancement /after/ the window has loaded enhanceCode(); }

No comments: