Umzugsbedingt lag das Blog ein paar Wochen brach, aber zum Glück nicht die Inbox. So haben sich wieder einige Fragen zu HTML5, CSS3 und JS (bzw. DOM) angesammelt, die ich mir Vergnügen beantwortet habe. Allerdings ist die Inbox jetzt auch schon wieder leer. Wenn euch also eine Frage zum Themenkomplex „futuristisches Frontend“ auf dem Herzen liegt, zögert nicht mir eine E-Mail oder einen Tweet zukommen zu lassen.

Welches semantische Element für Pull Quotes?

Wie sollte man Pull Quotes mit Zitaten aus dem umgebenden Text semantisch auszeichnen?

Hier ist <aside> das Mittel der Wahl, garniert mit ein ARIA-Extras. Konkurrenz-Element Nummer 1, <blockquote>, scheidet allein schon aus inhaltlichen Gründen aus. Zitat Spezifikationen:

The blockquote element represents a section that is quoted from another source.

Hingegen heißt es beim <aside>-Element ausdrücklich:

The [aside] element can be used for typographical effects like pull quotes or sidebars [...]

Da es sich bei einer solchen Pull Quote um einen reinen typografischen Effekt handelt, kann man über den ergänzenden Einsatz des Attributs aria-hidden auf dem <aside>-Element nachdenken. Zwar ist dieses Attribut eigentlich dafür gedacht, unsichtbare Elemente vor Screenreadern zu verbergen, aber da sich bei Pull Quotes um völlig redundante Informationen handelt, möchte sie sich ziemlich sicher niemand vorlesen lassen. Die korrekte Lösung für Pull Quotes heißt also <aside aria-hidden="true">.

Input-Value von File-Inputs setzen?

Ich habe ein spezielles Problem. Gibt es eine Möglichkeit mittels JavaScript den value eines Inputs vom Typ File zu setzen? Eigentlich ist das ja verboten, ich würde aber gerne, wenn ein Nutzer keine Datei ausgewählt hat, automatisch eine Datei vom Webserver laden lassen. Bekommt man das irgendwie hin?

Da gibt es leider keinen geheimen Trick. Das Setzen des value von File-Inputs mit etwas anderem als einem leeren String (um das Feld zu löschen) wird immer mit einem InvalidStateError quittiert; so wollen es die Spezifikationen.

Ohne Workaround geht es nicht. Man müsste also bei serverseitiger Verarbeitung nachsehen, ob eine Datei mitgeschickt wurde und andernfalls darauf reagieren. Bei clientseitiger Verarbeitung könnte man die Alternativ-Datei per XHR laden und für den Fall vorhalten, dass das Formular ohne ausgewählte Datei abgeschickt wird. Mit XHR2 ist das zum Glück ganz einfach, denn da gibt es das responseType-Attribut mit "blob" als möglichem Wert:

function handleFile(file){
  console.log(file)
}

document.querySelector('form').onsubmit = function(evt){
  evt.preventDefault();
  var fileField = document.querySelector('[name=file]');
  // Keine Datei gewählt? Default per XHR holen!
  if(fileField.files.length === 0){
    var req = new XMLHttpRequest();
    req.open('GET', 'formtest.html');
    req.responseType = 'blob';
    req.onload = function(evt){
      var blob = evt.target.response;
      handleFile(blob);
    };
    req.send();
  }
  // Gewählte Datei verarbeiten
  else {
    handleFile(fileField.files.item(0))
  }
};

Das ist keine sehr schöne Lösung, aber auch keine Katastrophe. Mit FormData könnte man einfach das ohnehin anfallende Formular-Datenset modifizieren und eine nicht ausgewählt Datei ergänzen, aber mit der Browserunterstützung der dafür nötigen Teile von FormData sieht es nicht besonders gut aus.

Shared Worker aus Strings oder Blobs erstellen

Kann man Shared Worker aus Strings bzw. Blobs erstellen?

Möglich ist alles! Damit sich alle Shared Worker in den gleichen Prozess einklinken, müssen sie mit dem gleichen Namen und der gleichen Quell-URL erstellt werden. Das Problem hierbei: nimmt man in zwei Webseiten das gleiche Ausgangsmaterial (also einen String JS-Code) werden hieraus zwei unterschiedliche Blob-Objekte mit unterschiedlichen Blob-URLs. Es muss also einen Weg geben, allen Webseiten eine kanonische Blob-URL mitzuteilen. Gelingt das, ist der Shared Worker selbst kein Problem, denn sowohl Worker selbst als auch Blob-URLs unterliegen der normalen Same Origin Policy (außer im IE). Also frisch ans Werk!

Zunächst brauchen wir Code für einen Shared Worker, um das Konzept zu testen. Dieser Schnipsel (nur stilecht als JS-Multiline-String) sendet als Worker allen verbundenen Webseiten jede Sekunde eine Zahl zwischen 23 und 42, so dass alle Seiten jeweils schön synchronisiert sind:

var workerCode = "var clients = [];\
self.onconnect = function(evt){\
  var client = evt.ports[0];\
  client.postMessage(42);\
  clients.push(client);\
};\
setInterval(function(){\
  var x = Math.floor(Math.random() * (42 - 23 + 1)) + 23;\
  clients.forEach(function(client){\
    client.postMessage(x);\
  });\
}, 1000);";

Die Webseiten sollen diesen Wert in einem DOM-Element anzeigen. Dazu dient die folgende Handler-Funktion:

function workerHandler(evt){
  var x = evt.data;
  document.querySelector('#ShowX').innerHTML = x;
}

Um die kanonische Blob-URL zu kommunizieren könnte man das Storage-Event oder eine der HTML5-Kommunikations-APIs nutzen. Für unser Beispiel nehmen wir Cross-Document-Messaging und um den Versuchsaufbau einfach zu halten, lassen wir die Webseiten einander mit window.open() öffnen. Um zu entscheiden, ob eine Webseite selbst eine Blob-URL erstellen oder auf eine Nachricht des Eltern-Elements soll, prüfen wir window.opener. Falls die Webseite die Pionierrolle einnehmen soll, erstellt sie Blob und URL selbst, ansonsten wird auf eine Cross-Document-Message gewartet:

var workerUrl;

// Bootstrap-Szenario: erstes Fenster, Blob, URL und Worker
// selbst aus dem Code erstellen
if(!window.opener){
  var workerBlob = new Blob([workerCode], {
    type: 'text/javascript'
  });
  workerUrl = window.URL.createObjectURL(workerBlob);
  var worker = new SharedWorker(workerUrl, 'SyncWorker');
  worker.port.onmessage = workerHandler;
  document.querySelector('button').removeAttribute('disabled');
}

// Externe-Worker-Quelle-Szenario: Popup-Fenster, Worker-URL
// vom Eltern-Fenster erwarten
else {
  window.onmessage = function(evt){
    workerUrl = evt.data;
    var worker = new SharedWorker(workerUrl, 'SyncWorker');
    worker.port.onmessage = workerHandler;
    document.querySelector('button').removeAttribute('disabled');
  };
}

Nun fehlt nur noch der Button für neue Fenster und schon sind wir fertig:

// Neues Fenster öffnen, Worker-URL per Message übergeben
document.querySelector('button').onclick = function(){
  var newWindow = window.open(window.location.href);
  newWindow.onload = function(){
    newWindow.postMessage(workerUrl, '*');
  };
};

Das Beispiel funktioniert zwar, ist aber naturgemäß etwas fragil. Sobald eine Seiten-Instanz nicht über den Button auf einer anderen Seite geöffnet wird, bricht alles zusammen; die Seite hält sich irrtümlich für die erste, erstellt eine eigene Blob-URL und scheitert mit der Erstellung des Workers, da bereits einer mit gleichem Namen existiert. Ein sinnvolleres Vorgehen könnte wie folgt aussehen:

  1. Grundsätzlich eine eigene Blob-URL erstellen
  2. In einem Try-Catch-Block versuchen mit der eigenen URL einen neuen Shared Worker zu eröffnen
    1. Im Erfolgsfall Blob-URL in Local Storage ablegen; die Blob URL ist jetzt kanonisch, da noch kein anderer Worker mit diesem Namen aktiv ist
    2. Im Falle einer Exception muss es bereits einen laufenden Prozess mit kanonischer URL geben und diese muss im Storage liegen. URL aus Local Storage auslesen und nochmal versuchen den Worker zu erstellen

Diese Variante sei dem geneigten Leser als Hausaufgabe überlassen.

Wie tiefgreifend wirkt Flexbox (und warum)?

Ich habe in letzter Zeit viel zu Flexbox gelesen. Meine Frage ist, wie sich die Flexboxeinstellungen des Elternelements auswirken. Gelten diese für alle Kindelemente? Oder nur auf für nächstgelegene Kindelement?

Flexbox-Einstellungen wirken sich immer nur auf die direkten Kindelemente aus. Das irritiert Flexbox-Newcomer manchmal – man würde vielleicht erwarten, dass CSS-Eigenschaften gefälligst durch alle Kindelemente weitergegeben werden sollten. Allerdings ist (technisch gesehen) Flexbox nichts weiter als ein zusätzlicher Layout-Modus. Flexbox steht damit komplett gleichberechtigt neben den altbekannten Block-, Inline-, Table- und Positioned-Layoutmodi (letzterer betrifft position:absolute etc.). Macht man sich das klar, wundert man sich nicht mehr, denn bei allen Layout-Modi regeln Elternelemente die Anordnung ihrer direkten Nachfahren, aber nicht aller Nachfahren. Und das ist auch gut so! Man stelle sich nur vor, wie nutzlos position:absolute wäre, wenn diese Eigenschaft an alle Kindelemente eines Elements vererbt würde …

Weitere Fragen?

Auch eure Fragen zu HTML5, JavaScript und anderen Webtechnologien beantworte ich gerne! Einfach eine E-Mail schreiben oder Twitter bemühen und ein bisschen Geduld mit der Antwort haben. Ansonsten kann man mich natürlich auch als Erklärbär zu sich kommen lassen.