HTML5 Web App Hackathon


Ernest Delgado, Pete LePage, Boris Smus
Daniel Hermes, Mihai Ionescu, Nadim Ratani, Peng Ying

August 1-11, 2011 (NYC, CHI, KIR, MTV)

#HTML5Hack

Schedule

Agenda

Thinking In Web Apps

Tight Focus

A great web application has a tight focus encouraging people to interact, engage, and accomplish something, rather than passively view content.

Provide Rich Visuals

A great web application provides rich visuals that will delight the eye without distracting the mind.

Great User Experiences

A great web application uses the latest web technologies enhance the user experience, so users can complete their work faster, easier and with confidence.

DJBreakpoint

DJBreakpoint

http://goo.gl/vyVol

Building web apps - now a breeze

CSS3: transitions/transforms/drop shadow/border radius

   -moz-border-radius: 12px; /* FF1-3.6 */
-webkit-border-radius: 12px; /* Saf3-4, iOS 1-3.2, Android <1.6 */
        border-radius: 12px; /* Opera 10.5, IE9, Saf5, Chrome,
                                FF4, iOS 4, Android 2.1+ */
  
CSS3

Web fonts

@font-face {
  font-family: 'WebFont';
  src: url('myfont.eot?#') format('eot'),  /* IE6–8 */
       url('myfont.woff') format('woff'),  /* FF3.6+, IE9, Chrome6+, 
                                              Saf5.1+*/
       url('myfont.ttf') format('truetype');  /* Saf3—5, Chrome4+, 
                                                 FF3.5, Opera 10+ */
}
Arial Comic Sans MS Courier New Georgia Impact Times New Roman Trebuchet MS Verdana

classList/dataset

var el = document.querySelector('#main').classList;
el.add('highlight');
el.remove('shadow');
el.toggle('highlight');

<div id="out" data-id="good" data-name="joe"></div>
document.querySelector('#out').dataset['name'] // joe

querySelectorAll(), querySelector()

var els = document.querySelectorAll("ul li:nth-child(odd)");
var tds = document.querySelectorAll("table.test > tr > td");
var el = document.querySelector("table.test > tr > td");
var els = document.getElementsByClassName('section');

Page Layout with Flex Box

.box {
  display: -webkit-box;
  -webkit-box-orient: ;
}
.box .one, .box .two {
  -webkit-box-flex: 1;
}
.box .three {
  -webkit-box-flex: 3;
}
Box one
Box two
Box three

HTML5 Drag and Drop

addEvent(drop, 'dragover', cancel);
addEvent(drop, 'dragenter', cancel);
addEvent(drop, 'drop', function (event) {
  // stops the browser from redirecting off to the text.
  if (event.preventDefault) {
    event.preventDefault();
  }
  this.innerHTML += '<p>' + event.dataTransfer.getData('Text') + '</p>';
  return false;
});

Speech Input

function startSearch(event) {
  if (event.target.results.length > 1) {
    var second = event.target.results[1].utterance;
    document.getElementById("second_best").value = second;
  }
  event.target.form.submit();
}

In DJBreakpoint DJBreakpoint - Better Coded UI

Drag and Drop + Speech Input + Rounded Corners + CSS Gradients + CSS Tranforms + CSS Transitions + etc

Messaging and Real Time Communication

Web Sockets

In DJBreakpoint DJBreakpoint - Socket.IO

Cross page messaging with postMessage

In DJBreakpoint DJBreakpoint - postMessage

File APIs

Importing tunes

<input type="file" accept="audio/*" multiple>
document.querySelector('input[type="file"]').onchange = function(e) {
  var files = e.target.files; // FileList

  for (var i = 0, f; f = files[i]; ++i) {
    // f.name
    // f.type // mimetype
    // f.size // bytes
    // f.lastModifiedDate.toLocaleDateString()
  }
};

Importing a directory of tunes

<input type="file" webkitdirectory />

Reading file content

Asynchronously read binary data into memory:

var reader = new FileReader();

reader.readAsBinaryString(File|Blob);

reader.readAsText(File|Blob, opt_encoding); // default UTF-8

reader.readAsDataURL(Blob|File);

reader.readAsArrayBuffer(Blob|File);

Reading byte ranges:

var blob = file.webkitSlice(startingByte, offset, opt_contentType);
reader.readAsBinaryString(blob);

Importing tunes using drag & drop

var reader = new FileReader();
reader.onload = function(e) {
  document.querySelector('img').src = e.target.result;
};

function onDrop(e) {
  reader.readAsDataURL(e.dataTransfer.files[0]);
};
Drop image files from your desktop

Uh oh...someone got a makeover!

Sending form data

function submitForm() {
  var fd = new FormData(); // or prefill: FormData(window.myform)
  fd.append("username", "Groucho");
  fd.append("accountnum", 123456);

  // Append File. Creates a multipart/form-data upload.
  var file = document.querySelector('#afile').files[0];
  if (file) {
    fd.append('afile', file);
  }

  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/upload', true);
  xhr.onload = function(e) { ... };
  xhr.send(fd);
}

Uploading a file

function upload(blob, opt_callback) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/upload', true);
  if (opt_callback) {
    xhr.onload = opt_callback;
  }
  xhr.send(blob);
}

window.BlobBuilder = window.BlobBuilder ||
    window.WebKitBlobBuilder || window.MozBlobBuilder;

var bb = new BlobBuilder(); // Create a .txt file on the fly.
bb.append('Hello World');

uploadFile(bb.getBlob('text/plain'), function(e) {
  console.log('upload complete');
});

In DJBreakpoint DJBreakpoint - DJ uploads a song

uploadNextSong: function(el) {
  if (USER_FILE_QUEUE.length) {
    el.textContent = 'Uploading your file...';

    var formData = new FormData();
    formData.append('file', USER_FILE_QUEUE.shift());

    var xhr = new XMLHttpRequest();
    xhr.open('POST', '/upload', true);
    xhr.onload = function(e) { el.textContent = 'Uploading complete.'; };
    xhr.send(formData); // Upload entire song.
  } else {
    setTimeout(function() {
      el.textContent = 'No songs selected! Skipping you.';
      socket.json.emit('nextuser', {}); // Tell server to try next user.
    }, 3000);
  }
}

Downloading a file

function download(url, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);

  xhr.responseType = 'arraybuffer'; // A raw buffer of bytes.

  xhr.onload = function(e) {
    callback(this, this.response); // Note: not xhr.responseText
  };
  xhr.send();
}

download('song.mp3', function(xhr, arrayBuffer) {
  // Create a file.
  var bb = new BlobBuilder();
  bb.append(arrayBuffer);
  var blob = bb.getBlob('audio/mpeg');
});

In DJBreakpoint DJBreakpoint - listeners sent a download msg

this.socket.on('startsong', function(file) {
  ...
  fetchSong_(file.url, file.name);
});

var fetchSong_ = function(url, name) {
  sm_ = new SoundManager();
  sm_.load(url, function(audioBuffer, arrayBuffer) {
    // Display song title, artist, year.

    // Load audio buffer into visualizer, hit play, and start the visuals.
    visualizer.audioPlayer.loadAudioBuffer(audioBuffer);
    visualizer.audioPlayer.play();
    visualizer.startVisuals();
  });
};

Typed Arrays

In DJBreakpoint DJBreakpoint - displaying song's ID3v1 info

var dv = new jDataView(arrayBuffer);

// "TAG" starts at byte -128 from EOF.
if (dv.getString(3, dv.length - 128) == 'TAG') {
  var title = dv.getString(30, dv.tell()).trim();
  var artist = dv.getString(30, dv.tell()).trim();
  var album = dv.getString(30, dv.tell()).trim();
  var year = dv.getString(4, dv.tell()).trim();
  boothHeader_.textContent = [
      artist, '-', title, year ? '(' + year + ')' : ''].join(' ');
} else {
  boothHeader_.textContent = name; // Just use filename.
}

Audio hotness

Web Audio API

In DJBreakpoint DJBreakpoint - playing tunes

Interlude: Visualization with HTML5 Canvas

  var canvas = document.getElementById('a');
  var context = canvas.getContext('2d');
  context.fillRect(50, 25, 150, 100);

Use requestAnimationFrame for render callbacks:

window.requestAnimationFrame = window.webkitRequestAnimationFrame ||
  window.mozRequestAnimationFrame || window.msRequestAnimationFrame;

window.requestAnimationFrame(animate, opt_elem /* bounding elem */);

In DJBreakpoint DJBreakpoint - visualization

In DJBreakpoint DJBreakpoint - beat detection

Step 1. Build web app
Step 2. ???????
Step 3. Profit!
Make it easy for users to discover great apps, extensions, and themes
Make it easy for developers and brands to reach the 160+ million Chrome users
  • Multiple monetization models, including one-time, subscriptions and in-app payments
  • One-time $5 developer registration fee
  • No review or approval process
  • No restrictions on advertising
  • No restrictions on the platform
Play 23% more and spend 147% more
100% more engagement

Let's do it - Building the manifest

{ "app": {
    "launch": {
      "urls": ["https://chrome.google.com/webstore"],
      "web_url": "https://chrome.google.com/webstore",
      "container": "tab"
    }
  },
  "permissions": [],
  "icons": {
    "16": "16.png",
    "128": "128.png"
  },
  "name": "Chrome Web Store - Apps, Extensions and Themes",
  "description": "Discover great apps, games, extensions and...
     themes for Google Chrome.",
  "version": "1.0."
}

Let's do it - Creating the images

Required

  • Icons - a 16x16 and a 128x128 application icon
  • Screen Shots - at least 1 screen shot (400x275)

Optional*

  • YouTube Video - promote your application with a short video
  • Promotional Images - banners used if your app is featured
  • Customized Header Background - increase the professional look of your app

* but highly recommended!

Let's do it - Uploading to the Chrome Web Store

  1. Zip the manifest and two icon files together
  2. Upload the zip file to the Chrome Web Store via the Developer Dashboard

Let's do it - Edit & Publish

  1. Upload additional resources like screen shots & promotional images
  2. Pick a category and provide a detailed description
  3. Choose a price or use a freemium model
  4. Verify your website, and provide analytics token
  5. Publish!

Monetization

What's up with Freemium

Transaction growth at Google

Introducing In-App Payments

Developers

  • Flat fee (5%)
  • PCI Compliance
  • Millions of buyers

Buyers

  • One account for payments
  • Pay with a few clicks
  • Private payment credentials

In-App Payments - The big picture

Users - click, pay, use

Developers - Monetize in two calls

API flow - Full picture

Lets integrate - Create account

  • Sign up as a seller
  • Get your Seller Id
  • Get your Seller Key
  • Configure your postback URL

Lets integrate - Define item

{
  "iss" : "1337133713371337",
  "aud" : "Google",
  "typ" : "google/payments/inapp/item/v1",
  "exp" : "1392348430",
  "iat" : "1392282180",
  "request" :{
    "name" : "Piece of Cake",
    "description" : "Virtual chocolate cake to fill your virtual tummy",
    "price" : "10.50",
    "currencyCode" : "USD",
    "sellerData" : "user_id:1224245,offer_code:3098576987"
  }
}

Lets integrate - Create JSON web token

Python:

cakeJwt = jwt.encode(item, "1ZRJNFIOSJSIDFJNVHF");

Ruby:

@cakeJwt = JWT.encode(item, "1ZRJNFIOSJSIDFJNVHF")

Lets integrate - Include JavaScript library

<script type="text/javascript" src="//www.google.com/jsapi"></script>
<script>
  google.load('payments', '1.0', {
  'packages': ['sandbox_config']
  });
</script>

Lets integrate - Define JavaScript purchase handler

function purchase() {
  ...
  goog.payments.inapp.buy({
    'jwt'     : cakeJwt,
    'success' : successHandler,
    'failure' : failureHandler
  });
}
Create purchase button
<button onclick="purchase()">Buy</button>

Lets integrate - Define postback handler

Python:

def post(self):
encoded_jwt = self.request.get('jwt', None)
if encoded_jwt is not None:
  decoded_jwt = jwt.decode(str(encoded_jwt), SELLER_SECRET)
  order_id = decoded_jwt['response']['orderId']
  self.response.out.write(order_id)
    
    

Lets integrate - Tying information together

In DJBreakpoint DJBreakpoint - IAP

context.buy = function (item){
xhr.open('POST', window.location.href + 'buy', true);
xhr.onreadystatechange = function(jwt){
  if(xhr.readyState == 4){
    if(xhr.status == 200){
      goog.payments.inapp.buy({
        'jwt' : xhr.responseText,
        'success' : successHandler,
        'failure' : failureHandler
      });
    }
    else{
      console.log("error" + xhr.status)
    }
  }
}
    

chromestatus.com

updates.html5rocks.com

Resources