marctrix: Sprachauswahl-Menü funktioniert nicht richtig

problematische Seite

Hej alle,

mein Problem: ein Menü für die Auswahl einer Sprache. Ein Beispiel habe ich bei Heydon Pickerings "Inclusive Components" gefunden.

Als JS-Unkundiger kann ich das zwar etwas an meine Bedürfnisse anpassen (sinnvolle ID z.B.), aber es hängt noch an folgender Stelle:

Ich möchte das Menü zweimal einbinden: auf Desktops soll es am Seitenkopf angezeigt werden, in der mobilen Darstellung am Seitenende. Das würde ich natürlich gerne mit nur einem Script beides abfrühstücken. Ich habe es aber nur hinbekommen, dass das erste Menü funktioniert.

edit: Den Code findet ihr über den "problematische Seite"-Link.

Ich würde mich über Hilfe freuen.

Ich nehme auch gerne jede Art von Verbesserungsvorschlägen!

Marc

akzeptierte Antworten

  1. problematische Seite

    Tach!

    Ich möchte das Menü zweimal einbinden:

    Momentan sucht es nur das erste Vorkommen von [id|="lang-menu"] button, da muss was anders werden. So zum Beispiel:

    document.querySelectorAll('[id|="lang-menu"] button').forEach(navButton => { 
    		navButton.addEventListener('click', function() {
    			let expanded = this.getAttribute('aria-expanded') === 'true' || false;
    			this.setAttribute('aria-expanded', !expanded);
    			let menu = this.nextElementSibling;
    			menu.hidden = !menu.hidden;
    		});
    });
    

    Aus dem querySelector() wurde ein querySelectorAll() und über die damit entstehende Liste gehts mit forEach() weiter.

    dedlfix.

    1. problematische Seite

      Tach!

      Wenn es auch mit dem IE 11 laufen soll, dann muss die erste Zeile so aussehen:

      document.querySelectorAll('[id|="lang-menu"] button').forEach(function(navButton) { 
      

      dedlfix.

      1. problematische Seite

        Hej dedlfix,

        Wenn es auch mit dem IE 11 laufen soll, dann muss die erste Zeile so aussehen:

        document.querySelectorAll('[id|="lang-menu"] button').forEach(function(navButton) { 
        

        Soll auch im IE11 laufen! - Dankeschön!

        Marc

      2. problematische Seite

        Hallo dedlfix,

        IE11 kennt kein forEach auf der Nodelist, da braucht's einen Polyfill.

        Rolf

        --
        sumpsi - posui - clusi
        1. problematische Seite

          Tach!

          IE11 kennt kein forEach auf der Nodelist, da braucht's einen Polyfill.

          Array.from() als Zwischenschritt kennt er auch nicht. Aber Polyfill brauchts nicht, wenn man ein for nimmt.

          dedlfix.

          1. problematische Seite

            Hallo dedlfix,

            hast Du Dich vertippt oder machst Du den Hotti? Wer hat von Array.from gesprochen?

            Rolf

            --
            sumpsi - posui - clusi
            1. problematische Seite

              Tach!

              hast Du Dich vertippt oder machst Du den Hotti? Wer hat von Array.from gesprochen?

              Ich, weil mir das als Idee kam, um aus der NodeList ein Array zu bekommen, über das man mit forEach() iterieren kann.

              Das MDN hat auch noch als IE-kompatibel im Angebot:

              var list = document.querySelectorAll('...');
              Array.prototype.forEach.call(list, function (element) { ... });
              

              dedlfix.

              1. problematische Seite

                hallo

                Tach!

                hast Du Dich vertippt oder machst Du den Hotti? Wer hat von Array.from gesprochen?

                Ich, weil mir das als Idee kam, um aus der NodeList ein Array zu bekommen, über das man mit forEach() iterieren kann.

                Das MDN hat auch noch als IE-kompatibel im Angebot:

                var list = document.querySelectorAll('...');
                Array.prototype.forEach.call(list, function (element) { ... });
                

                dedlfix.

                Ich bin mir nicht zu schade so was zu schreiben

                			// initialise functions
                			_.functions = document.querySelectorAll("script[data-initfunction]");
                			for(var i=0; i<_.functions.length;i++){
                				_.funcname = _.functions[i].getAttribute("data-initfunction");
                				window[_.funcname](); 
                			}
                

                Edgelodiness=0 Reliability=1

    2. problematische Seite

      Hallo,

      angesichts des let stellt sich die Frage nach Internet Explorer 11 nicht - aber wenn's dort nicht unbedienbar sein soll, muss es var sein und man braucht einen Polyfill für Nodelist.foreach.

      Eleganter geht's, wenn man Event Bubbling verwendet und das click-Event auf einem gemeinsamen Container der beiden Menüs abhandelt (z.B. auf dem Body-Element). Die IDs der beiden Sprachwähler sind dann durch eine class zu ersetzen, die bei beiden gleich ist. Zum Beispiel lang-menu. Die Frage, welcher von beiden sichtbar ist, löst man vermutlich sowieso über CSS-Selektoren die den Container des oberen oder unteren Sprachwählers beinhalten, d.h. eine eindeutige ID für die beiden Wähler dürfte unnötig sein.

      Allerdings braucht auch diese Variante einen IE11-Polyfill, nämlich für Element.closest. Oder man verwendet die darunter stehende Variante.

      Was NICHT funktioniert, ist die triviale Prüfung ob e.target ein Button ist. Grund ist der andauernde Streit zwischen Chrome und Firefox, ob Kindelemente innerhalb eines Buttons Event-Targets sein können oder nicht. Klickt man auf das SVG, liefert FF den Button als Target und Chrome den Path. Deswegen die Suche in den Elternelementen.

      document.body.addEventListener('click', function(e) {
         // Sprachmenü-Div finden, wenn null, dann war der click woanders -> raus
         let chooser = e.target.closest(".lang-menu");
         if (!chooser) return;
         
         // Button im Sprachmenü finden, wenn null, ist das Menü falsch -> raus
         let button = chooser.querySelector("button");
         if (!button) return;
         
         // aria-expanded Attribut des Button umschalten und das daneben
         // liegende Dropdown-Menü entsprechend ein- oder ausblenden.
         let expanded = button.getAttribute('aria-expanded') === 'true' || false;
         button.setAttribute('aria-expanded', !expanded);
      	 let menu = button.nextElementSibling;
         menu.hidden = !menu.hidden;
      });
      

      IE10/11 kompatible Version der beiden ersten Zeilen:

         // Sprachmenü-Div finden, wenn null, dann war der click woanders -> raus
         var chooser = e.target;
         while (chooser && !cho0ser.classList.contains("lang-menu") )
            chooser = chooser.parentElement;
         if (!chooser)
           return;
      

      Rolf

      --
      sumpsi - posui - clusi
      1. problematische Seite

        Hej Rolf,

        IE10/11 kompatible Version der beiden ersten Zeilen:

           // Sprachmenü-Div finden, wenn null, dann war der click woanders -> raus
           var chooser = e.target;
           while (chooser && !choser.classList.contains("lang-menu") )
              chooser = chooser.parentElement;
           if (!chooser)
             return;
        

        Danach kommen aber weitere let — funktioniert es deshalb noch nicht im IE11?

        Marc

        1. problematische Seite

          Hallo marctrix,

          ja. Wie ich schrub: let durch var ersetzen. Let ist eine neuere Form der Variablendeklaration, die den Scope für Variablen präziser festlegen lässt. In den hier verwendeten Scripten ist es stumpf ersetzbar.

          Rolf

          --
          sumpsi - posui - clusi
          1. problematische Seite

            Hej Rolf,

            ja. Wie ich schrub: let durch var ersetzen. Let ist eine neuere Form der Variablendeklaration, die den Scope für Variablen präziser festlegen lässt. In den hier verwendeten Scripten ist es stumpf ersetzbar.

            Ah - verstehe. Vielen Dank, natürlich auch @beatovich und @dedlfix — ihr habt mir sehr geholfen!

            Obwohl - ich traue es mich kaum es zu sagen - ich für die Sprachwahl doch eine andere Lösung bevorzugen würde. Mal sehen, ob ich das bei meiner Designerin durchbekomme (die derzeitige Lösung werde ich aber sicher für andere Klapp-Elemente verwenden!):

            ein Icon macht meiner Meinung nach Sinn obwohl ich in vielen (älteren) Artikeln gelesen habe, dass davon abgeraten wird, unter anderem weil es keinen Standard gibt, den Nutzer als Sprachwechsel-Icon erkennen. Ich meine aber, es lohnt sich den noch nicht bekannten Standard einzuhalten, zumal dessen Bekanntheitsgrad meiner Beobachtung nach zu steigen scheint. Die Rede ist vom language icon (http://www.languageicon.org/).

            Was die Darstellung der auszuwählenden Sprachen selber angeht, sind sich die meisten Autoren einig:

            1. Keine Länderflaggen
            2. Textlinks
            3. Sprachen sollten in der auszuwählenden Sprache präsentiert werden, also ‘Deutsch’ und ‘中文’ — nicht ‘German’ und ‘Chinese’

            Weniger einig sind sich die Texte und die großen Websites, was das verbergen der Links zu den Sprachversionen angeht, also in Aufklappmenüs, so wie wir das machen. Mir scheint aber, dass die Forderung nach lokalen Bezeichnungen für Sprachen (siehe 3. Punkt der Liste oben) wenig Sinn machen, wenn ‘中文’ hinter „Wählen Sie Ihre Sprache“ versteckt ist.

            Insofern gefällt mir die Lösung von facebook am besten (Liste von unterstützten Sprachen am Seitenende - https://www.facebook.com/) oder auch die wikipedia-Lösung (Liste von Sprachen am linken Rand). In beiden Fällen kann man die eigene Sprache sehen, finden und verstehen.

            Eine der besten Zusammnefassungen zum Thema habe ich hier gefunden:

            http://www.flagsarenotlanguages.com/blog/best-practice-for-presenting-languages/

            Vielleicht hilft diese Zusammenfassung ja dem einen oder anderen.

            Marc

            1. problematische Seite

              hallo

              ein Icon macht meiner Meinung nach Sinn obwohl ich in vielen (älteren) Artikeln gelesen habe, dass davon abgeraten wird, unter anderem weil es keinen Standard gibt, den Nutzer als Sprachwechsel-Icon erkennen. Ich meine aber, es lohnt sich den noch nicht bekannten Standard einzuhalten, zumal dessen Bekanntheitsgrad meiner Beobachtung nach zu steigen scheint. Die Rede ist vom language icon (http://www.languageicon.org/).

              Ich habe mal selber ein Icon entworfen. Icons sind aber bei Webseiten (im Unterschied zu Desktopanwendungen) nur ergänzender Inhalt zu Klartext Info.

              Mein Entwurf: Icon 7 in der ersten Zeile https://beat-stoecklin.ch/images/gui.svg

              1. problematische Seite

                Hej beatovich,

                ein Icon macht meiner Meinung nach Sinn obwohl ich in vielen (älteren) Artikeln gelesen habe, dass davon abgeraten wird, unter anderem weil es keinen Standard gibt, den Nutzer als Sprachwechsel-Icon erkennen. Ich meine aber, es lohnt sich den noch nicht bekannten Standard einzuhalten, zumal dessen Bekanntheitsgrad meiner Beobachtung nach zu steigen scheint. Die Rede ist vom language icon (http://www.languageicon.org/).

                Ich habe mal selber ein Icon entworfen.

                Ich würde aber wie gesagt gerne das languageicon aus gutem Grund unterstützen, es ist meiner Meinung nach kontraproduktiv verschiedene Icons hierfür einzustezen — es geht ja um den Wiedererkennungswert…

                Icons sind aber bei Webseiten (im Unterschied zu Desktopanwendungen) nur ergänzender Inhalt zu Klartext Info.

                Auf jeden Fall!

                Mein Entwurf: Icon 7 in der ersten Zeile https://beat-stoecklin.ch/images/gui.svg

                Hast dich ja an bestehenden Lösungen orientiert, was grundsätzlich gut ist. Dennoch: ein Icon überall fände ich besser.

                Marc

          2. problematische Seite

            Hej Rolf,

            ja. Wie ich schrub: let durch var ersetzen. Let ist eine neuere Form der Variablendeklaration, die den Scope für Variablen präziser festlegen lässt. In den hier verwendeten Scripten ist es stumpf ersetzbar.

            Habe ich gemacht - geht trotzdem immer noch nicht im IE11…

            Marc

            1. problematische Seite

              Hallo marctrix,

              ok, 2:1 für Dich. (Ich hab 2x Mist gebaut, Du 1x).

              Mein Mist 1: Ich hatte die Variable chooser vorher choser genannt, und die Umbenennung an einer Stelle vergessen.

              Mein Mist 2: Ich habe am Ende aus mir nicht näher verständlichen Gründen einen Doppler dringehabt (});}); statt });, das war ein Syntaxerror)

              Dein Mist: Mit „ersetzt die ersten beiden Zeilen“ meinte ich die beiden ersten Zeilen des Funktionsinhaltes, nicht die beiden ersten Zeilen des ganzen Scripts. Ersetzt werden muss Zeile mit dem ersten let und das darauf folgende if. Man könnte das auch als meinen Mist ansehen - ich habe ungenau beschrieben und deine JS Unkenntnis übersehen.

              Meinen Mist habe ich oben korrigiert. Remote-Programmierung ist immer etwas knifflig :)

              Rolf

              --
              sumpsi - posui - clusi
              1. problematische Seite

                Hej Rolf,

                erst einmal möchte ich mich entschuldigen, dass ich auf Deine tolle Hilfe erst reagiere. - Ich habe das Wochenende über einfach Pause gebraucht und gemacht…

                ok, 2:1 für Dich. (Ich hab 2x Mist gebaut, Du 1x).

                Hehe — passiert. Vielen Dank, dass du dran geblieben bist!

                Dein Mist: Mit „ersetzt die ersten beiden Zeilen“ meinte ich die beiden ersten Zeilen des Funktionsinhaltes, nicht die beiden ersten Zeilen des ganzen Scripts. Ersetzt werden muss Zeile mit dem ersten let und das darauf folgende if. Man könnte das auch als meinen Mist ansehen - ich habe ungenau beschrieben und deine JS Unkenntnis übersehen.

                Na ja, ich erinnere mich, dass ich an der Stelle gestutzt hatte, insofern hätte ich ja mal nachfragen können. Daher ist es schon in Ordnung, wenn das auf meine Kappe geht. 😉

                Es lief danach ja auch weiter in FF und Chrome.

                Meinen Mist habe ich oben korrigiert. Remote-Programmierung ist immer etwas knifflig :)

                Ja, das ist es. Jetzt funktioniert es im auch im IE. - Gut gemacht!

                Marc

  2. problematische Seite

    hallo

    Ich möchte das Menü zweimal einbinden: auf Desktops soll es am Seitenkopf angezeigt werden, in der mobilen Darstellung am Seitenende. Das würde ich natürlich gerne mit nur einem Script beides abfrühstücken. Ich habe es aber nur hinbekommen, dass das erste Menü funktioniert.

    Warum verwendest du keine Event-Delegation?

    function clickManager(ev){
    	if(ev.target.hasAttribute("aria-expanded") ){
    		ev.target.setAttribute("aria-expanded", ( ev.target.getAttribute("aria-expanded") == "false") ); 
    	}
    }
    
    1. problematische Seite

      Hej beatovich,

      Ich möchte das Menü zweimal einbinden: auf Desktops soll es am Seitenkopf angezeigt werden, in der mobilen Darstellung am Seitenende. Das würde ich natürlich gerne mit nur einem Script beides abfrühstücken. Ich habe es aber nur hinbekommen, dass das erste Menü funktioniert.

      Warum verwendest du keine Event-Delegation?

      Weil ich es nicht kann 😉

      function clickManager(ev){
      	if(ev.target.hasAttribute("aria-expanded") ){
      		ev.target.setAttribute("aria-expanded", ( ev.target.getAttribute("aria-expanded") == "false") ); 
      	}
      }
      

      Marc

    2. problematische Seite

      Tach!

      Warum verwendest du keine Event-Delegation?

      Lohnt sich das bei zwei Elementen, die als gemeinsames Elternelement nichts wesentlich besseres als body haben?

      function clickManager(ev){
      	if(ev.target.hasAttribute("aria-expanded") ){
      		ev.target.setAttribute("aria-expanded", ( ev.target.getAttribute("aria-expanded") == "false") ); 
      	}
      }
      

      Das wäre mir zu unspezifisch in einem "richtigen" Dokument. Da kommt bestimmt noch mehr hinzu, das expandierbar ist. Zudem soll der Button das Event-auslösende Element sein, das zu klappende Element ist aber ein anderes. Und wenn letzteres hidden ist, kann man auch nicht daraufklicken.

      dedlfix.

      1. problematische Seite

        hallo

        Tach!

        Warum verwendest du keine Event-Delegation?

        Lohnt sich das bei zwei Elementen, die als gemeinsames Elternelement nichts wesentlich besseres als body haben?

        Es lohnt sich generell weil buttons mit aria-expanded hochwahrscheilich öfters auftreten.

        Das wäre mir zu unspezifisch in einem "richtigen" Dokument.

        <button aria-expanded="false">Click</button> <div>Klappbox Inhalt</click>

        Was ist hier falsch?

        1. problematische Seite

          Hej beatovich,

          Warum verwendest du keine Event-Delegation?

          Lohnt sich das bei zwei Elementen, die als gemeinsames Elternelement nichts wesentlich besseres als body haben?

          Es lohnt sich generell weil buttons mit aria-expanded hochwahrscheilich öfters auftreten.

          Das halte ich auch für wahrscheinlich.

          Das wäre mir zu unspezifisch in einem "richtigen" Dokument.

          <button aria-expanded="false">Click</button> <div>Klappbox Inhalt</click>

          Was ist hier falsch?

          Ist das eine Fangfrage? Aus HTML-Sicht das </click> und im öffnendne div-Tag fehlt ein "hidden"…

          Marc

          1. problematische Seite

            hallo

            <button aria-expanded="false">Click</button> <div>Klappbox Inhalt</click>

            Was ist hier falsch?

            Ist das eine Fangfrage? Aus HTML-Sicht das </click> und im öffnendne div-Tag fehlt ein "hidden"…

            Ja, sollte natürlich ein </div> sein.

            Da fehlt kein hidden Attribut

            [aria-expanded="false"] + * {display:none}
            

            Aber man kann einwenden:

            Was, wenn im Button ein halbes Dokument ist? Was ist dann mit ev.target?

            Das betrifft deinen Fall wegen dem inline svg.

            1. problematische Seite

              Hallo beatovich,

              wie gut, dass ich auf all das eingegangen bin 😀

              Den click-Handler auf alles mit aria-expanded reagieren zu lassen halte ich aber auch für zu grob gesiebt - andere expandierbare Widgets könnten eine andere Behandlung brauchen. Zum Beispiel gar keine, weil da der Browser schon alles tut.

              Rolf

              --
              sumpsi - posui - clusi
              1. problematische Seite

                hallo

                Hallo beatovich,

                wie gut, dass ich auf all das eingegangen bin 😀

                Ich brauche des öfteren sekundäre Buttons für grössere Klapp-Boxen. Hier die Abfrage auf data-for-id

                Vielleicht lässt sich das sogar innerhalb eines Buttons anwenden.

                function clickManager(ev){
                	if(ev.target.hasAttribute("aria-expanded") ){
                		ev.target.setAttribute("aria-expanded", ( ev.target.getAttribute("aria-expanded") == "false") ); 
                	}
                	else if(ev.target.hasAttribute("data-for-id") ){
                		document.getElementById( ev.target.getAttribute("data-for-id") ).click();
                	}
                  // ... 
                }
                
                
                1. problematische Seite

                  Hallo beatovich,

                  Vielleicht lässt sich das sogar innerhalb eines Buttons anwenden.

                  Solange das DOM innerhalb des Buttons eine reine display-Angelegenheit ist, sicherlich. Denn es gilt ja die (vom FF forcierte) Grundregel: Was in einem Button ist, darf nicht interaktiv sein.

                  Rolf

                  --
                  sumpsi - posui - clusi
                  1. problematische Seite

                    Hej Rolf,

                    Vielleicht lässt sich das sogar innerhalb eines Buttons anwenden.

                    Solange das DOM innerhalb des Buttons eine reine display-Angelegenheit ist, sicherlich. Denn es gilt ja die (vom FF forcierte) Grundregel: Was in einem Button ist, darf nicht interaktiv sein.

                    Steht es so nicht (mehr) in der Spec?

                    Marc