del.icio.us .:. tweet

Case-Hardening the Del.icio.us Tag-O-Meter Badge .:. kentbrewster.com

During my first redesign--this would be two years ago or so--I decided I wanted to include a link to del.icio.us on each page, so my visitors could easily bookmark it if they wanted. A bit more research showed that there was an existing piece of hosted javascript that did this, and also showed the number of saves that had already occurred, and (optionally) popular tags and links to those who'd saved it recently.

Unfortunately, implementing the del.icio.us tagometer badge on the new site clashed with my main goal for the redesign: it had to be as fast as possible. I'm using YSlow to benchmark my site, and it's just not going to work for me if it's not getting an A.

When I tore it down to see how it worked, I discovered that--in addition to the first call to get the script--the tag-o-meter made three other calls, one to the script that actually returned the data, another to include some CSS, and a third to return an image.

What I'm currently running does only one thing: it queries the database, gets the save count, and displays it if it's there. (Popular tags come back from the script, but I haven't decided how--or if--I want to show them.) It's a tiny 6k script, and since I'm rendering it inline with PHP instead of calling it with a separate LINK, it does nothing to my YSlow score, which is currently a 96% on pages that don't have a bunch of external includes.

Building the Logo Without an Image Call

Images slow things down, jack up your HTTP call count, and kill your YSlow grade if they don't come down from a content distribution network. As it turns out, just a dash of non-semantic HTML plus a little CSS trickery will simulate the del.icio.us logo at the client end, in your choice of size and color. Here's the structure:

<span class="delicious">
<div class="db"><b></b><i></i><u></u></div>
<a href="http://del.icio.us/post?url=http%3A%2F%2Fkentbrewster.com%2Fdelicious-badge%2F&title=Case-Hardening+the+del.icio.us+Badge">save to del.icio.us</a>
<span id="dc"></span>
</span>

Span dc is reserved for the save count; more on this later. Span db has three empty deprecated tags (bold, italic, and underline) that we're going to style as blocks and position absolutely within the containing DIV. Here's the CSS:

.db { display:block; float:left; margin:3px 5px; height:10px; width:10px; position:relative; background-color:#fff; overflow:hidden;}
.db * {display:block; position:absolute; height:50%; width:50%;}
.db b {top:0; left:50%; background-color:#00f;}
.db i {top:50%; left:0; background-color:#000;}
.db u {top:50%; left:50%; background-color:#ddd;}

To change the size of your del.icio.us logo, alter the height and width in the first line. To change colors, look at the background-color parameter in the last three.

Getting the Save Count

The script uses several of the methods documented in Case-Hardened JavaScript, including the randomly-generated global global, dynamic script node and function creation, and delayed loading. It also leans heavily on Paul Johnson's JavaScript MD5 function, which I swiped straight from the original badge. Here it is:

// get saves to del.icio.us -- see http://kentbrewster.com/delicious-badge for info

( function() {
   // Paul Johnson's awesome MD5 function; see http://pajhome.org.uk/crypt/md5
   eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('e 1i=0;e 1g="";e p=8;f 1f(s){g K(A(D(s),s.o*p))}f 1w(s){g S(A(D(s),s.o*p))}f 1N(s){g L(A(D(s),s.o*p))}f 2b(w,v){g K(I(w,v))}f 2a(w,v){g S(I(w,v))}f 2c(w,v){g L(I(w,v))}f 2i(){g 1f("1R")=="1O"}f A(x,G){x[G>>5]|=1U<<((G)%E);x[(((G+1V)>>>9)<<4)+14]=G;e a=24;e b=-1Y;e c=-1X;e d=2h;z(e i=0;i<x.o;i+=16){e Y=a;e W=b;e X=c;e 1b=d;a=l(a,b,c,d,x[i+0],7,-2d);d=l(d,a,b,c,x[i+1],12,-28);c=l(c,d,a,b,x[i+2],17,29);b=l(b,c,d,a,x[i+3],22,-1T);a=l(a,b,c,d,x[i+4],7,-1Z);d=l(d,a,b,c,x[i+5],12,2j);c=l(c,d,a,b,x[i+6],17,-1P);b=l(b,c,d,a,x[i+7],22,-1Q);a=l(a,b,c,d,x[i+8],7,1S);d=l(d,a,b,c,x[i+9],12,-25);c=l(c,d,a,b,x[i+10],17,-26);b=l(b,c,d,a,x[i+11],22,-2f);a=l(a,b,c,d,x[i+12],7,2e);d=l(d,a,b,c,x[i+13],12,-2g);c=l(c,d,a,b,x[i+14],17,-27);b=l(b,c,d,a,x[i+15],22,1M);a=h(a,b,c,d,x[i+1],5,-1t);d=h(d,a,b,c,x[i+6],9,-1s);c=h(c,d,a,b,x[i+11],14,1u);b=h(b,c,d,a,x[i+0],20,-1v);a=h(a,b,c,d,x[i+5],5,-1r);d=h(d,a,b,c,x[i+10],9,1q);c=h(c,d,a,b,x[i+15],14,-1l);b=h(b,c,d,a,x[i+4],20,-1k);a=h(a,b,c,d,x[i+9],5,1m);d=h(d,a,b,c,x[i+14],9,-1n);c=h(c,d,a,b,x[i+3],14,-1p);b=h(b,c,d,a,x[i+8],20,1o);a=h(a,b,c,d,x[i+13],5,-1x);d=h(d,a,b,c,x[i+2],9,-1y);c=h(c,d,a,b,x[i+7],14,1I);b=h(b,c,d,a,x[i+12],20,-1H);a=k(a,b,c,d,x[i+5],4,-1J);d=k(d,a,b,c,x[i+8],11,-1K);c=k(c,d,a,b,x[i+11],16,1L);b=k(b,c,d,a,x[i+14],23,-1G);a=k(a,b,c,d,x[i+1],4,-1F);d=k(d,a,b,c,x[i+4],11,1A);c=k(c,d,a,b,x[i+7],16,-1z);b=k(b,c,d,a,x[i+10],23,-1B);a=k(a,b,c,d,x[i+13],4,1C);d=k(d,a,b,c,x[i+0],11,-1E);c=k(c,d,a,b,x[i+3],16,-1D);b=k(b,c,d,a,x[i+6],23,1W);a=k(a,b,c,d,x[i+9],4,-2z);d=k(d,a,b,c,x[i+12],11,-2F);c=k(c,d,a,b,x[i+15],16,2G);b=k(b,c,d,a,x[i+2],23,-2D);a=m(a,b,c,d,x[i+0],6,-2B);d=m(d,a,b,c,x[i+7],10,2I);c=m(c,d,a,b,x[i+14],15,-2O);b=m(b,c,d,a,x[i+5],21,-2M);a=m(a,b,c,d,x[i+12],6,2J);d=m(d,a,b,c,x[i+3],10,-2H);c=m(c,d,a,b,x[i+10],15,-2A);b=m(b,c,d,a,x[i+1],21,-2p);a=m(a,b,c,d,x[i+8],6,2q);d=m(d,a,b,c,x[i+15],10,-2o);c=m(c,d,a,b,x[i+6],15,-2n);b=m(b,c,d,a,x[i+13],21,2m);a=m(a,b,c,d,x[i+4],6,-2r);d=m(d,a,b,c,x[i+11],10,-2k);c=m(c,d,a,b,x[i+2],15,2y);b=m(b,c,d,a,x[i+9],21,-2t);a=u(a,Y);b=u(b,W);c=u(c,X);d=u(d,1b)}g H(a,b,c,d)}f F(q,a,b,x,s,t){g u(Z(u(u(a,q),u(x,t)),s),b)}f l(a,b,c,d,x,s,t){g F((b&c)|((~b)&d),a,b,x,s,t)}f h(a,b,c,d,x,s,t){g F((b&d)|(c&(~d)),a,b,x,s,t)}f k(a,b,c,d,x,s,t){g F(b^c^d,a,b,x,s,t)}f m(a,b,c,d,x,s,t){g F(c^(b|(~d)),a,b,x,s,t)}f I(w,v){e C=D(w);1d(C.o>16)C=A(C,w.o*p);e P=H(16),V=H(16);z(e i=0;i<16;i++){P[i]=C[i]^2L;V[i]=C[i]^2N}e 1c=A(P.18(D(v)),19+v.o*p);g A(V.18(1c),19+2C)}f u(x,y){e O=(x&N)+(y&N);e 1a=(x>>16)+(y>>16)+(O>>16);g(1a<<16)|(O&N)}f Z(T,M){g(T<<M)|(T>>>(E-M))}f D(n){e B=H();e J=(1<<p)-1;z(e i=0;i<n.o*p;i+=p)B[i>>5]|=(n.2l(i/p)&J)<<(i%E);g B}f L(B){e n="";e J=(1<<p)-1;z(e i=0;i<B.o*E;i+=p)n+=2s.2x((B[i>>5]>>>(i%E))&J);g n}f K(r){e U=1i?"2w":"2v";e n="";z(e i=0;i<r.o*4;i++){n+=U.R((r[i>>2]>>((i%4)*8+4))&1e)+U.R((r[i>>2]>>((i%4)*8))&1e)}g n}f S(r){e 1h="2u+/";e n="";z(e i=0;i<r.o*4;i+=3){e 1j=(((r[i>>2]>>8*(i%4))&Q)<<16)|(((r[i+1>>2]>>8*((i+1)%4))&Q)<<8)|((r[i+2>>2]>>8*((i+2)%4))&Q);z(e j=0;j<4;j++){1d(i*8+j*6>r.o*E)n+=1g;2K n+=1h.R((1j>>6*(3-j))&2E)}}g n}',62,175,'||||||||||||||var|function|return|md5_gg|||md5_hh|md5_ff|md5_ii|str|length|chrsz||binarray|||safe_add|data|key|||for|core_md5|bin|bkey|str2binl|32|md5_cmn|len|Array|core_hmac_md5|mask|binl2hex|binl2str|cnt|0xFFFF|lsw|ipad|0xFF|charAt|binl2b64|num|hex_tab|opad|oldb|oldc|olda|bit_rol|||||||||concat|512|msw|oldd|hash|if|0xF|hex_md5|b64pad|tab|hexcase|triplet|405537848|660478335|568446438|1019803690|1163531501|187363961|38016083|701558691|1069501632|165796510|643717713|373897302|b64_md5|1444681467|51403784|155497632|1272893353|1094730640|681279174|722521979|358537222|1530992060|35309556|1926607734|1735328473|378558|2022574463|1839030562|1236535329|str_md5|900150983cd24fb0d6963f7d28e17f72|1473231341|45705983|abc|1770035416|1044525330|0x80|64|76029189|1732584194|271733879|176418897|||||1732584193|1958414417|42063|1502002290|389564586|606105819|b64_hmac_md5|hex_hmac_md5|str_hmac_md5|680876936|1804603682|1990404162|40341101|271733878|md5_vm_test|1200080426|1120210379|charCodeAt|1309151649|1560198380|30611744|2054922799|1873313359|145523070|String|343485551|ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789|0123456789abcdef|0123456789ABCDEF|fromCharCode|718787259|640364487|1051523|198630844|128|995338651|0x3F|421815835|530742520|1894986606|1126891415|1700485571|else|0x36363636|57434055|0x5C5C5C5C|1416354905'.split('|'),0,{}));
   var trueName = '';
   for (var i = 0; i < 16; i++) { trueName += String.fromCharCode(Math.floor(Math.random() * 26) + 97); }
   window[trueName] = {};
   var $ = window[trueName];
   $.f = function() {
      return {
         init : function(el) {
            if (document.getElementById(el)) {
               var hash = hex_md5(location.href);
               $.w = document.getElementById(el);
               $.w.innerHTML = '';
               $.f.callback = trueName + '.f.runFunction';
               $.f.runFunction = function(r) {
                  delete $.f.runFunction;
                  if (document.getElementById($.f.callback)) {
                     var s = document.getElementById($.f.callback);
                     s.parentNode.removeChild(s);
                  }
                  if (r && r[0] && r[0].total_posts) {
                     $.w.innerHTML = ' (<a href="http://del.icio.us/url/' + r[0].hash + '">' + r[0].total_posts + '</a>)';
                  }
               };
               var url = 'http://badges.del.icio.us/feeds/json/url/blogbadge?callback=' + $.f.callback + '&hash=' + hash + '&url=' + encodeURIComponent(document.location);
               var s = document.createElement('script');
               s.id = $.f.callback;
               s.type = 'text/javascript';
               s.src = url;
               document.getElementsByTagName('body')[0].appendChild(s);
            }
         }
      };
   }();
   var init = function() { $.f.init('dc'); };
   if(typeof window.addEventListener !== 'undefined') {
      window.addEventListener('load', init, false);
   } else if(typeof window.attachEvent !== 'undefined') {
      window.attachEvent('onload', init);
   }
} )();  

( function() {
   var trueName = 'twttr';
   window[trueName] = {};
   var $ = window[trueName];
   $.f = function() {
      return {
         init : function(el) {
            if (document.getElementById(el)) {
               $.w = document.getElementById(el);
               $.w.innerHTML = '';
               $.f.callback = trueName + '.receiveCount';
               $.receiveCount = function(r) {
                  delete $.f.runFunction;
                  if (document.getElementById($.f.callback)) {
                     var s = document.getElementById($.f.callback);
                     s.parentNode.removeChild(s);
                  }
                  if (r && r.count) {
                     $.w.innerHTML = ' (<a href="http://twitter.com/#search?q=' + encodeURIComponent(document.location) + '">' + r.count + '</a>)';
                  }
               };
               var url = 'http://urls.api.twitter.com/1/urls/count.json?url=' + encodeURIComponent(document.location) + '&callback=' + $.f.callback;
               var s = document.createElement('script');
               s.id = $.f.callback;
               s.type = 'text/javascript';
               s.src = url;
               document.getElementsByTagName('body')[0].appendChild(s);
            }
         }
      };
   }();
   var init = function() { $.f.init('tw'); };
   if(typeof window.addEventListener !== 'undefined') {
      window.addEventListener('load', init, false);
   } else if(typeof window.attachEvent !== 'undefined') {
      window.attachEvent('onload', init);
   }
} )();

When the data returns, it winds up in the div identified by id dc, which can be changed at need, along with the messaging around the counts.

To truly case-harden this script I'd need to call it inline and build the entire structure and presentation package from scratch, instead of depending on my external CSS and <span id="dc"> to be there when I needed it. (Remember: CSS IDs are global variables, and global variables are evil!) Stay tuned ... this may actually happen at a later date, once the new-and-improved del.icio.us--which is spectacular, by the way--hits the aether.

Next time on This Old Site: we take a jackhammer to the MyBlogLog tracker! :)

Comments from before Disqus:

Kent Brewster .:. 2007-12-01 10:39:31
Thanks! Program list for getting the save count is above, under "Getting the Save Count." In English, we're doing it by:

- creating the MD5 hash for the current page's URL

- sending the hash, the URL, and a callback to del.icio.us's blogbadge feed

- waiting for data to show up and then displaying it

The heavy lifting is done by our dynamic function, $.f.runFunction. When data shows up from the blogbadge feed, $.f.runFunction deletes itself, deletes the script that called it, and either displays the save count or the invitation to create the first save. To peek at the guts of the thing while it's working, bring up Firebug's Net tab and reload the page; you'll see a call to blogbadge going by, with its random trueName instead of the $ prefix.
Schmelding .:. 2007-11-30 13:42:25
Excellent post. I gotta ask though: How are you displaying the "3 saves so far" text?

Copyright Kent Brewster 1987-2014 .:. FAQ .:. RSS .:. Contact