WHATWG

Demos from September 2008

The demos and segments of this talk are:

  1. <video> (00:35)
  2. postMessage() (05:40)
  3. localStorage (15:20)
  4. sessionStorage (21:00)
  5. Drag and Drop API (29:05)
  6. onhashchange (37:30)
  7. Form Controls (40:50)
  8. <canvas> (56:55)
  9. Validation (1:07:20)
  10. Questions and Answers (1:09:35)

The notes were:

HTML5 DEMOS
===========

This is a series of demos intended for showing implementations of
HTML5 in (non-final) browsers available in September 2008.

Configuration:
 - Open Terminal in Presentation mode
 - Hide dock
 - Open Opera, Firefox, Safari, VMWare with IE, Chrome
 - Clear all their caches, form autofill, history, download history.


1. <video>

   Scenario: Embedding a video

     <video src="firefox.ogg" controls></video>

   To make it autoplay:

     <video src="firefox.ogg" controls autoplay></video>

   Remove controls and add script:

     <script> var video = document.getElementsByTagName('video')[0]; </script>
     <p>
      <input type=button value="&#x25FC;" onclick="video.pause();">
      <input type=button value="&#x25B6;" onclick="video.play();">
     </p>

   Replace buttons with one button:

     <input type=button value="&#x2588; &#x2588"
            onclick="if (video.paused) video.play(); else video.pause();">

   Works in Firefox.


2. postMessage()

   Scenario: Gadget from damowmow.com hosted in site on whatwg.org. The
   site knows, or can ask for, the user's nickname, and the gadget
   wants to show the nickname in its UI.

   http://www.whatwg.org/demos/2008-sept/gadget-host/host.html

     <iframe src="http://damowmow.com/playground/demos/gadget/gadget.html">
     </iframe>

     <p>
      <label>
       Nick:
       <input type="text" name="nick" onkeyup="updateNick(value)">
      </label>
     </p>

     <script>
      function updateNick(nick) {
        frames[0].postMessage('nick=' + nick,
                    'http://damowmow.com/playground/demos/gadget/gadget.html');
      }
     </script>

   http://damowmow.com/playground/demos/gadget/gadget.html

     <p>Hello <span id=nick>Sir</span>!</p>
     <script>
       addEventListener('message', function (e) {
         if (e.origin == "http://www.whatwg.org" &&
             e.data.substr(0,5) == "nick=")
           document.getElementById('nick').firstChild.data = e.data.substr(5);
       }, false);
     </script>

   Works in Safari, Firefox.

   Internet Explorer doesn't support DOM Events. But we can work
   around this by adding a shim, so that we can make this work in IE
   too:

     if (!window.addEventListener)
       window.addEventListener = function (n, f, c) {
         attachEvent('on' + n, function () { f(event) });
       };

   Works in IE.


3. localStorage

   Scenario: A text editor that stores the user's files client-side.

   Start with a simple text editor:

     <form name=editor>

      <p><textarea name=data></textarea></p>

     </form>

   Then add Open and Save buttons:

     <p>
      <label>Filename: <input name=filename></label>
      <input type=button value="Open" onclick="doOpen()">
      <input type=button value="Save" onclick="doSave()">
     </p>

   Saving:

     <script>

      function doSave() {
        var filename = document.forms.editor.filename.value;
        var data = document.forms.editor.data.value;
        localStorage.setItem('file-' + filename, data);
      }

     </script>

   Opening:

     function doOpen() {
       var filename = document.forms.editor.filename.value;
       document.forms.editor.data.value = localStorage.getItem('file-' + filename);
     }

   Works in IE and Safari.

   Demo:
     1. filename = first.txt
     2. data = Hello World!
     3. save
     4. open "earlier.txt"
     5. open "first.txt"


4. sessionStorage

   Scenario: Multi-page form that doesn't involve server-side state
   until the last page. Let's say, a kind of madlibs wizard.

   Let's create a form for the first step:

     <form action="step2.html" method=post>
      <p> <label> Noun: <input name=n1> </label> </p>
      <p> <input type="submit" value="Next...">
     </form>

   But we don't want to rely on the server, so we have to save the data:

     onsubmit="save(n1)"

   We'll put the script in an external file so that each step of the
   wizard can reuse it:

     <script src="script.js"></script>

   The script is just:

     function save(element) {
       localStorage.setItem(element.name, element.value);
     }

   Steps 2 and 3 and the final page of the wizard which uses the data
   were prepared earlier, but are basically the same.

   Try it in Safari (face red pepper).

   Now try it in two windows at once (date, ugly, insult; volume, down,
   geezer, then click next on the first, then on the second).

   To fix this we use sessionStorage instead of localStorage. Fix
   script.js and end.html.

   Now try it in two windows again (cheese blue mould; mood up demo).

   Works in IE, Firefox, Safari. (opinion around argument; frown
   upside-down joke)


5. Drag and Drop

   Scenario: Dragging icons around.

   I have three images:

     <style>
      img { height: 4em; }
     </style>
     <p>
      <img src="spam.gif" id=spam alt="Spam">
      <img src="egg.gif" id=egg alt="Egg">
      <img src="cat.gif" id=cat alt="Cat">
     </p>

   To each image we add:

     ondragstart="drag(this, event)"

   ...with:

     <script>
      function drag(target, e) {
        e.dataTransfer.setData('Text', target.id);
      }
     </script>

   Now we need somewhere to drag them to:

     <style>
      div { margin: 1em 2em; border: solid black; text-align: center;
            height: 9em; width: 12em; float: left; }
      p { clear: both; margin: 1em 3em; }
     </style>
     ...
     <div>
      <p>Good</p>
     </div>
     <div>
      <p>Bad</p>
     </div>
     ...

   To make them drag targets, we add the following to each <div>:

     ondragenter="return false" ondragover="return false" ondrop="drop(this, event)"

   ...with:

     <script>
      function drop(target, e) {
        target.appendChild(document.getElementById(e.dataTransfer.getData('Text')));
        e.preventDefault();
      }
     </script>

   Works in IE, Safari, Firefox.


6. onhashchange

   Scenario: Reacting to changes in the fragment identifier so that
   you can use normal links to affect the application view, just like
   scrolling in a document.

     <p><a href="#a">A</a> <a href="#b">B</a></p>
     <p id="message">None</p>

     onload="update()" onhashchange="update()"

     <script>
      function update() {
        if (location.hash) {
          var msg = document.getElementById('message');
          msg.firstChild.data = location.hash.substr(1);
        }
      }
     </script>

   Works in IE.


7. New widgets

   Scenario: Creating user interfaces more expressive than HTML4
   allowed, without thousands of lines of JavaScript.

   Range control.

     <input type="range">

     <div>HTML5</div>

     oninput="document.getElementsByTagName('div')[0].style.fontSize = value + 'px'">

   Works in Opera, Safari.

   Or you could change the contents:

     <p>HTML5 will be ready in the year <span></span></p>

     oninput="document.getElementsByTagName('span')[0].textContent = value">

   Works in Opera, Safari.

   Of course we need to tweak the range to make this work:

     min=2000 max=2050 value=2008

   Works in Opera, Safari.

   Now the script here is kind of a pain, so we've introduced
   something that will make that easier too:

   Wrap:

     <form> ... </form>

   Replace <span> with:

     <output output=year> ... </output>

     oninput="year.value = value">

   Works in Opera.

   Of course if we're picking a date, we should be using a date
   picker, not a range control. HTML5 has that too:

     <input type=date oninput="year.value = value">

   Works in Opera.

   That shows the whole date selected, but we can pick out the year:

     <input type=date oninput="year.value = valueAsDate.getYear()">

   The valueAsDate value returns a Date object.

   Works in Opera.

   We also have other controls:

     <p><label> URL: <input type="url"></label></p>
     <p><label> E-mail: <input type="email"></label></p>

   Works in Opera. (demo URL autocompletion with whatwg.org)

   The controls can be used with CSS:

     <style>
       :invalid { background: red; color: white; }
     </style>

   Works in Opera. (demo e-mail with user@example.com)

   (Delete those controls.)

   We also have combo-box controls:

     <p><label> Favorite Color: <input list=colors></label></p>
     <datalist id=colors>
      <option>Red
      <option>Orange
      <option>Yellow
      <option>Green
      <option>Blue
      <option>Violet
     </datalist>

   Works in Opera. (demo with selecting Orange then typing Indigo)

   (Delete those controls.)

   You can also mark a control as required, so that when you submit
   the form, if it's not selected, the browser will complain:

     <p><label> Query: <input name=q required></label></p>
     <p><input type=submit></p>

   Works in Opera. (demo with selecting Orange then typing Indigo)

   Notice how it is red straight away because it's required but not
   yet filled in, and thus invalid.

   Demo trying to submit without typing a value.

   (Delete the style element, delete the required attribute, switch to
   Safari.)

   Should just be:

     <form>
      <p><label> Query: <input name=q autofocus></label></p>
      <p><input type=submit></p>
     </form>

   Reload, showing that the control isn't focused by default.

   We can autofocus:

     ... autofocus

   Works in Safari. (demo by reloading again)


9. <canvas>

   Scenario: A trippy graphical display.

   Start with a canvas:

   <canvas width="800" height="450"></canvas>

   Then add script:

   <script>

    var context = document.getElementsByTagName('canvas')[0].getContext('2d');

    context.beginPath();
    context.moveTo(context.canvas.width * Math.random(), context.canvas.height * Math.random());
    context.lineTo(context.canvas.width * Math.random(), context.canvas.height * Math.random());
    context.stroke();

   </script>

   function line() {
     // current code
   }
   setInterval(line, 50);

   function blank() {
     context.fillStyle = 'rgba(0,0,0,0.1)';
     context.fillRect(0, 0, context.canvas.width, context.canvas.height);
   }
   setInterval(blank, 40);

   context.strokeStyle = 'white';

   context.lineWidth = 5 + Math.random() * 10;

   // remove moveTo()/lineTo()
   var lastX = 0;
   var lastY = 0;
   ...
   context.moveTo(lastX, lastY);
   lastX = context.canvas.width * Math.random();
   lastY = context.canvas.height * Math.random();
   context.bezierCurveTo(context.canvas.width * Math.random(),
                         context.canvas.height * Math.random(),
                         context.canvas.width * Math.random(),
                         context.canvas.height * Math.random(),
                         lastX, lastY);

   var hue = 0;
   ...
   hue = hue + 10*Math.random();
   context.strokeStyle = 'hsl(' + hue + ', 50%, 50%)';

   context.save();
   context.translate(context.canvas.width/2, context.canvas.height/2);
   context.scale(0.9, 0.9);
   context.translate(-context.canvas.width/2, -context.canvas.height/2);
   ...
   context.restore();

   Works in Safari, Opera, Chrome, Firefox.

   context.shadowColor = 'white';
   context.shadowBlur = 10;

   Works in Safari.


10. Validation

   HTML5 also drops all the presentational attributes and tightens a
   lot of things up that HTML4 left vague.

   Google has started using HTML5 on some pages, for example:

     http://www.google.com/privacy

   Show View Source in Firefox, zoomed in. Show DOCTYPE. Show <meta
   charset>. Click Privacy Policy, show the source again.

   There is a validator available if you want to validate documents.

     http://html5.validator.nu/

   Validate the privacy policy.