Erstellen von HTML5-Anwendungen

Verlaufs-API

Clark Sell

Beispielcode herunterladen.

Im Webbrowserkontext bedeutete der Verlauf im Allgemeinen die „Zurück“-Schaltfläche, und die Verwaltung war noch nie einfach. Als die Bedeutung von AJAX in Webanwendungen zunahm, wurde es noch schwieriger, und die typische Webseite wurde immer mehr zur umfassenden Webanwendung. Schließlich wurde JavaScript standardmäßig aktiviert statt deaktiviert, und Einzelseiten-Webanwendungen wurden veröffentlicht, die das clientseitige JavaScript intensiv nutzten. Allerdings fehlte eine gute Methode für den clientseitigen Umgang mit Speicher, Datenbanken und sogar dem allgemeinen Status zwischen Seiten.

Aber jetzt hat HTML5 die clientseitige Statusverwaltung direkt übernommen und eine ganze Reihe neuer APIs eingeführt. Dazu gehören Spezifikationen wie IndexedDB, lokaler Speicher sowie die Verlaufs-API, die in diesem Artikel im Mittelpunkt steht.

Die Verlaufs-API stellt kurz gesagt eine Methode bereit, mit der umfangreiche JavaScript-Anwendungen nicht nur besser im Sitzungsverlauf navigieren können, sondern auch dessen Status verwalten und erstklassige URL-Unterstützung bieten. Sie können also mit ein paar einfachen API-Aufrufen und -Ereignissen navigieren und Statusdaten mittels Push bzw. Pop in den Sitzungsstapel übertragen, um beispielsweise die Funktionsweise der Schaltfläche „Zurück“ zu beeinflussen. Dabei bleibt eine ordentliche URL-Struktur erhalten.

Problemdefinition

Wenn Sie an einer Lösung arbeiten, muss das Problem genau definiert sein. Mit der Verlaufs-API behalten Sie nicht nur auf wunderbare Weise den Seitenstatus bei, während die Endbenutzer auf die Schaltflächen „Zurück“ und „Vorwärts“ klicken. Das trifft mit Sicherheit zu, aber das Wesentliche liegt unter der Oberfläche. Aus Benutzerperspektive gibt es in jedem Browser zwei Hauptfeatures: die URL und die Navigationsschaltflächen „Zurück“ und „Vorwärts“. Mit dieser Kombination können die Benutzer eine Reihe von Dokumenten im Internet anfordern oder durch diese navigieren.

Diese Features sind zwar über Jahre hinweg im Grunde gleich geblieben, mit der Ausbreitung von AJAX wurden aber Veränderungen hinter den Kulissen vorgenommen. Was als eine Reihe von Dokumenten begann, wurde zu einer einzelnen Datei mit AJAX-Aufrufen im Hintergrund. Eine großartige Entwicklung. Wir konnten ein optimiertes Feature mit nur geringer Asynchronie liefern und nebenbei Leistung und Benutzerfreundlichkeit verbessern. Das brachte allerdings auch einige Probleme mit sich. Es wurde schwieriger, die URL zu verfolgen, zum Beispiel die Information darüber, welche Seite der Browser aufrufen sollte, wenn die Benutzer auf die Schaltflächen „Zurück“ oder „Aktualisieren“ klickten.

Ich kann nicht genug betonen, wie wichtig die URL ist. URLs sind permanent: Die Benutzer fügen sie den Favoriten hinzu, Suchmaschinen indizieren sie und Unternehmen vermarkten sie. Wie können Sie die URLs in der Welt des Internets mit seinen Veränderungen verwalten?

Geben Sie das Hashzeichen (das Nummernzeichen [#]) und das sogenannte Hashbangzeichen (#!) ein. Alles, was auf # folgt, wird von den Browsern als URL-Fragmentbezeichner bestimmt und nicht an den Server gesendet. Ursprünglich bot das Hashzeichen eine Methode, um Ankertags für seiteninterne Links zu erstellen, aber dann wurde es für AJAX-bezogene Aktivitäten verwendet. In den Anfängen von AJAX wurden URLs, die nur ein Hashzeichen enthielten, allerdings nicht von Suchmaschinen indiziert. Die Lösung war das Hashbangzeichen, mit dem die Webanwendungen eine URL verwenden und ändern sowie von Suchmaschinen indizieren lassen konnten, ohne jemals eine Seitenanforderung an den Server zurückzusenden. URLs mit Hashbangs, wie zum Beispiel „twitter.com/#!replies“ wurden mit dem Aufkommen von Webanwendungen wichtig.

Die neue Verlaufs-API

Obwohl also große Fortschritte gemacht wurden, gab es keine offizielle Unterstützung durch den Browser. Damit alles funktionierte, wurden Skripte für die Webanwendungen erstellt. Mit der neuen Verlaufs-API können Webanwendungen den „Status verwalten“, d. h. die Reihenfolge der Dokumente erfassen, aus denen der Sitzungsverlauf besteht. Somit können Sie im Sitzungsverlauf navigieren, Statusdaten beibehalten und die URL ordnungsgemäß verarbeiten. Bevor wir näher auf diese API eingehen, sehen wir uns die Schnittstellendefinition an, so wie sie vom World Wide Web Consortium (W3C, bit.ly/MU89iZ) veröffentlicht wurde:

interface History {
  readonly attribute long length;
  readonly attribute any state;
  void go(optional long delta);
  void back();
  void forward();
  void pushState(any data, 
    DOMString title, 
    optional DOMString url);
  void replaceState(any data, 
    DOMString title, 
    optional DOMString url);
};

Auf keinen Fall eine komplizierte API.

Um die tatsächliche Funktionsweise zu verstehen, beginnen wir mit einer einfachen Navigation zwischen Dokumenten. Nehmen wir an, dass Sie über drei Dokumente verfügen, „a.cshtml“, „b.cshtml“ und „c.cshtml“, und in allen dreien navigieren möchten. Normalerweise würden Sie einen einfachen Ankertag <a href=“http://foo.com/a.cshmtl”>Zu a</a> erstellen, auf den die Benutzer klicken. Der Browser durchläuft daraufhin den normalen Seiten- und Serverlebenszyklus. Wenn ein Benutzer auf einer bestimmten Website mehrmals klickt, erstellt er den sogenannten Sitzungsverlauf.

Es gibt bei dieser Methode allerdings zwei potenzielle Probleme. Zunächst einmal zwingen Sie den Browser dazu, seinen vollständigen Seitenzyklus durchzuführen und sogar den Server aufzurufen. Und zweitens benötigen Sie ein physisches Dokument, das die URL darstellt.

AJAX löst das Problem zum Teil, da Sie Teile einer Seite anfordern können. Sie reduzieren die Anzahl der angeforderten vollständigen Seiten und aktualisieren die URL manuell auf beispielsweise „http://foo.com/#!a“ oder „http://foo.com/#a“. Mit der Verlaufs-API können Sie ein ähnliches Ergebnis erreichen. Dazu rufen Sie window.history.pushState(state, title, url) auf und übergeben einen Status, der beibehalten werden soll, zusammen mit dem Titel der Seite und der URL, die angezeigt werden soll. Es ist nicht erforderlich, dass Sie eine URL mit einem Hash- oder Hashbangzeichen verwenden. Sie können einfach „http://foo.com/a“ verwenden, sogar wenn „a“ physisch nicht vorhanden ist.

Durch den pushState-Aufruf erstellen Sie den Sitzungsverlauf für die Sitzung dieses Benutzers. Der Benutzer kann nach Bedarf navigieren, und alles funktioniert erwartungsgemäß. Wenn er auf die Schaltfläche „Zurück“ klickt, wird wie erwartet die vorherige URL aufgerufen. Auch die URLs, die vorher aufgerufen wurden, sind weiter vorhanden, genau wie bei einer normalen Reihe von Seiten.

Es gibt zudem Verknüpfungen, durch die Sie navigieren und den Sitzungsverlauf des Benutzers ansehen können. Sie können den Benutzer dynamisch im Stapel vorwärts- und zurückbewegen, als ob der Benutzer selbst auf die Schaltflächen „Zurück“ und „Vorwärts“ klicken würde.

Ein praktisches Beispiel

Zur Verdeutlichung hier ein Beispiel aus der Praxis. Ich betreibe die Website einer Technologiekonferenz namens „That Conference“ (thatconference.com). Es nehmen zahlreiche Redner an der Konferenz teil, aber ich möchte nicht für jeden Einzelnen eine Seite erstellen. Statt dessen möchte ich für jeden Redner dynamisch eine Seite erstellen, die echt aussieht. Mit der Verlaufs-API ist das einfach umsetzbar.

Wie bei jeder skriptlastigen Webanwendung benötige ich Daten. That Conference hat erfreulicherweise eine einfache REST-API (Representational State Transfer), die ich aufrufen kann, um die Redner abzurufen: „thatConference.com/api/person“. Dieser Aufruf erzeugt ein Array der Redner des entsprechenden Jahres in JSON oder XML. Abbildung 1 zeigt ein Element in diesem Array.

Abbildung 1: Rednerprofil

<PersonViewModel>
  <FirstName>Scott</FirstName>
  <LastName>Hanselman</LastName>
  <Company>Microsoft</Company>
  <Bio>
    My name is Scott Hanselman. I work out of my home office for Microsoft as a Principal Program Manager, aiming to spread good information about developing software, usually on the Microsoft stack. Before this I was the Chief Architect at Corillian Corporation, now a part of Checkfree, for 6-plus years. I was also involved in a few Microsoft Developer things for many years like the MVP and RD programs and I'll speak about computers (and other passions) whenever someone will listen.
  </Bio>
  <Twitter>shanselman</Twitter>
  <WebSite>http://www.hanselman.com</WebSite>
  <Gravatar>/Images/People/2012Speakers/ScottHanselman.png</Gravatar>
</PersonViewModel>

Jetzt muss ich die Daten noch anzeigen. Ich benötige eine einfache Markupvorlage, mit der ich dynamisch eine Seite für jeden Redner erstellen kann. Dazu verwende ich ein Framework namens Knockout (knockoutjs.com). Knockout ist eine JavaScript-Bibliothek, mit deren Hilfe Entwickler deklarative Bindungen mit dem Model-View-ViewModel-Muster verwenden können. Knockout ist keine Notwendigkeit für die Verlaufs-API, aber ich verwende es gerne.

Da jede Rednerseite gleich ist, definiere ich in Knockout eine einfache Markupvorlage. Ich muss einen Skriptblock erstellen und das Framework anweisen, wie dieser später aufgefüllt wird:

<script type="text/html" id="person-template">
  <div>
    <p>
      <strong>Name:</strong>
        <span data-bind="text: FirstName"></span>
        <span data-bind="text: LastName"></span>
    </p>
    <p>Company: <strong data-bind="text: Company"></strong></p>
    <p>Bio: <strong data-bind="text: Bio"></strong></p>
  </div>
</script>

Als Nächstes muss ich die Vorlage auffüllen. Dazu rufe ich ko.applyBindings(someData) auf, und Knockout verarbeitet jedes beliebige Objekt, dass ich in applyBindings übergebe. Ich habe somit die grundlegenden Mechanismen erstellt, um die Markupvorlage mit den Daten eines Rednerobjekts aufzufüllen.

Mein Ziel ist aber ein wenig komplexer. Ich möchte eine Reihe von Seiten haben, die die Benutzer durchblättern können, sozusagen ein Rednerbuch. Folgendes muss ausgeführt werden, wenn die Seite zum ersten Mal geladen wird:

  1. JSON-Text abrufen, der die Redner darstellt
  2. Das erste Element im Array an die Knockout-Vorlage als Standard binden
  3. window.pushState aufrufen und die entsprechenden Argumente übergeben

Die ersten zwei Schritte haben wir bereits besprochen. Wenden wir uns daher pushState zu. Durch den window.pushState-Aufruf erstellen Sie ein Element im Sitzungsverlauf des Benutzers. Ich rufe pushState auf und übergebe drei Elemente:

  • State: Dies ist in meinem Fall das Array-Element, das ich an die Knockout-Vorlage gebunden habe.
  • Title: Dies ist der Titel der Seite, der aus dem vollständigen Namen des Redners besteht.
  • URL: Dies ist die URL der Seite. Sie wird in diesem Fall in etwa „thatconference.com/speakers/speaker/SpeakerFullName“ lauten.

Ich habe diese Logik in einer Methode zusammengefasst, die ich „bind“ genannt habe.

function bind (speakerID) {
  var speakerVM = new speakerViewModel(speakerID);
  var fullName = speakers[speakerID].FirstName 
    + speakers[speakerID].LastName
  window.history.pushState(speakerVM, 
    fullName, "/speakers/" + fullName); 
  ko.applyBindings(speakerVM);
}

Ich füge dem Rednerbuch jetzt einige Schaltflächen hinzu, damit die Benutzer durch die Rednerseiten blättern können:

    <button id="prevSpeaker">previous speaker</button>
    <button id="nextSpeaker">next speaker</button>

Für die Schaltflächen „nextSpeaker“ und „prevSpeaker“ benötige ich natürlich einige Ereignishandler. Um zu erfassen, welcher Redner der Nächste sein muss, erstelle ich einen einfachen Zähler, den ich manipuliere, während der Benutzer navigiert. Ich übergebe den Zählerwert an die bind-Methode:

var counter = 0;
$('#nextSpeaker').click( function () {
  counter = counter + 1;   
  bind(counter);
});
$('#prevSpeaker').click( function () {
  counter = counter - 1;
  window.history.back();
});

Zu diesem Zeitpunkt habe ich eine Seite, die mit einigen Standarddaten geladen wird. Wenn ich auf „nextSpeaker“ klicke, erhalte ich weiter den nächsten Redner im Redner-Array. Wenn ich prevSpeaker aufrufe, passiert allerdings nichts. Es fehlt noch etwas.

Ereignisse

Durch den Aufruf von windows.history.back über die Schaltfläche „Zurück“ oder ein Skript wird das onpopstate-Ereignis ausgelöst. Durch diese Verknüpfung gehen wir in der Sitzungshistorie eines Benutzers zurück. Wenn onpopstate ausgelöst wird, übergibt das Ereignis die Statusdaten, die an pushState übergeben wurden. In diesem Fall ist dies ein Redner.

Ich muss jetzt diese Statusdaten nehmen und Knockout anweisen, sie zu binden:

window.onpopstate = function (event) {
  console.log('onpopstate event was fired');
  ko.applyBindings( event.state );
};

Hiermit kann ich wie erwartet im Sitzungsverlauf vor- und zurückgehen. Ob Sie nun auf die „Zurück“-Schaltfläche des Browsers oder auf die „prevSpeaker“-Schaltfläche klicken, die Redner ändern sich entsprechend.

Und nun?

Ich habe die Verlaufs-API hier nur oberflächlich angeschnitten. Ich bin nicht darauf eingegangen, wie vorzugehen ist, wenn ein Benutzer einen Redner als Favoriten hinzufügt und die Website später besucht, oder wenn der Benutzer eine der neuen Rednerseiten besucht, die ich gerade erstellt habe, und auf „Aktualisieren“ klickt.

Ich habe dieses Szenario bewusst ausgelassen, da Sie hier eine Vielzahl an Methoden verwenden können, die aber davon bestimmt werden, wie Sie die Website strukturiert haben und welche Technologien Sie einsetzen. Wenn Sie # oder #! verwenden möchten, kann es vollkommen ausreichen, das URL-Fragment durch den window.location.hash-Aufruf zu erhalten, anschließend einen Dienst aufzurufen, um die entsprechenden Hashdaten abzurufen, und schließlich die Bindung an das Markup vorzunehmen.

Es ist wichtig, dass meine Lösung zwar eine vollständige dynamische Seite erstellt, Sie die Verlaufs-API aber auch für einen Teil einer vorhandenen Seite verwenden können. So nutzt der Kern der Seite den Server, ein Teil der Seite aber die Verlaufs-API. Sie finden dazu unter bit.ly/vOlB2U ein sehr gutes, detailliertes Beispiel.

Implementieren Sie auch die Funktionserkennung in Ihre Webanwendung. Nutzen Sie ein Tool wie Modernizr (modernizr.com), um die Funktionalität des Browsers abzufragen, anstatt Aktionen anhand von Benutzer-Agents zu bestimmen. Falls der Browser eines Benutzers eine bestimmte Funktion nicht unterstützt, können Sie ein Polyfill verwenden – ein Shim zur Implementierung der Funktion für den Browser. Dies ist sogar bei Funktionen wie CSS möglich. Weitere Informationen zur Funktionserkennung finden Sie in Brandon Satroms Artikel vom September 2011: „Kein Browser bleibt unberücksichtigt: Eine Strategie für die Einführung von HTML5“ (msdn.microsoft.com/magazine/hh394148).

AJAX hat die Interaktion von Websites im Internet verändert, und die Webentwickler fanden kreative Lösungen, um Standardwebsites in umfassende Webanwendungen umzuwandeln. Auch mit solchen skriptlastigen Webanwendungen müssen die grundlegenden Browserfeatures intakt bleiben. Dabei hilft die Verlaufs-API.

Die Inhalte dieses Artikels wurden unter Windows 8 Release Preview mit Microsoft WebMatrix erstellt. Sie können den Code unter on.csell.net/msdn-historylesson abrufen. Unter on.csell.net/msdn-historylesson-linkstack finden Sie eine Reihe sehr guter Ressourcen zur Verlaufs-API.

Clark Sellarbeitet von Chicago aus als Senior Web and Windows 8 Evangelist für Microsoft. Seine Blogbeiträge finden Sie unter csell.net und seine Podcasts unter DeveloperSmackdown.com. Auf Twitter finden Sie seine Beiträge unter twitter.com/csell5.

Unser Dank gilt den folgenden technischen Experten für die Durchsicht dieses Artikels: John Hrvatin, Mark Nichols, Tony Ross und Brandon Satrom.