Neues Anfänger-Tutorial für JavaScript: Tic-Tac-Toe
bearbeitet von
@@Felix Riesterer
> … möchte kritisiert und verbessert werden:
Nachdem die Diskussion etwas ausgeschweift ist, zurück zum Artikel. Was wäre noch zu verbessern?
Nun, das **Markup**. Es ist so gut wie nicht vorhanden. Nur leere Tabellenzellen. Aber was sollten die auch für Inhalt haben?
Dazu fragen wir uns, was denn die **Gundfunktionalität** ist: Ganz einfach die Auswahl von Kreuz oder Kreis in jedem Feld.
# 1. Tic
Jede Zelle hat initial keinen Wert und kann durch Nutzerinteraktion mit ❌ (×, x) oder ⭕ (○, o) befüllt werden. Für 0 und 1 hatte ich schon eine [Möglichkeit](https://forum.selfhtml.org/meta/2016/jan/2/neues-anfaenger-tutorial-fuer-javascript-tic-tac-toe/1658506#m1658506) angedeutet.
Aber das passende UI-Element zur Auswahl ist ein **Drop-Down-Menü**. Das Innere der Tabellenzellen wäre also:
~~~html
<td>
<label for="top-left">top left field</label>
<select id="top-left">
<option></option>
<option value="x" aria-label="x">❌</option>
<option value="o" aria-label="o">⭕</option>
</select>
</td>
~~~
Und [so sieht’s aus](http://codepen.io/gunnarbittersmann/pen/gPWXWo). Nicht besonders schön, aber völlig funktional. So funktional, wie es mit HTML eben sein kann.
Das kann sogar ein Blinder bedienen. (Und das ist wörtlich gemeint. ♿️ Deswegen auch das `aria-label`-Attribut.)
# 2. Tac
Das Aussehen können wir ja verbessern. **Aussehen** heißt CSS.
`legend` wird _visuell_ versteckt; die `select`-Box wird größer und ihren Rahmen los. (War das jetzt schon ein [Zeugma](https://de.wikipedia.org/wiki/Zeugma_%28Sprache%29)?) Das war’s dann auch schon im Wesentlichen mit dem Styling.
[Sieht schon besser aus](http://codepen.io/gunnarbittersmann/pen/gPWowq), aber die Funktionalität ist noch unterste Stufe. Jeder Spieler ist selbst dafür verantwortlich, dass er sein Symbol auswählt, und beide dafür, dass sie abwechselnd ziehen.
# 3. Toe
Hier (erst!!) kommt nun **JavaScript** ins Spiel.
~~~js
if (document.querySelector)
~~~
Außer in alten Browsern. Aber das ist völlig OK – **_progressive enhancement_{: lang="en"}**. Die Grundfunktionalität ist ja auch in alten Browsern gegeben.
~~~js
{
document.documentElement.classList.add('js');
~~~
Wenn JavaScript ausgeführt wird, bekommt das `html`-Element eine Klasse `js`.
In einer booleschen Variablen `isPlayerXMoving` wird festgehalten, wer am Zug ist. Die Tabelle bekommt _einen_ Eventhandler verpasst. (Und nicht etwa jedes `select` einen eigenen – wir nutzen _event delegation_{: lang="en"}.) Darin passiert folgendes:
~~~js
function ticTacToeClickHandler(event)
{
var targetElement = event.target;
if (targetElement.nodeName == 'SELECT')
~~~
… nur für `select`-Elemente; nicht da, wo das Event sonst noch so vorbeibubblet.
~~~js
{
targetElement.blur();
~~~
Der Fokus wird schnell wieder vom `select`-Element weggenommen, damit das Aufklappen nicht (oder kaum) zu sehen ist. Aus demselben Grund werden per Stylesheet auch die `option`s ausgeblendet, mehr dazu später.
~~~js
if (!targetElement.disabled)
{
targetElement.value = isPlayerXMoving ? 'x' : 'o';
targetElement.disabled = true;
~~~
Das Feld erhält, wenn es noch frei ist, den entsprechenden Wert – je nachdem, wer gerade am Zug war. Dann wird es disablet, damit es nicht noch einmal ausgewählt werden kann.
~~~js
isPlayerXMoving = !isPlayerXMoving;
}
}
}
~~~
Der nächste Spieler ist am Zug.
Um die nun nicht benötigten `option`-Elemente auszublenden, erhält das Stylesheet noch eine Ergänzung:
~~~css
.js #tic-tac-toe option
{
display: none;
}
~~~
Diese Regel wirkt nur dann, wenn ein Vorfahrenelement die Klasse `js` hat. Das ist nur dann der Fall, wenn JavaScript ausgeführt wird, da wir diese Klasse fürs `html`-Element ja erst mit JavaScript gesetzt hatten.
Und so haben wir das Grundkonstrukt **_progressively enhanced_{: lang="en"}** – erst das Aussehen, dann das Verhalten; mit schon [ansehnlichem Ergebnis](http://codepen.io/gunnarbittersmann/pen/GomxRm).
Bei Ausfall von JavaScript oder CSS funktioniert die Grundfunktionalität immer noch – und zwar auch, wenn CSS ausfällt, JavaScript aber ausgeführt wird. Dann sieht das Feld wieder ungestylt aus, das JavaScript sorgt aber schon dafür, dass in den disableten `select`s nicht erneut auswählt werden kann.
Jetzt fehlt noch die Abfrage, ob ein Spieler „was auf die Reihe gekriegt hat“ – aber die sei mal _out of scope_{: lang="en"} dieses Postings.
`select`s sind recht störrisch gegenüber Styling. WebKits können nicht die Schriftgröße der `option`s kleiner setzen als die des `select`s. Auch die Positionierung des Drop-Downs innerhalb des Feldes ist Frickelei, wenn jeder Browser da etwas andere Vorstellungen hat.
Beschäftigen wir uns lieber mit etwas anderem:
---
Eine Auswahl von einer Option aus mehreren kann auch durch eine Gruppe von **Radiobuttons** geschehen.
# 1. Tic
Entgegen früheren HTML-Versionen, wo ohne Nutzerinteraktion der erste Radiobutton einer Gruppe als vorausgewählt galt, wenn keiner als `selected` gekennzeichnet war (woran sich Browser aber nicht gehalten haben), sieht HTML5 explizit vor, dass [initial kein Radiobutton ausgewählt](https://www.w3.org/html/wg/drafts/html/master/semantics.html#radio-button-state-%28type=radio%29:radio-button-group-10) sein muss.
Das Innere der Tabellenzellen wäre hier derart:
~~~html
<td>
<fieldset>
<legend>top left field</legend>
<input type="radio" name="top-left" id="top-left-x">
<label for="top-left-x">x</label>
<input type="radio" name="top-left" id="top-left-o">
<label for="top-left-o">o</label>
</fieldset>
</td>
~~~
Wieder nicht besonders schön, aber [funktioniert](http://codepen.io/gunnarbittersmann/pen/GomNJJ). Und blind bedienbar. ♿️
# 2. Tac
Und wieder setzen wir **_progressive enhancement_{: lang="en"}** ein, und Darstellung und Verhalten schrittweise zu verbessern. Zuerst wieder das Styling:
Die Radiobuttons werden ausgeblendet, `fieldset` büßt seinen Rahmen ein und auch hier wird `legend` wieder _visuell_ versteckt – und der Labeltext auch; dafür kommt SVG zum Einsatz. Und nicht `outline` für `:focus` vergessen!
Die Magie liegt nun darin:
~~~css
#tic-tac-toe :checked + label::after
{
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
~~~
Wenn ein Radiobutton ausgewählt wird, wird das zugehörige Label vergrößert, so dass es das ganze Feld ausfüllt.
~~~css
/* transition: width, height, left 0.1s; */
~~~
Diese Vergrößerung könnte man auch animieren; die betreffende Codezeile ist aber auskommentiert, denn diese Animation ist nicht gerade performant. Besser ist es, `transition` bspw. auf `transform` anzuwenden.
~~~css
@supports (transform: scale(1))
{
~~~
Dazu fragen wir ab, der der Browser `transform` denn kann. (Andernfalls würden wir die Vergrößerung in alten Browsern nicht mehr haben.)
~~~css
#tic-tac-toe label::after
{
width: 100%;
height: 100%;
transform: scale(0.25);
transition: transform 0.1s;
}
~~~
Initial werden die Label verkleinert, damit sie nebeneinander passen.
~~~css
#tic-tac-toe label[for$="-x"]::after
{
transform-origin: left top;
}
#tic-tac-toe label[for$="-o"]::after
{
left: 0;
transform-origin: center top;
}
~~~
Nebeneinander, nicht aufeinander! Deshalb haben sie verschiedene Streckungszentren.
~~~css
#tic-tac-toe :checked + label::after
{
transform: scale(1);
}
}
~~~
Und bei ausgewähltem Radiobutton füllt das zugehörige Label das ganze Feld.
Doch bei [dieser Darstellung](http://codepen.io/gunnarbittersmann/pen/EPmNgO) kommt wohl kaum noch jemand drauf, dass man hier Radiobuttons auswählt.
# 3. Toe
Das Label für den nicht ausgewähltem Radiobutton soll natürlich noch verschwinden und auch die Wer-ist-am-Zug?-Logik implementiert werden.
Auch hier wieder _ein_ Eventhandler und _event delegation_{: lang="en"}.
~~~js
function ticTacToeClickHandler(event)
{
var targetElement = event.target;
if (targetElement.nodeName == 'INPUT')
{
targetElement.parentNode.disabled = true;
switchPlayer();
}
}
~~~
Ähnlich wie oben, nur dass die Gruppe der Radiobuttons mittels deren Elternelement (das wäre hier `fieldset`) disablet wird. Und dass hier der Code, der initial auch einmal ausgeführt werden muss, in die Funktion `switchPlayer` ausgelagert ist.
~~~js
function switchPlayer()
{
isPlayerXMoving = !isPlayerXMoving;
~~~
Der nächste ist dran.
~~~js
for (var i = 0; i < xInputElements.length; i++)
{
xInputElements[i].disabled = !isPlayerXMoving;
}
for (var i = 0; i < oInputElements.length; i++)
{
oInputElements[i].disabled = isPlayerXMoving;
}
}
~~~
Wenn der Spieler am Zug ist, der die Kreuze macht, werden die Kreuz-Radiobuttons enablet; die Kreis-Radiobuttons disablet. Für den anderen Spieler entsprechend andersrum.
Im Stylesheet sind noch einige Anpassungen nötig. Damit diese nur greifen, wenn JavaScript ausgeführt wird, wieder mit `.js` im Selektor.
~~~css
.js #tic-tac-toe label::after
{
left: 0;
width: 100%;
height: 100%;
transform: scale(1);
opacity: 0;
transition: none;
z-index: 1;
}
~~~
Die Label lassen wir das gesamte Feld ausfüllen, damit die Spieler überall im Feld clicken können.
~~~css
.js #tic-tac-toe :disabled + label::after,
.js #tic-tac-toe :disabled > label::after
{
z-index: 0;
opacity: 0;
cursor: not-allowed;
}
~~~
Label von disableten Radiobuttons werden nicht angezeigt. Dass dann das Feld nicht anclickbar ist, wird durch einen entsprechenden Cursor angezeigt.
~~~css
.js #tic-tac-toe :checked + label::after
{
opacity: 1;
}
~~~
Label von ausgewählten Radiobuttons werden angezeigt.
Das Ganze [sieht damit so aus](http://codepen.io/gunnarbittersmann/pen/XXRExX) – Radiobuttons _progressively enhanced_{: lang="en"}.
Auch diese Lösung funktioniert, wenn JavaScript interpretiert wird, CSS aber nicht. disablete Radiobuttons können nicht ausgewählt werden und werden ausgegraut dargestellt.
An der Stelle käme dann wieder die Erkennung des Spielendes hinzu, um die wir uns in diesem Posting nicht kümmern wollen.
--
Kümmern wir uns lieber um die grundsätzliche Frage: Sollte das alles in einem Tutorial für _Anfänger_ stehen?
**Ja, unbedingt!!** Wie sonst sollen Anfänger das Prinzip von _progressive enhancement_{: lang="en"} verinnerlichen, wenn es ihnen in Tutorials nicht so vorgemacht wird? Kein Anfänger wird nach einem solchen JavaScript-Tutorial noch ein zweites lesen, denn die Lösung „funzt“ ja. Nur dass sie eben nicht _funktioniert_.
Wollen wir die nächste Generation von Entwicklern heranzüchten, die das dreiundzwölfzigste JavaScript-Framework entwickeln, ohne die geringste Ahnung von HTML zu haben? Was man dem erzeugten Code auch ansieht und das Ergebnis zu Lasten von UX und Barrierefreiheit, also zu Lasten der Nutzer geht?
LLAP 🖖
--
„Wir haben deinen numidischen Schreiber aufgegriffen, o Syndicus.“
„Hat auf dem Forum herumgelungert …“
(Wachen in Asterix 36: Der Papyrus des Cäsar)
Neues Anfänger-Tutorial für JavaScript: Tic-Tac-Toe
bearbeitet von
@@Felix Riesterer
> … möchte kritisiert und verbessert werden:
Nachdem die Diskussion etwas ausgeschweift ist, zurück zum Artikel. Was wäre noch zu verbessern?
Nun, das **Markup**. Es ist so gut wie nicht vorhanden. Nur leere Tabellenzellen. Aber was sollten die auch für Inhalt haben?
Dazu fragen wir uns, was denn die **Gundfunktionalität** ist: Ganz einfach die Auswahl von Kreuz oder Kreis in jedem Feld.
# 1. Tic
Jede Zelle hat initial keinen Wert und kann durch Nutzerinteraktion mit ❌ (×, x) oder ⭕ (○, o) befüllt werden. Für 0 und 1 hatte ich schon eine [Möglichkeit](https://forum.selfhtml.org/meta/2016/jan/2/neues-anfaenger-tutorial-fuer-javascript-tic-tac-toe/1658506#m1658506) angedeutet.
Aber das passende UI-Element zur Auswahl ist ein **Drop-Down-Menü**. Das Innere der Tabellenzellen wäre also:
~~~html
<td>
<label for="top-left">top left field</label>
<select id="top-left">
<option></option>
<option value="x" aria-label="x">❌</option>
<option value="o" aria-label="o">⭕</option>
</select>
</td>
~~~
Und [so sieht’s aus](http://codepen.io/gunnarbittersmann/pen/gPWXWo). Nicht besonders schön, aber völlig funktional. So funktional, wie es mit HTML eben sein kann.
Das kann sogar ein Blinder bedienen. (Und das ist wörtlich gemeint. ♿️ Deswegen auch das `aria-label`-Attribut.)
# 2. Tac
Das Aussehen können wir ja verbessern. **Aussehen** heißt CSS.
`legend` wird _visuell_ versteckt; die `select`-Box wird größer und ihren Rahmen los. (War das jetzt schon ein [Zeugma](https://de.wikipedia.org/wiki/Zeugma_%28Sprache%29)?) Das war’s dann auch schon im Wesentlichen mit dem Styling.
[Sieht schon besser aus](http://codepen.io/gunnarbittersmann/pen/gPWowq), aber die Funktionalität ist noch unterste Stufe. Jeder Spieler ist selbst dafür verantwortlich, dass er sein Symbol auswählt, und beide dafür, dass sie abwechselnd ziehen.
# 3. Toe
Hier (erst!!) kommt nun **JavaScript** ins Spiel.
~~~js
if (document.querySelector)
~~~
Außer in alten Browsern. Aber das ist völlig OK – **_progressive enhancement_{: lang="en"}**. Die Grundfunktionalität ist ja auch in alten Browsern gegeben.
~~~js
{
document.documentElement.classList.add('js');
~~~
Wenn JavaScript ausgeführt wird, bekommt das `html`-Element eine Klasse `js`.
In einer booleschen Variablen `isPlayerXMoving` wird festgehalten, wer am Zug ist. Die Tabelle bekommt _einen_ Eventhandler verpasst. (Und nicht etwa jedes `select` einen eigenen – wir nutzen _event delegation_{: lang="en"}.) Darin passiert folgendes:
~~~js
function ticTacToeClickHandler(event)
{
var targetElement = event.target;
if (targetElement.nodeName == 'SELECT')
~~~
… nur für `select`-Elemente; nicht da, wo das Event sonst noch so vorbeibubblet.
~~~js
{
targetElement.blur();
~~~
Der Fokus wird schnell wieder vom `select`-Element weggenommen, damit das Aufklappen nicht (oder kaum) zu sehen ist. Aus demselben Grund werden per Stylesheet auch die `option`s ausgeblendet, mehr dazu später.
~~~js
if (!targetElement.disabled)
{
targetElement.value = isPlayerXMoving ? 'x' : 'o';
targetElement.disabled = true;
~~~
Das Feld erhält, wenn es noch frei ist, den entsprechenden Wert – je nachdem, wer gerade am Zug war. Dann wird es disablet, damit es nicht noch einmal ausgewählt werden kann.
~~~js
isPlayerXMoving = !isPlayerXMoving;
}
}
}
~~~
Der nächste Spieler ist am Zug.
Um die nun nicht benötigten `option`-Elemente auszublenden, erhält das Stylesheet noch eine Ergänzung:
~~~css
.js #tic-tac-toe option
{
display: none;
}
~~~
Diese Regel wirkt nur dann, wenn ein Vorfahrenelement die Klasse `js` hat. Das ist nur dann der Fall, wenn JavaScript ausgeführt wird, da wir diese Klasse fürs `html`-Element ja erst mit JavaScript gesetzt hatten.
Und so haben wir das Grundkonstrukt **_progressively enhanced_{: lang="en"}** – erst das Aussehen, dann das Verhalten; mit schon [ansehnlichem Ergebnis](http://codepen.io/gunnarbittersmann/pen/GomxRm).
Bei Ausfall von JavaScript oder CSS funktioniert die Grundfunktionalität immer noch – und zwar auch, wenn CSS ausfällt, JavaScript aber ausgeführt wird. Dann sieht das Feld wieder ungestylt aus, das JavaScript sorgt aber schon dafür, dass in den disableten `select`s nicht erneut auswählt werden kann.
Jetzt fehlt noch die Abfrage, ob ein Spieler „was auf die Reihe gekriegt hat“ – aber die sei mal _out of scope_{: lang="en"} dieses Postings.
`select`s sind recht störrisch gegenüber Styling. WebKits können nicht die Schriftgröße der `option`s kleiner setzen als die des `select`s. Auch die Positionierung des Drop-Downs innerhalb des Feldes ist Frickelei, wenn jeder Browser da etwas andere Vorstellungen hat.
Beschäftigen wir uns lieber mit etwas anderem:
---
Eine Auswahl von einer Option aus mehreren kann auch durch eine Gruppe von **Radiobuttons** geschehen.
# 1. Tic
Entgegen früheren HTML-Versionen, wo ohne Nutzerinteraktion der erste Radiobutton einer Gruppe als vorausgewählt galt, wenn keiner als `selected` gekennzeichnet war (woran sich Browser aber nicht gehalten haben), sieht HTML5 explizit vor, dass [initial kein Radiobutton ausgewählt](https://www.w3.org/html/wg/drafts/html/master/semantics.html#radio-button-state-%28type=radio%29:radio-button-group-10) sein muss.
Das Innere der Tabellenzellen wäre hier derart:
~~~html
<td>
<fieldset>
<legend>top left field</legend>
<input type="radio" name="top-left" id="top-left-x">
<label for="top-left-x">x</label>
<input type="radio" name="top-left" id="top-left-o">
<label for="top-left-o">o</label>
</fieldset>
</td>
~~~
Wieder nicht besonders schön, aber [funktioniert](http://codepen.io/gunnarbittersmann/pen/GomNJJ). Und blind bedienbar. ♿️
# 2. Tac
Und wieder setzen wir **_progressive enhancement_{: lang="en"}** ein, und Darstellung und Verhalten schrittweise zu verbessern. Zuerst wieder das Styling:
Die Radiobuttons werden ausgeblendet, `fieldset` büßt seinen Rahmen ein und auch hier wird `legend` wieder _visuell_ versteckt – und der Labeltext auch; dafür kommt SVG zum Einsatz. Und nicht `outline` für `:focus` vergessen!
Die Magie liegt nun darin:
~~~css
#tic-tac-toe :checked + label::after
{
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
~~~
Wenn ein Radiobutton ausgewählt wird, wird das zugehörige Label vergrößert, so dass es das ganze Feld ausfüllt.
~~~css
/* transition: width, height, left 0.1s; */
~~~
Diese Vergrößerung könnte man auch animieren; die betreffende Codezeile ist aber auskommentiert, denn diese Animation ist nicht gerade performant. Besser ist es, `transition` bspw. auf `transform` anzuwenden.
~~~css
@supports (transform: scale(1))
{
~~~
Dazu fragen wir ab, der der Browser `transform` denn kann. (Andernfalls würden wir die Vergrößerung in alten Browsern nicht mehr haben.)
~~~css
#tic-tac-toe label::after
{
width: 100%;
height: 100%;
transform: scale(0.25);
transition: transform 0.1s;
}
~~~
Initial werden die Label verkleinert, damit sie nebeneinander passen.
~~~css
#tic-tac-toe label[for$="-x"]::after
{
transform-origin: left top;
}
#tic-tac-toe label[for$="-o"]::after
{
left: 0;
transform-origin: center top;
}
~~~
Nebeneinander, nicht aufeinander! Deshalb haben sie verschiedene Streckungszentren.
~~~css
#tic-tac-toe :checked + label::after
{
transform: scale(1);
}
}
~~~
Und bei ausgewähltem Radiobutton füllt das zugehörige Label das ganze Feld.
Doch bei [dieser Darstellung](http://codepen.io/gunnarbittersmann/pen/EPmNgO) kommt wohl kaum noch jemand drauf, dass man hier Radiobuttons auswählt.
# 3. Toe
Das Label für den nicht ausgewähltem Radiobutton soll natürlich noch verschwinden und auch die Wer-ist-am-Zug?-Logik implementiert werden.
Auch hier wieder _ein_ Eventhandler und _event delegation_{: lang="en"}.
~~~js
function ticTacToeClickHandler(event)
{
var targetElement = event.target;
if (targetElement.nodeName == 'INPUT')
{
targetElement.parentNode.disabled = true;
switchPlayer();
}
}
~~~
Ähnlich wie oben, nur dass die Gruppe der Radiobuttons mittels deren Elternelement (das wäre hier `fieldset`) disablet wird. Und dass hier der Code, der initial auch einmal ausgeführt werden muss, in die Funktion `switchPlayer` ausgelagert ist.
~~~js
function switchPlayer()
{
isPlayerXMoving = !isPlayerXMoving;
~~~
Der nächste ist dran.
~~~js
for (var i = 0; i < xInputElements.length; i++)
{
xInputElements[i].disabled = !isPlayerXMoving;
}
for (var i = 0; i < oInputElements.length; i++)
{
oInputElements[i].disabled = isPlayerXMoving;
}
}
~~~
Wenn der Spieler am Zug ist, der die Kreuze macht, werden die Kreuz-Radiobuttons enablet; die Kreis-Radiobuttons disablet. Für den anderen Spieler entsprechend andersrum.
Im Stylesheet sind noch einige Anpassungen nötig. Damit diese nur greifen, wenn JavaScript ausgeführt wird, wieder im `.js` im Selektor.
~~~css
.js #tic-tac-toe label::after
{
left: 0;
width: 100%;
height: 100%;
transform: scale(1);
opacity: 0;
transition: none;
z-index: 1;
}
~~~
Die Label lassen wir das gesamte Feld ausfüllen, damit die Spieler überall im Feld clicken können.
~~~css
.js #tic-tac-toe :disabled + label::after,
.js #tic-tac-toe :disabled > label::after
{
z-index: 0;
opacity: 0;
cursor: not-allowed;
}
~~~
Label von disableten Radiobuttons werden nicht angezeigt. Dass dann das Feld nicht anclickbar ist, wird durch einen entsprechenden Cursor angezeigt.
~~~css
.js #tic-tac-toe :checked + label::after
{
opacity: 1;
}
~~~
Label von ausgewählten Radiobuttons werden angezeigt.
Das Ganze [sieht damit so aus](http://codepen.io/gunnarbittersmann/pen/XXRExX) – Radiobuttons _progressively enhanced_{: lang="en"}.
Auch diese Lösung funktioniert, wenn JavaScript interpretiert wird, CSS aber nicht. disablete Radiobuttons können nicht ausgewählt werden und werden ausgegraut dargestellt.
An der Stelle käme dann wieder die Erkennung des Spielendes hinzu, um die wir uns in diesem Posting nicht kümmern wollen.
--
Kümmern wir uns lieber um die grundsätzliche Frage: Sollte das alles in einem Tutorial für _Anfänger_ stehen?
**Ja, unbedingt!!** Wie sonst sollen Anfänger das Prinzip von _progressive enhancement_{: lang="en"} verinnerlichen, wenn es ihnen in Tutorials nicht so vorgemacht wird? Kein Anfänger wird nach einem solchen JavaScript-Tutorial noch ein zweites lesen, denn die Lösung „funzt“ ja. Nur dass sie eben nicht _funktioniert_.
Wollen wir die nächste Generation von Entwicklern heranzüchten, die das dreiundzwölfzigste JavaScript-Framework entwickeln, ohne die geringste Ahnung von HTML zu haben? Was man dem erzeugten Code auch ansieht und das Ergebnis zu Lasten von UX und Barrierefreiheit, also zu Lasten der Nutzer geht?
LLAP 🖖
--
„Wir haben deinen numidischen Schreiber aufgegriffen, o Syndicus.“
„Hat auf dem Forum herumgelungert …“
(Wachen in Asterix 36: Der Papyrus des Cäsar)
Neues Anfänger-Tutorial für JavaScript: Tic-Tac-Toe
bearbeitet von
@@Felix Riesterer
> … möchte kritisiert und verbessert werden:
Nachdem die Diskussion etwas ausgeschweift ist, zurück zum Artikel. Was wäre noch zu verbessern?
Nun, das **Markup**. Es ist so gut wie nicht vorhanden. Nur leere Tabellenzellen. Aber was sollten die auch für Inhalt haben?
Dazu fragen wir uns, was denn die **Gundfunktionalität** ist: Ganz einfach die Auswahl von Kreuz oder Kreis in jedem Feld.
# 1. Tic
Jede Zelle hat initial keinen Wert und kann durch Nutzerinteraktion mit ❌ (×, x) oder ⭕ (○, o) befüllt werden. Für 0 und 1 hatte ich schon eine [Möglichkeit](https://forum.selfhtml.org/meta/2016/jan/2/neues-anfaenger-tutorial-fuer-javascript-tic-tac-toe/1658506#m1658506) angedeutet.
Aber das passende UI-Element zur Auswahl ist ein **Drop-Down-Menü**. Das Innere der Tabellenzellen wäre also:
~~~html
<td>
<label for="top-left">top left field</label>
<select id="top-left">
<option></option>
<option value="x" aria-label="x">❌</option>
<option value="o" aria-label="o">⭕</option>
</select>
</td>
~~~
Und [so sieht’s aus](http://codepen.io/gunnarbittersmann/pen/gPWXWo). Nicht besonders schön, aber völlig funktional. So funktional, wie es mit HTML eben sein kann.
Das kann sogar ein Blinder bedienen. (Und das ist wörtlich gemeint. ♿️ Deswegen auch das `aria-label`-Attribut.)
# 2. Tac
Das Aussehen können wir ja verbessern. **Aussehen** heißt CSS.
`legend` wird _visuell_ versteckt; die `select`-Box wird größer und ihren Rahmen los. (War das jetzt schon ein [Zeugma](https://de.wikipedia.org/wiki/Zeugma_%28Sprache%29)?) Das war’s dann auch schon im Wesentlichen mit dem Styling.
[Sieht schon besser aus](http://codepen.io/gunnarbittersmann/pen/gPWowq), aber die Funktionalität ist noch unterste Stufe. Jeder Spieler ist selbst dafür verantwortlich, dass er sein Symbol auswählt, und beide dafür, dass sie abwechselnd ziehen.
# 3. Toe
Hier (erst!!) kommt nun **JavaScript** ins Spiel.
~~~js
if (document.querySelector)
~~~
Außer in alten Browsern. Aber das ist völlig OK – **_progressive enhancement_{: lang="en"}**. Die Grundfunktionalität ist ja auch in alten Browsern gegeben.
~~~js
{
document.documentElement.classList.add('js');
~~~
Wenn JavaScript ausgeführt wird, bekommt das `html`-Element eine Klasse `js`.
In einer booleschen Variablen `isPlayerXMoving` wird festgehalten, wer am Zug ist. Die Tabelle bekommt _einen_ Eventhandler verpasst. (Und nicht etwa jedes `select` einen eigenen – wir nutzen _event delegation_{: lang="en"}.) Darin passiert folgendes:
~~~js
function ticTacToeClickHandler(event)
{
var targetElement = event.target;
if (targetElement.nodeName == 'SELECT')
~~~
… nur für `select`-Elemente; nicht da, wo das Event sonst noch so vorbeibubblet.
~~~js
{
targetElement.blur();
~~~
Der Fokus wird schnell wieder vom `select`-Element weggenommen, damit das Aufklappen nicht (oder kaum) zu sehen ist. Aus demselben Grund werden per Stylesheet auch die `option`s ausgeblendet, mehr dazu später.
~~~js
if (!targetElement.disabled)
{
targetElement.value = isPlayerXMoving ? 'x' : 'o';
targetElement.disabled = true;
~~~
Das Feld erhält, wenn es noch frei ist, den entsprechenden Wert – je nachdem, wer gerade am Zug war. Dann wird es disablet, damit es nicht noch einmal ausgewählt werden kann.
~~~js
isPlayerXMoving = !isPlayerXMoving;
}
}
}
~~~
Der nächste Spieler ist am Zug.
Um die nun nicht benötigten `option`-Elemente auszublenden, erhält das Stylesheet noch eine Ergänzung:
~~~css
.js #tic-tac-toe option
{
display: none;
}
~~~
Diese Regel wirkt nur dann, wenn ein Vorfahrenelement die Klasse `js` hat. Das ist nur dann der Fall, wenn JavaScript ausgeführt wird, da wir diese Klasse fürs `html`-Element ja erst mit JavaScript gesetzt hatten.
Und so haben wir das Grundkonstrukt **_progressively enhanced_{: lang="en"}** – erst das Aussehen, dann das Verhalten; mit schon [ansehnlichem Ergebnis](http://codepen.io/gunnarbittersmann/pen/GomxRm).
Bei Ausfall von JavaScript oder CSS funktioniert die Grundfunktionalität immer noch – und zwar auch, wenn CSS ausfällt, JavaScript aber ausgeführt wird. Dann sieht das Feld wieder ungestylt aus, das JavaScript sorgt aber schon dafür, dass in den disableten `select`s nicht erneut auswählt werden kann.
Jetzt fehlt noch die Abfrage, ob ein Spieler „was auf die Reihe gekriegt hat“ – aber die sei mal _out of scope_{: lang="en"} dieses Postings.
`select`s sind recht störrisch gegenüber Styling. WebKits können nicht die Schriftgröße der `option`s kleiner setzen als die des `select`s. Auch die Positionierung des Drop-Downs innerhalb des Feldes ist Frickelei, wenn jeder Browser da etwas andere Vorstellungen hat.
Beschäftigen wir uns lieber mit etwas anderem:
---
Eine Auswahl von einer Option aus mehreren kann auch durch eine Gruppe von **Radiobuttons** geschehen.
# 1. Tic
Entgegen früheren HTML-Versionen, wo ohne Nutzerinteraktion der erste Radiobutton einer Gruppe als vorausgewählt galt, wenn keiner als `selected` gekennzeichnet war (woran sich Browser aber nicht gehalten haben), sieht HTML5 explizit vor, dass [initial kein Radiobutton ausgewählt](https://www.w3.org/html/wg/drafts/html/master/semantics.html#radio-button-state-%28type=radio%29:radio-button-group-10) sein muss.
Das Innere der Tabellenzellen wäre hier derart:
~~~html
<td>
<fieldset>
<legend>top left field</legend>
<input type="radio" name="top-left" id="top-left-x">
<label for="top-left-x">x</label>
<input type="radio" name="top-left" id="top-left-o">
<label for="top-left-o">o</label>
</fieldset>
</td>
~~~
Wieder nicht besonders schön, aber [funktioniert](http://codepen.io/gunnarbittersmann/pen/GomNJJ). Und blind bedienbar. ♿️
# 2. Tac
Und wieder setzen wir **_progressive enhancement_{: lang="en"}** ein, und Darstellung und Verhalten schrittweise zu verbessern. Zuerst wieder das Styling:
Die Radiobuttons werden ausgeblendet, `fieldset` büßt seinen Rahmen ein und auch hier wird `legend` wieder _visuell_ versteckt – und die Label auch; dafür kommt SVG zum Einsatz. Und nicht `outline` für `:focus` vergessen!
Die Magie liegt nun darin:
~~~css
#tic-tac-toe :checked + label::after
{
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
~~~
Wenn ein Radiobutton ausgewählt wird, wird das zugehörige Label vergrößert, so dass es das ganze Feld ausfüllt.
~~~css
/* transition: width, height, left 0.1s; */
~~~
Diese Vergrößerung könnte man auch animieren; die betreffende Codezeile ist aber auskommentiert, denn diese Animation ist nicht gerade performant. Besser ist es, `transition` bspw. auf `transform` anzuwenden.
~~~css
@supports (transform: scale(1))
{
~~~
Dazu fragen wir ab, der der Browser `transform` denn kann. (Andernfalls würden wir die Vergrößerung in alten Browsern nicht mehr haben.)
~~~css
#tic-tac-toe label::after
{
width: 100%;
height: 100%;
transform: scale(0.25);
transition: transform 0.1s;
}
~~~
Initial werden die Label verkleinert, damit sie nebeneinander passen.
~~~css
#tic-tac-toe label[for$="-x"]::after
{
transform-origin: left top;
}
#tic-tac-toe label[for$="-o"]::after
{
left: 0;
transform-origin: center top;
}
~~~
Nebeneinander, nicht aufeinander! Deshalb haben sie verschiedene Streckungszentren.
~~~css
#tic-tac-toe :checked + label::after
{
transform: scale(1);
}
}
~~~
Und bei ausgewähltem Radiobutton füllt das zugehörige Label das ganze Feld.
Doch bei [dieser Darstellung](http://codepen.io/gunnarbittersmann/pen/EPmNgO) kommt wohl kaum noch jemand drauf, dass man hier Radiobuttons auswählt.
# 3. Toe
Das Label für den nicht ausgewähltem Radiobutton soll natürlich noch verschwinden und auch die Wer-ist-am-Zug?-Logik implementiert werden.
Auch hier wieder _ein_ Eventhandler und _event delegation_{: lang="en"}.
~~~js
function ticTacToeClickHandler(event)
{
var targetElement = event.target;
if (targetElement.nodeName == 'INPUT')
{
targetElement.parentNode.disabled = true;
switchPlayer();
}
}
~~~
Ähnlich wie oben, nur dass die Gruppe der Radiobuttons mittels deren Elternelement (das wäre hier `fieldset`) disablet wird. Und dass hier der Code, der initial auch einmal ausgeführt werden muss, in die Funktion `switchPlayer` ausgelagert ist.
~~~js
function switchPlayer()
{
isPlayerXMoving = !isPlayerXMoving;
~~~
Der nächste ist dran.
~~~js
for (var i = 0; i < xInputElements.length; i++)
{
xInputElements[i].disabled = !isPlayerXMoving;
}
for (var i = 0; i < oInputElements.length; i++)
{
oInputElements[i].disabled = isPlayerXMoving;
}
}
~~~
Wenn der Spieler am Zug ist, der die Kreuze macht, werden die Kreuz-Radiobuttons enablet; die Kreis-Radiobuttons disablet. Für den anderen Spieler entsprechend andersrum.
Im Stylesheet sind noch einige Anpassungen nötig. Damit diese nur greifen, wenn JavaScript ausgeführt wird, wieder im `.js` im Selektor.
~~~css
.js #tic-tac-toe label::after
{
left: 0;
width: 100%;
height: 100%;
transform: scale(1);
opacity: 0;
transition: none;
z-index: 1;
}
~~~
Die Label lassen wir das gesamte Feld ausfüllen, damit die Spieler überall im Feld clicken können.
~~~css
.js #tic-tac-toe :disabled + label::after,
.js #tic-tac-toe :disabled > label::after
{
z-index: 0;
opacity: 0;
cursor: not-allowed;
}
~~~
Label von disableten Radiobuttons werden nicht angezeigt. Dass dann das Feld nicht anclickbar ist, wird durch einen entsprechenden Cursor angezeigt.
~~~css
.js #tic-tac-toe :checked + label::after
{
opacity: 1;
}
~~~
Label von ausgewählten Radiobuttons werden angezeigt.
Das Ganze [sieht damit so aus](http://codepen.io/gunnarbittersmann/pen/XXRExX) – Radiobuttons _progressively enhanced_{: lang="en"}.
Auch diese Lösung funktioniert, wenn JavaScript interpretiert wird, CSS aber nicht. disablete Radiobuttons können nicht ausgewählt werden und werden ausgegraut dargestellt.
An der Stelle käme dann wieder die Erkennung des Spielendes hinzu, um die wir uns in diesem Posting nicht kümmern wollen.
--
Kümmern wir uns lieber um die grundsätzliche Frage: Sollte das alles in einem Tutorial für _Anfänger_ stehen?
**Ja, unbedingt!!** Wie sonst sollen Anfänger das Prinzip von _progressive enhancement_{: lang="en"} verinnerlichen, wenn es ihnen in Tutorials nicht so vorgemacht wird? Kein Anfänger wird nach einem solchen JavaScript-Tutorial noch ein zweites lesen, denn die Lösung „funzt“ ja. Nur dass sie eben nicht _funktioniert_.
Wollen wir die nächste Generation von Entwicklern heranzüchten, die das dreiundzwölfzigste JavaScript-Framework entwickeln, ohne die geringste Ahnung von HTML zu haben? Was man dem erzeugten Code auch ansieht und das Ergebnis zu Lasten von UX und Barrierefreiheit, also zu Lasten der Nutzer geht?
LLAP 🖖
--
„Wir haben deinen numidischen Schreiber aufgegriffen, o Syndicus.“
„Hat auf dem Forum herumgelungert …“
(Wachen in Asterix 36: Der Papyrus des Cäsar)
Neues Anfänger-Tutorial für JavaScript: Tic-Tac-Toe
bearbeitet von
@@Felix Riesterer
> … möchte kritisiert und verbessert werden:
Nachdem die Diskussion etwas ausgeschweift ist, zurück zum Artikel. Was wäre noch zu verbessern?
Nun, das **Markup**. Es ist so gut wie nicht vorhanden. Nur leere Tabellenzellen. Aber was sollten die auch für Inhalt haben?
Dazu fragen wir uns, was denn die **Gundfunktionalität** ist: Ganz einfach die Auswahl von Kreuz oder Kreis in jedem Feld.
# 1. Tic
Jede Zelle hat initial keinen Wert und kann durch Nutzerinteraktion mit ❌ (×, x) oder ⭕ (○, o) befüllt werden. Für 0 und 1 hatte ich schon eine [Möglichkeit](https://forum.selfhtml.org/meta/2016/jan/2/neues-anfaenger-tutorial-fuer-javascript-tic-tac-toe/1658506#m1658506) angedeutet.
Aber das passende UI-Element zur Auswahl ist ein **Drop-Down-Menü**. Das Innere der Tabellenzellen wäre also:
~~~html
<td>
<label for="top-left">top left field</label>
<select id="top-left">
<option></option>
<option value="x" aria-label="x">❌</option>
<option value="o" aria-label="o">⭕</option>
</select>
</td>
~~~
Und [so sieht’s aus](http://codepen.io/gunnarbittersmann/pen/gPWXWo). Nicht besonders schön, aber völlig funktional. So funktional, wie es mit HTML eben sein kann.
Das kann sogar ein Blinder bedienen. (Und das ist wörtlich gemeint. ♿️ Deswegen auch das `aria-label`-Attribut.)
# 2. Tac
Das Aussehen können wir ja verbessern. **Aussehen** heißt CSS.
`legend` wird _visuell_ versteckt; die `select`-Box wird größer und ihren Rahmen los. (War das jetzt schon ein [Zeugma](https://de.wikipedia.org/wiki/Zeugma_%28Sprache%29)?) Das war’s dann auch schon im Wesentlichen mit dem Styling.
[Sieht schon besser aus](http://codepen.io/gunnarbittersmann/pen/gPWowq), aber die Funktionalität ist noch unterste Stufe. Jeder Spieler ist selbst dafür verantwortlich, dass er sein Symbol auswählt, und beide dafür, dass sie abwechselnd ziehen.
# 3. Toe
Hier (erst!!) kommt nun **JavaScript** ins Spiel.
~~~js
if (document.querySelector)
~~~
Außer in alten Browsern. Aber das ist völlig OK – **_progressive enhancement_{: lang="en"}**. Die Grundfunktionalität ist ja auch in alten Browsern gegeben.
~~~js
{
document.documentElement.classList.add('js');
~~~
Wenn JavaScript ausgeführt wird, bekommt das `html`-Element eine Klasse `js`.
In einer booleschen Variablen `isPlayerXMoving` wird festgehalten, wer am Zug ist. Die Tabelle bekommt _einen_ Eventhandler verpasst. (Und nicht etwa jedes `select` einen eigenen – wir nutzen _event delegation_{: lang="en"}.) Darin passiert folgendes:
~~~js
function ticTacToeClickHandler(event)
{
var targetElement = event.target;
if (targetElement.nodeName == 'SELECT')
~~~
… nur für `select`-Elemente; nicht da, wo das Event sonst noch so vorbeibubblet.
~~~js
{
targetElement.blur();
~~~
Der Fokus wird schnell wieder vom `select`-Element weggenommen, damit das Aufklappen nicht (oder kaum) zu sehen ist. Aus demselben Grund werden per Stylesheet auch die `option`s ausgeblendet, mehr dazu später.
~~~js
if (!targetElement.disabled)
{
targetElement.value = isPlayerXMoving ? 'x' : 'o';
targetElement.disabled = true;
~~~
Das Feld erhält, wenn es noch frei ist, den entsprechenden Wert – nachdem, wer gerade am Zug war. Danach wird es disablet, damit es nicht noch einmal ausgewählt werden kann.
~~~js
isPlayerXMoving = !isPlayerXMoving;
}
}
}
~~~
Der nächste Spieler ist am Zug.
Um die nun nicht benötigten `option`-Elemente auszublenden, erhält das Stylesheet noch eine Ergänzung:
~~~css
.js #tic-tac-toe option
{
display: none;
}
~~~
Diese Regel wirkt nur dann, wenn ein Vorfahrenelement die Klasse `js` hat. Das ist nur dann der Fall, wenn JavaScript ausgeführt wird, da wir diese Klasse fürs `html`-Element ja erst mit JavaScript gesetzt hatten.
Und so haben wir das Grundkonstrukt **_progressively enhanced_{: lang="en"}** – erst das Aussehen, dann das Verhalten; mit schon [ansehnlichem Ergebnis](http://codepen.io/gunnarbittersmann/pen/GomxRm).
Bei Ausfall von JavaScript oder CSS funktioniert die Grundfunktionalität immer noch – und zwar auch, wenn CSS ausfällt, JavaScript aber ausgeführt wird. Dann sieht das Feld wieder ungestylt aus, das JavaScript sorgt aber schon dafür, dass in den disableten `select`s nicht erneut auswählt werden kann.
Jetzt fehlt noch die Abfrage, ob ein Spieler „was auf die Reihe gekriegt hat“ – aber die sei mal _out of scope_{: lang="en"} dieses Postings.
`select`s sind recht störrisch gegenüber Styling. WebKits können nicht die Schriftgröße der `option`s kleiner setzen als die des `select`s. Auch die Positionierung des Drop-Downs innerhalb des Feldes ist Frickelei, wenn jeder Browser da etwas andere Vorstellungen hat.
Beschäftigen wir uns lieber mit etwas anderem:
---
Eine Auswahl von einer Option aus mehreren kann auch durch eine Gruppe von **Radiobuttons** geschehen.
# 1. Tic
Entgegen früheren HTML-Versionen, wo ohne Nutzerinteraktion der erste Radiobutton einer Gruppe als vorausgewählt galt, wenn keiner als `selected` gekennzeichnet war (woran sich Browser aber nicht gehalten haben), sieht HTML5 explizit vor, dass [initial kein Radiobutton ausgewählt](https://www.w3.org/html/wg/drafts/html/master/semantics.html#radio-button-state-%28type=radio%29:radio-button-group-10) sein muss.
Das Innere der Tabellenzellen wäre hier derart:
~~~html
<td>
<fieldset>
<legend>top left field</legend>
<input type="radio" name="top-left" id="top-left-x">
<label for="top-left-x">x</label>
<input type="radio" name="top-left" id="top-left-o">
<label for="top-left-o">o</label>
</fieldset>
</td>
~~~
Wieder nicht besonders schön, aber [funktioniert](http://codepen.io/gunnarbittersmann/pen/GomNJJ). Und blind bedienbar. ♿️
# 2. Tac
Und wieder setzen wir **_progressive enhancement_{: lang="en"}** ein, und Darstellung und Verhalten schrittweise zu verbessern. Zuerst wieder das Styling:
Die Radiobuttons werden ausgeblendet, `fieldset` büßt seinen Rahmen ein und auch hier wird `legend` wieder _visuell_ versteckt – und die Label auch; dafür kommt SVG zum Einsatz. Und nicht `outline` für `:focus` vergessen!
Die Magie liegt nun darin:
~~~css
#tic-tac-toe :checked + label::after
{
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
~~~
Wenn ein Radiobutton ausgewählt wird, wird das zugehörige Label vergrößert, so dass es das ganze Feld ausfüllt.
~~~css
/* transition: width, height, left 0.1s; */
~~~
Diese Vergrößerung könnte man auch animieren; die betreffende Codezeile ist aber auskommentiert, denn diese Animation ist nicht gerade performant. Besser ist es, `transition` bspw. auf `transform` anzuwenden.
~~~css
@supports (transform: scale(1))
{
~~~
Dazu fragen wir ab, der der Browser `transform` denn kann. (Andernfalls würden wir die Vergrößerung in alten Browsern nicht mehr haben.)
~~~css
#tic-tac-toe label::after
{
width: 100%;
height: 100%;
transform: scale(0.25);
transition: transform 0.1s;
}
~~~
Initial werden die Label verkleinert, damit sie nebeneinander passen.
~~~css
#tic-tac-toe label[for$="-x"]::after
{
transform-origin: left top;
}
#tic-tac-toe label[for$="-o"]::after
{
left: 0;
transform-origin: center top;
}
~~~
Nebeneinander, nicht aufeinander! Deshalb haben sie verschiedene Streckungszentren.
~~~css
#tic-tac-toe :checked + label::after
{
transform: scale(1);
}
}
~~~
Und bei ausgewähltem Radiobutton füllt das zugehörige Label das ganze Feld.
Doch bei [dieser Darstellung](http://codepen.io/gunnarbittersmann/pen/EPmNgO) kommt wohl kaum noch jemand drauf, dass man hier Radiobuttons auswählt.
# 3. Toe
Das Label für den nicht ausgewähltem Radiobutton soll natürlich noch verschwinden und auch die Wer-ist-am-Zug?-Logik implementiert werden.
Auch hier wieder _ein_ Eventhandler und _event delegation_{: lang="en"}.
~~~js
function ticTacToeClickHandler(event)
{
var targetElement = event.target;
if (targetElement.nodeName == 'INPUT')
{
targetElement.parentNode.disabled = true;
switchPlayer();
}
}
~~~
Ähnlich wie oben, nur dass die Gruppe der Radiobuttons mittels deren Elternelement (das wäre hier `fieldset`) disablet wird. Und dass hier der Code, der initial auch einmal ausgeführt werden muss, in die Funktion `switchPlayer` ausgelagert ist.
~~~js
function switchPlayer()
{
isPlayerXMoving = !isPlayerXMoving;
~~~
Der nächste ist dran.
~~~js
for (var i = 0; i < xInputElements.length; i++)
{
xInputElements[i].disabled = !isPlayerXMoving;
}
for (var i = 0; i < oInputElements.length; i++)
{
oInputElements[i].disabled = isPlayerXMoving;
}
}
~~~
Wenn der Spieler am Zug ist, der die Kreuze macht, werden die Kreuz-Radiobuttons enablet; die Kreis-Radiobuttons disablet. Für den anderen Spieler entsprechend andersrum.
Im Stylesheet sind noch einige Anpassungen nötig. Damit diese nur greifen, wenn JavaScript ausgeführt wird, wieder im `.js` im Selektor.
~~~css
.js #tic-tac-toe label::after
{
left: 0;
width: 100%;
height: 100%;
transform: scale(1);
opacity: 0;
transition: none;
z-index: 1;
}
~~~
Die Label lassen wir das gesamte Feld ausfüllen, damit die Spieler überall im Feld clicken können.
~~~css
.js #tic-tac-toe :disabled + label::after,
.js #tic-tac-toe :disabled > label::after
{
z-index: 0;
opacity: 0;
cursor: not-allowed;
}
~~~
Label von disableten Radiobuttons werden nicht angezeigt. Dass dann das Feld nicht anclickbar ist, wird durch einen entsprechenden Cursor angezeigt.
~~~css
.js #tic-tac-toe :checked + label::after
{
opacity: 1;
}
~~~
Label von ausgewählten Radiobuttons werden angezeigt.
Das Ganze [sieht damit so aus](http://codepen.io/gunnarbittersmann/pen/XXRExX) – Radiobuttons _progressively enhanced_{: lang="en"}.
Auch diese Lösung funktioniert, wenn JavaScript interpretiert wird, CSS aber nicht. disablete Radiobuttons können nicht ausgewählt werden und werden ausgegraut dargestellt.
An der Stelle käme dann wieder die Erkennung des Spielendes hinzu, um die wir uns in diesem Posting nicht kümmern wollen.
--
Kümmern wir uns lieber um die grundsätzliche Frage: Sollte das alles in einem Tutorial für _Anfänger_ stehen?
**Ja, unbedingt!!** Wie sonst sollen Anfänger das Prinzip von _progressive enhancement_{: lang="en"} verinnerlichen, wenn es ihnen in Tutorials nicht so vorgemacht wird? Kein Anfänger wird nach einem solchen JavaScript-Tutorial noch ein zweites lesen, denn die Lösung „funzt“ ja. Nur dass sie eben nicht _funktioniert_.
Wollen wir die nächste Generation von Entwicklern heranzüchten, die das dreiundzwölfzigste JavaScript-Framework entwickeln, ohne die geringste Ahnung von HTML zu haben? Was man dem erzeugten Code auch ansieht und das Ergebnis zu Lasten von UX und Barrierefreiheit, also zu Lasten der Nutzer geht?
LLAP 🖖
--
„Wir haben deinen numidischen Schreiber aufgegriffen, o Syndicus.“
„Hat auf dem Forum herumgelungert …“
(Wachen in Asterix 36: Der Papyrus des Cäsar)