Hallo!

„ … etwas über HTML5 erzählen“

  • Unfertig und in ständiger Weiterentwicklung
  • Wenig für Sie wirklich neues
  • Großer Umfang, unübersichtliche Gesamtsituation, zu wenig Zeit
  • Was tun?

Partytricks?



  • HTML5-Demos enthalten oftmals gar kein HTML5
  • Wenn doch, immer das Gleiche (Canvas, Video)
  • Warum? Rest unspektakulär!
  • Demos oft schön, aber ohne viel prakitsche Relevanz
  • Pure Partytricks
  • Könnten wir jetzt auch machen …

Lieber etwas produktives?

  • HTML5-Offlinefeatures
  • In vielen Browsern schon implementiert (Firefox 3.6+, IE8+, Opera 10+, Safari 4+)
  • Schwierig genug, um interessant zu sein
  • Optisch unspektakulär …
  • …aber für HTML5-Webapps nützlich und dringend nötig!

HTML5-Webapps

  • HTML5 + WWW = Anwendungsplattform der Zukunft
  • Gegenentwurf zu Flash, App Stores, etc.

HTML5 verwandelt den Browser von einem bloßen Dokumentbetrachter in ein Multitalent!

Grafiken generieren
Sound erzeugen
Videos abspielen
Echtzeit-Networking
Multithreading
Funktioniert offline

Unser Schlachtplan

  • Normale Ajax-Webapp (jQuery) nehmen und offlinetauglich machen!
  • Statische Ressourcen cachen
  • Clientseitige Datenbank verwenden
  • Nebenbei ein paar praktische Dinge rund um HTML5
// Neuen Listenpunkt einfügen
var addItem = function(item){
    if(item) $('#list').append($('<li>' + item + '</li>'));
}

// Vorhandene Einträge anzeigen
var loadItems = function(){
    $('#list').html('');
    $.get('read.php', null, function(data){
        data.split("\n").map(function(item){
            addItem(item);
        });
    });
}
// Neuen Eintrag abspeichern
$('#new').submit(function(evt){
    evt.preventDefault();
    if($('#date').val() && $('#date').val()){
        var item = $('#date').val() + ': ' + $('#todo').val();
        $.get('save.php', { q: item }, addItem.bind(null, item));
    }
});

// Beim laden der Seite Einträge vom Server holen
$(window).load(function(){
    loadItems();
});

Aufgabe I: Online-Status


return navigator.onLine; // Einfache Status-Abfrage

window.addEventListener('online', function(){  // Online-Event
    alert('Ab jetzt online!');
}, false);

window.addEventListener('offline', function(){ // Offline-Event
    alert('Ab jetzt offline!');
}, false);
// Online-Status-Anzeige ändern
var changeStatusDisplay = function(){
    var status = (navigator.onLine) ? 'Online' : 'Offline';
    $('#status').attr('class', status.toLowerCase()).html(status);
}

// On- und Offline-Events abfangen
$(window).bind({
    'online': changeStatusDisplay,
    'offline': changeStatusDisplay
});

// Beim laden der Seite Einträge vom Server holen
$(window).load(function(){
    loadItems();
    changeStatusDisplay(); // Status-Anzeige aktualisieren
});
// Vorhandene Einträge anzeigen
var loadItems = function(){
    $('#list').html('');
    if(navigator.onLine){
        $.get('read.php', null, function(data){
            data.split("\n").map(function(item){
                addItem(item);
            });
        });
    }
    else {
        alert('Offline-Modus: Kann Einträge nicht laden');
    }
}
// Neuen Eintrag abspeichern
$('#new').submit(function(evt){
    evt.preventDefault();
    if($('#date').val() && $('#date').val()){
        var item = $('#date').val() + ': ' + $('#todo').val();
        if(navigator.onLine){
            $.get('save.php', { q: item }, addItem.bind(null, item));
        }
        else {
            alert('Offline-Modus: Speichern nicht möglich!');
        }
    }
});

Das Problem mit dem Offline-Status

  • Unzuverlässig, uneinheitlich, betrifft nur den Browser
    • Was macht der Server?
    • Was weiß der Browser?
  • Lösung: Hilf dir selbst!*
// Besser Online-Status-Detektor
navigator.onlineStatus = (function(){
    var pingSuccess = function(){
        if(!navigator.onlineStatus){
            navigator.onlineStatus = true;
            $(window).trigger('online');
        }
    }
    var pingFailure = function(){
        if(navigator.onlineStatus){
            navigator.onlineStatus = false;
            $(window).trigger('offline');
        }
    }
    var ping = function(timeout){
        var start = Date.now();
        $.ajax('ping.php', {
            'complete': function(xhr, txt){
                var time    = Date.now() - start, success = (txt == 'success' && time < timeout);
                if(success) pingSuccess();
                else pingFailure();
            }
        });
    }
    setInterval(ping.bind(null, 1000), 2000);
    return navigator.onLine;
})();

DIY-HTML5

  • Die Browser haben Bugs und es fehlen HTML5-Features? Selber nachrüsten!
  • Es gibt keinen Grund sich von Browsern terrorisieren zu lassen!
  • Vgl. CSS- und JavaScript-Frameworks
  • Warum nicht genau so bei HTML5?
  • Möglich für Geolocation, Canvas, WebGL uvm.

Aufgabe II: Statische Ressourcen

  • HTML-Dateien, Scripts, Bilder, CSS vorrätig halten
  • Lösung: HTML5 Application Cache, ein steuerbarer Speicher für Webapp-Dateien

CACHE MANIFEST
foo.html
images/bild.png
scripts/jquery.js
CACHE MANIFEST
about.html
styles.css
jquery.js
scripts.js

# Die index.html brauchen wir nicht aufzulisten
# Dort binden wir das Manifest ein
<html manifest="pfad/zum/manifest.manifest">
// Cache-Status-Anzeige ändern
var changeCacheStatusDisplay = function(status){
    var titles = {
           'checking' : 'Caching läuft...',
           'noupdate' : 'Cache bereit',
        'downloading' : 'Caching läuft...',
           'progress' : 'Caching läuft...',
             'cached' : 'Cache bereit',
        'updateready' : 'Cache bereit',
           'obsolete' : 'Cache-Fehler',
              'error' : 'Cache-Fehler'
    };
    $('#cacheStatus').attr('class', status).html(titles[status]);
}

// Cache-Events abfangen
'checking|noupdate|downloading|progress|cached|updateready|obsolete|error'.split('|')
.map(function(evt){
    $(applicationCache).bind(evt, changeCacheStatusDisplay.bind(null, evt));
});

Die Probleme des Application Cache

  • Extrem kompliziert
  • Nicht in IE < 10 und anderen alten Browsern implementiert
  • Lösung: Alte Browser ignorieren!
  • Webapps ≠ Webseiten
  • Systemanforderungen sind erlaubt!

Aufgabe III: Offline-Datenspeicher

  • Neue Einträge auch offline speichern!
  • Lösung: Web Storage, ein Key-Value-Speicher im Browser (Cookies 2.0)

if(window.localStorage){                // Wenn speichern möglich ist...
    localStorage.setItem('foo', 42);    // ... speichern wir etwas...
    return localStorage.getItem('foo'); // ... und geben es wieder aus!
}

Das Problem mit Local Storage

  • Nicht in IE6 und 7 sowie anderen älteren Browsern
  • Allerdings können alle Browser irgendwie offline Daten speichern …
  • Lösung: Abstraktionsschicht

jStorage

if($.jStorage.storageAvailable()){  // Wenn speichern möglich ist...
    $.jStorage.set('foo', 42);      // ... speichern wir etwas...
    return $.jStorage.get('foo');   // ... und geben es wieder aus!
}

Funktioniert in:
Internet Explorer ab 6.0 (!)
Firefox ab 2.0 (!)
Safari ab 4.0
Opera ab 10.50
iPhone & Android

// Neuen Eintrag abspeichern
$('#new').submit(function(evt){
    evt.preventDefault();
    if($('#date').val() && $('#date').val()){
        var item = $('#date').val() + ': ' + $('#todo').val();
        if(navigator.onLine){
            $.get('save.php', { q: item }, addItem.bind(null, item));
        }
        else {
            alert('Offline-Modus: Speichern nicht möglich!');
        }
    }
});
// Neuen Eintrag abspeichern
$('#new').submit(function(evt){
    evt.preventDefault();
    if($('#date').val() && $('#date').val()){
        var item = $('#date').val() + ': ' + $('#todo').val();
        if(navigator.onLine){
            $.get('save.php', { q: item }, addItem.bind(null, item));
        }
        else {
            if($.jStorage.storageAvailable()){
                var lastIndex = $.jStorage.index().length,
                    slot  = 'slot' + lastIndex;
                $.jStorage.set(slot, item);
                addItem(item);
            }
        }
    }
});
// Vorhandene Einträge anzeigen
var loadItems = function(){
    $('#list').html('');
    if(navigator.onLine){
        $.get('read.php', null, function(data){
            data.split("\n").map(function(item){
                addItem(item);
            });
        });
    }
    else {
        alert('Offline-Modus: Kann Einträge nicht laden');
    }
}
// Vorhandene Einträge anzeigen
var loadItems = function(){
    $('#list').html('');
    if(navigator.onLine){
        $.get('read.php', null, function(data){
            data.split("\n").map(function(item){
                addItem(item);
            });
        });
    }
    else {
        if($.jStorage.storageAvailable()){
            var keys = $.jStorage.index();
            keys.map(function(key){
                var item = $.jStorage.get(key);
                addItem(item);
            });
        }
    }
}

Aufgabe IV: Synchronisation

  • Im Moment: zwei getrennte Welten
  • Synchronisierung (für uns) einfach:
    1. Onlinegehen abpassen
    2. Lokale Datensätze an Server senden
    3. Lokalen Speicher leeren
  • Schwieriger: Multiuser-Apps, Server-Client-Synchronisierung
// Synchronisierungsfunktion
var sync = function(){
    if($.jStorage.storageAvailable()){
        var keys = $.jStorage.index();
        if(keys.length > 0 && navigator.onLine){
            console.log('Synchronisiere ' + keys.length + ' Einträge');
            var to_sync = keys.length, synced  = 0;
            keys.map(function(key){
                var item = $.jStorage.get(key);
                $.get('save.php', { q: item }, function(){
                    $.jStorage.deleteKey(key);
                    ++synced;
                    if(synced == to_sync){
                        loadItems();
                    }
                });
            });
        }
    }
}
// On- und Offline-Events abfangen
$(window).bind({
    'online': function(){
        changeStatusDisplay();
        sync();
    },
    'offline': changeStatusDisplay
});

// Beim laden der Seite Einträge vom Server holen
$(window).load(function(){
    loadItems();
    sync();
    changeStatusDisplay(); // Status-Anzeige aktualisieren
});

Fertig!

  • Fertig ist die HTML5-Offline-App!
  • Basics einfach: Appcache + clientseitige DB
  • Es gäbe noch mehr zu tun …

Verbliebene Probleme

  • Besseren Online-Status verwenden
  • Cache-Updates
  • Server-Client-Synchronisation
  • Cross-Browser-Kompatibilität
  • Sehr komische Browserbugs (XHR-Verweigerung? Cache-Verwendung um jeden Preis?)

Ausblick Offline-Technologie

  • API für Speicherplatz-Management
  • Leistungsfähige clientseitige Datenbank (Indexed DB)
  • Weniger Bugs, einheitliches Verhalten
  • Mehr Tools und Frameworks!

Fazit

  • HTML5 ist mehr als Grafikdemos
  • Fundament für zukünftige Webapplikationen
  • Offline-Features elementarer Baustein zukünftiger Webapps
  • Polyfills und Abstraktionsschichten sind unverzichtbar
  • Aber: HTML5 jetzt! Vom Warten wird‘s nicht besser

Schöne neue Welt

  • HTML5 = Living Standard
  • Niemals „fertig“, ständig neues
  • Das Neue und Tolle braucht immer Trickserei
  • Pionierarbeit gefragt! Frameworks, Polyfills, Tools!
  • Schlimm? Lieber HTML5 als wieder den IE6!

Danke!

Fragen?
peter@peterkroener.de