• You are currently browsing the archives for the badges category.

  • Archive for the ‘badges’ 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:

    
    <script src="badge.js" size="small" skin="blue">Brandname</script>
    

    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 = '<p>Awesome badge! </p>';
    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.

    
    <div class="fpebadge"><a href="http://realurl?brand=brandname1&skin=green&size=small">Information sponsored by brandname1</a></div>
    <div class="fpebadge"><a href="http://realurl?brand=brandname2&skin=blue&size=large">Information sponsored by brandname2</a></div>
    <div class="fpebadge"><a href="http://realurl?brand=brandname3">Information sponsored by brandname3</a></div>
    

    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 = &#8216;<p>Awesome badge! </p>&#8216;;
    if(badgecfg.brand){
    div.appendChild(document.createTextNode(badgecfg.brand));
    }
    var col,width;
    switch(badgecfg.size){
    case &#8216;small&#8217;:width = 100;break;
    case &#8216;large&#8217;:width = 400;break;
    default:width = 200;break;
    }
    switch(badgecfg.skin){
    case &#8216;blue&#8217;:col = &#8216;#ccf&#8217;;break;
    case &#8216;green&#8217;: col = &#8216;#cfc&#8217;;break;
    default:col = &#8216;#ccc&#8217;;break;
    }
    div.style.background = col;
    div.style.width = width + &#8216;px&#8217;;
    return div;
    }
    }
    function convertURL(urldata){
    if(urldata.indexOf(&#8216;?&#8217;)!==-1){
    var chunks = urldata.split(&#8216;?&#8217;)[1].split(&#8216;&#38;&#8217;);
    var badgecfg = {};
    for(var i=0,j=chunks.length;i<j;i++){
    var vp = chunks[j].split(&#8216;=&#8217;);
    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:

    
    &lt;div&nbsp;class="landingbadge&nbsp;skin-green&nbsp;size-small"&gt;&lt;a&nbsp;href="http://realurl?brand=brandname1"&gt;Information&nbsp;sponsored&nbsp;by&nbsp;brandname1&lt;/a&gt;&lt;/div&gt;
    &lt;div&nbsp;class="landingbadge&nbsp;skin-blue&nbsp;size-large"&gt;&lt;a&nbsp;href="http://realurl?brand=brandname2"&gt;Information&nbsp;sponsored&nbsp;by&nbsp;brandname2&lt;/a&gt;&lt;/div&gt;
    

    The code is not much different:

    
    (pebadge&nbsp;=&nbsp;function(){
    var&nbsp;divs&nbsp;=&nbsp;document.getElementsByTagName('div');
    var&nbsp;b&nbsp;=&nbsp;[];
    for(var&nbsp;i=0;divs[i];i++){
    if(divs[i].className.indexOf('landingbadge')&nbsp;!==&nbsp;-1){
    b.push(divs[i]);
    }
    }
    for(var&nbsp;i=0;b[i];i++){
    var&nbsp;badgecfg&nbsp;=&nbsp;convertClassData(b[i].className);
    var&nbsp;link&nbsp;=&nbsp;b[i].getElementsByTagName('a')[0];
    if(link){
    var&nbsp;linkdata&nbsp;=&nbsp;link.getAttribute('href').split('brand=')[1];
    badgecfg.brand&nbsp;=&nbsp;linkdata;
    }
    var&nbsp;div&nbsp;=&nbsp;buildBadge(badgecfg);
    if(div){
    b[i].parentNode.replaceChild(div,b[i]);
    }
    }
    function&nbsp;buildBadge(badgecfg){
    if(badgecfg){
    var&nbsp;div&nbsp;=&nbsp;document.createElement('div');
    div.innerHTML&nbsp;=&nbsp;'&lt;p&gt;Awesome&nbsp;badge!&nbsp;&lt;/p&gt;';
    if(badgecfg.brand){
    div.appendChild(document.createTextNode(badgecfg.brand));
    }
    var&nbsp;col,width;
    switch(badgecfg.size){
    case&nbsp;'small':width&nbsp;=&nbsp;100;break;
    case&nbsp;'large':width&nbsp;=&nbsp;400;break;
    default:width&nbsp;=&nbsp;200;break;
    }
    switch(badgecfg.skin){
    case&nbsp;'blue':col&nbsp;=&nbsp;'#ccf';break;
    case&nbsp;'green':&nbsp;col&nbsp;=&nbsp;'#cfc';break;
    default:col&nbsp;=&nbsp;'#ccc';break;
    }
    div.style.background&nbsp;=&nbsp;col;
    div.style.width&nbsp;=&nbsp;width&nbsp;+&nbsp;'px';
    return&nbsp;div;
    }
    }
    function&nbsp;convertClassData(c){
    var&nbsp;chunks&nbsp;=&nbsp;c.split('&nbsp;');
    var&nbsp;badgecfg&nbsp;=&nbsp;{};
    for(var&nbsp;i=0,j=chunks.length;i&lt;j;i++){
    var&nbsp;vp&nbsp;=&nbsp;chunks[j].split('-');
    if(vp[1]){
    badgecfg[vp[0]]&nbsp;=&nbsp;vp[1];
    }
    }
    return&nbsp;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&nbsp;=&nbsp;function(){
    var&nbsp;divs&nbsp;=&nbsp;document.getElementsByTagName('div');
    var&nbsp;b&nbsp;=&nbsp;[];
    for(var&nbsp;i=0;divs[i];i++){
    if(divs[i].className.indexOf('skinnedbadge')&nbsp;!==&nbsp;-1){
    b.push(divs[i]);
    }
    }
    for(var&nbsp;i=0;b[i];i++){
    var&nbsp;link&nbsp;=&nbsp;b[i].getElementsByTagName('a')[0];
    if(link){
    var&nbsp;div&nbsp;=&nbsp;document.createElement('div');
    var&nbsp;linkdata&nbsp;=&nbsp;link.getAttribute('href').split('brand=')[1];
    div.appendChild(document.createTextNode(linkdata));
    div.innerHTML&nbsp;=&nbsp;'&lt;p&gt;Awesome&nbsp;badge!&nbsp;&lt;/p&gt;';
    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

    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!

    [tags]badges,javascript,theartandscienceofjavascript,book,sitepoint[/tags]

    YUI on the go – load YUI components on demand

    Friday, December 7th, 2007

    This is one of my talks at the Yahoo! Frontend Engineering summit in London and it deals with the options of cutting down the size of the YUI library components. There have been many articles and posts about this subject already but none really explained the idea of using YAHOO_config to load components on demand instead of using the YUI loader.

    This is also the trick I used to create the unobtrusive flickr badge v2.

    [tags]yahoof2elondon07,webdevelopmentsummit,yahoo,yui,javascript,lazyload,widgets,badges,performance,speed,webdevtrick[/tags]