Freitag, 26. Februar 2010

Closures in JavaScript

Nachdem ich schon Beispiele für Closures (Funktionsabschlüsse) in JavaFX und Scala vorgestellt hatte, schauen wir uns dasselbe Beispiel in JavaScript (ECMAScript) an:
// closures.js

function zaehlerMitStartwert(startwert) {
return function() {
return startwert++
}
}

next1 = zaehlerMitStartwert(10)
next2 = zaehlerMitStartwert(20)

document.writeln( next1() ) // 10
document.writeln( next2() ) // 20
document.writeln( next2() ) // 21
document.writeln( next1() ) // 11

Sehr viel mehr gibt es dazu eigentlich nicht zu sagen, außer dass es
  1. wirklich so einfach ist (was auch zu Problemen führen kann)
  2. schon seit den ersten JavaScript-Versionen (und damit seit über 10 Jahren) zur Verfügung steht und
  3. im Gegensatz zu JavaFX Script und Scala dynamisch typisiert ist; zudem ist keine zusätzliche lokale Variable nötig, weil der Übergabeparameter startwert bereits eine veränderbare Variable ist. Beides ist im Hinblick auf stabile, wartbare Software nicht unbedingt als positiv zu bewerten.
Viele moderne JavaScript-Bibliotheken machen starken Gebrauch von solchen Funktionsliteralen und Closures, beispielsweise jQuery.

Dienstag, 16. Februar 2010

Hibernate/JPA, hashCode() und Eclipse

Entitäten in Hibernate/JPA haben oft einen synthetischen (technischen) Primärschlüssel, der im folgenden Beispiel als Long id implementiert wird. Sofern es keine zusätzlichen fachlichen Schlüsselkandidaten gibt, muss man die hashCode()-Methode für Hibernate/JPA so implementieren, dass sie ausschließlich mit dieser Id arbeitet. (Es gibt auf hibernate.org eine lange Diskussion zu diesem Thema, denn die "offiziell" vorgeschlagene Variante, "halb-eindeutige" Felder zu verwenden, macht in der Praxis meistens mehr Probleme als die Id-Variante.)

Damit man hashCode() (und das zwingend dazu gehörende equals()) nicht immer wieder von Hand neu schreiben muss, bietet Eclipse über Source > Generate hashCode() and equals() die Möglichkeit, die beiden Methoden mit wählbaren Attributen zu generieren. Eine Beispiel-Entität könnte dann wie folgt aussehen:
@Entity
public class Datensatz {

@Id @GeneratedValue
private Long id;

// weitere Felder, Getter, Setter, equals() etc. ...

/**
* Von Eclipse 3.5 generiert.
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((id == null) ? 0 : id.hashCode());
return result;
}
}
An sich liefert die Hashwert-Berechnung mittels Primzahlen eine gute Streuung, und der automatisch generierte Code beachtet auch, dass die Id null sein kann – nämlich bei noch nicht in der Datenbank gespeicherten (persistierten) Objekten.

Nun testen wir diese Entity-Klasse:
@Test
public void testAddToHashSet() {

Set<Datensatz> menge = new HashSet<Datensatz>();

Datensatz ds1 = new Datensatz();
Datensatz ds2 = new Datensatz();

assertTrue("Menge sollte leer sein", menge.isEmpty());

menge.add(ds1);

assertTrue("Menge sollte ein Element besitzen",
menge.size() == 1);
// ok

menge.add(ds2);

assertTrue("Menge sollte zwei Elemente besitzen",
menge.size() == 2);
// immer noch 1... Ups!?!
}
Immer, wenn man Assoziationen im Objekt-Modell aufbaut, bevor die Datensätze gespeichert sind (also z.B. bei Unit-Tests wie im obigen Beispiel), tritt das gezeigte Szenario auf. Wir haben hier zwei Objekte, die beide keine Id (=null) besitzen. Es sind aber trotzdem zwei nicht identische Objekte in separaten Speicherbereichen, die beim Persistieren entsprechend zwei Datensätze mit unterschiedlichen Primärschlüsseln (Ids) erzeugen würden. Leider kann sich das HashSet nur eines der beiden Objekte merken – und auch beim Persistieren einer Assoziation ginge so eines der Objekte verloren!

Eine Anpassung der generierten hashCode()-Methode ist denkbar einfach. Statt bei einer nicht vorhandenen Id die Zahl 0 für die Hashwert-Berechnung zu verwenden, nehmen wir den Hashwert des Objekts, der bei nicht identischen Objekten in den allermeisten Fällen unterschiedlich ist:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((id == null) ? super.hashCode() : id.hashCode());
return result;
}
Eine vergleichbare Implementierung nutze ich seit Jahren ohne Probleme, weshalb ich diese Variante pauschal für die praktikabelste halte. In Einzelfällen mögen natürlich speziellere Implementierungen sinnvoller sein.

Mittwoch, 3. Februar 2010

HTML-Accesskey mit CSS darstellen

Mit dem Standard-HTML-Attribut accesskey lassen sich Tastaturkürzel (Shortcuts) definieren, mit denen man den Eingabefokus auf die zugehörigen Eingabeelemente setzen kann:
<label for="nachname" accesskey="n">Nachname:</label>
<input id="nachname" type="text">
Mit welcher Tastenkombination der Fokus gesetzt wird, ist browser- und systemabhängig. Obiges Kürzel aktiviert man im IE/Win mit Alt+N, im Firefox/Win mit Shift+Alt+N, im Firefox/Mac mit Ctrl+N, in Safari/Mac mit Ctrl+Alt+N...

Natürlich sollte man dem Anwender auch anzeigen, dass Tastaturkürzel vorhanden sind. Man kann diese Information zwar noch einmal statisch in den HTML-Quelltext schreiben (zusätzlich zum accesskey-Attribut). Mit einer einfachen CSS2-Regel kann man aber auf solche redundanten Angaben verzichten und die Kürzel an allen label-Elementen darstellen lassen, bei denen accesskey gesetzt ist:

label[accesskey]:after {
content: " [" attr(accesskey) "]";
text-transform: uppercase;
color: #999999;
font-size: x-small;
}

Die CSS-Regel basiert vor allem auf einem Attribut-Selektor und einem Pseudo-Element. Dadurch wird nach (:after) jedem label-Element, bei dem das accesskey-Attribut vorhanden ist, der Wert des Attributs in Großschreibung (uppercase) in den Dokument-Inhalt (content) eingefügt:



An sich sind diese Techniken recht alt, die entsprechenden Standards wurden schon vor Jahren offiziell verabschiedet (die HTML 4.01-Spezifikation ist über 10 Jahre alt, CSS 2 ebenso). Was macht dieses Thema wieder aktuell?

Google hat vor kurzem angekündigt, ab dem 1. März dieses Jahres ältere Browser-Versionen und insbesondere den Internet Explorer 6 (IE6) nicht mehr in seinen Web-Anwendungen zu unterstützen.
Dieser – eigentlich längst überfällige – Schritt dürfte auch andere Anbieter dazu bewegen, neuere IE-Versionen (oder Alternativen wie Firefox) vorauszusetzen. Und dann könnten auch endlich IE-Nutzer von Web-Standards wie den Attribut-Selektoren profitieren, die der IE6 leider seit Jahren ignoriert.