Christian Heilmann

Posts Tagged ‘javascript’

Five things to do to a script before handing it over to the next developer

Thursday, February 7th, 2008

Let’s face fact folks: not too many developers plan their JavaScripts. Instead we quickly write something that works, and submit it. We come up with variable and function names as we go along and in the end know that we’ll never have to see this little bit of script ever again.

The problems start when we do see our script again, or we get scripts from other developers, that were built the same way. That’s why it is good to keep a few extra steps in mind when it comes to saying “this is done, I can go on”.

Let’s say the job was to add small link to every DIV in a document with the class collapsible that would show and hide the DIV. The first thing to do would be to use a library to get around the issues of cross-browser event handling. Let’s not concentrate on that for the moment but go for oldschool onevent handlers as we’re talking about different things here. Using a module pattern we can create functionality like that with a few lines of code:

collapser = function(){
  var secs = document.getElementsByTagName('div');
  for(var i=0;i<secs.length;i++){
    if(secs[i].className.indexOf('collapsible')!==-1){
      var p = document.createElement('p');
      var a = document.createElement('a');
      a.setAttribute('href','#');
      a.onclick = function(){
        var sec = this.parentNode.nextSibling;
        if(sec.style.display === 'none'){
          sec.style.display = 'block';
          this.firstChild.nodeValue = 'collapse'
        } else {
          sec.style.display = 'none';
          this.firstChild.nodeValue = 'expand'
        }
        return false;
      };
      a.appendChild(document.createTextNode('expand'));
      p.appendChild(a);
      secs[i].style.display = 'none';
      secs[i].parentNode.insertBefore(p,secs[i]);
    }
  }
}();

This is already rather clean (I am sure you’ve seen innerHTML solutions with javascript: links) and unobtrusive, but there are some things that should not be there.

Step 1: Remove look and feel

The first thing to do is not to manipulate the style collection in JavaScript but leave the look and feel to where it belongs: the CSS. This allows for ease of skinning and changing the way of hiding the sections without having to mess around in the JavaScript. We can do this by assigning a CSS class and removing it:

collapser = function(){
  var secs = document.getElementsByTagName('div');
  for(var i=0;i<secs.length;i++){
    if(secs[i].className.indexOf('collapsible')!==-1){
      secs[i].className += ' ' + 'collapsed';
      var p = document.createElement('p');
      var a = document.createElement('a');
      a.setAttribute('href','#');
      a.onclick = function(){
        var sec = this.parentNode.nextSibling;
        if(sec.className.indexOf('collapsed')!==-1){
          sec.className = sec.className.replace(' collapsed','');
          this.firstChild.nodeValue = 'collapse'
        } else {
          sec.className += ' ' + 'collapsed';
          this.firstChild.nodeValue = 'expand'
        }
        return false;
      }
      a.appendChild(document.createTextNode('expand'));
      p.appendChild(a);
      secs[i].parentNode.insertBefore(p,secs[i]);
    }
  }
}();

Step 2: Remove obvious speed issues

There are not many issues in this script, but two things are obvious: the for loop reads out the length attribute of the secs collection on every iteration and we create the same anonymous function for each link to show and hide the section. Caching the length in another variable and creating a named function that gets re-used makes more sense:

collapser = function(){
  var secs = document.getElementsByTagName('div');
  for(var i=0,j=secs.length;i<j;i++){
    if(secs[i].className.indexOf('collapsible')!==-1){
      secs[i].className += ' ' + 'collapsed';
      var p = document.createElement('p');
      var a = document.createElement('a');
      a.setAttribute('href','#');
      a.onclick = toggle;
      a.appendChild(document.createTextNode('expand'));
      p.appendChild(a);
      secs[i].parentNode.insertBefore(p,secs[i]);
    }
  }
  function toggle(){
    var sec = this.parentNode.nextSibling;
    if(sec.className.indexOf('collapsed')!==-1){
      sec.className = sec.className.replace(' collapsed','');
      this.firstChild.nodeValue = 'collapse'
    } else {
      sec.className += ' ' + 'collapsed';
      this.firstChild.nodeValue = 'expand'
    }
    return false;
  }
}();

Step 3: Removing every label and name from the functional code

This makes a lot of sense in terms of maintenance. Of course it is easy to do a quick search + replace when the label names or class names have to change, but it is not really necessary. By moving everything human readable into an own config object you won’t have to hunt through the code and suffer search + replace errors, but instead keep all the changing bits and bobs in one place:

collapser = function(){
  var config = {
    indicatorClass : 'collapsible',
    collapsedClass : 'collapsed',
    collapseLabel : 'collapse',
    expandLabel : 'expand'
  }
  var secs = document.getElementsByTagName('div');
  for(var i=0,j=secs.length;i<j;i++){
    if(secs[i].className.indexOf(config.indicatorClass)!==-1){
      secs[i].className += ' ' + config.collapsedClass;
      var p = document.createElement('p');
      var a = document.createElement('a');
      a.setAttribute('href','#');
      a.onclick = toggle;
      a.appendChild(document.createTextNode(config.expandLabel));
      p.appendChild(a);
      secs[i].parentNode.insertBefore(p,secs[i]);
    }
  }
  function toggle(){
    var sec = this.parentNode.nextSibling;
    if(sec.className.indexOf(config.collapsedClass)!==-1){
      sec.className = sec.className.replace(' ' + config.collapsedClass,'');
      this.firstChild.nodeValue = config.collapseLabel
    } else {
      sec.className += ' ' + config.collapsedClass;
      this.firstChild.nodeValue = config.expandLabel
    }
    return false;
  }
}();

Step 4: Use human-readable variable and method names

This is probably the most useful step when it comes to increasing the maintainability of your code. Sure, during development sec made a lot of sense, but doesn’t section make it easier to grasp what is going on? What about a, and especially when it needs to be changed to a button later on? Will the maintainer rename it to button?

collapser = function(){
  var config = {
    indicatorClass : 'collapsible',
    collapsedClass : 'collapsed',
    collapseLabel : 'collapse',
    expandLabel : 'expand'
  }
  var sections = document.getElementsByTagName('div');
  for(var i=0,j=sections.length;i<j;i++){
    if(sections[i].className.indexOf(config.indicatorClass) !== -1){
      sections[i].className += ' ' + config.collapsedClass;
      var paragraph = document.createElement('p');
      var trigger = document.createElement('a');
      trigger.setAttribute('href','#');
      trigger.onclick = toggleSection;
      trigger.appendChild(document.createTextNode(config.expandLabel));
      paragraph.appendChild(trigger);
      sections[i].parentNode.insertBefore(paragraph,sections[i]);
    }
  }
  function toggleSection(){
    var section = this.parentNode.nextSibling;
    if(section.className.indexOf(config.collapsedClass) !== -1){
      section.className = section.className.replace(' ' + config.collapsedClass,'');
      this.firstChild.nodeValue = config.collapseLabel
    } else {
      section.className += ' ' + config.collapsedClass;
      this.firstChild.nodeValue = config.expandLabel
    }
    return false;
  }
}();

Step 5: Comment, sign and possibly eliminate the last remaining clash with other scripts

The last step is to add comments where they are really needed, give your name and date (so people can ask questions and know when this was done), and to be really safe we can even get rid of the name of the script and keep it an anonymous pattern.

//  Collapse and expand section of the page with a certain class
//  written by Christian Heilmann, 07/01/08
(function(){
 
  // Configuration, change CSS class names and labels here
  var config = {
    indicatorClass : 'collapsible',
    collapsedClass : 'collapsed',
    collapseLabel : 'collapse',
    expandLabel : 'expand'
  }
 
  var sections = document.getElementsByTagName('div');
  for(var i=0,j=sections.length;i<j;i++){
    if(sections[i].className.indexOf(config.indicatorClass)!==-1){
      sections[i].className += ' ' + config.collapsedClass;
      var paragraph = document.createElement('p');
      var triggerLink = document.createElement('a');
      triggerLink.setAttribute('href','#');
      triggerLink.onclick = toggleSection;
      triggerLink.appendChild(document.createTextNode(config.expandLabel));
      paragraph.appendChild(triggerLink);
      sections[i].parentNode.insertBefore(paragraph,sections[i]);
    }
  }
  function toggleSection(){
    var section = this.parentNode.nextSibling;
    if(section.className.indexOf(config.collapsedClass)!==-1){
      section.className = section.className.replace(' ' + config.collapsedClass,'');
      this.firstChild.nodeValue = config.collapseLabel
    } else {
      section.className += ' ' + config.collapsedClass;
      this.firstChild.nodeValue = config.expandLabel
    }
    return false;
  }
})();

All very obvious things, and I am sure we’ve all done them before, but let’s be honest: how often do we forget them and how often do you have to alter code where it’d have been nice if someone had taken these steps?

JavaScript countdown solution

Tuesday, February 5th, 2008

This is not going to be amazing, but I had to find / write a script like that for every hackday / barcamp I attended so far. Being lazy, I just wanted to create one I can re-use later on. So here you are:

You can either use the counter directly, or right-click and save it for local use. The look and feel is all in the CSS, yo u can set some of the preferences in the counter interface and there are several configuration settings you can change:


var cfg = {
displayID:’display’, // element to show the seconds
startButtonID:’c’, // start button ID
pauseButtonID:’p’, // pause button ID
finalClass:’final’, // class to add when final coutdown is reached
overClass:’over’, // class to add when the time is over
initialText:{
value:’2:00’,
label:’Initial Text’
},
seconds:{
value:2*60,
label:’Time in Seconds’
},
finalCountdown:{
value:30,
label:’Time when the warning starts’
},
pauseLabel:{
value:’pause’,
label:’Pause Text’
},
resumeLabel:{
value:’resume’,
label:’Resume Text’
}

};

Some configuration settings have values and labels, this is because of the preferences form being created from this object.

As said, nothing special, but I hope you can use it, I know I will.

Code tutorials for lazy people with Ajax Code Display

Monday, January 28th, 2008

Currently I am writing a lot of tutorials for an online self-training course about web standards and I ran into the annoyance of having to maintain example code in two places: the code itself and the HTML document with the explanations. Therefore I took jQuery and wrote a small script that automatically turns links to HTML code examples with HTML entities and line numbers. You can define which lines to display, which lines should be highlighted and you can add a live preview in an IFRAME when the link is clicked.

My roadblock of one – now also blogging for Ajaxian and Yahoo Developer Network

Friday, January 25th, 2008

Today was a great day: I finally got my Macbook Pro to work with, got myself one of those fancy new slick keyboards and went for it answering a lot of emails. It is amazing how many more emails you can answer when you don’t have to wait for virus scans in the background and Outlook to get a move on.

Not only did I get some good leeway on what I am going to do this year (amongst other things a trip to Beijing to talk to developers about best practices and going back to Germany to talk to Flash developers), I also finally got some other answers I have been waiting for.

To cut it short, I am now not only sneakily blogging for the Yahoo Developer Network blog but was asked to contribute more often. What is even more surprising (considering my history of snide comments there) is that I am now also blogging on Ajaxian.

So if you have some good JavaScript/Ajax or general web development stuff that needs coverage, please drop me a line. I am available here (either via comment or email), on twitter or pownce.

I love the idea of being able to reach a lot of readers and developers through very successful and large channels and it shows me once again how powerful the web is as a media. When I worked as a radio journalist, this wouldn’t have been possible as everything I did had to be connected to my employer exclusively :)

The Art and Science of JavaScript arrived

Thursday, January 24th, 2008

My chapter in The Art and Science of JavaScript

My latest contribution to the ink-on-dead-tree media is a chapter for Sitepoint’s new book
The Art and Science of JavaScript. I’ve been giving details about the history and the contents of the book in detail in a blog post on the Yahoo Developer Blog and while it has been out for a while I just got my free copies today, hence the delay.

My chapter in detail covers how you can build a badge to display information you stored on another site in yours without having to resort to a server side solution or slow down your site. All the magic happens after the page has been loaded and if there is no JavaScript available, visitors will still see a link to the same online resource.

It is a detailed explanation of the rationale and script that feeds my del.icio.us plugin for wordpress shown below:

[delicious:My links about JavaScript,codepo8,10,javascript]

Whilst not the flashiest of the chapters I hope that people can learn something about APIs, REST and dynamic script node generation from it.

The art and science of JavaScript

I was personally very positively surprised by the quality of the book itself: the full colour print, typography and iconography are very nice. The only thing that is missing is an author name or short bio on the chapter start page, it is a bit tricky to know who did what. Well done Sitepoint!