I'm currently in the process of developing a website that has to match AA accessibility standards. I have a custom-built dropdown being a part of a form and can't figure out how to label it for screen-readers. Sadly I can't use a native select dropdown (despite being accessible, the website is rather design heavy and it was the best way to apply custom styling to this), so can't use a regular label tag. So far I've used the below, but I believe that it's not the correct approach.
<form>
(...) other inputs
<ul class="dropdown" role="listbox" aria-labelledby="location" aria-multiselectable="false" aria-expanded="false" tabindex="0">
<li class="first" aria-labelledby="location">
<span>Location</span>
</li>
<li tabindex="-1" role="option" aria-selected="false">Location1</li>
<li tabindex="-1" role="option" aria-selected="false">Location2</li>
<li tabindex="-1" role="option" aria-selected="false">Location3</li>
</ul>
</form>
Just to describe the behaviour - this is a regular dropdown with only li.first visible initially (on click/enter all the fields become visible and the value can be chosen). Once an option is picked, aria-selected is set to true and the value is replacing the Location text in span.
Could anyone suggest a better solution for this? My biggest concern at the moment is possibly the incorrect usage of aria-labelledby, but I don't know what should be the right option.
Thanks,
E.
Edit: JS + jQuery
$('.dropdown').on('click', function () {
$(this).toggleClass('opened');
if ($(this).attr('aria-expanded') === 'false') {
$(this).attr('aria-expanded', 'true');
} else {
$(this).attr('aria-expanded', 'false');
}
})
$('.dropdown li').on('click', function () {
if (!$(this).hasClass('first')) {
var text_drop = $(this).html();
$(this).parent().find('span').html(text_drop);
$(this).parent().children().not('.first').attr('aria-selected', 'false')
$(this).attr('aria-selected', true);
$('.dropdown').animate({scrollTop: 0}, 200);
}
});
The aria-labelledby should refer to the id of an existing element. You have a problem with your label being inside the object itself. Something like the following would make more sense.
<form>
(...) other inputs
<div id="location" aria-expanded="false" aria-owns="listchoices">Location</div>
<ul class="dropdown" role="listbox" aria-labelledby="location"
aria-multiselectable="false" tabindex="0">
<li tabindex="-1" role="option" aria-selected="false">Location1</li>
<li tabindex="-1" role="option" aria-selected="false">Location2</li>
<li tabindex="-1" role="option" aria-selected="false">Location3</li>
</ul>
</form>
Without the javascript part, I can't give you more help
Related
We upgraded from Angular 4 to Angular 8.1 and a lot of our drop downs are broken. From what I can tell they all contain these two style classes: the class js-dropdown and js-dropdown-menu. We can't find where these style classes are coming from or how they work. It's hard to search these terms on google because there's no way to have a must-include for hyphens, that I know of. Here's an example of the html:
<div class="select-wrapper" id="searchOption">
<li class="dropdown nav__item is-parent" tabindex="0" style="outline: 0" (blur)="closeDropdown($event)">
<div class="select-dropdown js-dropdown">
<span class="selection">{{ searchType }}</span>
<i class="nav__icon nav__icon--dropdown"></i>
</div>
<ul class="details-search nav__menu js-dropdown-menu">
<li (click)="optionSelected($event, 1)">
<a class="nav__link">Option 1</a>
</li>
<li (click)="optionSelected($event, 2)">
<a class="nav__link">Option 2</a>
</li>
<li (click)="optionSelected($event, 3)">
<a class="nav__link">Option 3</a>
</li>
</ul>
</li>
</div>
Does anyone have any insight to the class js-dropdown and js-dropdown-menu and how to fix them after this upgrade?
Update: so i think i found out where the js-dropdown style class comes from.... it doesn't come from any style... it's just used as a label and component.js looks for that label to show or hide it. The now is that component.js function isn't getting called. Anyone know how to fix this?
$('#app-container').on('click', '.js-dropdown, .js-dropdown-menu > *', function(event){
event.preventDefault();
var that = this;
var parent = $(this).closest('.is-parent');
//is our dropdown open?
if(!parent.hasClass('is-open')){
//... then open it.
parent.addClass('is-open');
//handle clicking away
$(document).one('click', function closeMenu(docEvent){
//if the parent does not contain the clicked element...
if($(parent).has(docEvent.target).length === 0) {
//close it.
parent.removeClass('is-open');
} else {
// else, set up another listener to check the next user click.
$(document).one('click', closeMenu);
}
});
} else {
// ...else we close it.
parent.removeClass('is-open');
}
event.stopPropagation();});
Figured it out. We were not loading a components.js file (as well as other scripts) in the angular.json file. Our previous version of angular did not contain an angular.json file.
I am not able to click on the link nestled inside a list tag.
Here is the HTML code:
<div class="sideBarContent" ng-include="'routes/sidebar/sidebar.tpl.html'">
<div id="innerSidebarContent" ng-controller="SidebarController">
<div>
<ul class="menuItems bounceInDown">
<li id="menuHome" class="" ui-sref="home" ng-click="closeMobileMenu()" href="/home/">
<li id="menuConfigurator" ui-sref="configurator" ng-click="closeMobileMenu()" href="/configurator/">
<span class="menuIcon regularImage blueHighlight activated icon-selectAndTailor"></span>
<span class="menuIcon icon-selectAndTailor_active activeImage">
<p class="mainMenuLabel multiLine">Select & Tailor Methods</p>
</li>
I tried all these ways to locate the text and click on it:
describe('Test objects in /configurator/ route', function() {
it('Click on select and tailor banner icon', function(){
//element(by.css('ul.menuItems > li[href=/configurator/]')).click();
//element(by.className('menuIcon icon-selectAndTailor_active activeImage')).click();
//element(by.css("li[#id='menuConfigurator' and #href='/configurator/']")).click();
//element(by.id('menuConfigurator')).click();
//element(by.xpath("//div[#class='sideBarContent']/p")).click();
//element(by.css("#menuConfigurator > p")).click();
//element(by.partialLinkText('Select & Tailor Methods')).click();
element(by.linkText("Select & Tailor Methods")).click();
console.log('in the configspec ...');
})});
Can someone help me resolve this?
Just had the same issue.
It turned out that wrapping the list in a < div > block was the problem.
Once the list was moved to be outside any < div > block the < a > tags worked.
li can not have href attribute
Use
<li id="menuHome" class="" ui-sref="home" ng-click="closeMobileMenu()"></li>
Or
<li id="menuHome" class="" ui-sref="home" ng-click="closeMobileMenu()" href="/home/"></li>
Instead of
<li id="menuHome" class="" ui-sref="home" ng-click="closeMobileMenu()" href="/home/"></li>
According to html this is not link.
Select it using other selectors:
element(by.className("multiLine")).click();
element(by.css(".mainMenuLabel.multiLine")).click();
element(by.css("[class='mainMenuLabel multiLine']")).click();
element(by.xpath(".//p[#class='mainMenuLabel multiLine']")).click();
I hope this is a good question and I am not just missing something total simple. I am very new to Angular 2 and I am always into saving code lines and time :)
I want to change the active css class of my tabs (I dont want to use router!) and I ended up with something like this:
activeTab: string;
switchActiveTab(newTab: string) {
this.activeTab = newTab;
}
<div class="buttonLine">
<ul class="nav nav-pills">
<li role="presentation" [ngClass]="{'active': activeTab === 'Example Tab 1'}" (click)="switchActiveTab('Example Tab 1');">
<a>Example Tab 1</a>
</li>
<li role="presentation" [ngClass]="{'active': activeTab === 'Example Tab 2'}" (click)="switchActiveTab('Example Tab 2');">
<a>Example Tab 2</a>
</li>
</ul>
</div>
So I had to declare the string value "Example Tab 1" three times in my HTML. This is pretty annoying, especially when I would have 5 or more tabs here.
Is it possible to avoid reapeating the expression "Example Tab 1" three times in my HTML? Or is it possible to do this kind of stuff in a more elegant way?
Method 1
To simplify the template code, you can declare the list of tabs in the component class:
public tabList: Array<string> = ["Example Tab 1", "Example Tab 2"];
and generate the li elements with the *ngFor directive:
<li *ngFor="let tab of tabList" [ngClass]="{'active': activeTab === tab}" (click)="switchActiveTab(tab);" role="presentation">
<a>{{tab}}</a>
</li>
Method 2
To keep the code more declarative, each item could refer to itself with a template reference variable instead of using the tab caption (as illustrated in this plunker):
<div class="buttonLine">
<ul class="nav nav-pills">
<li #tab1 [ngClass]="{'active': activeTab === tab1}" (click)="switchActiveTab(tab1);" role="presentation">
<a>Example Tab 1</a>
</li>
<li #tab2 [ngClass]="{'active': activeTab === tab2}" (click)="switchActiveTab(tab2);" role="presentation">
<a>Example Tab 2</a>
</li>
</ul>
</div>
The code would be modified accordingly:
activeTab: HTMLElement;
switchActiveTab(newTab: HTMLElement) {
this.activeTab = newTab;
}
how to select croissant (the one with class 'selected' ) to $scope.selected variable in angular ?
<ul id='ulsel' placement="top-left" style="max-height: 154px;">
<li value="Apple fritter" class="" tabindex="-1">Apple fritter</li>
<li value="Croissant" tabindex="-1" class="selected">Croissant</li>
<li value="Donut" tabindex="-1" class="">Donut</li>
<li value="Financier" tabindex="-1" class="">Financier</li>
<li value="Jello" tabindex="-1">Jello</li><li value="Madeleine" tabindex="-1">Madeleine</li>
<li value="Pound cake" tabindex="-1">Pound cake</li>
<li value="Pretzel" tabindex="-1">Pretzel</li>
<li value="Sfogliatelle" tabindex="-1">Sfogliatelle</li></ul>
Your markup is very strange, li's having values attributes and using a class as selected rather then a input is an unusually approach. Regardless you can use the below:
$scope.selected = $('#ulsel li.selected').html();
This just gets the text inside the li with the selected class.
I have used the text inside the element rather then the value attribute you have put on, as I'm not sure how supported that's going to be
Agreed with atmd. I'd instead have Angular build the li's with ng-repeat, then you have much more control instead of relying on jQuery to parse.
Here's another way to get the value with jQuery:
$scope.selected = $("#ulsel").find('.selected').attr('value');
Works in Chrome, didn't test further.
I am trying to put html in the bootstrap popover and the content shows nicely but when I click on my links inside the popover it just goes to the parent 'OpenRecent' method on the anchor tag that triggers the popover. You can see below in the data-content html the different ways I try to call the function I want. How can I achieve this?
<ul id="RecentSearches" class="nav nav-tabs nav-stacked well" data-bind="foreach: CurrentItems">
<li>
<div>
<input type="checkbox" class="checkbox inline" data-bind="value: $data.TransId,
checked: $root.ItemsSelected"/>
<a href="#"
data-bind="text: $data.Description + '-' + $data.County,
attr:
{
id: $data.TransId,
'data-title': $data.Description
},
click: $root.OpenRecent"
data-trigger="hover"
rel="popover"
data-html="true"
data-content="<a data-bind='click: $parent.Print.bind($data)'>Print</a> |
<a data-bind='click: app.RecentSearches.Print.bind($data)'>Print 2</a>
<a data-bind='click: $parent.Email'>Email</a> |
<a data-bind='click: $parent.SaveToPDF'>PDF</a> |
<a data-bind='click: $parent.SavetToCSV'>CSV</a>">
</a>
</div>
</li>
</ul>
The HTML is your data-content will not have been bound by Knockout, as it was added dynamically later.
One option is to add a way to identify the links (class) and then use an unobstrusive approach to adding an event handler.
$("ul").on("click", ".print", function(event) {
var context = ko.contextFor(this);
context.$root.Print(context.$data);
event.preventDefault();
});
I am not sure why your OpenRecent function is getting called. It appears to me that bootstrap creates the popover outside of the element that it is triggered on. So, this would not be a parent of the popover. If you are still having an issue with that part, then maybe you could get it working in jsFiddle.
Here is a fiddle with the unobtrusive handler: http://jsfiddle.net/rniemeyer/Edww3/