Christian Heilmann

You are currently browsing the archives for the javascript category.

Archive for the ‘javascript’ Category

The great implementer swindle – making badge use easy doesn’t make it clever!

Tuesday, February 12th, 2008

No matter how you’ll turn it – the future of web design will not revolve around pages and sites but around modules. The social web and the systems providing canvasses to put up applications and badges is here and will be here to stay.
This is cool as it puts the end user much more in control of their own experience. However it is also bad as it means the standards we tried to set up and follow in the last, err 9 years are rapidly going down the drain.

The main reason is the promise of “quick wins” and “low hanging fruit” and once again the attempt to use technology to make the web a WYSIWYG and drag and drop interface.

Here’s my latest find that made me wonder why we don’t learn from errors of the past – making things easy to implement is not the same as making them easy to use!

Consider the job to be to offer third parties a chance to add a badge to their site. Not hard at all – the main task is really on the implementation level, i.e. making sure the styles of the parent side don’t clobber the badge.

Easy to implement badges

Now, to make it easy for the implementers, here’s the proposal:



Implementers can choose the size and the skin in attributes and give a brand name to display. This is pretty easy to understand and the script to make it work is no voodoo either:


(badge = function(){
var s = document.getElementsByTagName(‘script’);
for(var i=0;s[i];i++){
if(s[i].getAttribute(‘src’) 'badge.js'){
var div = document.createElement('div');
var content = s[i].firstChild.nodeValue;
div.innerHTML = '

Awesome badge!

';
div.appendChild(document.createTextNode(content));
var size = s[i].getAttribute('size');
var skin = s[i].getAttribute('skin');
var col,width;
switch(size){
case 'small':width = 100;break;
case 'large':width = 400;break;
default:width = 200;break;
}

switch(skin){
case 'blue':col = '#ccf';break;
case 'green':col = '#cfc';break;
default:col = '#ccc';break;
}

div.style.background = col;
div.style.width = width + 'px';
s[i].parentNode.replaceChild(div,s[i]);
}

}
})();

You loop through all script elements, compare the src with yours, read the attributes and the element content, assemble a badge, set the visual style and replace the script with the badge.

This makes implementation a breeze and allows the implementer even to choose the size and colour for each badge they use.

However there are several problems with this:

Invalid HTML

You cannot add custom attributes to HTML nilly-willy as it makes your HTML invalid. This is a tough one to make people understand as HTML validation is considered a nice-to-have than a real necessity. Years of lax browsers have made us immune to that issue. And, actually you could work around that with a custom DTD!

If you wondered if adding a src and content in the SCRIPT is invalid HTML - it isn’t. All the recommendations say is that user agents should ignore what is in the SCRIPT when there is a src attribute:

The script may be defined within the contents of the SCRIPT element or in an external file. If the src attribute is not set, user agents must interpret the contents of the element as the script. If the src has a URI value, user agents must ignore the element’s contents and retrieve the script via the URI. Note that the charset attribute refers to the character encoding of the script designated by the src attribute; it does not concern the content of the SCRIPT element.

Poor Performance.

This way of adding a badge loads the badge.js file for every instance. While it may be cached this is still an unnecessary overhead. The other issue is that every SCRIPT element makes the browser stop rendering until the script inside it or the one it links to with the src attribute is executed.

The more scripts you have in your document body, the slower your site will be! I can safely say that most of my firefox crashes are because of third party JS includes (really: ads). As a real badge implementation would do more HTTP calls – most of the time directly after rendering – this would add even more overhead.

Bad Accessibility and SEO

Non-JavaScript clients (like search robots) will not find anything with this kind of badge. Screen readers might get the badge but the replacement of a script after calling itself must be hard to stomach for any engine that ties into the browser’s DOM.

Alternative proposals

All of the following proposals here do require more of the implementer, but also make sure that none of the above problems occur. Using progressive enhancement we change a valid HTML structure that can be indexed by search spiders and end up with the same result.

All solutions work with one script include – in this case at the end of the document, but with an onload tweak this could also be in the head. This means the HTTP overhead is much less and your script does not get hit hard by every implementation.

Full-on progressive enhancement with a server-side fallback

This solution simply uses links to a proper backend solution that’ll offer a landing page for each brand. This means proper SEO as the links can be followed and the landing page becomes a starting point. The properties of the badge are URL parameters. While not all of them must be used by the server-side fallback they still can. You can never have too much data.





The script is not hard:


(fpebadge = function(){
var divs = document.getElementsByTagName(‘div’);
var b = [];
for(var i=0;divs[i];i++){
if(divs[i].className.indexOf(‘fpebadge’) ! -1){
b.push(divs[i]);
}

}
for(var i=0;b[i];i++){
var link = b[i].getElementsByTagName(‘a’)[0];
if(link){
var urldata = link.getAttribute(‘href’);
var badgecfg = convertURL(urldata);
var div = buildBadge(badgecfg);
if(div){
b[i].parentNode.replaceChild(div,b[i]);
}

}
}

function buildBadge(badgecfg){
if(badgecfg){
var div = document.createElement(‘div’);
div.innerHTML = ‘

Awesome badge!

‘;
if(badgecfg.brand){
div.appendChild(document.createTextNode(badgecfg.brand));
}

var col,width;
switch(badgecfg.size){
case ‘small’:width = 100;break;
case ‘large’:width = 400;break;
default:width = 200;break;
}

switch(badgecfg.skin){
case ‘blue’:col = ‘#ccf’;break;
case ‘green’: col = ‘#cfc’;break;
default:col = ‘#ccc’;break;
}

div.style.background = col;
div.style.width = width + ‘px’;
return div;
}

}
function convertURL(urldata){
if(urldata.indexOf(‘?’)!==-1){
var chunks = urldata.split(‘?’)[1].split(‘&’);
var badgecfg = {};
for(var i=0,j=chunks.length;i var vp = chunks[j].split(‘=’);
badgecfg[vp[0]] = vp[1];
}

}
return badgecfg;
}

})();

Progressive enhancement with a server-side landing page

If you don’t want to provide a branded landing page, but just have the name displayed and if you don’t trust your implementers to be able to properly encode ampersands :) then you can also just have a landing page and keep the badge properties in class names:




The code is not much different:


(pebadge = function(){
var divs = document.getElementsByTagName(‘div’);
var b = [];
for(var i=0;divs[i];i++){
if(divs[i].className.indexOf(‘landingbadge’) !== -1){
b.push(divs[i]);
}

}
for(var i=0;b[i];i++){
var badgecfg = convertClassData(b[i].className);
var link = b[i].getElementsByTagName(‘a’)[0];
if(link){
var linkdata = link.getAttribute(‘href’).split(‘brand=’)[1];
badgecfg.brand = linkdata;
}

var div = buildBadge(badgecfg);
if(div){
b[i].parentNode.replaceChild(div,b[i]);
}

}
function buildBadge(badgecfg){
if(badgecfg){
var div = document.createElement(‘div’);
div.innerHTML = ‘

Awesome badge!

‘;
if(badgecfg.brand){
div.appendChild(document.createTextNode(badgecfg.brand));
}

var col,width;
switch(badgecfg.size){
case ‘small’:width = 100;break;
case ‘large’:width = 400;break;
default:width = 200;break;
}

switch(badgecfg.skin){
case ‘blue’:col = ‘#ccf’;break;
case ‘green’: col = ‘#cfc’;break;
default:col = ‘#ccc’;break;
}

div.style.background = col;
div.style.width = width + ‘px’;
return div;
}

}
function convertClassData©{
var chunks = c.split(’ ‘);
var badgecfg = {};
for(var i=0,j=chunks.length;i var vp = chunks[j].split(‘-’);
if(vp[1]){
badgecfg[vp[0]] = vp[1];
}

}
return badgecfg;
}

})();

Hey, you use classes, why not provide a skin file?

Seeing that the last option actually uses classes, why not leave the styling to CSS and offer some skinning files the implementer can change? That way your badge script would be very small indeed:


(pebadge = function(){
var divs = document.getElementsByTagName(‘div’);
var b = [];
for(var i=0;divs[i];i++){
if(divs[i].className.indexOf(‘skinnedbadge’) !== -1){
b.push(divs[i]);
}

}
for(var i=0;b[i];i++){
var link = b[i].getElementsByTagName(‘a’)[0];
if(link){
var div = document.createElement(‘div’);
var linkdata = link.getAttribute(‘href’).split(‘brand=’)[1];
div.appendChild(document.createTextNode(linkdata));
div.innerHTML = ‘

Awesome badge!

‘;
b[i].parentNode.replaceChild(div,b[i]);
}

}
})();

You can extend that to load and apply CSS files on demand, but it’ll make more sense to keep them in one file, once again to cut down on HTTP overhead but also to use the cascade to produce small CSS files.

Comments? You can go and try out all the examples here

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?

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.