// der php hacker

// archiv

Von Links und JavaScript

Geschrieben am 29. Okt 2010 von Cem Derin

Der Knalli hat gestern bei Twitter auf einen eigentlich recht interessant und vor allem richtig klingenden Artikel verlinkt. Es geht darum, dass man Links, die in irgend einer Weise auf JavaScript-Events reagieren, nicht mit href=“#“ ausstatten soll. Soweit so gut. Der Autor gibt an, auf zwei Alternativen hinzuweisen, wie man es besser machen könnte. Zwei – so viele Fallen mir auch ein, und eigentlich habe ich nur weitergelesen, weil der Autor schon im Teaser von „History-Zumüllen“ geschrieben hat.

Was ich dann lesen musste, sorgte dafür, dass sich meine Fußnägel sprungartig aufrollten: Es wurde tatsächlich empfohlen, keinen Link zu benutzen, sondern Text einfach per CSS wie einen aussehen zu lassen.

<span
  style="text-decoration:underline; cursor:pointer;"
  onclick="foo();">click me</span>

Ziemlich unsauber. Nehmen wir an, ich habe CSS abgeschaltet. Was sehe ich? Nichts. Nur Text. Der, sofern ich JavaScript abgeschaltet habe, auch noch überhaupt nichts macht. Nicht sehr Benutzerfreundlich.

Die zweite Möglichkeit ist zwar weniger dramatisch aus Benutzersicht, dafür aber technisch wesentlich unsauberer: Man soll als Verweisziel JavaScript benutzen.

<a href="javascript:void(0);" onclick="foo();">click me</a>

oder noch hässlicher

<a href="javascript:foo();">click me</a>

Warum das alles keine guten Alternativen sind (im Grunde sogar ähnliches Gepfusche) und wie man es richtig macht, will ich erläutern!

Denk an Benutzer

Man darf nie vergessen, wie der Benutzer denkt, reagiert, was er erwartet und wie er mit Elementen auf einer Seite umgeht. Ein Link ist zum draufklicken. Das weiß jeder Benutzer. Ein Link, auf den man klickt, und es passiert nichts, irritert den Benutzer. Ein Link, der was anderes macht, als der Beschreibungstext vermuten lässt, irritert den Benutzer nicht nur, es nervt ihn dazu auch noch (insofern ist es natürlich richtig, nicht einfach einen leeren Anker zu setzen, weil hier in der Tat so gut wie jeder Browser zum Seitenanfang springt).

Was mich besonders stört, ist die Tatsache, dass nicht berücksichtigt wird, was passiert, wenn der Benutzer JavaScript ausgeschaltet hat (das hat eigentlich kaum einer, aber es passiert eben doch manchmal). Hier passiert krudes Zeug: Alle Möglichkeiten sorgen dafür, dass einfach nichts passiert. Toller Link: Ich klick drauf, draussen bellt ein Hund aber auf der Seite tut sich nichts. Lädt es vielleicht? Es gibt kein Feedback! Ätzend – nein – ätzendst!

Zu allem Überfluss bleiben mir als Benutzer auch noch Funktionen komplett verwehrt. Ich bin sauer, kann eh nichts benutzen und in letzter Konsequenz bin ich weg.

Und wie nun richtig?

Im Grunde ist es ganz einfach: Ich setze einen passenden Link.

Szenario 1

Ich biete beispielsweise eine Funktion an, mit der sich dynamisch ein Formular um einen File-Upload erweitert. Da ich aber auch dafür sorgen will, dass Benutzer ohne JavaScript ebenfalls Daten hochladen können, verweise ich auf eine Seite, in der das Upload-Formular bereits enthalten ist (beispielsweise über einen Parameter getriggert).

<a
  href="page.php?upload=true"
  onclick="displayUploadField(); return false;">Datei hochladen</a>

Wunderbar! Egal, ob ich JavaScript eingeschaltet habe oder nicht, ich bekomme die Funktionalität, die ich brauche, die ich erwarte und vor allem die, die mir versprochen wird.

Szenario 2

Biete ich eine Webapplikation an, die schwer auf AJAX setzt, kann es passieren, dass sich alles auf der Index-Seite abspielt, ohne dass sich die Adresse effektiv ändert. Um dann zum einen Deep-Links anzubieten und zum anderen das erwartete Verhalten der Navigationsbuttons des Browser nicht zu verändern kann ich den Anker benutzen, um solche Informationen zu verarbeiten und weiterzureichen. Nehmen wir also an, ich habe eine Übersicht mit Videos, die dynamisch in einen Player geladen werden, dann könnte der Link wie folgt aussehen:

<a
  href="#video/1"
  onclick="displayVideo(1);">Video mit der ID 1</a>

Wenn der Benutzer nun das Video mit der ID 1 anschaut und den Link dazu verschicken will, kann er das tun, wenn die Applikation den Hash-Part (also alles ab dem #-Zeichen) auswertet. Aber Achtung: Das Beispiel dient nur der Veranschaulichung. Da der Anker nicht wirklich existiert, wird trotzdem zum Anfang der Seite gesprungen, da wir nach dem onclick-Event die weitere Ausführung nicht durch return:false; verhindern. Machen wir das, wird der Anker gar nicht ersetzt. Was nun?

Keine Panik, das ist ziemlich einfach. Denn wer aufgepasst hat, dem wird aufgefallen sein, dass Leute ohne JavaScript mal wieder in die Röhre starren. Deswegen arbeiten wir hier wieder mit einer URL, mit der ein JS-loser etwas anfangen kann, wir verhindern wieder die weitere Ausführung nach dem onclick-Event und fackeln alles weitere in der displayVideo() Funktion ab. Code:

<script type="text/javascript">
	function displayVideo(id) {
		// … hier der Code, der das Anzeigen des Videos besorgt
		window.location.hash = '#video/' + id;
	}
</script>
<a href="#/video/1" onclick="displayVideo(1); return false;">Video mit der ID 1</a>

Alles was wir nun noch machen müssen, ist die Änderung des Hash-Tags überwachen, um auf die Naviogationselemente des Browsers zu reagieren. Native Möglichkeiten sind hier leider rar bzw kompliziert bzw unzuverlässig bzw träge. Da wir aber wirklich nicht die ersten sind, die sich Gedanken darüber gemacht haben, werft einfach mal einen Blick in euer favorisiertes JS-Framework. Solltet ihr da nicht fündig werden, könnt ihr immer noch Really Simple History verwenden.

Geschrieben in Javascript 13 Kommentare

#001
29. Okt 2010
daniel

Das sehe ich genau so, es ist aus Nutzer Sicht einfach ein schlechter weg, wenn ich an Screenreader denke, dann werden Leute die diese Hilfsmittel benutzen keine große Freude mit dieser Technik haben. Deine vorgeschlagenen Wege sind ja so weit ich das Überblicken kann ein Regelsatz für den Einsatz von Javascript im Bereich der Verlinkung. D.h., ich baue zum Beispiel eine Seite zuerst immer ohne Javascript.
Bei solch Diskussionen hilft mir immer die Devise von Chris Coyer “Works fine with JavaScript disabled”.

VG Daniel


#002
29. Okt 2010
Martin Kuckert

Verbessere mich, wenn ich mich täusche, aber fehlt im letzten Beispiel nicht das return false; im onclick-Attribut?


#003
29. Okt 2010

Toller Artikel! Wenn ich mir den von dir referenzierten Artikel anschaue, bekomme ich alleine auch schon aus semantischer Sicht das Grausen… Falsche semantische Auszeichnung und noch dazu muss man ja auch erwähnen, dass quasi JEDER Screen-Reader ohne JavaScript arbeitet, mit dieser Pseudo-Link Lösung also nichts anfangen könnte.

Ansonsten stimme dir zu 99% zu, mit der Ausnahme, dass ich von “return false;” in Events stark abraten würde. Man killt damit das GESAMTE Event-Handling, d.h. es findet auch keine Event-Bubbling statt. Stattdessen sollte man im Event-Handler lieber preventDefault verwendenden. Das verhindert, dass der Klick durchgeführt wird und das Event-Bubbling wird nicht beeinträchtigt.


#004
29. Okt 2010
Cem Derin

@Martin: Stimmt, korrigiert.
@Sebastian: Das ist sogar noch besser. Ich bin kein JavaScript-Guru – solche Sachen entfallen mir dann immer ;)


#005
29. Okt 2010

@Cem: ich bei weitem auch nicht ;) Auf der PHP Conf gab es darüberhinaus auch einen netten Vortrag von Jordi Boggiano zu dem Thema: http://slides.seld.be/?file=2010-10-12+JavaScript+Events+and+Scopes.html#1


#007
29. Okt 2010
Michael

ich hasse blog die html in kommentaren interpretieren…

also nochmal:
Man sollte auch daran denken, dass die Verwendung von <span> statt <a> auch die Semantik zerstört. Wie im Artikel steht: <span> ist ja nur Text ohne direkte Funktion, insofern wird auch der Google Bot (mangels Augen) das nicht als klickbaren Link erkennen…


#008
29. Okt 2010
knalli

Wobei, meine persönliche Variante ist eigentlich komplett ohne jeglichem JavaScript im Markup.

Im Vergleich zu deinem letzten Beispiel also:
Video mit der ID 1

Ein einfaches Query mit “a.dynamicLink” und einen addClick/addEvent/o.ä. je nach Framework erledigt den Rest. Die “Zweckentfremdung” einer “Style-CSS-Klasse” halte ich weiterhin für ausreichend; allerdings sollte man es tunlichst vermeiden, und solche “Controller-CSS-Klassen” wiederum mit Style-Angaben zu versehen.

Die Vorteile: 1) Man benötigt nur einen Handler und hat nicht für jeden Link einen eigenen (bzw. einen Aufruf des Handlers); 2) im Markup ist kein JavaScript vorhanden; beim empfehlenswerten Auslagern des JavaScript-Codes erhalten wir eine zusätzliche Optimierung (Stichworte: Cache, Auslieferung, Layout).

Facebook geht sogar ein paar Schritte weiter, und schreibt im “Ajax-Fall” die URL in Teilen um: Aus der Gesamtseite wird dann nur ein Häppchen (nämlich das relevante “Neue”).


#009
29. Okt 2010
knalli

Code: <a href="#/video/1" class="dynamicLink">Video mit der ID 1</a>


#010
31. Okt 2010

Ich persönlich mag solche verwendeten “Hacks” auch nicht, keine Frage. Es ist unsauber.

Allerdings frage ich mich ob man als Argument wirklich dagegen halten kann, welche Darstellung Benutzer erhalten, die JavaScript oder sogar CSS deaktiviert haben? Du schreibst selbst “das hat eigentlich kaum einer”.

Über wen unterhalten wir uns dann?

Ich weiß, mit so einer Aussage macht man sich aus dem Stand verdächtig, aber wenn ich das berücksichtigen möchte käme ich auch nicht umhin für das Layout ausgerechnet Tabellen zu benutzen.

JavaScript zu deaktivieren kann ich bei den Ängsten einiger User durchaus nachvollziehen, warum man bewusst CSS deaktiviert (nicht nur die Farben überschreiben lässt) bleibt mir verschlossen.
Für User die eingeschränkte Möglichkeiten haben, sollte gerade über CSS eine alternative Darstellung angeboten werden wenn es nicht sowieso ein spezielles Angebot geben sollte.

Wer JavaScript und besonders CSS ausschaltet, der weiß was er macht – und er macht es bewusst und kann sich wohl kaum darüber wundern, dass wie Du schreibst “krudes Zeug” passiert?


#011
05. Nov 2010

Das kaum einer JS ausgeschaltet hat, ist zwar prinzipiell richtig, aber man darf nicht vergessen, dass das AddOn NoScript eines der meistgenutzten Firefox Addon ist.

Ich nutze es auch und ärger mich oft über solche Links, meistens verlasse ich die Seite, es gibt zum Glück viele die auch ohne JS funktionieren. Aber das AddOn läßt mich für Seiten denen ich vertraue auch JS aktivieren. Das ist schön, erspart mir einerseits unnötiges gezappel und ich kann trotzdem google maps nutzen.

CSS schalte ich bei Seiten aus, die so überladen sind, dass die Benutzerführung schwerfällig wird. Aber das halte ich nicht für ein Argument, ein mit CSS als Link gestaltetes Element nicht für JS Aktionen zu nutzen.

Wobei die Beispiele im Artikel insoweit besser sind, wenn der Link einen Fallback anbietet. Aber wenn es z.b. um das einblenden von Infofenster geht, finde ich dir Variante von Knalli durchaus praktikabel.

(und reingefallen, das Formular läßt sich nur mit JS abschicken, naja muss ich’s halt temporär zulassen)


#012
05. Nov 2010
Cem Derin

Ein Link ist ein Link, und wenn ein Link ein JavaScript Event auslöst, bleibt es ein Link. Dem Benutzer ein anderes Element als einen Link vorzugaukeln ist aus vielerlei Sicht ein unerlaubter Vorstoß in die Privatsphäre des Benutzers … zumal ich nicht mal wirklich genau weiß, wie dem Benutzer Links dargestellt werden. Denn wer so etwas macht, der nutzt auch keine Reset-Styles.

Bzgl. des Formular-Buttons muss ich mich in der Tat schuldig bekennen. Ich habe es neulich jemandem per E-Mail geschrieben: Durch ein Update ist mein Theme kaputt gegangen und ich hab eine alte (um genau zu sein SEHR alte) Entwicklungsversion hochgeladen, in der ich zu faul war den Button richtig zu stylen. Wurde ich erst dort drauf hingewiesen, hab ich aber bisher noch nicht geändert. Mein Fehler.


#013
05. Nov 2010
knalli

Nicht unbedingt [nur] ein Infofenster.

Am Beispiel von Facebook (so ist mir das Beispiel im Kopf geblieben, inwieweit das heute noch nachvollziehbar ist, weiß ich nicht) wäre das: Der Klick auf “Ältere Beiträge” ist ein normaler Link/Button, der einen entsprechenden [GET-]Parameter beinhaltet. Mittels JavaScript wird dieser Link umgeformt/umgeschrieben, sodass der Server nicht mehr die komplette Seite, sondern nur noch den wirklich neuen Inhalt (evtl. sogar als fertig gerendertes HTML) lädt.

Das ganze setzt natürlich eine tiefsitzende MVC-Integration im System vor — oder eine entsprechende Nutzung von Pattern, auch im View.

// kommentieren

// senden
theme von mir, software von wordpress, grid von 960 grid system. funktioniert in allen browsern, aber der safari bekommt das mit der schrift am schönsten hin.