Angular Accordion accessibility issues - html

have an issue with the expansion of the accordion containing a list of items using a keyboard.
There is a complication in that all the accordion's data is being populated from an API, so a lot of the solutions that I had seen are hardcoded. For example, using the information found in the article below didn't work as the unique values are hard coded:
https://www.hassellinclusion.com/blog/accessible-accordion-pattern/
I've added in a lot of the appropriate ARIA labels and navigating via tab around it works well as does the screen reader, but I can't get the accordion to expand.
I tried to talking it from another angle by getting the enter button to be interpreted as a click on the element to expand the accordion but got completely lost trying to do such a thing in Angular as inserting Vanilla JS is not as straight forward as it would seem.
Here is the code, it's spread over three components so I've compiled into one and removed some styling classes for legibility:
<div tabindex="0" (keydown.enter)="myFunction()" role="button">
<h2 tabindex="0">
<img>
<i tabindex="0"></i>{{ organiser.name }}
</h2>
//this component displays the selected item. the accessibility on this works fine
<app-selected-area role="region">
</app-selected-area>
</div>
<div class="content">
//this component displays the items that can be selected
<app-skill-item class="item">
<div tabindex="0" role="button">
<h3>{{ skill.name }}</h3>
<button *ngIf="updateable && isSelected()" (click)="select()">
Remove
<span class="screen-reader-only">
{{ skill.name }}
</span>
skill
</button>
<button *ngIf="updateable && !isSelected()" (click)="select()" tabindex="0">
Add
<span class="screen-reader-only">
{{ skill.name }}
</span>
skill
</button>
</app-skill-item>
</div>
Any help or hints would be much appreciated!

Ended up finding the answer for this in the follow question/thread:
Trigger click in Typescript - Property 'click' does not exist on type 'Element'
let element: HTMLElement = document.getElementsByClassName('btn')[0] as HTMLElement;
element.click();
this was the following code that let me manipulate the accordion via the enter key as a click

Related

JAWS reads labels of parent tags before reading first element

I am trying to fix an accessibility defect in an Angular project at work. When a page loads and I start to tab through the page, the first element that is visible in the form is read twice. My code is something like below
<form [formGroup]="form" role="form" attr.aria-label="Edit Form" novalidate>
<div class="form-row">
<div class="col-md-9 col-sm-12 col-lg-9 paddingLR0">
<!-- Hidden code not rendered due to ngIf=false -->
<div role="region" attr.aria-label="Edit button region" *ngIf="viewMode">
<!-- Hidden elements not rendered due to ngIf=false -->
<button *ngIf="isEditable" [disabled]="!canEdit" type="submit" (click)="enableEdit()">
Edit
</button>
<div class="back-header">
<a tabindex=0 (keyup.enter)="back()" (click)="back()" (mouseover)="changeBackIconOnHover('back-region-top')" (mouseout)="changeBackIcon('back-region-top')" id="back-region-top">
<img src="{{pathImg}}/back_black.png"
<span class="margin-left10">Back</span>
</a>
</div>
</div>
</div>
</div>
<!-- More code here -->
</form>
If you see the code the first element that is visible is the Edit button, nested in a div with role as region, which is in turn inside a form with role form. When I tab through the page instead of just reading the button just once Edit button JAWS reads Edit Form form region. Edit Button on first tab, then reads Edit button region. Edit Button. There are no tabindexes on the parent elements. Removing the role attribute and the corresponding labels does not work. How do I only make jaws read the edit button once?
The above problem was occuring because of an empty div which had a tabindex=0.
So in the code snippet above I had a commented line
<!-- Hidden code not rendered due to ngIf=false -->
That referred to multiple divs which are warning, success and error messages related to the form in my actual code. Each of those divs have an ngIf for conditional rendering and a tabindex=0 to make it tab accessible. Except one of those divs simply had a tabindex but no ngIf. So it was always rendered. Like below.
<div tabindex=0>
<div ngIf="condition"> {{errormessage}}
</div>
</div>
If JAWS tabs onto an empty element it reads the previous labels. I removed the outer div which was remaining empty and the problem is solved.

Embedded view not showing when switching between views using ngIf

My component A has a child component B. On start up, A shows up because of conditition in *ngIf. A uses an EmbeddedView show show image thumbnails. A also has a button which when clicked changes the condition of *ngIf and shows B (because B shows on some other condition of *ngIf). If a button in B is pressed then A shows back again.
A's html is something like
<div *ngIf="this.tabType == this.questionTab" id="form-div-question" class="body__div--background">
....
<ng-template #thumbnailTemplate let-context="context">
<div id="{{context.divId}}">
<button id="{{context.buttonId}}" type="button" data-toggle="modal" (click)="showEnlargeImage(context)">
<img id="{{context.imgId}}" src="{{context.imgSrc}}"/>
</button>
<a *ngIf="this.isEditing" href="javascript:void(0)" id="{{context.closeId}}" (click)="deleteThumbnailFromContainer(context)"></a>
</div>
</ng-template>
...
<div id="image-thumbnail-container">
<ng-container #thumbnailContainer ></ng-container>
</div>
</div>
<div *ngIf="this.tabType == this.submittedAnswerTab" id="form-div-submitted-answer" class="body__div--background">
<b-component #bComponent [someInput]="..." (someEventFromB)="switchToQuestionTab($event)"></b-component>
</div>
The EmbeddedView in A is access as follows
#ViewChild("thumbnailContainer",{read:ViewContainerRef})
thumbnailContainerRef:ViewContainerRef;
What I am noticing is that on start up A shows fine including its embedded view. Then B also shows when the button in A is clicked. But when the view switches back to A then the embedded view doesn't show up!! (rest of A shows up correctly). I tried to force recreating the embedded view, I get error that the reference to the container is not defined (i.e.thumbnailContainerRef is undefined).
I also notice that when B is shown, it seem to be initiated everytime but when A is shown, it is not reinitialised everytime (maybe because B is child and A is parent)
Why is the container reference becoming undefined?
Taking inspiration from Angular 2 Show and Hide an element and What is the difference between *ngIf and [hidden]?
I seem to have solved the problem by using [hidden] instead of ngIf of A component.
<div [hidden]= "this.tabType != this.questionTab" ...>
</div>
<div *ngIf="this.tabType == this.submittedAnswerTab" id="form-div-submitted-answer" class="body__div--background">
<b-component #bComponent [someInput]="..." (someEventFromB)="switchToQuestionTab($event)"></b-component>
</div>
It seems that when '*ngIf' is false for A, Angular removes the embedded view and it doesn't get created again which seem to to be the reason why the ViewContainerRef is undefined when I switch back to A. Using [hidden] just hides A. Maybe there is code smell in my implementation but hey, I am still learning!

Screen Reader not reading <h1> on entering the screen

My HTML is as below (an extracted portion):
<h1><span>Main Menu</span></h1>
<div>
<button tabindex="0">New Customer</button><br>
<button tabindex="0">Existing Customer</button>
</div>
I am using NVDA. When the user enters the screen, the focus is on the first button. NVDA reads the text on the first button(New Customer). It skips the heading (h1). I assumed it should read the h1 tags without any of the aria tags like aria-label. Am I wrong in my assumption? What do I need to do to make this work?
If you want a group label around your buttons that will be read when focus moves into the group, then use role='group' and aria-label on the container. The group label will be read when you TAB forward into the group ("New Customer") and when you TAB backwards into the group ("Existing Customer").
<div role="group" aria-label="Main Menu">
<div>
<button>New Customer</button><br>
<button tabindex="0">Existing Customer</button>
</div>
</div>
The above example does not visually show the text "Main Menu". You can still have that displayed if you want using:
<div role="group" aria-label="Main Menu">Main Menu
<div>
<button>New Customer</button><br>
<button>Existing Customer</button>
</div>
</div>
You could also use a <nav> element, but with your brief snippet, it doesn't sound like the buttons are for navigation so I wouldn't recommend this but wanted to show it for completeness:
<nav aria-label="Main Menu">
<div>
<button>New Customer</button><br>
<button>Existing Customer</button>
</div>
</nav>
If you are using a script to send focus to the first button, screenreader software will skip over anything in the markup before that and go straight to the button. You should be able to use the arrow keys to go back up to hear it.
Generally it’s best practise to let the user control focus with clicks/taps instead of automating it for them on page load.

How to open/close a angular-material menu

I recently started using angular-material and am struggling/unsure about opening/closing a mat-menu... I see in the examples on the angular-material documentation site that they assign an id to the menu and then apply a directive to the button that is used to toggle the menu. e.g. [matMenuTriggerFor]="menu"
How can I go about writing a directive that does that? I'm not sure how to pass a reference to a specific menu to a directive that then calls the toggle() method on the DOM element with that id?
The following code produces the errors:
Can't bind to 'matMenuTriggerFor' since it isn't a known property of 'button'.
There is no directive with "exportAs" set to "matMenu".
My code:
<li>
<button mat-icon-button [matMenuTriggerFor]="stockSystemMenu">
<mat-icon class="sn-item">
<i class="material-icons">archive</i>
</mat-icon>
</button>
<span class="sn-item" (click)="toggleMenu(stockSystemMenu)">Stok System</span>
<mat-menu #stockSystemMenu="matMenu">
<button mat-menu-item>
<mat-icon>
<i class="material-icons">chevron_right</i>
</mat-icon>
<span>Service 1</span>
</button>
</mat-menu>
</li>
There is confusion because Material introduced a breaking change as I understand it.. See material 2 Changelog - Breaking Changes
Starting with Material 2.0.0-beta.12. Use mat instead of md-*.. Seems only some of the docs at material.angular.io are updated with mat. Specifically, if you click view source and see md, I believe they have yet to replace it with mat.
So either update to Material 2.0.0-beta.12 and use mat-*, or use md-*.
"Your code is correct, you don't need to write matMenuTriggerFor directive, it is part of the API, make sure you have imported the MatMenuModule, MatButtonModule, and MatIconModule into your app module." - from comments

Access Aurelia custom attribute function from within nested HTML elements

I have a View/View-Model pair that implements a popover custom attribute. My specific goals include dismissing the popover upon both button click within the popover itself, and from clicking anywhere else on the page.
I can get my doSomething() VM function to work on the element level in the view, but not within an attribute containing an element. I've explained the issue further in the code comments. I'd appreciate some guidance. Thanks!
blog.html
<template>
<require from="../popover/popover"></require>
...
<!-- doSomething() works here! -->
<button type='button' click.trigger='doSomething()'>ClickMe</button>
<!-- doSomething() does not work here! -->
<!-- `click.trigger`/`click.delegate` does not trigger anything, while `onclick` shows
"Uncaught ReferenceError: doSomething is not defined" -->
<span class="glyphicon glyphicon-star" popover="title.bind: blogpost.title; placement.bind: 'top'"
data-content='<button type="button" click.trigger="doSomething()">ClickMe</button>' ></span>
...
</template>
blog.ts
...
doSomething() {
console.log('doing something');
}
popover.ts
...
bind() {
$(this.element).popover({
title: this.title,
placement: this.placement,
content: this.content,
trigger: 'click',
html: true
});
}
I recently went through the same problem. I'm currently working on a Bootstrap port for Aurelia, it is not done yet, and I haven't written any documentation, but the popover is already implemented.
You are more than welcome to take a look:
https://github.com/tochoromero/aurelia-bootstrap/tree/master/src/popover
The way you are trying to implement the popover is going to be very complicated (if even possible), things are going to get messy with the scopes. If you only had text then it would be fine, but you basically want to be able to put anything in the popover and have it bound to the right View-Model.
The way I solved this was having a couple of Custom Elements that represent the popover, I have AubsCustomPopover, AubsPopoverTitle and AubsPopoverContent.
With this 3 custom elements you will create the bootstrap markup for the popover, and because you are adding them directly in the view they will have the right View-Model, and you can do whatever you want inside them.
And then, there is a custom attribute, this is AubsPopover, this is the one that will be in charge of showing and hiding the custom attribute depending on the trigger action you specify (hover, click, focus, outsideClick).
The code using it looks something like this:
<aubs-custom-popover model.bind="customPopover">
<aubs-popover-title>
This is my awesome title <i class="fa fa-star"></i>
</aubs-popover-title>
<aubs-popover-content>
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
<input type="password" class="form-control" text.bind="password"
placeholder="Password">
</div>
<button class="btn btn-default" click.delegate="isOpen = false">
Close
</button>
</aubs-popover-content>
</aubs-custom-popover>
<button class="btn btn-primary"
aubs-popover="custom-model.bind: customPopover;
trigger: outsideClick;
position: bottom;
open.bind:isOpen">
Custom Popover
</button>
As I said the Popover is fully implemented but I haven't written any documentation, I want to have a couple of more components before I do that.
If you want to use it, I can give you some help setting it up.