Christian Heilmann

Posts Tagged ‘webdevtrick’

Pragmatic Progressive Enhancement

Tuesday, May 6th, 2008

Last week I went to AKQA in London to give a brown-bag presentation on progressive enhancement. I took this chance to vent some of my ideas on the subject and counteract some of the criticisms I heard about the need for enhancing web solutions progressively.

I’ve come up with the following “Seven rules of progressive enhancement”:

  1. Separate as much as possible
  2. Build on things that work
  3. Generate dependent markup
  4. Test for everything before you apply it
  5. Explore the environment
  6. Load on demand
  7. Modularize code

Instead of explaining them here, I’ve used a longer train ride to write up an article on the subject explaining the details of all the “rules” and examples of why and how to use them: Pragmatic Progressive Enhancement.

The article is licensed with creative commons, so you are very much invited to use and remix it to your needs.

I will upload my slides together with a video of the presentation once I got the material and checked if the video quality is good enough for publication.

The new web development challenge – independent modules in larger frameworks

Wednesday, March 5th, 2008

I just wrote a post on the YDN Developer blog how our work as web developers is very likely to change in the nearer future. I am not talking about the imminent coming of IE8 and its – thankfully – standard new rendering engine but about web architectural decisions I have witnessed in lots of talks with large corporations, big web sites and people that try to move their products into backend frameworks.

The new challenge is that the page as we know it will be very likely to die out soon and be replaced with a set of modules that can be customized for the user needs and by what we know of them or even opened up to third party developers. The success of the likes of facebook and the new MySpace developer framework are very likely to be just the start. A shame that the technologies and standards we use to develop clean and maintainable web products are not geared towards that kind of approach. Where is the cascade if everything should be self-contained?

Retrieving del.icio.us tags for the current URL with JavaScript

Monday, February 11th, 2008

If you scroll down the older entries of this blog you’ll see that there is a new feature, namely a box that shows reader tags and a link to del.icio.us:

Screenshot of a list of tags with a link to del.icio.us

This is not a WordPress plugin (although it would be easy to make and i’d be amazed if it hadn’t been done) but pure JavaScript. You can also download the script that does this and use the following to embed it in any page you’d like to know the delicious data for:




There is not much magic going on here, I basically souped up the example on the del.icio.us site,minified and embedded Paul Johnson’s implementation of MD5 in JavaScript and created the necessary HTML.

The HTML structure inside the DIV will be a definition list with tags as dd’s and the text as the dt and a paragraph with a link. You can style it by using the #deliciousinfo ID.

I like the outcome and I am always amazed what good tags readers of my stuff come up with. If you want to know, get the src commented version and check the information in there.

Shall I create a WordPress plugin for this?

Edit: if you wondered what the difference to the tagometer is, there isn’t much, I just forgot about it….

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?

A quick idea: JavaScript version controlling for static HTML documents

Monday, January 14th, 2008

When you write tutorials and you want people to use them wherever they are it is a good idea to offer the HTML documents as a zip for downloading. The benefit to the end user is that they don’t need to be online to look something up (I for example have the HTML 4.01 documents on my machine as HTML documents). The drawback is that the documents could be outdated without the user knowing – even when they are online while watching them.

Now, I pondered a bit about this and wondered if something like this wouldn’t be a solution:

  • You add a version number to the title of each document.
  • You add a remotely hosted versions.js script at the end of each document.
  • This script has a JSON object with the version information of each document and compares the file name and version.
  • If the version is outdated, it generates an error message that gets shown to the user.

You can try it out by downloading the two demo documents un-zip them and open them on a computer that is connected to the internet. The second document linked from the first one should be outdated.

The source of versions.js


(checkversion = function(){
// change as fit
var versions = {
‘documentexample.html’:’1.0’,
‘documentexample2.html’:’2.0’
};
var errorID = ‘versionerror’;
var errorMessage = ‘This document is outdated, please go to the homepage to download the new version!’;

// checking code
var d = document;
// get the version number
var cv = d.title.match(/(version (.*))$/);
// get the file name
var cn = window.location.href.split(‘/’);
cn = cn[cn.length-1].split(‘#’)[0];
// check and create error message if there is a mismatch
if(cv[1] && versions[cn]){
if(versions[cn] !== cv[1]){
if(!d.getElementById(errorID)){
var m = d.createElement(‘div’);
m.id = errorID;
m.appendChild(d.createTextNode(errorMessage));
d.body.insertBefore(m,d.body.firstChild);
}

}
}

}());

You could create both the titles and the JSON object in versions.js on the backend automatically by scanning the titles or from a version control system. What do you think?