marctrix: Button zum Hinzufügen von Klassen

Hej alle,

was ich bräuchte, könnte ich auch selber im Web finden. Aber den Lösungen aus dem Web vertraue ich nicht so recht und weil ich von JS so gar keine Ahnung habe, frage ich hier einfach mal nach, ob jemand ein best practice verlinken kann — am liebesten mit Erklärung, denn ich liebe die Energie des Verstehens. 😉

Es geht um etwas ganz einfaches: ein Button, der eine Klasse zu einem Element hinzufügt.

Ich möchte damit Nutzern die Möglichkeit geben, Seiten an eigene Bedürfnisse anzupassen.

So soll man mit den Buttons etwa ein compact-layout ins öffnende html-Tag einfügen können, was eine nicht empfohlene und daher standardmäßig nicht umgesetzte, aber bei vielen beliebte kompaktere Darstellung der Seite bewirkt mit weniger Weißraum usw.

compact-form dagegen stellt Formulare entegegen Usability-Empfehlungen nicht einspaltig dar, sondern ändert die einspaltige Darstellung so, dass so viele label-input-Paare nebeneinander dargestellt, wie der Platz und eine minmax-Grid-Definition es zulassen.

Ich möchte natürlich gerne bei jedem Button bestimmen, welchem Element eine Klasse hinzugefügt werden soll und wie die Klasse heißen soll. Mehr brauche ich erst mal nicht.

Erst mal heißt, dass ich später vielleicht noch weiter Ideen haben werde. Der Button sollte also ausbaufähig sein.

Wenn das eine Kleinigkeit sein sollte, die mir einer von Euch in einem Codepen o.ä. bereit stellt, würde ich den natürlich auch gerne nehmen… 😉

Marc

--
Ceterum censeo Google esse delendam

akzeptierte Antworten

  1. Tach!

    was ich bräuchte, könnte ich auch selber im Web finden. Aber den Lösungen aus dem Web vertraue ich nicht so recht und weil ich von JS so gar keine Ahnung habe,

    Zeig mal ein paar.

    Es geht um etwas ganz einfaches: ein Button, der eine Klasse zu einem Element hinzufügt.

    Eventhandler auf dem Button setzen, darin das Ziel-Element identifizieren und dessen classList bearbeiten. Ist grundlegend erstmal keine Raketenwissenschaft. Aber vielleicht steckt der Teufel ja in irgendeinem Detail, das ich überlesen habe.

    dedlfix.

    1. Hej dedlfix,

      was ich bräuchte, könnte ich auch selber im Web finden. Aber den Lösungen aus dem Web vertraue ich nicht so recht und weil ich von JS so gar keine Ahnung habe,

      Zeig mal ein paar.

      So rum geht es natürlich auch. Werde mal danach suchen, kann aber bis morgen dauern, habe heute viele Besprechungen…

      Es geht um etwas ganz einfaches: ein Button, der eine Klasse zu einem Element hinzufügt.

      Eventhandler auf dem Button setzen, darin das Ziel-Element identifizieren und dessen classList bearbeiten. Ist grundlegend erstmal keine Raketenwissenschaft. Aber vielleicht steckt der Teufel ja in irgendeinem Detail, das ich überlesen habe.

      Nein, hast du nicht. Ist wirklich so einfach, dennoch: ich verstehe nicht, was du damit sagen willst. 😉

      Marc

      --
      Ceterum censeo Google esse delendam
  2. Hallo Marc,

    hier mal mein Entwurf:

    <!DOCTYPE html>
    <html lang="de">
    	<head>
    		<meta charset="UTF-8" />
    		<meta name="viewport" content="width=device-width, initial-scale=1.0">
    		<title></title>
    		<style type="text/css">
    		
    			html, body { min-height: 100vh }
    		
    			.toggleClass { background-color: red }
    
    		</style>
    		
    		<script>
    		
    			// Eventhandler auf "Dom fertig"
    			window.addEventListener("DOMContentLoaded",function() { 
    				// Button suchen und Eventhandler auf button-click
    				document.querySelector("#toggleClassBody").addEventListener("click", function() {
    					// Klasse umschalten
    					document.body.classList.toggle("toggleClass"); 
    				}, false);
    			}, false);
    		
    		</script>
    
    	</head>
    
    	<body>
    
    		<button id="toggleClassBody" type="button">Toggle Klasse "toggleClass" beim body.</button>
    	
    	</body>
    </html>
    

    Ich schalte mit document.body.classList.toggle("toggleClass"); die Klasse toggleClass am body an bzw. aus. Von da aus kannst du dann mit den css-Selectoren ja jedes weitere Element beeinflussen.

    Gruß
    Jürgen

    1. Hej JürgenB,

      Ich schalte mit document.body.classList.toggle("toggleClass"); die Klasse toggleClass am body an bzw. aus. Von da aus kannst du dann mit den css-Selectoren ja jedes weitere Element beeinflussen.

      Vielen Dank — wenn ich mehrere Buttons möchte, gebe ich denen dann einfach eine andere ID?!

      Kann ich statt "body" in document.body.classList.toggle("toggleClass"); auch eine ID verwenden, um einer Tabelle eine Klasse zu geben, also so z.B.:

      document.#ID_my-table.classList.toggle("toggleClass");

      Marc

      --
      Ceterum censeo Google esse delendam
      1. Hallo Marc,

        Vielen Dank — wenn ich mehrere Buttons möchte, gebe ich denen dann einfach eine andere ID?!

        ja.

        Kann ich statt "body" in document.body.classList.toggle("toggleClass"); auch eine ID verwenden, um einer Tabelle eine Klasse zu geben, also so z.B.:

        document.#ID_my-table.classList.toggle("toggleClass");

        So nicht. Du kannst mit document.querySelector("#ID_my-table") auf Elemente über die ID zugreifen. querySelector verwendet css-Slektoren. Aber warum willst du das?

        Marc

        Gruß
        Jürgen

        1. Hej JürgenB,

          Kann ich statt "body" in document.body.classList.toggle("toggleClass"); auch eine ID verwenden, um einer Tabelle eine Klasse zu geben, also so z.B.:

          document.#ID_my-table.classList.toggle("toggleClass");

          So nicht. Du kannst mit document.querySelector("#ID_my-table") auf Elemente über die ID zugreifen. querySelector verwendet css-Slektoren. Aber warum willst du das?

          Um einer Tabelle eine Klasse compact-tablemitgeben zu können.

          Marc

          --
          Ceterum censeo Google esse delendam
  3. Hallo marctrix,

    zunächst mal hätten wir eine business clarification phase zu absolvieren.

    • sind compact-layout und compact-form gegenseitig ausschließend oder können sie gleichzeitig aktiv sein?
    • sollen die Buttons Toggle-Buttons sein (d.h. ein klick schaltet ein, der andere aus), oder soll es Buttons zum einschalten und Buttons zum ausschalten geben?

    dann gäbe es eine technical clarification phase:

    • welche Art von JavaScript willst Du nutzen? (synchron/asynchron laden, module-Technik)
    • gibt es Browser, bei denen sagt: "Zu alt, dafür biete ich das Feature nicht an"?

    Danach hast Du drei Aufgaben zu lösen:

    • Registrieren des click-Eventhandlers pro Button
    • Schreiben des JS Code der auf die Klicks reagiert
    • Erstellen der nötigen CSS Styles, die auf die class-Angabe am html Element reagieren. Es würde mich nämlich wundern, wenn irgendein Browser diese Angaben mit etwas anderem als einem Schulterzucken quittieren würde. Oder ist das tatsächlich ein undocumented feature von Browsern?

    Unter Festlegung gewisser Annahmen habe ich mal rumgefiddelt.

    Das Script, das ich da verfasst habe, prüft zu Beginn dem readyState des Dokuments und kommt deshalb mit jeder Script-Ladetechnik klar.

    Als UI habe ich 3 Buttons verwendet, um die Zustände compact-layout, compact-form und regular zu schalten. Mittels CSS müsste man noch etwas bauen, das den aktiven Zustand im Button darstellt.

    Rolf

    --
    sumpsi - posui - clusi
  4. @@marctrix

    Ich hab da mal was gebastelt: toggle buttons.

    Die Buttons sollten Toggle-Buttons sein: Entweder mit aria-pressed oder du gehst den role="switch"/aria-checked-Weg. Jeder Toggle-Button hat in seinem data-toggle-Attribut das drinzustehen, was getogglet werden soll.

    Event delegation, d.h. du lauschst ganz oben aufs click-Event und prüfst, ob es durch einen Toggle-Button ausgelöst wurde.

    Wenn ja, setze/entferne die entsprechende Klasse bzw. setze das entsprechende data-*-Attribut beim Wurzelelement. (Beides implementiert, du brauchst nur eins davon.)

    Mit Nachfahrenselektoren kannst du dann je nach gesetzten Werten auf der Seite Dinge tun – angedeutet für „foo“.

    LLAP 🖖

    --
    „Wer durch Wissen und Erfahrung der Klügere ist, der sollte nicht nachgeben. Und nicht aufgeben.“ —Kurt Weidemann
    1. Hej Gunnar,

      @@Gunnar Bittersmann

      Wenn ja, setze/entferne die entsprechende Klasse bzw. setze das entsprechende data-*-Attribut beim Wurzelelement. (Beides implementiert, du brauchst nur eins davon.)

      Noch mal vielen dank, komme erst jetzt dazu, das umzusetzen.

      Mein Problem dabei: ich bräuchte tatsächlich einen Button, der in eine Tabelle, die z.B. per ID ausgewählt wird oder in alle Tabellen die Klasse compact-table schreibt. Dasselbe analog zu compact-form.

      Lässt sich das noch ergänzen, also Dein Button kombinieren mit einem der anderen hier gezeigten?

      Edit: besser nur in ausgewählte Tabellen. Dann kann man so einen Button an jedem Formular oder an jeder Tabelle anbieten, die überhaupt Bedarf hat. Die Stichwortsuche jeder Seite ist ja beispielsweise ein formular, in dem compact-form nichts zu suchen hat.

      Derzeit kann ich keine Klassen angeben, die ein Minus - im Namen haben. Das schreiben aber unsere Guidelines zum Bennenen von Klassen so vor… :-}

      Marc

      --
      Ceterum censeo Google esse delendam
      1. Hallo marctrix,

        ich bräuchte tatsächlich einen Button, der in eine Tabelle, die z.B. per ID ausgewählt wird oder in alle Tabellen die Klasse compact-table schreibt. Dasselbe analog zu compact-form.

        Wenn es alle Tabellen sind, brauchst Du das nicht. Dann hilft Dir eine CSS Regel dieser Art

        .compact-table table { ... }

        ausreichend beim Stylen. Wenn nur bestimmte tables per Design "kompaktierbar" sein sollen, könnten diese bspw. mit einer class can-compact versehen sein, dann nimmst Du .compact-table table.can-compact, oder in einem Container mit dieser Class stehen, dann wäre .compact-table .can-compact table der Selektor der Wahl.

        Wenn es nur eine Table sein soll, ist die Frage, woher du weißt, welche Tabelle das ist.

        1. Es gibt nur einen Button und eine Table. Welche Table das ist, ist per Design festgelegt

        -> Siehe oben, table.can-compact

        1. Es gibt mehrere Buttons. Jeder Button kann 1-n Tables kompaktieren. Welche das sind, ist per Design festgelegt

        -> Du brauchst mehrere globale Klassen. Jeder Button setzt eine davon, z.B. compact-foo-tables und compact-bar-tables. Auch dann kannst Du CSS Selektoren formulieren, die die Styles zur kompakten Darstellung setzen.

        .compact-foo-tables table.foo-compact { } .compact-bar-tables table.bar-compact { }

        Letztlich hat Gunnar bereits gezeigt, wie man pro Button unterschiedliche Klassen setzt.

        Das Gesagte gilt analog für Forms.

        Wenn es mehrere Buttons und mehrere globale Klassen gibt, kann das Ganze in eine ziemliche Selektoren-Wüste ausarten, weil Du dann ggf. jede Menge Kombinationen von Selektoren auflisten musst. Dabei können Dir Tools wie LESS oder SASS helfen, die erstellen die Kombinationen für Dich.

        So. Und nun sehe ich deinen Edit. Wenn Du sozusagen lokale Toggles anbieten willst, ergibt das Ganze mehr Sinn. Es gibt dann mehrere Vorgehensweisen; ich würde aber möglichst nahe an Gunnars Technik dranbleiben wollen.

        Zum einen musst Du davon weg, außer der Klasse auch noch ein data-Attribut zu setzen. Da gibt es offenbar eine Regel, nach der Minuszeichen in dataset-Attributen verboten sind und deswegen fliegt Dir das um die Ohren. Dieser Teil ist aber keine Kunst und kann darum weg. Das CSS muss dann entsprechend geändert werden, dass ein getoggelter Button nicht über data[...] erkannt wird, sondern über eine Klasse.

        Ich hätte diese Idee anzubieten:

        <button data-target=".foo" data-toggle="compact" aria-pressed="false">Compact</button>
        
        document.documentElement.addEventListener('click', event => {
        	if (event.target.dataset.toggle)
        	{
        		const isSwitchedOn = event.target.getAttribute('aria-pressed') != 'true';
        		event.target.setAttribute('aria-pressed', isSwitchedOn);
            
            let targetName = event.target.dataset.target;
            let target = targetName ?
            							document.querySelector(event.target.dataset.target) :
                          document.documentElement;
            let className = event.target.dataset.toggle || "toggled";
            if (target) {
               target.classList.toggle(className, isSwitchedOn)
            }
        	}
        })
        

        Der Button hat zwei data-Attribute. data-target enthält den Selektor, für den der Button wirken soll (ist keiner da, nimmt das Script das documentElement). Und data-toggle enthält den Klassennamen, der den Toggle-Zustand des Buttons am Target darstellen soll (ist keiner da, wird die Klasse "toggled" getoggelt.

        Fiddle Beispiel

        Rolf

        --
        sumpsi - posui - clusi
        1. Hej Rolf,

          ich bräuchte tatsächlich einen Button, der in eine Tabelle, die z.B. per ID ausgewählt wird oder in alle Tabellen die Klasse compact-table schreibt. Dasselbe analog zu compact-form.

          Wenn es alle Tabellen sind, brauchst Du das nicht. Dann hilft Dir eine CSS Regel dieser Art

          .compact-table table { ... }

          Vielleicht. Aber dazu müsste ich weite Teile des Bereits bestehenden css umschreiben. Problematisch ist es mit den Ausnahmen. Ich habe einiges an :not-selektoren. Man kann das vermutlich positiv schreiben, aber dann würde es weniger flexibel, weil ich dann alle Elemente aufzählen müsste, die betroffen sein sollen. Jedesmal wenn etwas Neues hinzukäme, müsste ich es in die Liste aufnehmen.

          ausreichend beim Stylen. Wenn nur bestimmte tables per Design "kompaktierbar" sein sollen, könnten diese bspw. mit einer class can-compact versehen sein, dann nimmst Du .compact-table table.can-compact, oder in einem Container mit dieser Class stehen, dann wäre .compact-table .can-compact table der Selektor der Wahl.

          Wenn es nur eine Table sein soll, ist die Frage, woher du weißt, welche Tabelle das ist.

          Im schlimmsten Fall müsste der Redakteur der Tabelle eine ˋidˋ mitgeben.

          In den wenigsten Angeboten gibt es viele große Tabellen. Der Aufwand für Redakteure wäre überschaubar. Mit Sicherheit kann der TYPO3-Integrator das sogar automatisieren. Der Redakteur müsste dann nur ein Häkchen setzen, dass eine Tabelle solch einen Button bekommen soll.

          1. Es gibt nur einen Button und eine Table. Welche Table das ist, ist per Design festgelegt

          -> Siehe oben, table.can-compact

          Mal abgesehen davon, dass ich keine präsentationsbezogenen Klassennamen verwenden möchte (dass so ein Button, der das Aussehen verändert, eine Klasse mit einem sprechenden Namen hinzufügt ist ok, aber nicht das html ohne JS), können es mehrere sein. Nicht das Design legt das Fest (obwohl es dafür eines gibt), sondern der Redakteur, wenn er Tabellen anlegt, die sehr breit sind. Hier möchte ich dem Besucher anbieten, eine für ihn angenehme Darstellungsform zu wählen: normal (th ohne Word wrap, td ohne Silbentrennung usw) wo dann horizontales scrollen nötig wäre oder eben die kompaktere Darstellung, wo kein oder deutlich weniger horizontales scrollen nötig wäre.

          Ähnliches bei den Formularen. Diese sollten einspaltig Wien, linksbündig, Labels über den Eingabefeldern. Das macht aber große Formulare bei online-Erfassungen sehr lang. Die Nutzer wünschen sich immer wieder (entgegen a11y- und ux-Empfehlungen) mehrspaltige Formulare. Das möchte ich eben nicht standardmäßig anbieten, aber jenen, die das unbedingt wollen auch nicht verwehren.

          Dafür die Button…

          Wenn es mehrere Buttons und mehrere globale Klassen gibt, kann das Ganze in eine ziemliche Selektoren-Wüste ausarten, weil Du dann ggf. jede Menge Kombinationen von Selektoren auflisten musst. Dabei können Dir Tools wie LESS oder SASS helfen, die erstellen die Kombinationen für Dich.

          Wie geht so etwas mit SASS?

          So. Und nun sehe ich deinen Edit.

          Sorry! - Du hättest es aber auch nicht beim Schreiben gesehen, wenn ich es in einen neuen Post geschrieben hätte…

          Wenn Du sozusagen lokale Toggles anbieten willst, ergibt das Ganze mehr Sinn. Es gibt dann mehrere Vorgehensweisen; ich würde aber möglichst nahe an Gunnars Technik dranbleiben wollen.

          Zum einen musst Du davon weg, außer der Klasse auch noch ein data-Attribut zu setzen. Da gibt es offenbar eine Regel, nach der Minuszeichen in dataset-Attributen verboten sind und deswegen fliegt Dir das um die Ohren. Dieser Teil ist aber keine Kunst und kann darum weg. Das CSS muss dann entsprechend geändert werden, dass ein getoggelter Button nicht über data[...] erkannt wird, sondern über eine Klasse.

          Ich hätte diese Idee anzubieten:

          <button data-target=".foo" data-toggle="compact" aria-pressed="false">Compact</button>
          
          document.documentElement.addEventListener('click', event => {
          	if (event.target.dataset.toggle)
          	{
          		const isSwitchedOn = event.target.getAttribute('aria-pressed') != 'true';
          		event.target.setAttribute('aria-pressed', isSwitchedOn);
              
              let targetName = event.target.dataset.target;
              let target = targetName ?
              							document.querySelector(event.target.dataset.target) :
                            document.documentElement;
              let className = event.target.dataset.toggle || "toggled";
              if (target) {
                 target.classList.toggle(className, isSwitchedOn)
              }
          	}
          })
          

          Danke, werde das erst nächste Woche ausprobieren können und melde mich daher nicht eher zurück…

          Der Button hat zwei data-Attribute. data-target enthält den Selektor, für den der Button wirken soll (ist keiner da, nimmt das Script das documentElement). Und data-toggle enthält den Klassennamen, der den Toggle-Zustand des Buttons am Target darstellen soll (ist keiner da, wird die Klasse "toggled" getoggelt.

          Fiddle Beispiel

          Merci!

          Marc

          --
          Ceterum censeo Google esse delendam
        2. @@Rolf B

          Da gibt es offenbar eine Regel, nach der Minuszeichen in dataset-Attributen verboten sind

          Es gibt keine dataset-Attribute. Es gibt data-…-Attribute in HTML und es gibt dataset-Objekte in JavaScript. Genau die Unterscheidung ist hier wichtig.

          Bindestriche (warum nennst du die „Minuszeichen“?) sind in data-…-Attributen durchaus erlaubt. In JavaScript geht Element.dataset.love-zero so nicht; da ist - tatsächlich ein Minuszeichen. In JavaScript wird daraus CamelCase:

          <main data-just-a-four-letter-word="love"></main>
          
          console.log(document.querySelector('main').dataset.justAFourLetterWord); // "love"
          

          LLAP 🖖

          --
          „Wer durch Wissen und Erfahrung der Klügere ist, der sollte nicht nachgeben. Und nicht aufgeben.“ —Kurt Weidemann
          1. Hallo Gunnar,

            es gibt aber Attribute [a.k.a. Properties] der DOMStringMap, die dem dataset Property eines HTML Elements zu Grunde liegt. (Ja, ich eiere herum. Properties heißen nur im Datenmodell „Attribute“[1]. Aber warum nicht. In 8 Tagen ist Ostern[2]).

            Dass sowas wie fooElement.dataset.bar-baz nicht funktionieren kann[4], ist logisch - das würde ja auch mit normalen JavaScript-Objekten nicht funktionieren. Aber in JavaScript ist fooObj['bar-Prop'] = 4711 durchaus möglich - deswegen hatte mich zuerst überrascht, dass das nicht ging. Dich ja auch, sonst hättest Du in deinem Script Vorkehrungen getroffen 😉. Angesichts der Abbildung von DOMStringMap Propertynamen auf data-Attributnamen ist es natürlich logisch.

            Und ansonsten - Minuszeichen, Bindestriche, pfff, nerv mich nicht. Es ist immer die gleiche Taste, die ich da drücke. Sourcecode hat keine Typographie. Die Taste rechts neben dem Punkt (de-DE) erzeugt im Sourcecode ein Minuszeichen.

            Rolf

            --
            sumpsi - posui - clusi

            1. Look Ma, I know typography ↩︎

            2. Es sei denn, man ist orthodox. Dann dauert es eine Woche länger[3] ↩︎

            3. Es sei denn, man ist Atheist. Dann geht es einem nur auf die Eier. ↩︎

            4. Zumindest nicht dann, wenn man die Absicht hat, der DOMStringMap ein Property "bar-baz" hinzuzufügen ↩︎

        3. Hej Rolf,

          Fiddle Beispiel

          Das funktioniert super, verbindet die Vorteile eines html- descendant- Selektors mit denen (z.B. etwas für alle Tabellen machen zu können), mit den Vorteilen, Elemente gezielt auswählen zu können.

          Klasse Lösung, genau das, was ich brauche.

          Danke Euch allen für die Hilfe!

          Marc

          --
          Ceterum censeo Google esse delendam
  5. Meine Antwort konzentriert sich auf Software-Engineering Best-Practices.

    Vorweg ein paar Worte zu den bisherigen Lösungsvorschlägen: Alle halten sich an das KISS-Prinzip: Keep it simple, stupid! Im Flur unseres Lehrstuhls prangert ein mahnendes Zitat von Brian W. Kernighan, das uns daran erinnern soll, Dinge nicht zu kompliziert zu gestalten:

    Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

    Wenn wir das KISS-Prinzip als Maßstab anlegen, dann ist alles Weitere, was ich nun vorzubringen habe, grober Unfug. Etwas milder formuliert, könnte man auch sagen, dass das folgende nur für hinreichend komplexe Applikationen einen Mehrwert hat, nicht aber für dein simples Problem. Software-Projekte sind in den meisten Fällen etwas lebendiges, die oft mit einem überschaubaren Feature-Set starten und über die Zeit an Masse und Komplexität zulegen. Gerade bei Projekten, die sehr klein starten und dann hochfrequentiert neue Features integrieren, lässt sich beobachten, dass sie im Laufe ihres Lebens immer wartungsintensiver werden. Projekte, die von Beginn an als umfangreich eingestuft werden, haben dieses Problem auch, werden aber häufiger auch von Beginn an mit einer Architektur konstruiert, die es erlaubt den Wartungsbedarf über längere Entwicklungsperioden nicht proportional ansteigen zu lassen.

    In den 2000ern war das Model-View-Controller-Pattern eine beliebte Architektur, um Web-Anwendungen zu konstruieren und ist es in Teilen auch heute noch. Speziell im JavaScript-Ökosystem haben sich aber andere Architekturen durchgesetzt, mein Eindruck ist, dass Komponenten-basierte Systeme mit unidirektionalem Datenfluss heute die dominierende Blaupause für Web-Apps und Mobile-Apps sind. Bevor ich das genauer erläutere, muss ich aber nochmal auf das Problem eingehen, schließlich bringt es nichts, die Lösung vor dem Problem zu diskutieren. Das mache ich jetzt nochmal am Beispiel der bisher genannten Lösungen, damit will ich aber nicht sagen, dass sie schlecht wären. Im Gegenteil, im Sinne des KISS-Prinzips sind das die besseren Lösungen für dein konkretes Problem. Stellen wir uns deshalb vor, dass diese Lösungen nur ein Teil einer viel umfangreicheren Web-App sind, die aus unzähligen Ad-Hoc Lösungen zusammengeschustert wurde.

    Alle Lösungen haben gemeinsam, dass sie kein explizites Zustandsmodell besitzen: Der Anwendungszustand ist implizit im DOM gespeichert und wird dort direkt manipuliert. Der Zustand ist hier das aktuell aktive Layout. In welchem Zustand sich die App befindet, wird implizit durch das Vorhandensein der jeweiligen Klasse auf dem html bzw. body-Element determiniert. Gunnar setzt außerdem das aria-pressed-Attribut auf den jeweiligen Buttons. Er stellt in seinem Code sicher, dass die Zustände der Buttons und das aktuell ausgewählte Layout immer im Einklang miteinander stehen. Es wäre unsinnig, wenn das Layout "foo" aktiviert ist, aber der dazugehörige foo-Button nicht aktiv ist. Aber was wäre, wenn nun eine dritte Komponente ebenfalls die foo-Klasse vergeben oder entfernen könnte, die nichts von dem foo-Button weiß? In dem Fall würden der foo-Button und das Layout nicht mehr synchronisiert sein, sondern könnten widersprüchliche Informationen zeigen. Das ist das größte Problem der GUI-Programmierung: Alle GUI-Komponenten müssen synchronisiert sein, nicht aufeinander abgestimmte Komponenten zeigen widersprüchliche Informationen an.

    Unidirektionaler Datenfluss

    Das Problem kann man mit unidirektionalem Datenfluss lösen: Es verlangt vom Programmierer den Anwendungszustand explizit zu modellieren. Der Anwedungszustand kann von keiner GUI-Komponente direkt geändert werden, sondern nur indirekt über eine Schnittstelle. Die Schnittstelle sorgt dann dafür, dass alle GUI-Komponenten, die über die Änderung des Anwendungszustands informiert werden, sodass diese sich aktualisieren können.

    Komponten-baiserte Architektur

    Allen Lösungen ist außerdem gemeinsam, dass sie direkt auf das DOM zugreifen. Das DOM wirkt im Web manchmal wie ein Fremdkörper, während HTML und CSS rein deklarative Sprachen sind, ist das DOM eine rein imperative Schnittstelle. Eine Änderung am DOM sieht völlig anders aus, als der HTML-Code den man initial schreibt. Das Problem löst man klassich mit Templating-Engines, die erlauben es HTML mit Platzhaltern zu schreiben. Frühe Templating-Lösungen waren in sofern dumm, als dass sie keine Ahnung von HTML hatten, sie waren rein text-basiert. Moderne Templating-Engines, die HTML verstehen, sind als Virtual DOM bekannt und werden nicht mehr Templating-Engine genannt. Die früheste und bis heute eine der beliebtesten Implementierungen ist React.

    Genug Gebrabbel, zeig mir Code!

    Ich hab mir Gunnars und Rofls Lösungen als Vorlagen genommen und sie auf React mit unidirektionalem Datenfluss portiert. Gunnars Pendant, Rolfs Pendant.

    Die Hauptdatei in Gunnars Pendant sieht so aus:

    import React, { useState } from 'react'
    import { render } from 'react-dom'
    import './style.css'
    
    function App () {
      const [foo, setFoo] = useState(false) // 6
      const [bar, setBar] = useState(true)  // 7
      return (
        <div className={'app' + (foo ? ' foo' : '') + (bar ? ' bar' : '')}>
          <button aria-pressed={foo} onClick={() => setFoo(!foo)}>foo</button>
          <button aria-pressed={bar} onClick={() => setBar(!bar)}>bar</button>
        </div>
      )
    }
    render(<App />, document.getElementById('root')) // 16
    

    Die drei ersten Zeilen importieren schlicht Library-Code von React und die CSS-Datei.

    Die Zeilen 6 und 7 modellieren den explizit den Anwendungszustand. Wir haben zwei boolsche Variablen foo und bar, die kodieren, ob die jeweiligen Layouts aktiv sind oder nicht. Ferner haben wir zwei Setter setFoo und setBar mit denen wir indirekt den Anwendungszustand verändern können. Der Initial-Zustand für das Layout "foo" ist false, für "bar" true. Darunter folgt das Virtual DOM, also sozusagen das Template. Das sieht aus wie HTML, ist aber eine Syntax-Erweiterung von JavaScript. Die geschweiften Klammern sind Markierungen für Platzhalter. Ein Platzhalter ist ein beliebiger JavaScript-Ausdruck. Zum Beispiel wird die Zeile

    <div className={'app' + (foo ? ' foo' : '') + (bar ? ' bar' : '')}>
    

    von React zu folgedem HTML umgeformt:

    <div class="app bar">
    

    Das ist technisch nicht ganz korrekt: Unter der Haube berechnet React eine minimale Anzahl von DOM-Operationen, die ausgeführt werden müssen, um diesen Zustand zu erreichen

    Bei einem Klick auf den foo-Button wird der Setter setFoo aufgerufen und zwar mit dem negierten aktuellen Zustand als Paramater. Daraufhin wird auch direkt der foo-Button aktualisiert, sodass das aria-pressed-Attribut immer synchron mit dem Anwendungszustand ist. Der bar-Button macht das gleiche für das "bar"-Layout.

    Die Zeile 16 initialisiert schließlich die App.

    React Best Practices

    Wie jede Technologie kommt React natürlich mit Vor- und Nachteilen daher.

    Progressive Enhancement

    Out of the box ist der oben gezeigte Code nicht im Geiste von Progressive Enhancement. Ein Nutzer ohne JavaScript sieht in dem obigen Beispiel erstmal gar nichts. Das HTML-Grundgerüst muss vom Server ausgeliefert werden. Dazu gibt es die Möglichkeit React-Komponenten serverseitig zu rendern. Clientseitig kann man dann den Aufruf von render durch hydrate ersetzen. Das hat übrigens die nette Nebenwirkung, dass man server- und clientseitig ein und dieselben "Templates" verwenden kann. Spart also enorme Entwicklungszeit.

    Download-Volumen von React

    Der obige Code funktioniert nicht ohne Teile des React-Frameworks zu laden. React selber hat nicht gerade den schmalsten Fußabdruck und da man in den seltensten Fällen die ganze Library braucht, mutet man dem Nutzer ziemlich viel Overhead zu. Das ist für sich genommen schon ärgerlich, der Effekt wird noch verschlimmert, weil das Runterladen natürlich auch Zeit braucht. Inzwischen gibt es deshalb eine Reihe an Alternativen zu React, die sich alle damit brüsten schmaler zu sein, Vue.js zum Beispiel. Auf der anderen Seite gibt es aber inzwischen auch sehr viele Entwickler-Tools, um nur die Teile aus React auszuliefern, die auch tatsächlich benötigt werden. "Tree-Shaking" und "Code-Splitting" sind zwei gute Stichworte für weitere Recherchen.

    Overhead von React

    Neben dem Download-Volumen hat React natürlich auch einen Overhead zur Laufzeit auf das JavaScript-Programm. Das laufende Beispiel ist in diesem Punkt mit Sicherheit den Lösungen von Rolf, Jürgen und Gunnar unterlegen. Aber die Prämisse war ja, dass das Projekt eine "hinreichende" Komplexität birgt. Für hinreichend komplex gibt es natürlich keine scharfe Grenze, das muss immer am jeweiligen Projekt entschieden werden. Jedenfalls optimiert React unter der Haube sehr viele DOM-Operationen weg, die oft einen Performance-Engpass bilden. Außerdem spart React-Entwicklung sehr viel Entwicklungsaufwand, der dann bspw. in Performance-Optimierung reinvestiert werden kann.

  6. Hej marctrix,

    was ich bräuchte, könnte ich auch selber im Web finden. Aber den Lösungen aus dem Web vertraue ich nicht so recht und weil ich von JS so gar keine Ahnung habe, frage ich hier einfach mal nach, ob jemand ein best practice verlinken kann — am liebesten mit Erklärung, denn ich liebe die Energie des Verstehens. 😉

    Jetzt nachdem ich hier eine schöne Lösung bekommen habe, die für mich funktioniert, habe ich im Netz doch etwas gefunden, was einen soliden Eindruck macht und Scott O'Hara ist auch jemand, dem ich eine saubere Umsetzung zutraue. Da die Lösung von Ende 2018 ist und zudem dokumentiert würde ich die jetzt noch lieber einsetzen, als unsere hier gemeinsam erarbeitete.

    Auch weil es sich um eine ganze Komponenten-Bibliothek handelt und ich noch mehr Dinge finde, die ich wohl mal verwenden möchte. Die Sammlung ist schon recht umfangreich, deckt viele benötigte Komponenten ab und wird voraussichtlich auch weiter wachsen.

    Es wäre aber trotzdem super, wenn Ihr Euch mal die Zeit die nehmen würdet, um das JS anzuschauen und zu sagen, ob das so eine vernünftige Basis ist, wie ich es mir erhoffe, denn wie gesagt kann ich das so überhaupt nicht beurteilen…

    Zunächst ist der JS-Code erst mal sehr viel umfangreicher, als das, was ihr mir hier geliefert hat und ich wüsste gerne, was das JS da alles macht.

    Das würde mir echt helfen! @Orlok, schaust du vielleicht mal mit?

    Umgekehrt hoffe ich, dass der Link auch Euch nützlich ist bei zukünftigen Entwicklungen. Hier erst mal der Link zu meiner Frage:

    Was macht das JS und ist das ordentlich geschrieben?

    JS by Scott O'Hara für Toggle-Buttons

    Hier der Link zu den gestylten Formular-Elementen

    Und zu guter letzt einer zum GitHub-Profil von Scott O'Hara mit zahlreichen a11y-Lösungen

    Marc

    --
    Ceterum censeo Google esse delendam
    1. Tach!

      Auch weil es sich um eine ganze Komponenten-Bibliothek handelt und ich noch mehr Dinge finde, die ich wohl mal verwenden möchte. Die Sammlung ist schon recht umfangreich, deckt viele benötigte Komponenten ab und wird voraussichtlich auch weiter wachsen.

      Ich sehe, du bist auf den Geschmack von Bibliotheken gekommen, obwohl man auch die benötigten Lösungen alle einzeln im Netz finden und einbinden könnte. 😉 (An dieser Stelle hätte ich ein Posting von dir verlinken wollen, aber ich hatte grad keine Lust, das herauszusuchen. Ich möchte das Thema hier auch nicht unbedingt vertiefen.)

      Es wäre aber trotzdem super, wenn Ihr Euch mal die Zeit die nehmen würdet, um das JS anzuschauen und zu sagen, ob das so eine vernünftige Basis ist, wie ich es mir erhoffe, denn wie gesagt kann ich das so überhaupt nicht beurteilen…

      JS by Scott O'Hara für Toggle-Buttons

      Sieht professionell aus. Ich habe es nicht bis ins Detail angeschaut, aber das was mir auffiel macht einen ordentlichen Eindruck. Dass es so viel aussieht, liegt auch an den vielen Kommentaren. Einige kleinere hätte man ich sparen können, weil bereits aus dem Namen der Funktion hervorgeht, was diese tut und/oder aus dem überschaubaren Inhalt.

      Zunächst ist der JS-Code erst mal sehr viel umfangreicher, als das, was ihr mir hier geliefert hat und ich wüsste gerne, was das JS da alles macht.

      Das Problem bei gutem Code ist nicht der Teil, der zum gewünschten Ergebnis führt, sondern dass man häufig jede Menge Drumherum schreiben muss, um all die ungewünschten Fälle abzufangen und Sonderfälle zu berücksichtigen. Sowas wie: eine Zeile Datenbankzugriff zieht zehn Zeilen Fehlerbehandlung nach sich. Kann man weglassen, ist dann aber im Fehlerfall nicht robust und stirbt mitunter an der Stelle oder in der Folge.

      Siehe zum Beispiel setSwitchUI in Zeile 77ff., da muss wohl ein Fall abgefangen werden, dass irgendwelche Elemente fehlen könnten, die zur Lösung gebraucht werden.

      Wenn du nach einer speziellen Lösung suchst, dann wissen wir™ mitunter zwar generell, wie man das angeht, aber im Detail fehlt dann doch Erfahrung beim speziellen Thema, und deinen vollständigen Anwendungsfall kennen wir auch nicht. Wir sind dann nicht immer in der Lage, ganzheitliche Lösungen anzubieten oder für den Anwendungsfall alle Problemfälle zu überblicken. Deswegen beschränke ich mich meist auch darin, keine konkrete Codelösung hinzuschreiben, sondern eher, das Prinzip zu erklären und Hilfen zur Vorgehensweise aufzuzeigen. Den Rest muss der Probleminhaber eigenverantwortlich umsetzen. Oder anders gesagt, ich kann dir keinen Segen für das Vorhaben geben, auch wenn ich eine Meinung zum gezeigten Code geäußert habe. (Vermutlich ist dir das sowieso klar.)

      dedlfix.

      1. Hej dedlfix,

        Tach!

        Auch weil es sich um eine ganze Komponenten-Bibliothek handelt und ich noch mehr Dinge finde, die ich wohl mal verwenden möchte. Die Sammlung ist schon recht umfangreich, deckt viele benötigte Komponenten ab und wird voraussichtlich auch weiter wachsen.

        Ich sehe, du bist auf den Geschmack von Bibliotheken gekommen, obwohl man auch die benötigten Lösungen alle einzeln im Netz finden und einbinden könnte. 😉 (An dieser Stelle hätte ich ein Posting von dir verlinken wollen, aber ich hatte grad keine Lust, das herauszusuchen. Ich möchte das Thema hier auch nicht unbedingt vertiefen.)

        Diese Bibliothek ist anders als die von mir gern kritisierten Frameworks, handelt es sich doch um eine Sammlung, aus der man sich nur das raus holt, was man für ein konkretes Projekt benötigt.

        Man kann sich hier auch etwas für Tabellen raus holen und z.B. die toggle buttons nach der Anleitung aus dem Forum oder von Heydon Pickerings inclusive components selber bauen.

        Aber auch ich will das nicht vertiefen, wer mag, kann es ja nachlesen: ich sage ja immer: nicht jedes Mal das Rad neu erfinden, das ist viel zu viel Arbeit, auch wenn man kein framework nutzt ist das nicht nötig.

        JS by Scott O'Hara für Toggle-Buttons

        Sieht professionell aus. Ich habe es nicht bis ins Detail angeschaut, aber das was mir auffiel macht einen ordentlichen Eindruck.

        Ganz herzlichen Dank dafür!

        Marc

        --
        Ceterum censeo Google esse delendam