welcome

Case-Hardened JavaScript:
the Short, Fast Version
(Fifteen Slides in Fifteen Minutes)
(Including This One)


Kent Brewster

Web Guy, API Agitator, and Technology Evangelist, Netflix

Online: http://kentbrewster.com

Twitter: @kentbrew

what-we-gonna-do-tonight-brain

In the Next Fifteen Fourteen Minutes We Will:

  • Build a Badge
  • One Line of JavaScript to Include
  • No document.write!
  • Accept User Parameters
  • Create Structure, Presentation, and Behavior
  • Query Outside APIs
  • Allow for Further Interaction

Our Target for Tonight:

Twitter Search

ye-demo
housekeeping

Getting Started

  • Create one anonymous function, with closure
  • Inside it, create a randomly-named global variable, to allow for callbacks and make namespace collisions highly unlikely
  • Hang another anonymous function off your random global, and create a $ shortcut, to help keep things simple

To Run:

  • Find the SCRIPT tag on the page
  • Insert a DIV to contain the badge
  • Remove the SCRIPT node
  • Quit here -- other copies of the same script will ignore each other
  • Wait politely until the page loads before firing off
houskeeeping-code
( function() {
   var trueName = '';
   for (var i = 0; i < 16; i++) {
      trueName += String.fromCharCode(Math.floor(Math.random() * 26) + 97);
   }
   window[trueName] = {};
   var $ = window[trueName];
   $.d = document;
   $.f = function() {
      return {
         init : function(target) {
            var k = $.d.getElementsByTagName('SCRIPT');
            var n = k.length;
            for (var i = 0; i < n; i++) {
               if (k[i].src.match(target)) {
                  $.s = $.d.createElement('DIV');
                  k[i].parentNode.insertBefore($.s, k[i]);
                  k[i].parentNode.removeChild(k[i]);
                  break;
               }
            }
         }
      }
   }();
   var thisScript = /behavior.js$/;
   if(typeof window.addEventListener !== 'undefined') {
      window.addEventListener('load', function() {
         $.f.init(thisScript);
      }, false);
   } else if(typeof window.attachEvent !== 'undefined') {
      window.attachEvent('onload', function() {
         $.f.init(thisScript);
      });
   }
})();
structure

Build the Badge

  • Header, containing INPUT box
  • Results area, hidden until something shows up
  • Footer, with link back to where interested readers can get the badge. Also hidden until results show up.
structure-code
( function() {
   var trueName = '';
   for (var i = 0; i < 16; i++) {
      trueName += String.fromCharCode(Math.floor(Math.random() * 26) + 97);
   }
   window[trueName] = {};
   var $ = window[trueName];
   $.d = document;
   $.f = function() {
      return {
         init : function(target) {
            var k = $.d.getElementsByTagName('SCRIPT');
            var n = k.length;
            for (var i = 0; i < n; i++) {
               if (k[i].src.match(target)) {
                  $.s = $.d.createElement('DIV');
                  $.f.buildStructure();
                  k[i].parentNode.insertBefore($.s, k[i]);
                  k[i].parentNode.removeChild(k[i]);
                  break;
               }
            }
         },
         buildStructure : function() {
            $.s.className = trueName;
            $.s.q = $.d.createElement('INPUT');
            $.s.appendChild($.s.q);
            $.s.r = $.d.createElement('UL');
            $.s.r.className = 'hidden';
            $.s.appendChild($.s.r);
            $.s.f = $.d.createElement('P');
            $.s.f.className = 'hidden';
            var a = $.d.createElement('A');
            a.innerHTML = 'get this';
            a.target = '_blank';
            a.href = 'http://kentbrewster.com/twitter-search-badge';
            $.s.f.appendChild(a);
            $.s.appendChild($.s.f);
         }
      }
   }();
   var thisScript = /behavior.js$/;
   if(typeof window.addEventListener !== 'undefined') {
      window.addEventListener('load', function() {
         $.f.init(thisScript);
      }, false);
   } else if(typeof window.attachEvent !== 'undefined') {
      window.attachEvent('onload', function() {
         $.f.init(thisScript);
      });
   }
})();
config

Pass Some Variables

  • Create a root object to hold all variables, $.a
  • Read the SCRIPT tag's innerHTML*
  • Carefully run this by Hedger Wang's marvelous JSON parser
  • If it looks like a good JSON object, use it
  • Set default values whether or not they're explicitly declared

* Right, I know: technically there's no such thing. But it works cross-browser.

config-code
( function() {
   var trueName = '';
   for (var i = 0; i < 16; i++) {
      trueName += String.fromCharCode(Math.floor(Math.random() * 26) + 97);
   }
   window[trueName] = {};
   var $ = window[trueName];
   $.d = document;
   $.f = function() {
      return {
         init : function(target) {
            var k = $.d.getElementsByTagName('SCRIPT');
            var n = k.length;
            for (var i = 0; i < n; i++) {
               if (k[i].src.match(target)) {
                  $.s = $.d.createElement('DIV');
                  $.a = {};
                  if (k[i].innerHTML) {
                     $.a = $.f.parseJson(k[i].innerHTML);
                  }
                  $.f.houseKeep();
                  $.f.buildStructure();
                  k[i].parentNode.insertBefore($.s, k[i]);
                  k[i].parentNode.removeChild(k[i]);
                  break;
               }
            }
         },
         buildStructure : function() {
            $.s.className = trueName;
            $.s.q = $.d.createElement('INPUT');
            $.s.appendChild($.s.q);
            $.s.r = $.d.createElement('UL');
            $.s.r.className = 'hidden';
            $.s.appendChild($.s.r);
            $.s.f = $.d.createElement('P');
            $.s.f.className = 'hidden';
            var a = $.d.createElement('A');
            a.innerHTML = 'get this';
            a.target = '_blank';
            a.href = 'http://kentbrewster.com/twitter-search-badge';
            $.s.f.appendChild(a);
            $.s.appendChild($.s.f);
         },
         parseJson : function(json) {
            this.parseJson.data = json;
            if ( typeof json !== 'string') {
               return {"err":"trying to parse a non-string JSON object"};
            }
            try {
               var f = Function(['var document,top,self,window,parent,Number,Date,Object,Function,',
                  'Array,String,Math,RegExp,Image,ActiveXObject;',
                  'return (' , json.replace(/<\!--.+-->/gim,'').replace(/\bfunction\b/g,'function­') , ');'].join(''));
               return f();
            } catch (e) {
               return {"err":"trouble parsing JSON object; running with defaults"};
            }
         },
         houseKeep : function() {
            var defaults = {
               "count" : 10,
               "height" : 350,
               "width" : 300,
               "background" : "white",
               "border" : "1px solid black",
               "headerBackground" : "#ffa",
               "headerColor" : "#000",
               "evenBackground" : "#fff",
               "oddBackground" : "#eee",
               "linkColor" : "#00e",
               "linkColorVisited" : "#551A8B",
               "padding" : 5
            };
            for (var d in defaults) {
               if ($.a[d] === undefined) {
                  $.a[d] = defaults[d];
               }
            }
            $.p = [];
         }
      }
   }();
   var thisScript = /behavior.js$/;
   if(typeof window.addEventListener !== 'undefined') {
      window.addEventListener('load', function() {
         $.f.init(thisScript);
      }, false);
   } else if(typeof window.attachEvent !== 'undefined') {
      window.attachEvent('onload', function() {
         $.f.init(thisScript);
      });
   }
})();
outside-data

Hook Up the Outside API

  • Check the INPUT box every second
  • If there's a new query, run it
  • If the query has been blanked out, hide everything.
  • Create a callback to handle the data when it shows up
  • Append a new SCRIPT tag that calls the outside API and asks for data in the callback you created in the previous step **
  • When the return shows up, throw up an alert (which we'll fix later). Then delete the callback and the extra SCRIPT node, to keep things clean

** Obviously this is an insecure method. Do not do this unless you completely trust your data source not to throw you something sneaky that might hijack your client's browser session!

outside-data-code
( function() {
   var trueName = '';
   for (var i = 0; i < 16; i++) {
      trueName += String.fromCharCode(Math.floor(Math.random() * 26) + 97);
   }
   window[trueName] = {};
   var $ = window[trueName];
   $.d = document;
   $.f = function() {
      return {
         init : function(target) {
            var k = $.d.getElementsByTagName('SCRIPT');
            var n = k.length;
            for (var i = 0; i < n; i++) {
               if (k[i].src.match(target)) {
                  $.s = $.d.createElement('DIV');
                  $.a = {};
                  if (k[i].innerHTML) {
                     $.a = $.f.parseJson(k[i].innerHTML);
                  }
                  $.f.houseKeep();
                  $.f.buildStructure();
                  $.f.buildBehavior();
                  k[i].parentNode.insertBefore($.s, k[i]);
                  k[i].parentNode.removeChild(k[i]);
                  break;
               }
            }
         },
         buildStructure : function() {
            $.s.className = trueName;
            $.s.q = $.d.createElement('INPUT');
            $.s.appendChild($.s.q);
            $.s.r = $.d.createElement('UL');
            $.s.r.className = 'hidden';
            $.s.appendChild($.s.r);
            $.s.f = $.d.createElement('P');
            $.s.f.className = 'hidden';
            var a = $.d.createElement('A');
            a.innerHTML = 'get this';
            a.target = '_blank';
            a.href = 'http://kentbrewster.com/twitter-search-badge';
            $.s.f.appendChild(a);
            $.s.appendChild($.s.f);
         },
         parseJson : function(json) {
            this.parseJson.data = json;
            if ( typeof json !== 'string') {
               return {"err":"trying to parse a non-string JSON object"};
            }
            try {
               var f = Function(['var document,top,self,window,parent,Number,Date,Object,Function,',
                  'Array,String,Math,RegExp,Image,ActiveXObject;',
                  'return (' , json.replace(/<\!--.+-->/gim,'').replace(/\bfunction\b/g,'function­') , ');'].join(''));
               return f();
            } catch (e) {
               return {"err":"trouble parsing JSON object; running with defaults"};
            }
         },
         houseKeep : function() {
            var defaults = {
               "count" : 10,
               "height" : 350,
               "width" : 300,
               "background" : "white",
               "border" : "1px solid black",
               "headerBackground" : "#ffa",
               "headerColor" : "#000",
               "evenBackground" : "#fff",
               "oddBackground" : "#eee",
               "linkColor" : "#00e",
               "linkColorVisited" : "#551A8B",
               "padding" : 5
            };
            for (var d in defaults) {
               if ($.a[d] === undefined) {
                  $.a[d] = defaults[d];
               }
            }
            $.p = [];
         },
         buildBehavior : function() {
            if ($.a.err) {
               alert($.a.err);
            } else {
               setInterval($.f.runSearch, 1000);
            }
         },
         runSearch : function() {
            if ($.s.q.value) {
               if ($.s.q.value !== $.lastQuery) {
                  $.lastQuery = $.s.q.value;
                  var n = $.p.length;
                  var id = trueName + '.p[' + n + ']';
                  $.p[n] = function(r) {
                     delete($.p[n]);
                     $.f.removeScript(id);
                     $.f.renderSearch(r);
                  };
                  var url = 'http://search.twitter.com/search.json?rpp=' + $.a.count + '&lang=en&q=' + encodeURIComponent($.s.q.value) + '&callback=' + id;
                  $.f.runScript(url, id);
               }
            } else {
               if ($.lastQuery) {
                  $.lastQuery = '';
                  $.s.r.innerHTML = '';
                  $.s.r.className = $.s.f.className = 'hidden';
               }
            }
         },
         renderSearch : function(z) {
            alert('Back with results!');
         },
         runScript : function(url, id) {
            var s = $.d.createElement('script');
            s.id = id;
            s.type ='text/javascript';
            s.src = url;
            $.d.getElementsByTagName('body')[0].appendChild(s);
         },
         removeScript : function(id) {
            if ($.d.getElementById(id)) {
               var s = $.d.getElementById(id);
               s.parentNode.removeChild(s);
            }
         }
      }
   }();
   var thisScript = /behavior.js$/;
   if(typeof window.addEventListener !== 'undefined') {
      window.addEventListener('load', function() {
         $.f.init(thisScript);
      }, false);
   } else if(typeof window.attachEvent !== 'undefined') {
      window.attachEvent('onload', function() {
         $.f.init(thisScript);
      });
   }
})();
rendering

Rendering the Returned Data

  • Render each tweet as a LI
  • Render the user name as a CITE, with link to next search.
  • Insert links and next-searches into the tweet itself, and render it as a SPAN
  • Link the date to the original tweet.
  • Link the image to the user's home page.
  • Be sure the containing UL and the footer is visible afterwards.
  • If nothing shows up, say so!
rendering-code
( function() {
   var trueName = '';
   for (var i = 0; i < 16; i++) {
      trueName += String.fromCharCode(Math.floor(Math.random() * 26) + 97);
   }
   window[trueName] = {};
   var $ = window[trueName];
   $.d = document;
   $.f = function() {
      return {
         init : function(target) {
            var k = $.d.getElementsByTagName('SCRIPT');
            var n = k.length;
            for (var i = 0; i < n; i++) {
               if (k[i].src.match(target)) {
                  $.s = $.d.createElement('DIV');
                  $.a = {};
                  if (k[i].innerHTML) {
                     $.a = $.f.parseJson(k[i].innerHTML);
                  }
                  $.f.houseKeep();
                  $.f.buildStructure();
                  $.f.buildBehavior();
                  k[i].parentNode.insertBefore($.s, k[i]);
                  k[i].parentNode.removeChild(k[i]);
                  break;
               }
            }
         },
         buildStructure : function() {
            $.s.className = trueName;
            $.s.q = $.d.createElement('INPUT');
            $.s.appendChild($.s.q);
            $.s.r = $.d.createElement('UL');
            $.s.r.className = 'hidden';
            $.s.appendChild($.s.r);
            $.s.f = $.d.createElement('P');
            $.s.f.className = 'hidden';
            var a = $.d.createElement('A');
            a.innerHTML = 'get this';
            a.target = '_blank';
            a.href = 'http://kentbrewster.com/twitter-search-badge';
            $.s.f.appendChild(a);
            $.s.appendChild($.s.f);
         },
         parseJson : function(json) {
            this.parseJson.data = json;
            if ( typeof json !== 'string') {
               return {"err":"trying to parse a non-string JSON object"};
            }
            try {
               var f = Function(['var document,top,self,window,parent,Number,Date,Object,Function,',
                  'Array,String,Math,RegExp,Image,ActiveXObject;',
                  'return (' , json.replace(/<\!--.+-->/gim,'').replace(/\bfunction\b/g,'function­') , ');'].join(''));
               return f();
            } catch (e) {
               return {"err":"trouble parsing JSON object; running with defaults"};
            }
         },
         houseKeep : function() {
            var defaults = {
               "count" : 10,
               "height" : 350,
               "width" : 300,
               "background" : "white",
               "border" : "1px solid black",
               "headerBackground" : "#ffa",
               "headerColor" : "#000",
               "evenBackground" : "#fff",
               "oddBackground" : "#eee",
               "linkColor" : "#00e",
               "linkColorVisited" : "#551A8B",
               "padding" : 5
            };
            for (var d in defaults) {
               if ($.a[d] === undefined) {
                  $.a[d] = defaults[d];
               }
            }
            $.p = [];
         },
         buildBehavior : function() {
            if ($.a.err) {
               alert($.a.err);
            } else {
               setInterval($.f.runSearch, 1000);
            }
         },
         runSearch : function() {
            if ($.s.q.value) {
               if ($.s.q.value !== $.lastQuery) {
                  $.lastQuery = $.s.q.value;
                  var n = $.p.length;
                  var id = trueName + '.p[' + n + ']';
                  $.p[n] = function(r) {
                     delete($.p[n]);
                     $.f.removeScript(id);
                     $.f.renderSearch(r);
                  };
                  var url = 'http://search.twitter.com/search.json?rpp=' + $.a.count + '&lang=en&q=' + encodeURIComponent($.s.q.value) + '&callback=' + id;
                  $.f.runScript(url, id);
               }
            } else {
               if ($.lastQuery) {
                  $.lastQuery = '';
                  $.s.r.innerHTML = '';
                  $.s.r.className = $.s.f.className = 'hidden';
               }
            }
         },
         stuffQuery : function(v, p) {
            if (p) {
               $.s.q.value = p + v;
            } else {
               $.s.q.value = v;
            }
         },
         renderSearch : function(z) {
            $.s.r.innerHTML = $.s.r.className = '';
            var r = z.results;
            if (r.length) {
               var n = r.length;
               for (var i = 0; i < n; i++) {
                  var li = $.d.createElement('LI');
                  var a = $.d.createElement('A');
                  a.href = 'http://twitter.com/' + r[i].from_user;
                  a.title = r[i].from_user + ' on Twitter';
                  a.target = '_twitter';
                  var img = $.d.createElement('IMG');
                  img.align = 'left';
                  img.src = r[i].profile_image_url;
                  a.appendChild(img);
                  li.appendChild(a);
                  var cite = $.d.createElement('CITE');
                  var a = $.d.createElement('A');
                  a.innerHTML = r[i].from_user;
                  a.onclick = function() {
                     $.s.q.value = 'from:' + this.innerHTML;
                  }
                  cite.appendChild(a);
                  li.appendChild(cite);
                  li.appendChild($.d.createTextNode(': '));
                  var span = $.d.createElement('SPAN');
                  var raw = r[i].text;
                  var cooked = raw.replace(/\/u([^ ]+)/gi, "&#$1;");
                  cooked = cooked.replace(/http:\/\/([^ ]+)/g, "<a href=\"http://$1\" target=\"_blank\">http://$1</a>");
                  var woo = '@<a onclick="' + trueName + '.f.stuffQuery(this.innerHTML, \'from:\');">$1</a>';
                  cooked = cooked.replace(/@([\w*]+)/g, woo);
                  var yay = '#<a onclick="' + trueName + '.f.stuffQuery(this.innerHTML);">$1</a>';
                  cooked = cooked.replace(/#([\w*]+)/g, yay)
                  span.innerHTML = cooked;
                  li.appendChild(span);
                  li.appendChild($.d.createTextNode(' '));
                  var date = $.d.createElement('DATE');
                  var a = $.d.createElement('A');
                  a.innerHTML = r[i].created_at.split(' +')[0];
                  a.href = 'http://twitter.com/' + r[i].from_user + '/status/' + r[i].id;
                  a.target = '_twitter';
                  date.appendChild(a);
                  li.appendChild(date);
                  $.s.r.appendChild(li);
               }
            } else {
               var li = $.d.createElement('LI');
               li.innerHTML = 'Got nothing, sorry!';
               $.s.r.appendChild(li);
            }
            $.s.f.className = '';
         },
         runScript : function(url, id) {
            var s = $.d.createElement('script');
            s.id = id;
            s.type ='text/javascript';
            s.src = url;
            $.d.getElementsByTagName('body')[0].appendChild(s);
         },
         removeScript : function(id) {
            if ($.d.getElementById(id)) {
               var s = $.d.getElementById(id);
               s.parentNode.removeChild(s);
            }
         }
      }
   }();
   var thisScript = /behavior.js$/;
   if(typeof window.addEventListener !== 'undefined') {
      window.addEventListener('load', function() {
         $.f.init(thisScript);
      }, false);
   } else if(typeof window.attachEvent !== 'undefined') {
      window.attachEvent('onload', function() {
         $.f.init(thisScript);
      });
   }
})();
presentation

And Finally: Making it Pretty

  • Create a STYLE node
  • Loop through a set of rules
  • Caution: many browser-specific gotchas lurk here!
  • The empty rule is for the root object, which has trueName as its class
  • Do your best to reset fonts and selectors at the top; this is very hard to get right 100% of the time
  • For best results, let as much of this go to default as possible and allow your users to sort it out
presentation-code
( function() {
   var trueName = '';
   for (var i = 0; i < 16; i++) {
      trueName += String.fromCharCode(Math.floor(Math.random() * 26) + 97);
   }
   window[trueName] = {};
   var $ = window[trueName];
   $.d = document;
   $.f = function() {
      return {
         init : function(target) {
            var k = $.d.getElementsByTagName('SCRIPT');
            var n = k.length;
            for (var i = 0; i < n; i++) {
               if (k[i].src.match(target)) {
                  $.s = $.d.createElement('DIV');
                  $.a = {};
                  if (k[i].innerHTML) {
                     $.a = $.f.parseJson(k[i].innerHTML);
                  }
                  $.f.houseKeep();
                  $.f.buildStructure();
                  $.f.buildPresentation();
                  $.f.buildBehavior();
                  k[i].parentNode.insertBefore($.s, k[i]);
                  k[i].parentNode.removeChild(k[i]);
                  break;
               }
            }
         },
         buildStructure : function() {
            $.s.className = trueName;
            $.s.q = $.d.createElement('INPUT');
            $.s.appendChild($.s.q);
            $.s.r = $.d.createElement('UL');
            $.s.r.className = 'hidden';
            $.s.appendChild($.s.r);
            $.s.f = $.d.createElement('P');
            $.s.f.className = 'hidden';
            var a = $.d.createElement('A');
            a.innerHTML = 'get this';
            a.target = '_blank';
            a.href = 'http://kentbrewster.com/twitter-search-badge';
            $.s.f.appendChild(a);
            $.s.appendChild($.s.f);
         },
         parseJson : function(json) {
            this.parseJson.data = json;
            if ( typeof json !== 'string') {
               return {"err":"trying to parse a non-string JSON object"};
            }
            try {
               var f = Function(['var document,top,self,window,parent,Number,Date,Object,Function,',
                  'Array,String,Math,RegExp,Image,ActiveXObject;',
                  'return (' , json.replace(/<\!--.+-->/gim,'').replace(/\bfunction\b/g,'function­') , ');'].join(''));
               return f();
            } catch (e) {
               return {"err":"trouble parsing JSON object; running with defaults"};
            }
         },
         houseKeep : function() {
            var defaults = {
               "count" : 10,
               "height" : 350,
               "width" : 300,
               "background" : "white",
               "border" : "1px solid black",
               "headerBackground" : "#ffa",
               "headerColor" : "#000",
               "evenBackground" : "#fff",
               "oddBackground" : "#eee",
               "linkColor" : "#00e",
               "linkColorVisited" : "#551A8B",
               "padding" : 5
            };
            for (var d in defaults) {
               if ($.a[d] === undefined) {
                  $.a[d] = defaults[d];
               }
            }
            $.p = [];
         },
         buildBehavior : function() {
            if ($.a.err) {
               alert($.a.err);
            } else {
               setInterval($.f.runSearch, 1000);
            }
         },
         buildPresentation : function () {
            var ns = $.d.createElement('style');
            $.d.getElementsByTagName('head')[0].appendChild(ns);
            if (!window.createPopup) {
               ns.appendChild($.d.createTextNode(''));
               ns.setAttribute("type", "text/css");
            }
            var s = $.d.styleSheets[$.d.styleSheets.length - 1];
            var rules = {
               "*" : "{margin:0;padding:0;text-align:left;color:#000;font:13px/1.2em tahoma,verdana,arial,helvetica,clean,sans-serif;}",
               "" : "{zoom:1;width:" + ($.a.width) + "px;background:" + $.a.background + ";border:" + $.a.border + ";padding:5px;}",
               "a" : "{text-decoration:none; color:" + $.a.linkColor + ";cursor:pointer;}",
               "a:visited" : "{color:" + $.a.linkColorVisited + ";}",
               "a:hover":"{text-decoration:underline;}",
               "cite" : "{font-weight:bold;font-style:normal;}",
               "ul" : "{list-style:none; margin-top:5px;height:" + $.a.height + "px;overflow-x:hidden;overflow-y:auto;border:1px solid #ccc;}",
               "p" : "{font-size:92%;text-align:right;padding:" + $.a.padding + "px; margin:0;}",
               "li" : "{margin:0;border-bottom:1px dashed #ccc;padding:" + $.a.padding + "px;}",
               "li:hover" : "{background-color:#F7F7F7}",
               "input":"{background: transparent url('http://twitter.com/favicon.ico') 0 50% no-repeat; text-indent:18px; width:100%;border:1px solid #ccc;}",
               "date" : "{display:block;margin-top:" + $.a.padding + "px; text-align:right; font-family:georgia; font-style:italic; font-size:87%;}",
               "date:after" : "{clear:both; content:\".\"; display:block; height:0; visibility:hidden; }",
               "img" : "{margin:" + $.a.padding + "px " + $.a.padding + "px 0 0; height:36px; width:36px;border:1px solid #000;}",
               ".hidden" : "{display:none;}"
            };
            var ieRules = "";
            for (r in rules) {
               var selector = '.' + trueName + ' ' + r;
               var t = rules[r].replace(/;/g, '!important;');
               if (!window.createPopup) {
                  var theRule = $.d.createTextNode(selector + t);
                  ns.appendChild(theRule);
               } else {
                  ieRules += selector + t;
               }
            }
            if (window.createPopup) {
               s.cssText = ieRules;
            }
         },
         runSearch : function() {
            if ($.s.q.value) {
               if ($.s.q.value !== $.lastQuery) {
                  $.lastQuery = $.s.q.value;
                  var n = $.p.length;
                  var id = trueName + '.p[' + n + ']';
                  $.p[n] = function(r) {
                     delete($.p[n]);
                     $.f.removeScript(id);
                     $.f.renderSearch(r);
                  };
                  var url = 'http://search.twitter.com/search.json?rpp=' + $.a.count + '&lang=en&q=' + encodeURIComponent($.s.q.value) + '&callback=' + id;
                  $.f.runScript(url, id);
               }
            } else {
               if ($.lastQuery) {
                  $.lastQuery = '';
                  $.s.r.innerHTML = '';
                  $.s.r.className = $.s.f.className = 'hidden';
               }
            }
         },
         stuffQuery : function(v, p) {
            if (p) {
               $.s.q.value = p + v;
            } else {
               $.s.q.value = v;
            }
         },
         renderSearch : function(z) {
            $.s.r.innerHTML = $.s.r.className = '';
            var r = z.results;
            if (r.length) {
               var n = r.length;
               for (var i = 0; i < n; i++) {
                  var li = $.d.createElement('LI');
                  var a = $.d.createElement('A');
                  a.href = 'http://twitter.com/' + r[i].from_user;
                  a.title = r[i].from_user + ' on Twitter';
                  a.target = '_twitter';
                  var img = $.d.createElement('IMG');
                  img.align = 'left';
                  img.src = r[i].profile_image_url;
                  a.appendChild(img);
                  li.appendChild(a);
                  var cite = $.d.createElement('CITE');
                  var a = $.d.createElement('A');
                  a.innerHTML = r[i].from_user;
                  a.onclick = function() {
                     $.s.q.value = 'from:' + this.innerHTML;
                  }
                  cite.appendChild(a);
                  li.appendChild(cite);
                  li.appendChild($.d.createTextNode(': '));
                  var span = $.d.createElement('SPAN');
                  var raw = r[i].text;
                  var cooked = raw.replace(/\/u([^ ]+)/gi, "&#$1;");
                  cooked = cooked.replace(/http:\/\/([^ ]+)/g, "<a href=\"http://$1\" target=\"_blank\">http://$1</a>");
                  var woo = '@<a onclick="' + trueName + '.f.stuffQuery(this.innerHTML, \'from:\');">$1</a>';
                  cooked = cooked.replace(/@([\w*]+)/g, woo);
                  var yay = '#<a onclick="' + trueName + '.f.stuffQuery(this.innerHTML);">$1</a>';
                  cooked = cooked.replace(/#([\w*]+)/g, yay)
                  span.innerHTML = cooked;
                  li.appendChild(span);
                  li.appendChild($.d.createTextNode(' '));
                  var date = $.d.createElement('DATE');
                  var a = $.d.createElement('A');
                  a.innerHTML = r[i].created_at.split(' +')[0];
                  a.href = 'http://twitter.com/' + r[i].from_user + '/status/' + r[i].id;
                  a.target = '_twitter';
                  date.appendChild(a);
                  li.appendChild(date);
                  $.s.r.appendChild(li);
               }
            } else {
               var li = $.d.createElement('LI');
               li.innerHTML = 'Got nothing, sorry!';
               $.s.r.appendChild(li);
            }
            $.s.f.className = '';
         },
         runScript : function(url, id) {
            var s = $.d.createElement('script');
            s.id = id;
            s.type ='text/javascript';
            s.src = url;
            $.d.getElementsByTagName('body')[0].appendChild(s);
         },
         removeScript : function(id) {
            if ($.d.getElementById(id)) {
               var s = $.d.getElementById(id);
               s.parentNode.removeChild(s);
            }
         }
      }
   }();
   var thisScript = /behavior.js$/;
   if(typeof window.addEventListener !== 'undefined') {
      window.addEventListener('load', function() {
         $.f.init(thisScript);
      }, false);
   } else if(typeof window.attachEvent !== 'undefined') {
      window.attachEvent('onload', function() {
         $.f.init(thisScript);
      });
   }
})();
th-th-that's-all-folks!

Thank You

  • The folks at Yahoo and Netflix for bankrolling and encouraging the nuttiness
  • Isaac Schleuter, Doug Crockford, Scott Schiller, Hedger Wang, Dave Ballmer, and John Resig, for constant inspiration and much patient advice.
  • BayJax organizers, for letting me do it!

How to Reach Me Afterwards:

Twitter:
@kentbrew

Online:
http://kentbrewster.com