Aufstand der Webworker: In JavaScript geschriebener Codec bringt Firefox MP3 bei

Veröffentlicht am 20. Juni 2011

Eins der schönsten Features von HTML5 könnten die Audio- und Videoelemente sein. Mit <audio> und <video> ist es kinderleicht Ton und Bewegtbild in Webseiten einzubetten und die sehr durchdachte API lädt zum Programmieren eigener Player-Interfaces geradezu ein. Leider gibt es das Codec-Problem: kein einziger Audio- oder Videocodec wird von allen Browsern unterstützt. Die beteiligten Parteien (die Browserhersteller) haben auch sehr gute Gründe – wirtschaftliche Gründe – sich dem jeweiligen Feindes-Codec zu verweigern und es ist nicht wirklich abzusehen, wo in nächster Zeit eine Einigung unter den Browserherstellern herkommen soll. Fakt ist also: <audio> und <video> sind bis auf weiteres praktisch unbrauchbar.

Weil ich zum Glück keinen Browserhersteller gehöre, sondern unabhängiger HTML5-Erklärbär bin, darf ich das am Ende eines Workshops auch immer so offen formulieren. Meine Vorschläge für den Umgang mit dieser Situation sind immer die gleichen: entweder weiterhin das bewärhte Flash benutzen oder den fehlenden Codec in JavaScript nachbauen. Letzteres wird dabei meist nicht ganz ernst genommen, aber wenn es Dinge wie einen in JS geschriebenen PC-Emulator gibt, sehe ich nicht ein, dass es nicht Möglich sein soll, seinen eigenen Decoder im Browser zu programmieren. Dank Audio Data API und <canvas> ist es schließlich möglich, jedwede Ton- oder Bildinformation in einem modernen Browser abzubilden – es müsste halt nur mal jemand wagen.

Und nun hat es endlich mal jemand gewagt: JSmad ist das Script, das dem Firefox 4 MP3-Support beibringt. Das Projekt ist noch recht jung und es hat so seine Performance-Probleme, aber in diesem Fall zählt wirklich vor allem erst mal der Grundgedanke: es gibt keinen Grund, sich HTML5 von den Browserherstellern kaputt machen zu lassen! Sie geben uns ein kaputtes DOM und wir werfen so lange jQuery darauf, bis es funktioniert. Sie liefern uns keine Codecs, wir bauen sie. Das ist genau die richtige Einstellung für ein entspannt-produktives Verhältnis zu HTML5 – denn von Klagen und Jammern allein wird es nicht besser.

Langfristig gesehen wäre es vermutlich rechtlich unbedenklicher, die freien Codecs für die proprietären Browser nachzubauen statt wie im Fall von JSmad umgekehrt. Dazu müssten sich Safari und Internet Explorer zwar erst mal die Audio Data API zulegen, aber auch dieser Tag kann so fern nicht sein. Das wird schon.

Warum man die neuen Formularelemente von HTML5 nicht (kaum) mit CSS stylen kann

Veröffentlicht am 6. Juni 2011

Wenn ich auf meinen HTML5-Seminaren die neuen Formularelemente vorstelle, währt die allgemeine Begeisterung in der Regel exakt so lange, bis einer fragt, wie man die tollen neuen Dinger denn gestalten könnte. Denn das ist praktisch nicht möglich. Nimmt man etwa einen Datumspicker, wie er uns in Opera dargestellt wird, und verpasst ihm via CSS einen roten Hintergrund, so ist das Ergebnis etwas unbefriedigend:

HTML5-Datumspicker + CSS = Desaster

Zwar ist das nicht im engeren Sinne falsch (ein roter Hintergrund ist ja vorhanden), aber auch nicht wirklich hilfreich – die Sonntage sind unsichtbar und die Buttons noch ohne jedes Design. Das Interface des Datumspickers ist einfach eine zu komplexe Konstruktion, als dass man ihr durch so einfache Anweisungen wie „roter Hintergrund“ gerecht werden könnte. Unter der Haube baut der Browser den Datumspicker aus HTML zusammen, so genanntem Shadow DOM, das gegen Stylingmaßnahmen von außen abgeschirmt ist. Theoretisch könnten es uns die Browserhersteller durch Pseudoelemente ermöglichen, die Einzelteile des Datumspickers zu gestalten, doch auch auch dann würde sich der Vorgang zwischen den verschiedenen Browsern extrem unterscheiden. Bei praktischer Betrachtungsweise muss man also sagen: die neuen Formularelemente von HTML5 lassen sich kaum bis gar nicht gestalten.

Der Grund hierfür ist einfach: der Standard legt überhaupt nicht fest, wie die neuen Formularelemente auszusehen haben. So verlieren die Spezifikationen z.B. über die Natur des <input type="date"> nicht mehr als die folgenden Worte:

The input element represents a control for setting the element's value to a string representing a specific date.

Das ist ein Freibrief für die Browserhersteller, ihr Eingabefeld so zu gestalten wie es ihnen gerade passt. Daraus folgen die unterschiedlichsten Umsetzungen der Browser und die praktisch nicht gegebenen Gestaltungsmöglichkeiten. Und das ist kein Versehen, sondern durchaus ein Feature, denn wie ein ein Eingabefeld idealerweise auszusehen hat, ist von dem Kontext abhängig, in dem es verwendet wird. So sportet das Number-Input in Desktopbrowsern in aller Regel zwei kleine Buttons zum hoch- und runterzählen:

Number-Input in einem Desktopbrowser

Das ist eine absolut sinnvolle Umsetzung eine solchen Zahlen-Eingabefeldes, doch es sind durchaus Umstände denkbar, unter denen andere Gestaltungsansätze besser wären. Das gleiche Zahlen-Eingabefeld sieht in einem iPhone so aus:

Number-Input von HTML5 im iPhone-Browser

Das Eingabefeld sieht aus wie jedes andere Feld auch; die Umsetzung des Elements erfolgt ausschließlich über die Bildschirmtastatur. Denn mit den winzigen Buttons zum hoch- und runterzählen möchte man sich in einem Touch-Interface sicher nicht herumschlagen und sich die Möglichkeiten einer angepassten Bildschirmtastatur entgehen zu lassen wäre nicht sonderlich clever. So geht der iPhone-Browser seinen eigenen Weg, den er aber auch nur gehen kann, weil eben die Spezifikationen die genaue Umsetzung der neuen HTML5-Formularelemente offenlassen. Dass dabei die Gestaltungsfreiheit leidet, ist eine unschöne, aber wohl nicht vermeidbare Nebenwirkung.

ECMAScript 5, die nächste Version von JavaScript – Teil 5: Kleine Helferlein für Arrays

Veröffentlicht am 30. Mai 2011

Auf unserer Tour durch die neuen Features von ES5 wird es Zeit, den letzten großen Themenkomplex in Angriff zu nehmen: Arrays. Arrays in JavaScript sind seltsame Zeitgenossen. Sie sind kein eigener Datentyp, sondern spezielle Objekte, die dem Programmierer viele Möglichkeiten bieten, sich in den eigenen Fuß zu schießen (Stichwort Array-Constructor, length-Eigenschaft). Auch in ES5 ändert sich daran nicht viel, denn anstelle größerer Umbauten es gibt „nur“ ein paar neue Funktionen, die das Arbeiten mit Arrays erleichtern.

Array oder nicht?

Dass der typeof-Operator mit schlafwandlerischer Sicherheit jedes Array als object identifiziert, dürfte einer der Gründe dafür sein, dass JavaScript nicht ernstgenommen wird. Dabei liegt typeof nicht falsch – JavaScript-Arrays sind nun mal Objekte. Das ändert natürlich nichts daran, dass typeof an dieser Stelle nicht sonderlich hilfreich ist. ES5 hilft, indem es die Funktion Array.isArray() einführt, die genau das macht, was man von ihr erwartet:

var foo = [];
typeof foo;         // "object"
Array.isArray(foo); // true

Warum „repariert“ man nicht einfach typeof? Das hat zwei Gründe: erstens hat, wie erwähnt, typeof nicht unrecht wenn es Arrays als Objekte identifiziert, zweitens ist das Web voll mit Scripts die sich auf das alte Verhalten von typeof verlassen. Würden das alle Browser von heute auf morgen ändern, wären die Auswirkungen so verheerend, dass kein Weg an einer neuen Funktion vorbeiführt. Wenn man bedenkt, wie kompliziert die Identifizierung von Arrays ist, sollte man einfach froh sein, dass es Array.isArray() gibt.

ForEach, Filter und Map

ForEach-Schleifen sollte jedem JavaScript-Programmierer aus seinem liebsten Framework bekannt sein und werden in der bekannten Form in ES5 fest eingeführt. Das Array wird einmal durchlaufen und eine Callback-Funktion wird der Reihe nach auf die Array-Elemente angewendet:

// Drei Alerts für drei Zahlen
var zahlen = [6, 9, 12];
zahlen.forEach(function(zahl){
    alert(zahl);
});

Während forEach() nichts zurückgibt und nur mit den einzelnen Array-Elementen arbeitet, produzieren map() und filter() neue Arrays. Dabei filtert mittels filter() eines Callbacks ein Array; gibt der Callback true zurück, wird das Element in das neue Array gepackt, bei false nicht:

// Sortiert ungerade Zahlen aus
var zahlen = [6, 9, 12];
var gerade_zahlen = zahlen.filter(function(zahl){
    if(zahl % 2 == 0){
        return true;
    }
    else {
        return false;
    }
});

Der dritte Kandidat im Bunde, map(), wendet einen Callback auf alle Array-Elemente an. Die vom Callback zurückgegebenen Werte bilden dann ein neues Array:

// Verdoppelt die Zahlen im Array
var zahlen = [6, 9, 12];
var verdoppelte_zahlen = zahlen.map(function(zahl){
    return zahl * 2;
});

Vorsicht Falle: Die Callbacks von forEach(), filter() und map() bekommen drei Parameter übergeben! Neben dem akuellen Array-Element wird auch der aktuelle Index sowie das Array an sich übergeben. Das kann nützlich sein, kann aber zur Falle werden:

["6", "9", "12"].map(parseInt);

Hier sollen die Strings im Array in Integer verwandelt werden; man würde erwarten, dass man ein Array mit dem Inhalt [6, 9, 12] erhält. Das tatsächliche Ergebnis ist aber [6, NaN, 1]. Wie das sein kann? Ganz einfach: Wenn parseInt() ein zweites Argument übergeben bekommt, behandelt es diesen als Basis und produziert entsprechende Ergebnisse. Das ist zwar logisch, aber nicht gerade intuitiv – also immer schön aufpassen.

Alle drei neuen Funktionen nehmen neben dem Callback auch noch einen zweiten Parameter an, der bestimmt, welches Objekt im Callback für this verwendet wird. Wird hier nichts oder null angegeben, ist this das globale Objekt (es sei denn man befindet sich im Strict Mode, wo dies bekanntlich nicht mehr möglich ist).

Every und Some

Die beiden Array-Methoden every() und some() wenden einen Prüf-Callback auf die Elemente eines Arrays an. Der Callback prüft, ob die Elemente einer gewissen Bedingung entsprechen und gibt true oder false zurück. Der Unterschied zwischen every() und some(): ersteres gibt true zurück, wenn alle Array-Elemente den Test bestanden haben, letzteres auch dann, wenn nur ein einziges Element die Bedingungen erfüllt.

var arr = [2, 4, 6, 7, 11];

// False - es sind nicht ALLE Elemente gerade Zahlen
arr.every(function(element){
    return (element % 2 === 0);
});

// True - Einige Elemente SIND gerade Zahlen
arr.some(function(element){
    return (element % 2 === 0);
});

Wie auch bei forEach(), filter() und map() bekommt der Callback drei Argumente übergeben – neben dem zu prüfenden Element auch seinen Index und das gesamte Array. Auch das this des Callbacks kann über ein zweites Argument für every() und some() bestimmt werden.

Reduce und ReduceRight

Wenn es darum geht, ein Array auf einen einzigen Wert einzudampfen, sind reduce() und reduceRight() die Mittel der Wahl. Beide gehen ein Array Element für Element durch (leere Elemente werden übersprungen) und wenden einen Callback auf die Elemente an. Dem Callback wird dabei einerseits der Wert des aktuellen Elements übergeben, andererseits auf der Rückgabewert des vorherigen Callback-Ausrufs. So kann man zum Beispiel bequem die Zahlen in einem Array aufsummieren:

var arr = [1, 2, 3];

// Summiert alle Elemente des Arrays auf (Resultat: 6)
arr.reduce(function(prev, curr){
    return prev + curr;
});

Der Unterschied zwischen reduce() und reduceRight() ist, dass ersteres das Array von links nach rechts durchgeht, letzteres von rechts nach links:

var arr = ["A", "B", "C"];

// Ergebnis: ABC
arr.reduce(function(prev, curr){
    return prev + curr;
});

// Ergebnis: CBA
arr.reduceRight(function(prev, curr){
    return prev + curr;
});

Der Callback erhält wie üblich neben dem vorherigen Rückgabewert und dem aktuellen Elemente auch den Index des aktuellen Elements und das gesamte Array. Über ein zweites Argument von reduce() bzw. reduceRight() kann man den Startwert für den ersten Callback-Aufruf festlegen:

var arr = [1, 2, 3];

// Summiert alle Elemente des Arrays und den Startwert auf (Resultat: 10)
arr.reduce(function(prev, curr){
    return prev + curr;
}, 4);

IndexOf und LastIndexOf

Schon gewusst, dass indexOf() ein Teil von ES5 und damit eine so eine Art Neuheit ist? Zusammen mit lastIndexOf() dient es bei der Positionsbestimmung eines Elements in einem Arrray, wobei indexOf() den ersten Index und lastIndexOf() den letzten Index zurückgibt.

var arr = ["a", "b", "c", "a", "d"];
arr.indexOf("a");     // 0
arr.lastIndexOf("a"); // 3

Neben dem Element, nach dem in dem Array gesucht werden soll, kann auch ein Startindex für die Suche angegeben werden. Dabei sucht indexOf() von diesem Startindex aus vorwärts und lastIndexOf() rückwärts.

var arr = ["a", "b", "c", "a", "d"];
arr.indexOf("a", 1);     // 3
arr.lastIndexOf("a", 2); // 0

Wie geht es weiter?

Nachdem, wie zu erwarten war, uns der Blick auf die allmächtige Kompatibilitätstabelle freudig stimmt (außer in ältere IE funktioniert der Array-Teil von ES5 überall) bleibt die Frage wie es denn jetzt weitergeht. In Sachen ES5 gibt es nicht mehr viel zu berichten, denn alles wirklich neue haben wir bereits abgearbeitet. Dinge wie JSON und String.prototype.trim sind zwar streng genommen auch ES5, sind aber auch bereits allgemein bekannt und von den Browsern gut unterstützt.

In den folgenden Teilen der Serie werden wir daher noch weiter in die Zukunft vorstoßen. ECMAScript 5 ist ja streng genommen schon ein altes Eisen – immerhin datieren die Spezifikationen vom Dezember 2009. Zeit also, sich mit dem wirklich Neuen zu befassen, das zur Zeit noch den Arbeitstitel „ECMAScript Harmony“ trägt. Das wenige davon, das man tatsächlich schon anfassen kann, ist nur punktuell in Browsern implementiert und es ist nicht gesagt, dass es irgendwann in seiner heutigen Form auch Standard wird, aber zum experimentieren reicht allemal. Themen wie Traceur, Node.js und CoffeeScript werden wir sicher auch mal anschneiden können.

Hardware-Review: SureFlap

Veröffentlicht am 16. Mai 2011

Mein pelziger Mitbewohner Jacky ist nicht die einzige Katze hier in der Gegend und muss daher um jeden Quadratzentimeter seines Reviers einen erbitterten Abwehrkampf ausfechten. Doch wie Herrchen auf dem Fußballfeld, so besticht auch Jacky vor allem durch die große Anzahl der bestrittenen Zweikämpfe und nicht unbedingt durch eine hohe Erfolgsquote. Die Folge: fremde Katzen kombinierten sich wiederholt durch die Katzenklappe und erzwangen Standardsituationen in der Küche! Die Abwehr konnte am Ende nur durch ein Austauschen des Tores stabilisiert werden.

Das Sure-Flap-Prinzip

SureFlap ist eine smarte Katzenklappe, die im Tier implantierte Identifikationschips scannt und sich nur für vorher eingespeicherte Katzen entriegelt. Die ca. 90€, die man dafür auf den Tisch legen muss, erscheinen für eine Katzenklappe recht stattlich, allerdings muss man sagen, dass die SureFlap sehr gut funktioniert, einfach zu montieren ist und auch der Support kann sich sehen lassen. Das in England designte, in China produzierte und aus Florida heraus vertriebene Produkt hat die Maße einer handelsüblichen Katzenklappe, passt also in eventuell schon vorhandene Löcher hinein. Der Einbau gestaltet sich simpel; ein paar Schrauben festdrehen, fertig.

SureFlap

Als Vorbereitung für den Betrieb müssen nur noch Batterien eingelegt, ein Knopf gedrückt und die zu speichernde Katze (bis zu 32 Katzen kann sich die SureFlap merken) einmal durch den Durchgang bugsiert werden. Ab hier lässt die Klappe von außen nur noch gespeicherte Chipträger durch. Konstruktionsbedingt passt nie mehr als eine Katze auf einmal in den Tunnel, so dass Unbefugte sicher ausgesperrt werden. Zwischen Normalbetrieb, Tag-der-offenen-Tür-Modus und Komplettverriegelung kann man mit einem Drehknopf hin- und herschalten.

Das erste hier ausprobierte Exemplar der SureFlap war fehlerhaft; manchmal entriegelte sich die Klappe einfach nicht. Dies war aber kein Problem, die freundliche Hotline und ein schneller Umtausch schafften das Problem rasch aus der Welt. Seither kann Jacky kommen und gehen wie er will und der Gegner bleibt im Abseits – die SureFlap ist also absolut zu empfehlen. Zu beachten ist:

  • Der Zoofachhandel um die Ecke hatte die SureFlap nicht auf Lager (man war dort eher über die Existenz von intelligenten Katzenklappen erstaunt), aber Amazon hilft
  • Optimale Isolierung ist etwas anderes. Es empfiehlt sich, nachträglich am Scharnier einen Isolierstreifen anzubringen.
  • Die Tür, in die die Klappe eingesetzt werden soll, sollte tunlichst nicht viel Metall an sich haben, da dies den Empfang des Chipsensors empfindlich stören kann. Fragt bitte nicht, woher ich das weiß.

Der einzige, dem die SureFlap nicht so sehr gefällt, ist ihr Nutzer selbst, denn durch die Tunnelkonstruktion, die verhindert, dass Fremde im Windschatten einer zugangsberechtigten Katze durch die Klappe schlüpfen, gestaltet sich der Durchgang für Jacky nicht ganz so bequem wie bei herkömmlichen Katzenklappen. Aber wer sein Revier nicht ordentlich absteckt, muss eben mit den Konsequenzen leben.