I'm refactoring pattern library components to comply with accessibility standards, and I'm dealing with an expandable header menu that is in actuality a hidden checkbox. It opens and closes on the checkbox's checked state. Because it's hidden it didn't have any labeling for ChromeVox to read, and it no longer could be activated via the spacebar, which is normally a default. I decided to add some spans that described when the menu is expanded or collapsed, and an event listener that triggers on a keystroke, and changed the display from one to the other so ChromeVox could read the spans and announce the change. The menu works completely on a click, the state changes and the reader works, but on keystroke the menu will open and the reader fails to announce the change to the spans.
This is my event listener-
function checkboxToggle() {
const el = document.querySelector('.site-header__button');
function handleCheckboxEvent(e) {
const checkbox = document.querySelector('.site-header-toggle');
if (e.keyCode === 13) {
checkbox.checked = !checkbox.checked;
}
}
el.addEventListener('keydown', handleCheckboxEvent);
}
The html is created through a Twig template-
<input hidden type="checkbox" id="site-header-toggle" class="site-header-toggle">
<header class="{{ classes|join(' ') }}">
<label tabindex="0" for="site-header-toggle" class="site-header__button icon--menu">
<span class="u-hide expanded-text">navigation menu expanded</span>
<span class="u-hide collapsed-text">navigation menu collapsed</span>
</label>
{% if menu_items %}
<nav class="site-header__menu">
{...}
</nav>
{% endif %}
</header>
With Scss I'm using an interpolation to effect everything in the .site-header parent when it's sibling, the checkbox, changes states. On keystroke all the displays change as expected, but it's just not triggering ChromeVox to read the spans as they are changing displays.
$open: '.site-header-toggle:checked + .site-header';
.site-header {
{...}
}
&__button {
font-size: 1.5rem;
.expanded-text {
display: none;
}
.collapsed-text {
display: inline;
}
&:hover { cursor: pointer; }
#{$open} & {
#include icon(close);
.expanded-text {
display: inline;
}
.collapsed-text {
display: none;
}
}
}
&__menu {
display: none;
width: 100%;
#{$open} & {
display: block;
}
}
}
I'm newish to screen readers so maybe there's something about ChromeVox that I'm just not taking into account. Any insight would be so helpful or resources I can check out to try and find a solution.
Related
I used the following code to toggle a button class in order to make a full-screen mobile menu.
HTML
button class="hamburger hamburger--slider" type="button">
<a href='#'><div class="hamburger-box">
<div class="hamburger-inner"></div>
</div>
</a>
document.addEventListener('DOMContentLoaded', function() {
jQuery(function($){
$('.hamburger').click(function(){
$('.hamburger--slider').toggleClass('is-active');
});
});
});
Now I would like to hide another item in my header when the toggled class .is-active is present.
The following code works to hide the item, but once the toggled class is gone, the item does not reappear but stays hidden until the page is reloaded.
jQuery(function($) {
if ($('.hamburger--slider.is-active').length) {
$('.rey-headerCart-wrapper').hide();
}
});
Appreciate any help :) !
you have to show the element again after the burger menu closes:
document.addEventListener('DOMContentLoaded', function() {
jQuery(function($){
$('.hamburger').click(function(){
$('.hamburger--slider').toggleClass('is-active');
// hide / show other element
if ($('.hamburger--slider.is-active').length) {
$('.rey-headerCart-wrapper').hide();
} else {
$('.rey-headerCart-wrapper').show();
}
});
});
});
Or in vanilla javascript:
window.addEventListener("load", () => {
document.querySelector(".hamburger").addEventListener("click", () => {
document.querySelector(".hamburger--slider").classList.toggle("is-active");
// hide / show other element
const cart = document.querySelector(".rey-headerCart-wrapper");
if (document.querySelector(".hamburger--slider.is-active")) {
cart.style.display = "none";
} else {
cart.style.display = "block";
// apply original display style
// cart.style.display = "inline-block";
// cart.style.display = "flex";
};
});
})
In order to make toggle functions like this more understandable, maintainable and extendable you need to think about your HTML structure.
In your current structure, you have a button that toggles a class on itself. Therefore any element beyond that button that has to change appearance or beaviour has to check which class that button has, or you have to extend the click-event handler in order to add these elements (that's what you did here).
This can get quite messy really fast.
A better approach could be to not toggle a class on the button but on an element that is a common parent to all elements that you want to change the behavior of.
That way anything you ever add to that wrapper already can be manipulated via CSS, without the need of changing your JS.
$('.nav-toggler').on('click', function() {
$('#nav-wrapper').toggleClass('active');
});
.menu, .cart {
padding: 1em;
margin: 2px;
}
.cart {
background: #FFF000;
}
.menu{
background: #F1F1F1;
display: none;
}
#nav-wrapper.active > .menu {
display: block;
}
#nav-wrapper.active > .cart {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="nav-wrapper">
<button class="nav-toggler">Toggle</button>
<div class="menu">My Menu</div>
<div class="cart">My Cart</div>
</div>
I currently have a menu that appears when a mobile button is clicked. When a user clicks off the div, I want the div to hide. Right now, I have an 'X' the user must click to hide the div. How can I accomplish what I'm trying to do? (I believe I included all necessary code - but if I missed something, let me know.)
Thank you in advance!
HTML
<div class="nav-mobile">
Menu Item 1
Menu Item 2
</div>
JS
if (nav-mobile) {
nav-mobile.addEventListener('click', () => {
nav.classList.toggle('active');
});
}
SCSS
(by default is hidden)
.nav-mobile {
visibility: hidden;
}
(when mobile navigation is active, it becomes visible)
&.active {
.nav-mobile {
visibility: visible;
opacity: 1;
h5:not(nav-mobile-title) {
margin: 0;
}
}
}
What I'm trying to do is display two different menus based on an option that is selected.
In my snippet you can see I have option a and option b
.option-a, .option-b {
text-transform: uppercase;
display: inline-block;
}
.option-a:hover, .option-b:hover {
text-decoration: underline;
}
.option-a-content {
display: block;
}
.option-b-content {
display: none;
}
<div class="option-a">
Option A
</div>
<div class="option-b">
Option B
</div>
<br>
<br>
<div class="option-a-content">
This is the contents of A
</div>
option-a-content is set to display: block and option-b-content is set to display: none;
I want to make it so when you click 'Option B' it changes the css of option-a-content to display: none and it to display: block
Edit: for clarification, my issue is that I can get the css to change on click, however when I navigate away from the page, it reverts back to default.
Here's a simple example of storing the state in localstorage:
$(function(){
$(".option-toggle").on("click", function(){
//get the href of the clicked item. This is the id of the element we wish to show
var selectedOptionID=$(this).attr("href");
//hide all option-content elements
$(".option-content").attr("aria-hidden",true);
//show the one we want
$(selectedOptionID).attr("aria-hidden",false);
//remember the active one in localstorage
localStorage.setItem("selectedOptionID", selectedOptionID);
});
//onload, read from local storage and trigger a click on the appropriate option:
var selectedOptionIdFromStorage = localStorage.getItem("selectedOptionID");
if(selectedOptionIdFromStorage){
//trigger a click on the select-option having a href equal to selectedOptionIdFromStorage:
$(".option-toggle[href='"+selectedOptionIdFromStorage+"']").trigger("click");
}
});
https://jsfiddle.net/jvsnphmn/3/
When you enter my website (goerann.com) the dropdown register-box is down by default.
If I click in Register, the register-box toogles it visibility as I want, but it doesn't start hidden by default.
$(document).ready(function() {
$('#signup').click(function() {
$('.signupmenu').slideToggle("fast");
});
});
I want it to only show when you click on it. How can I make this happen?
Here's my jsfiddle (http://jsfiddle.net/bdv2doxr/)
Since you're already using the $(document).ready event, you can hide the menu there:
$(document).ready(function() {
$('.signupmenu').hide();
$('#signup').click(function() {
$('.signupmenu').slideToggle("fast");
});
});
And here is your fiddle updated.
You need to make two changes, both involving the removal of display: block. When you toggle this div, it will make the display block. Therefore, you can initialize it as display: none.
Change this:
<div class="signupmenu" style="display: block;">
to this:
<div class="signupmenu">
And also change this:
.signupmenu {
background-color: #FFF;
display: block;
...
to this:
.signupmenu {
background-color: #FFF;
display: none;
...
Updated fiddle here
To preface, I am utterly incompetent with CSS. However, I have been watching/reading tutorials, and still cannot figure out why my page would render the way it is. I'm hoping that this will be quick and obvious for somebody here.
I want the page to be shaped something like this:
Filter by: CPM Owner: [DROPDOWN] CP: [DROPDOWN] Series: [DROPDOWN]
[APPROVE SELECTED] [REJECT SELECTED]
[there's a table down here, but it isn't relevant to the question]
However, it looks like this when I load the page:
EDIT: I changed the flexbox container to a span instead of a div, and now the page looks like:
There are all kinds of newlines I don't want, and strange spacing being put before the dropdowns. Here are the relevant bits of CSS, JS, and HTML that are making this part of the page:
HTML:
<div id="filters">
<span id="filter_dropdowns_label" class="dropdowns_bar_label">
Filter by:
</span>
<ul id="filter_dropdowns" class="dropdowns_bar">
<!-- populated by changerequest.js -->
</ul>
</div>
<ul id="buttons_top" class="buttons">
<li><input type="button" value="Approve Selected" id="approve_button_top" class="approve_button" onclick="approveSelected()"/></li>
<li><input type="button" value="Reject Selected" id="reject_button_top" class="reject_button" onclick="rejectSelected()"/></li>
</ul>
CSS:
.dropdowns_bar {
margin: 0;
padding: 0;
list-style-type: none;
}
.dropdowns_bar li {
display: inline;
}
.buttons li {
display: inline;
}
.hidden_column {
display: none;
}
#filters {
display: inline;
}
And the JS:
// Create a dropdown
function createDropdown(id, label, data, defaultValue) {
// initialize elements
listEl = document.createElement('li');
flexboxContainerEl = document.createElement('div');
spanEl = document.createElement('span');
// setup dropdown
$(flexboxContainerEl).flexbox(data);
$(flexboxContainerEl).attr('id', id);
if (defaultValue != null) {
$(flexboxContainerEl).setValue(defaultValue);
}
// setup label
$(spanEl).html(label + ':');
// add to document
$(listEl).append(spanEl);
$(listEl).append(flexboxContainerEl);
$('#filter_dropdowns').append(listEl);
}
// Creates and populates the filter dropdowns, selects default if present
function createDropdowns() {
createDropdown('cpm_dropdown', 'CPM Owner', cpmList, cpmDefault);
createDropdown('cp_dropdown', 'CP', cpList, cpDefault);
createDropdown('series_dropdown', 'Series', seriesList, seriesDefault);
}
Sorry, I realize that this question is very much "help, my code's broken, fix it." I am just spending a lot of time looking at CSS tutorials and documents and I cannot find anything resembling my problem.
EDIT: Modified my CSS according to this answer: https://stackoverflow.com/a/17417451/1532702
The page now looks like:
I've tried swapping the .appends, but nothing affects the order on the labels. Obviously, they should be in front of each of the corresponding inputs.
EDIT: I solved my problem by just rewriting that portion as a table instead of a list. Made everything much, much simpler.
EDIT: Ok, better answer I think. Try this:
.dropdowns_bar li {
display: inline-block;
}
.buttons {
display:block;
margin-top:10px;
}
.buttons li {
display: inline-block;
}