del.icio.us .:. tweet

Content Syndication with Case-Hardened JavaScript .:. kentbrewster.com

[just about everything in here has been overcome by broken APIs. Please see my GitHub repo for the current state of case-hardened JavaScript.]

[updated 6/17/2008: the related presentation from Web 2.0 is finally online. Please go check it out; much of what's here is outdated.]

Everybody likes a nice little Web badge. Myspace is crawling with spiffy Flash boxes. Facebook has an entire API dedicated to creating applications. Google's just released their embedded maps product. Yahoo!'s had a customizable home page and off-network search badges for over ten years. Ning and Pageflakes are nothing but badges. Here's a bookmark badge from del.icio.us, which you ought to use right now:

In This Article

Example: Twitterati Badge

Up next to the table of contents (if you're using an A-grade browser, and if r8ar.com stands up under the load) you ought to see the Twitterati badge. I've done a bit of customizing to make it look just like this page

Things to Do and Notice

  • Click an icon and the badge will refresh with that user's friends.
  • Click a name and you'll head on over to their page.
  • Heh-heh ... sorry, there's no "back" button. If you find a set that has posts only from its user, you're hosed.

Valid arguments for the badge are:

  • user : defaults to my account, kentbrew.
  • height : defaults to 350
  • width : defaults to 250
  • border : defaults to 2px solid blue
  • background : defaults to white; if you set evenBackground, oddBackground, and headerBackground, you won't see it at all.
  • userBorder : defaults to 1px dashed gray
  • userColor : defaults to whatever link color's on your page
  • headerBackground : defaults to your main background.
  • headerColor : defaults to black.
  • evenBackground : defaults to your main background.
  • oddBackground : defaults to gray.

Caveats and Gotchas

  • A badge like this is a two-corner bank shot: you load a script from my outside site, r8ar.com, which then spins around and queries an API or two. Lots could go wrong, so please be patient.
  • The Twitter API has a couple of quirks that I've posted on their development forum ... they seem to be caching requests for this particular service, so if you use this badge and then find some other service that looks at friends_timeline, and happen to request the same user id you saw here, you might get another copy of your cached call, with my callback twitBack and not the other guy's.

Directions for Future Development

Anybody got an iPhone? I bet this would look rilly kewl on an iPhone....

Try It Out

Here are the settings for my badge:

<script type="text/javascript" src="http://r8ar.com/twitterati.js">
{
   "user":"kentbrew",
   "border":"3px solid #442",
   "headerBackground":"#055",
   "headerColor":"#fff",
   "userBorder":"2px solid #f4bb2e",
   "oddBackground":"#ffe"
}

</script>

White space doesn't matter; I've spread this one out for readability. Substitute your own Twitter id, and fiddle with the settings until it look like your page.

The Current State of the Art

Currently, many advanced JavaScript applications look like this:

var MYGLOBAL = window.MYGLOBAL || {};
MYGLOBAL.myProgram = function() {
   return {
      myFunction : function() }{
         alert('Hello, world!');
      }
   };
}();
window.onload = function() {
   MYGLOBAL.myProgram.myFunction();
}

This is all familiar stuff, straight from the Module Pattern, first attributed to Cornford and Crockford, explained in such a manner that my tiny brain could wrap around it by Dustin Diaz in JSON for the Masses, used here many times, and ably summarized by Eric Miraglia on the YUIBlog, in A JavaScript Module Pattern.

Much of this is based on the (correct) idea that global variables are evil. The example program above could be compromised by any application rendered further down the stream that happened to redefined MYGLOBAL or grab the window.onload event, which is based on the global variable window.

If you're lucky enough to be in full control of your page, none of this matters, at least not right here and now. You can do whatever you want.

If, on the other hand, there's any chance your page might include somebody else's code--perhaps you have a Wordpress blog?--or you forsee a need to make changes to it sometime in the future, you might want to consider another approach.

Hello, World

The basic script does nothing but find itself in the page, create a DIV to play in, run itself, and delete itself.

Here's how we're calling it:

<script type="text/javascript" src="http://r8ar.com/cha.js"></script>

Here's our result:

Magnificent, no? Reload the page to see a different sixteen-character string of gibberish. If you view the post-rendered DOM tree (use Firebug, please) you won't actually see a script node anywhere, just a DIV containing the script's output.

Things to Do and Notice

  • Because our intention is to never reveal our script's true name or anything else about its internal work, we wrap the whole thing in an anonymous function return: ( function() { myFunction } )();
  • Instead of balancing our entire application on a single easily-subverted global variable, we create random string trueName and base the application on it: window[trueName] = {};
  • Right, there's a slight chance that we'll get the same randomly-generated trueName from two different runs on the same page. I like the odds, which are 26^16--about 43 octillion--to one.
  • Once we have our root global window[trueName] set, we assign private variable $, and return our subfunctions under our main function, $.f.
  • The dollar-sign is funky-looking but valid, and visually very distinctive. I use it because I'm old and my eyes are getting smaller every day. That and the letter f is purely arbitrary on my part; they could be any valid object names.
  • We're grabbing all the scripts on the page with document.getElementsByTagName('SCRIPT'), running through them one at at time, and finding one that matches our target, specified at the bottom in $.f.init(thisScript);
  • The pattern we're sending in thisScript forces a match to the whole string with the ^ in front and the $ in back. In the middle we've got a little regexp magic (thanks to JR Conlin) that matches any sub-domain in front of r8ar.com. I could force it to exactly match my domain name, but I'm leaving the pattern in for folks who don't have a choice. It should work for most other domains; use it if there's any possibility that your domain will respond to www.yourthing.com and yourthing.com.
  • Once we find the script (or one of its instantiations) we create a DIV in our private object $, name it $.w, and insert it before the script node. $.w becomes our main structure object in the DOM; everything else the user sees gets appended here.
  • Finally, we remove the script node, so the next run of the same script--if it happens to show up on the same page--doesn't mistakenly duplicate our efforts.
  • To fire the thing up, we use James "brothercake" Edwards' marvelous Generic OnLoad pattern, which--instead of stealing document.onload from everything else on the page or freezing everything else up during load while it renders--attaches an event listener and waits to render until the page has loaded.

Sorry that took so long; it gets easier further down. Here's the script, which is shorter than its explanation:

( function() {
   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(target) {
            var theScripts = document.getElementsByTagName('SCRIPT');
            for (var i = 0; i < theScripts.length; i++) {
               if (theScripts[i].src.match(target)) {
                  $.w = document.createElement('DIV');
                  $.w.innerHTML = 'Hello, world.  My name is ' + trueName + '.';
                  theScripts[i].parentNode.insertBefore($.w, theScripts[i]);
                  theScripts[i].parentNode.removeChild(theScripts[i]);
                  break;
               }
            }
         }
      };
   }();
   var thisScript = /^https?:\/\/[^\/]*r8ar.com\/cha.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); });
   }
} )();

Just for fun, run this and pop open Firebug and examine the post-rendered HTML. You'll notice that you can't actually see a reference to the script that created the node above, just a DIV with some content inside.

Passing User Arguments

We'll want to allow the user to pass in an argument or two, or six. There are several ways to do this; we'll test the innerHTML of the script for malicious-looking contents, and attempt to treat it as JSON and parse it into an object if it passes.

Here are our calls and results:

<script type="text/javascript" src="http://r8ar.com/chb.js"></script>
<script type="text/javascript" src="http://r8ar.com/chb.js">
{ "color":"red" }
</script>
<script type="text/javascript" src="http://r8ar.com/chb.js">
{ "color":"green" }
</script>

Things to Do and Notice

  • To test if the contents of the script is a valid JSON object, we're using a complicated regular expression heisted from Douglas Crockford's JSON parser. If the innerHTML of the script passes this test, we try evaling it. If the result is an object, we store it in $.a, and assume it's a bunch of user arguments. As $.w became our structure object, $.a becomes our arguments object. Anything the user wants the script to do will be stored as a member of $.a.
  • If the user hasn't passed any arguments, or the JSON parser can't make sense of what did come through, we run the script without any arguments.
  • When we find the argument color, we're applying it to our structure through another try{} catch{} block. This is very important; if we attempt to apply an invalid value to part of our DOM tree, we could mess things up badly.

About Those JSON Objects

JSON stands for "JavaScript Object Notation," a structure first discovered by Douglas Crockford, and documented at JSON.org. Here's the ten-cent tour: JSON is data expressed as native JavaScript code, consisting of an object surrounded by curly brackets. Inside the object are key-value pairs, delimited by double-quotes, punctuated by colons, and separated by commas. Values can be null, boolean, numeric, or string, plus arrays or objects containing any of the above, so JSON objects can be much more complex than the usual string of key-value pairs passed to JavaScript objects.

The bottom line: by using JSON, we are able to pass much more complex information to our script, and parse it and test it for validity with fewer lines of code.

Here's the script:

( function() {
   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(target) {
            var theScripts = document.getElementsByTagName('SCRIPT');
            for (var i = 0; i < theScripts.length; i++) {
               if (theScripts[i].src.match(target)) {
                  $.a = {};
                  if (theScripts[i].innerHTML) {
                     if (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]+$/.test(theScripts[i].innerHTML.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) {
                        try {
                           $.a = eval( '(' + theScripts[i].innerHTML + ')' );
                        }
                        catch(err) { }
                     }
                  }
                  if (typeof $.a !== 'object') {
                     $.a = {};
                  }
                  $.w = document.createElement('DIV');
                  $.w.innerHTML = 'Yo! My name is ' + trueName + '.';
                  if ($.a.color) {
                     try {
                        $.w.style.color = $.a.color;
                     }
                     catch(err) { }
                  }
                  theScripts[i].parentNode.insertBefore($.w, theScripts[i]);
                  theScripts[i].parentNode.removeChild(theScripts[i]);
                  break;
               }
            }
         }
      };
   }();
   var thisScript = /^https?:\/\/[^\/]*r8ar.com\/chb.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); });
   }
} )();

If our JSON validity test fails, or the result won't eval into an object, we set $.a to an empty object and run the script without any arguments.

Our First Interactive Function

Let's make it actually do something, shall we? Here we generate a link that calls a function to reveal the script's true name when clicked. This illustrates two things: simplification of scope concerns--$.f is one character shorter than this, and works for all scopes--and that the value of trueName is available throughout the script.

Here are our calls and results:

<script type="text/javascript" src="http://r8ar.com/chc.js"></script>
<script type="text/javascript" src="http://r8ar.com/chc.js">
{ "color" : "gold" }
</script>
<script type="text/javascript" src="http://r8ar.com/chc.js">
{ "color" : "mcfoobar" }
</script>

Things to Do and Notice

  • When you click one of the links, you'll learn its script's true name. If the user specified a color, you'll also learn that, whether or not it's a valid CSS color.
  • The third example tries an invalid color. The link should render, but since we're being very careful to use try{} and catch{} to apply styles, bad values shouldn't break anything. (Right, we could use that catch block to assign bad arguments to a bad-argument container and do something with them later, if needed.)
  • Nowhere in this script (or any of the rest to come) does the script have to actually state its own true name. Remember the soul-crushing pain and anguish of having to track down and replace all those instances of YOURGLOBAL.yourDomain.yourScript.yourFunction whenever you made a significant update, or wanted to plug in a piece of somebody else's library? Gone. From now on, all you need to call is $.f.yourFunction.
  • Also gone: scratching your head over whether you need to drop in the function's full name--that awful YOURGLOBAL.yourDomain.yourScript.yourFunction again--or could get away with using this.yourFunction. As above, from now on, all you need is $.f.yourFunction..

Here's the script:

( function() {
   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(target) {
            var theScripts = document.getElementsByTagName('SCRIPT');
            for (var i = 0; i < theScripts.length; i++) {
               if (theScripts[i].src.match(target)) {
                  if (theScripts[i].innerHTML) {
                     if (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]+$/.test(theScripts[i].innerHTML.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) {
                        try {
                           $.a = eval( '(' + theScripts[i].innerHTML + ')' );
                        }
                        catch(err) { }
                     }
                  }
                  if (typeof $.a !== 'object') {
                     $.a = {};
                  }
                  $.w = document.createElement('DIV');
                  var a = document.createElement('A');
                  a.innerHTML = 'Click me to reveal my true name and color, if any.';
                  a.onmousedown = function() {
                     $.f.revealName();
                  };
                  if ($.a.color) {
                     try {
                        a.style.color = $.a.color;
                     }
                     catch(err) { }
                  }
                  $.w.appendChild(a);
                  theScripts[i].parentNode.insertBefore($.w, theScripts[i]);
                  theScripts[i].parentNode.removeChild(theScripts[i]);
                  break;
               }
            }
         },
         revealName : function() {
            var msg = 'My true name is ' + trueName;
            if ($.a.color) {
               msg += ' and my color is ' + $.a.color;
            }
            alert(msg);
         }
      };
   }();
   var thisScript = /^https?:\/\/[^\/]*r8ar.com\/chc.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); });
   }
} )();

Dynamic Functions

Creating and destroying dynamic functions is the key to interactivity with outside data sources. To be safe, every time we tap outside data, we should create a separate bucket to hold the possibly-corrupt reply. Further on down the line we'll create a new function which will wait for something to happen, make a change in the structure of the page, and then delete itself.

Here we'll wait for a click and then generate a unique instance of a dynamic function. Each instance of the function will be a new member of a single array; to see the index climbing, click a link more than once.

Calls and results:

<script type="text/javascript" src="http://r8ar.com/chd.js">
{ "color" : "red" }
</script>
<script type="text/javascript" src="http://r8ar.com/chd.js"></script>
<script type="text/javascript" src="http://r8ar.com/chd.js">
{ "color" : "teal" }
</script>

Things to Do and Notice

  • As above, we're running the same script three times. Since each instance creates its own true name and associated program space, it won't hose (or be hosed by) anything else on the page.
  • Just after the line returning our main function $.f, we're adding an array, runFunction. This array will hold all of our dynamic functions as they are created, and, since it's an array, the value of each member's index--the number inside the square brackets--will be accessible to functions further down the line.
  • Our new function makeFunction gets the next available slot in runFunction and uses it in several places: first, to determine which slot in the array should be filled with a function, second as part of the string rendered as the function, and finally as the index pointing to whichever member of the (potentially very large) array of dynamically-generated functions should be run.
  • Our other new function renderResult doesn't do much; it just alerts whatever's been sent to it, and lets us know if the optional color argument has been passed.

Here's the script:

( function() {
   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 {
         runFunction : [],
         init : function(target) {
            var theScripts = document.getElementsByTagName('SCRIPT');
            for (var i = 0; i < theScripts.length; i++) {
               if (theScripts[i].src.match(target)) {
                  if (theScripts[i].innerHTML) {
                     if (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]+$/.test(theScripts[i].innerHTML.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) {
                        try {
                           $.a = eval( '(' + theScripts[i].innerHTML + ')' );
                        }
                        catch(err) { }
                     }
                  }
                  if (typeof $.a !== 'object') {
                     $.a = {};
                  }
                  $.w = document.createElement('DIV');
                  var p = document.createElement('P');
                  var a = document.createElement('A');
                  a.innerHTML = 'Click me to run a unique instance of a dynamically generated function.';
                  a.onmousedown = function() {
                     $.f.makeFunction(this);
                  };
                  if ($.a.color) {
                     try {
                        a.style.color = $.a.color;
                     }
                     catch(err) { }
                  }
                  $.w.appendChild(a);
                  theScripts[i].parentNode.insertBefore($.w, theScripts[i]);
                  theScripts[i].parentNode.removeChild(theScripts[i]);
                  break;
               }
            }
         },
         makeFunction : function(el) {
            var n = $.f.runFunction.length;
            $.f.runFunction[n] = function() {
               $.f.renderResult(trueName + '.runFunction['+ n + '] has run');
               delete($.f.runFunction[n]);
            };
            $.f.runFunction[n]();
         },
         renderResult: function(r) {
            if ($.a.color) {
               r += ' from a lovely ' + $.a.color + ' link';
            }
            alert(r);
         }
      };
   }();
   var thisScript = /^https?:\/\/[^\/]*r8ar.com\/chd.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); });
   }
} )();

Where might code like this be useful? In any script that makes repeated calls to an outside data source--such as, say, oh, I don't know ... an inline search application--it's important to know which incoming reply corresponds to which outgoing request. Having access to the index of runFunction allows the script to definitively match reponses to requests, so we never serve up the wrong set at the wrong time.

Did Somebody Say "Inline Search Application?"

Here are three instances of the same script, with different arguments passed to each and stored in $.a. The first is plain old Web search, with no default query. The second and third have domains and default queries specified, so they're more fun to demo. Enter something into each box and either hit Enter or click Search. If Yahoo! Search finds any results, you'll see up to the top five. (No, this isn't my usual search widget; this one is intentionally small and primitive, for clarity of instruction.)

<script type="text/javascript" src="http://r8ar.com/che.js"></script>
<script type="text/javascript" src="http://r8ar.com/che.js">
{
   "site" : "en.wikipedia.org",
   "query" : "Nixon"
}
</script>
<script type="text/javascript" src="http://r8ar.com/che.js">
{
   "site" : "developer.yahoo.com",
   "query" : "JavaScript"
}
</script>

Things to Do and Notice

  • Yes, this solves the age-old "how can I put a site-search engine on my blog?" problem.
  • Whitespace does not matter in JSON objects, so I've padded out the examples to make them clearer.
  • New structure elements: an input blank, a button, and a place to exhibit results, all attached to our private structure object, $.w. We can read from or write to them from anywhere in the script.
  • Several new functions:
    • buildStructure removes the sticky business of building the thing from the main loop in init.
    • runScript creates and runs a new script node, by appending it to the body of our document.
    • removeScript removes the script of our choice.
    • We've renamed makeFunction to runSearch, which is what it's really doing.
  • Two event captures: onkeypress and onmousedown. These are both attached them to runSearch. (To make the application behave more like a form, we're capturing onkeypress over the input blank and watching for event.keyCode 13, which means the user has hit Enter while inside the input box.)

Here's the script:

( function() {
   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 {
         runFunction : [],
         init : function(target) {
            var theScripts = document.getElementsByTagName('SCRIPT');
            for (var i = 0; i < theScripts.length; i++) {
               if (theScripts[i].src.match(target)) {
                  if (theScripts[i].innerHTML) {
                     if (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]+$/.test(theScripts[i].innerHTML.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) {
                        try {
                           $.a = eval( '(' + theScripts[i].innerHTML + ')' );
                        }
                        catch(err) { }
                     }
                  }
                  if (typeof $.a !== 'object') {
                     $.a = {};
                  }
                  $.f.buildStructure();
                  theScripts[i].parentNode.insertBefore($.w, theScripts[i]);
                  theScripts[i].parentNode.removeChild(theScripts[i]);
                  break;
               }
            }
         },
         buildStructure : function() {
            $.w = document.createElement('DIV');
            $.w.q = document.createElement('INPUT');
            if ($.a.query) {
               $.w.q.value = $.a.query;
            }
            $.w.q.onkeypress = function(e) {
               if ( (e ? e.which : event.keyCode) == 13) {
                   $.f.runSearch();
               }
            };
            $.w.appendChild($.w.q);
            $.w.b = document.createElement('BUTTON');
            $.w.b.innerHTML = 'Search';
            if ($.a.site) {
               $.w.b.innerHTML += ' ' + $.a.site;
            }
            $.w.b.onmouseup = function() {
               $.f.runSearch();
            };
            $.w.appendChild($.w.b);
            $.w.r = document.createElement('UL');
            $.w.appendChild($.w.r);
         },
         runSearch : function() {
            $.w.r.innerHTML = '';
            if ($.w.q.value) {
               var n = $.f.runFunction.length;
               var id = trueName + '.f.runFunction[' + n + ']';
               $.f.runFunction[n] = function(r) {
                  delete($.f.runFunction[n]);
                  $.f.removeScript(id);
                  $.f.renderResult(r);
               }
               var url = 'http://search.yahooapis.com/WebSearchService/V1/webSearch?';
               url += '&appid=YahooSearch';
               url += '&results=5';
               url += '&output=json';
               url += '&query=' + $.w.q.value;
               url += '&callback=' + id;
               if ($.a.site) {
                  url += '&site=' + $.a.site;
               }
               $.f.runScript(url, id);
            }
         },
         renderResult: function(r) {
            for (var i = 0; i < r.ResultSet.Result.length; i++) {
               var li = document.createElement('LI');
               var a = document.createElement('A');
               a.innerHTML = r.ResultSet.Result[i].Title;
               a.href = r.ResultSet.Result[i].ClickUrl;
               a.target = '_blank';
               li.appendChild(a);
               $.w.r.appendChild(li);
            }
         },
         runScript : function(url, id) {
            var s = document.createElement('script');
            s.id = id;
            s.type ='text/javascript';
            s.src = url;
            document.getElementsByTagName('body')[0].appendChild(s);
         },
         removeScript : function(id) {
            var s = '';
            if (s = document.getElementById(id)) {
               s.parentNode.removeChild(s);
            }
         }
      };
   }();
   var thisScript = /^https?:\/\/[^\/]*r8ar.com\/che.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); });
   }
} )();

If you're running Firebug, open it up, click the HTML tab, go all the way to the bottom of the body, and run it again. You'll see your dynamic script tag pop up, wait, and go away.

Directions for Future Development

Topics to be discussed next: including stylesheets, ignoring invalid arguments, and what to do if the API won't give you a proper callback.

(Actually ... you should check out the Twitterati badge at the top of the page, which takes on much of this stuff.)

Interesting Reading

Thank You:

Friends and family, past, present, and future: Adam Platti, Andy Baio, Ava Hristova, Barney Mok, Bill Scott, Chad Dickerson, Chanel Wheeler, Christian Heileman, Dan Theurer, Dave Balmer, Douglas Crockford, Ed Ho, Eric Miraglia, Gina Groom, Hedger Wang, Heidi Pollack, Isaac Schleuter, JR Conlin, Jason Levitt, Jeremy Gillick, Jeremy Zawodny, Jimmy Byrum, Jonathan Trevor, Leonard Lin, Matt McAlister, Matt Sweeney, Micah Laaker, Mike Davidson, Mike Lee, Nate Koechley, Pasha Sadri, Paul Bakaus, Peter Michaux, Rasmus Lerdorf, Scott Schiller, Steve Carlson, Taylor McKnight, and Vickie Brewster.

Comments from before Disqus:

Terry Riegel .:. 2010-03-17 09:53:43
Great article! I am using this as a starting point for building an embeddable cut and paste application into any web site. I have some prototypes I have been working with and it seems to lose its case hardenedness when the content is added with some sort of ajax request (in my test environment I am using prototype. On a hunch I tried sending a youtube video (embed) with this same ajax and it seemed to still display properly.

Any ideas/suggestions on how this might be expanded to have similar resiliency? It looks like youtube is using the object and embed tag. I wonder if these could be employed to make it even more case hardened?

This is the actual script tag...
<script type="text/javascript" src="http://clearimageonline.com/pages/start/pasteboard/p8ste.js">{ "p8ste": "f28", "text": "Show" }</script>

And here is what it should render...
{ "p8ste": "f28", "text": "Show" }

Kent Brewster .:. 2009-06-03 10:06:10
Fixing up some CHJ right now at Netflix; it's been hiding in their add-to-your-queue buttons (visible on rottentomatoes.com) for a while.
Kent Brewster .:. 2008-10-20 15:14:17
Thrilled to find CHJ in use in the Yahoo! Buzz Badge, here:

http://fe.shortcuts.search.yahoo.com/widget/buzz.php

Some interesting techniques I hadn't thought of; need to go ponder.
Kent Brewster .:. 2008-06-17 09:10:13
L.M. Orchard used CHJ in the process of building the Firefox Download Day Mega Widget, here:

http://decafbad.com/blog/2008/06/16/firefox-3-download-day-mega-widget
Kent Brewster .:. 2007-10-16 10:42:58
Hey! First example of CHJ in the wild: Paul O'Shaugnessy's yWeather, a University Hack Day entry from CMU. Very cool!

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