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.

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

Veröffentlicht am 9. Mai 2011

ECMAScript 5 führt nicht nur komplett neue Konzepte wie den Strict Mode oder schreibgeschütze Objekte ein, sondern besteht zum einem großen Teil aus kleinen Helferlein. Viele dieser neuen Funktionen kommen einem aus den verbreiteten JavaScript-Frameworks bekannt vor und eigentlich könnte sie sich jeder halbwegs fähige Programmierer selbst mit JavaScript-Bordmitteln zusammenstricken. Doch wenn diese oft gebrauchten Funktionen durch ES5 verbindlich standardisiert und fest in den Browser eingebaut werden, braucht man eben genau das nicht tun und kann sich auf überall einheitliches Verhalten verlassen – und das ist doch eigentlich ganz fein.

Neue Helferlein gibt es für fast alle Bestandteile der Sprache, wobei die neuen Funktionen für Arrays und Objekte den größten Brocken ausmachen. In diesem Artikel geht es dabei zunächst um Objekte, denn für diese hat ES5 neben den aus Teil drei bekannten Funktionen zur Steuerung der Erweiterbarkeit von Objekten und Eigenschaften (Object.defineProperty, Object.freeze, Object.isSealed usw.) noch weitere Neuheiten an Bord.

Prototypen bei Objekterstellung festlegen

Es ist recht umständlich, ohne ES5 ein Objekt mit einem bestimmten Prototypen zu erstellen. Zunächst muss der Objekt-Constructor angelegt werden, dann kann der Prototyp bestimmt werden und erst danach darf man sein Objekt erstellen:

var Vogel = {
    federn: true
}

var Huhn = function(){};
Huhn.prototype = Vogel;

var meinHuhn = new Huhn();
meinHuhn.eier = 42;

Mit dem neuen Object.create() (MDC-Docs) ist es möglich, diesen ganzen Prozess in einem Statement abzuwickeln – property descriptor map für die Eigenschaften inklusive:

var meinHuhn = Object.create(Vogel, {
    eier: {
        value: 42,
        enumerable: true
    }
});

Das erste Argument ist der gewünschte Prototyp, das zweite die property descriptor map für die Eigenschaften. Als Prototyp kann entweder ein Objekt (auch ein leeres Objektliteral oder object.prototype) oder null angegeben werden.

Welchen Prototyp hat mein Objekt?

Mit dem in ES5 ebenfalls neuen Object.getPrototypeOf() (MDC-Docs) können wir uns vergewissern, dass Object.create() seine Arbeit ordnungsgemäß getan hat und dass unser Huhn wirklich Vogel als Prototyp hat:

Object.getPrototypeOf(meinHuhn); // Objekt "Vogel"

Object.getPrototypeOf() ist der standardkonforme Ersatz für die sonst an dieser Stelle gern verwendete Nicht-Standard-Eigenschaft __proto__.

Eigene Objekteigenschaften abfragen

Eine weitere Aufgabe, die ohne ES5 immer recht aufwändig ist, ist die Abfrage aller (eigenen) Eigenschaften eines Objekts. Man muss eine for-in-Schleife bemühen und in diesem noch eine Abfrage einbauen, die die gesamte Konstruktion auf die eigenen Eigenschaften des Objekt beschränkt und die vererbten Eigenschaften ausschließt:

var eigenschaften = [];
for(prop in meinHuhn){
    if(meinHuhn.hasOwnProperty(prop)){
        eigenschaften.push(prop);
    }
}

In ES5 geht das viel einfacher: Object.getOwnPropertyNames (MDC-Docs) und Object.keys (MDC-Docs) geben je ein Array mit den Eigenschaftsnamen des Objekts zurück. Object.getOwnPropertyNames gibt alle Eigenschaftsnamen des Objekts zurück – auch jene, die nicht enumerable (siehe Teil drei) sind. Hingegen beschränkt sich Object.keys auf Eigenschaftsnamen, die auch enumerable sind und verhält sich damit ziemlich genau wie die althergebrachte for-in-Schleife:

var Vogel = {
    federn: true
}

var meinHuhn = Object.create(Vogel, {
    eier: {
        value: 42,
        enumerable: true
    },
    hackordnung_level: {
        value: 6,
        enumerable: false
    }
});

Object.keys(meinHuhn);                // ["eier"]
Object.getOwnPropertyNames(meinHuhn); // ["eier", "hackordnung_level"]

Die federn-Eigenschaft des Prototypen fehlt in beiden Fällen, da die Funktionen jeweils nur die eigenen Objekteigenschaften abfragen.

Reservierte Namen als Objektschlüssel

Ein letztes kleines Extra: in ES5 dürfen reservierte Namen für Objekteigenschaften verwendet werden:

// Funktioniert nicht
var class = "foo";

// Funktioniert!
var foo = {
    class: "bar"
}

Das kann man zwar für inkonsequent halten, dürfte aber in letzter Konsequenz eher die allgemeine Verwirrung reduzieren als erhöhen – schließlich hagelt es weniger (mehr oder minder) grundlose Fehler.

Ausblick

Sieht man einmal von Opera ab, so bietet uns die allmächtige Kompatibilitätstabelle ein überwiegend grünes Bild was die neuen Funktionen für Objekte angeht – selbst der IE9 lässt sich nicht lumpen. Sogar noch besser sieht es im Array-Bereich aus, denn da spielt auch Opera mit. Arrays nehmen wir uns dann im nächsten Teil der Serie vor.