Sprachauswahl-Menü funktioniert nicht richtig
marctrix
- javascript
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
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.
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.
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
Hallo dedlfix,
IE11 kennt kein forEach auf der Nodelist, da braucht's einen Polyfill.
Rolf
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.
Hallo dedlfix,
hast Du Dich vertippt oder machst Du den Hotti? Wer hat von Array.from gesprochen?
Rolf
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.
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
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
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
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
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:
- Keine Länderflaggen
- Textlinks
- 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
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
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
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
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
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 folgendeif
. 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
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") );
}
}
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
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.
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?
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
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.
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
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();
}
// ...
}
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
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