Paul Kinlan

Designing & Building

Great Web Applications

  -   Tel Aviv, Israel

Of course we're using Geolocation!




The craze for native apps is a short one and we are already seeing it on the wane.
  • Launched in June, 2011
  • Over 1 million unique users
  • 2.5 times more likely to subscribe
  • Drove digital subscriptions to beyond 250,000
  • In January 2012, Financial Times bought Assanka
  • Simplifying development with the web app as the basis for all platforms
If we want the web to win, we have to make it win. #winning


Defining the modern web app

The Power Of HTML5
Native & Desktop Experiences
Modern Web Apps

Tying It All Together

Designing modern web apps

  • Does it cost more to support browser X than it generates?
  • Is the browser older than the your socks?
  • Is an exorcist required to debug the app's behavior?

Be explicit about the browsers you support

MVC Frameworks - Prevent Spaghetti Code

MVC controller
view model

MVC frameworks

CSS frameworks

Backbone - Model

var Todo = Backbone.Model.extend({
  // Default attributes for the todo.
  defaults: {
    content: "empty todo...",
    done: false

  // Ensure that each todo created has `content`.
  initialize: function() {

  // Toggle the `done` state of this todo item.
  toggle: function() {{done: !this.get("done")});
  database: databasev1,
  storeName: "todos"

Backbone - View

var TodoView = Backbone.View.extend({
  tagName:  "li",
  template: _.template($('#item-template').html()),
  events: {
    "click .check"              : "toggleDone",
    "dblclick label.todo-content" : "edit",
    "click span.todo-destroy"   : "clear",
    "keypress .todo-input"      : "updateOnEnter"
  initialize: function() {
    this.model.bind('change', this.render);
    this.model.bind('destroy', this.remove);
  render: function() {
    this.input = this.$('.todo-input');
    return this;


@base: #f938ab;

.box-shadow(@style, @c) when (iscolor(@c)) {
  box-shadow:         @style @c;
  -webkit-box-shadow: @style @c;
  -moz-box-shadow:    @style @c;

.box-shadow(@style, @alpha: 50%) when (isnumber(@alpha)) {
  .box-shadow(@style, rgba(0, 0, 0, @alpha));

.box { 
  color: saturate(@base, 5%);
  border-color: lighten(@base, 30%);
  div { .box-shadow(0 0 5px, 30%) }

Best practices for a great app

Make it easy to try & use

Use responsive layout for different form factors

Media Queries for Style Sheets

<link rel="stylesheet" media="all" href="/static/css/base.min.css" />
<link rel="stylesheet" media="only screen and (max-width: 800px)"
  href="/static/css/mobile.min.css" />

Testing CSS media queries in JavaScript with window.matchMedia()

if (window.matchMedia('only screen and (max-width: 480px)').matches) {
  // Asynchronously provide experience optimized for phone
} else if (window.matchMedia('only screen and (min-width: 481px) and ' +
                             '(max-width: 1024px)').matches) {
  // Asynchronously provide experience optimized for table or smaller screen
} else {
  // Asynchronously provide full screen experience

Single Page Apps


But don't worry...

Allow for deep linking & HIJAX

function updateHash(push) {
  if (push) {
    var slideNo = curSlide + 1;
    var hash = '#' + slideNo;
    window.history.pushState(slideNo, 'Slide ' + slideNo, hash);
window.addEventListener('popstate', handlePopState, false);
function handlePopState(event) {
  if (event.state != null) {
    curSlide = event.state - 1;

Make it work offline

Use a primarily client side architect model for your app

Server side rendering doesn't work very well when offline!

Application Cache to enable the offline scenario

<html manifest="cache.appcache">

Store and cache data on the client

window.requestFileSystem(PERSISTENT, 1048576, initFs, fsError);
var idbRequest ='Database Name');
localStorage["key"] = "value";

Persist users data

var elems = document.querySelectorAll("textarea, input");
var len = elems.length;
for (var i = 0; i < len; i++) {
  var elem = elems[i];
  elem.addEventListener("input", function(item) {
    localStorage[formName + "-" +] =
    var debug = document.getElementById("data-persistence-debug");
    debug.innerHTML = "Last auto-saved at: " + new Date();
  }, false);

LawnChair - simple json storage

Make entering information easy & understandable

<input type="text" required />
<input type="email" value="" />
<input type="date" min="2010-08-14" max="2011-08-14"
<input type="range" min="0" max="50" value="10" />
<input type="search" results="10" placeholder="Search..." />
<input type="tel"  placeholder="(555) 555-5555"
 pattern="^\(?\d{3}\)?[-\s]\d{3}[-\s]\d{4}.*?$" />
<input type="color" placeholder="e.g. #bbbbbb" />
<input type="number" step="1" min="-5" max="10" value="0" />

Let the user tell you what they want

<input type="text" x-webkit-speech />
function startSearch(event) {
  if ( > 1) {
    var second =[1].utterance;
    document.getElementById("second_best").value = second;

Keep your user informed

function showNotifications(pic, title, text) {
  if (window.webkitNotifications.checkPermission() == 0) {
    var notificationWindow =
      window.webkitNotifications.createNotification(pic, title, text);;

    // close notification automatically after a timeout
    setTimeout(function(popup) {
    }, 6000, notificationWindow);

Treat performance like a feature

Use Application Cache to provide local caching

<html manifest="cache.appcache">

Store data locally

window.requestFileSystem(PERSISTENT, 1048576, initFs, fsError);
var idbRequest ='Database Name');
localStorage["key"] = "value";

Use CSS3 transitions, transforms and animations

transition: all 1s ease-in-out;

Use WebWorkers for non-blocking JavaScript

var worker = new Worker('myworker.js');

More great tips at

It should all just work, right?

Chrome Frame

<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">

One last thing...

Aim for a great experience, everything else will follow.




The web is what you make of it.