在模态内捕获焦点,使 ADA 在 Angular 10 中兼容
Trapping focus inside modal making ADA compliant in Angular 10
我想办法使我现有的模式弹出窗口与 ADA 兼容,以便键盘 Tab 的焦点保留在这个使用用户定义的模式容器下 handleTabKeyFocus()
。但是 querySelector
选择器总是 returns null with querySelectorAll
结果是
ERROR TypeError: Cannot read property 'querySelectorAll' of null
<!-- Modal Popup for Candidate Information -->
<ng-template #candidateInfoTemplate>
<div class="modal-header" id="xc">
<h5 class="modal-title float-left"> <b>Candidate Information </b></h5>
<button type="button" class="close pull-right" aria-label="Close" (click)="closeModalWithFocus(candidateDetailTCInfo.examUserID)" title="CLOSE">
<span aria-hidden="true">x</span>
</button>
</div>
<!-- Modal body -->
<div class="modal-body backgroundColorWhite" id="modalBody">
<div class="row mb-1 backgroundColorgray" *ngIf="!disableColumn">
<div class="col-sm-5">
Candidate Id
</div>
<div class="col-sm-1">
:
</div>
<div class="col-sm-6">
{{candidateDetailTCInfo?.loginName}}
</div>
</div>
<div class="clearfix"></div>
<!-- Modal Footer-->
<div style="text-align:center" ModFocus>
<button title="{{ResourceKeys.sbclose}}" type="button" class="btn btn-light btn_mng1 mr5" aria-label="Close" (click)="closeModalWithFocus(candidateDetailTCInfo.examUserID)">
{{ResourceKeys.sbclose}}
</button>
</div>
</div>
</ng-template>
@HostListener('document:keydown', ['$event'])
handleTabKeyFocus(e) {
if (e.keyCode === 9) {
let focusable = document.querySelector('#candidateInfoTemplate').querySelectorAll('input,button,select,textarea,a,[tabindex]:not([tabindex="-1"])');
// It returns null
if (focusable.length) {
let first = focusable[0];
let last = focusable[focusable.length - 1];
let shift = e.shiftKey;
if (shift) {
if (e.target === first) { // shift-tab pressed on first input in dialog
(last as HTMLElement).focus();
e.preventDefault();
}
} else {
if (e.target === last) { // tab pressed on last input in dialog
(first as HTMLElement).focus();
e.preventDefault();
}
}
}
}
}
<div (click)="opencandidateDetailsModal(candidateInfoTemplate, detail)" >
<a href="#" onclick="return false;" [attr.id]="'candidateNameFocus_' + detail.examUserID" > {{detail.candidateName}} </a>
</div>
opencandidateDetailsModal(template: TemplateRef<any>, candObj: any) {
try {
this.getCandidateDetailTestInfo(candObj);
this.modalRef = this.modalService.show(template, this.modalPopupconfig);
} catch (error) {
console.log('Method: opencandidateDetailsModal', error);
}
}
closeModalWithFocus(examUserID: any) {
try {
this.modalRef.hide();
const candidateFocusElement: HTMLElement = (<HTMLElement>document.getElementById('candidateNameFocus_' + examUserID));
if (candidateFocusElement !== null) {
candidateFocusElement?.focus();
}
const previousActiveElement = document.activeElement;
if (document.body.contains(previousActiveElement)) {
(previousActiveElement as HTMLElement)?.focus();
}
} catch (error) {
console.log('Method: closeModalWithFocus', error);
}
}
内置 ngx-bootstrap
使用给定 id 的 ng-template 作为 candidateInfoTemplate
let focusable = document.querySelector('#candidateInfoTemplate').querySelectorAll('input,button,select,textarea,a,[tabindex]:not([tabindex="-1"])');
在打开模态弹出窗口之前发生键盘事件时抛出错误
Cannot read property 'querySelectorAll' of null
我们如何在 ngx-bootstrap
的模态弹出窗口中保持焦点?...
#candidateInfoTemplate
不是 id,它是对 <ng-template>
的引用。您可以使用 @ViewChild 装饰器访问它。
但是对于上述问题,您可以通过在带有 id
的 <ng-template>
标签内添加额外的 <div>
来解决这个问题
<ng-template #candidateInfoTemplate>
<div id="candidateInfoTemplate">
...
</div>
</ng-template>
通过指定包含在 <div>
标签中的 id
属性解决了
使用 ElementRef
涉及安全风险:允许直接访问 DOM 会使您的应用程序更容易受到 XSS 攻击。
所以使用 <HTMLElement>document.querySelector()
而不是 this.elementRef.nativeElement.querySelector()
some.html
<!-- Modal Popup for Candidate Information -->
<ng-template #candidateInfoTemplate>
<div id="candidateInfoDialog" aria-modal="true">
</div>
</ng-template>
<!-- Modal Popup for Confirmation -->
<ng-template #confirmationTemplate>
<div id="confirmationDialog" aria-modal="true">
</div>
</ng-template>
some.component.ts
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
@Component({
providers: [BsModalService]
})
export class SomeComponent implements OnInit {
modalRef: BsModalRef;
config = {
backdrop: true,
ignoreBackdropClick: true,
hasAllCheckBox: false,
hasFilter: false,
hasCollapseExpand: true,
decoupleChildFromParent: true,
maxHeight: 558
};
constructor( private modalService: BsModalService) { }
@HostListener('document:keydown', ['$event'])
handleAllModalDialogs(event: any) {
this.handleTabKeyModal(event, '#candidateInfoDialog', 'input,button,select,textarea,a,[tabindex]:not([tabindex="-1"])');
this.handleTabKeyModal(event, '#confirmationDialog', 'input,button,select,textarea,a,[tabindex]:not([tabindex="-1"])');
}
handleTabKeyModal(e, modelId: string, tagsList: string) {
try {
if (e.keyCode === 9) {
const focusByID = <HTMLElement>document.querySelector(modelId);
if(focusByID != null) {
const focusable = focusByID.querySelectorAll(tagsList);
if (focusable.length) {
let first = focusable[0];
let last = focusable[focusable.length - 1];
let shift = e.shiftKey;
if (shift) {
if (e.target === first) { // shift-tab pressed on first input in dialog
(last as HTMLElement)?.focus();
e.preventDefault();
}
} else {
if (e.target === last) { // tab pressed on last input in dialog
(first as HTMLElement)?.focus();
e.preventDefault();
}
}
}
}
}
} catch (ex) {
console.log('Method: handleTabKeyModal ' + ex.message)
}
}
}
我想办法使我现有的模式弹出窗口与 ADA 兼容,以便键盘 Tab 的焦点保留在这个使用用户定义的模式容器下 handleTabKeyFocus()
。但是 querySelector
选择器总是 returns null with querySelectorAll
结果是
ERROR TypeError: Cannot read property 'querySelectorAll' of null
<!-- Modal Popup for Candidate Information -->
<ng-template #candidateInfoTemplate>
<div class="modal-header" id="xc">
<h5 class="modal-title float-left"> <b>Candidate Information </b></h5>
<button type="button" class="close pull-right" aria-label="Close" (click)="closeModalWithFocus(candidateDetailTCInfo.examUserID)" title="CLOSE">
<span aria-hidden="true">x</span>
</button>
</div>
<!-- Modal body -->
<div class="modal-body backgroundColorWhite" id="modalBody">
<div class="row mb-1 backgroundColorgray" *ngIf="!disableColumn">
<div class="col-sm-5">
Candidate Id
</div>
<div class="col-sm-1">
:
</div>
<div class="col-sm-6">
{{candidateDetailTCInfo?.loginName}}
</div>
</div>
<div class="clearfix"></div>
<!-- Modal Footer-->
<div style="text-align:center" ModFocus>
<button title="{{ResourceKeys.sbclose}}" type="button" class="btn btn-light btn_mng1 mr5" aria-label="Close" (click)="closeModalWithFocus(candidateDetailTCInfo.examUserID)">
{{ResourceKeys.sbclose}}
</button>
</div>
</div>
</ng-template>
@HostListener('document:keydown', ['$event'])
handleTabKeyFocus(e) {
if (e.keyCode === 9) {
let focusable = document.querySelector('#candidateInfoTemplate').querySelectorAll('input,button,select,textarea,a,[tabindex]:not([tabindex="-1"])');
// It returns null
if (focusable.length) {
let first = focusable[0];
let last = focusable[focusable.length - 1];
let shift = e.shiftKey;
if (shift) {
if (e.target === first) { // shift-tab pressed on first input in dialog
(last as HTMLElement).focus();
e.preventDefault();
}
} else {
if (e.target === last) { // tab pressed on last input in dialog
(first as HTMLElement).focus();
e.preventDefault();
}
}
}
}
}
<div (click)="opencandidateDetailsModal(candidateInfoTemplate, detail)" >
<a href="#" onclick="return false;" [attr.id]="'candidateNameFocus_' + detail.examUserID" > {{detail.candidateName}} </a>
</div>
opencandidateDetailsModal(template: TemplateRef<any>, candObj: any) {
try {
this.getCandidateDetailTestInfo(candObj);
this.modalRef = this.modalService.show(template, this.modalPopupconfig);
} catch (error) {
console.log('Method: opencandidateDetailsModal', error);
}
}
closeModalWithFocus(examUserID: any) {
try {
this.modalRef.hide();
const candidateFocusElement: HTMLElement = (<HTMLElement>document.getElementById('candidateNameFocus_' + examUserID));
if (candidateFocusElement !== null) {
candidateFocusElement?.focus();
}
const previousActiveElement = document.activeElement;
if (document.body.contains(previousActiveElement)) {
(previousActiveElement as HTMLElement)?.focus();
}
} catch (error) {
console.log('Method: closeModalWithFocus', error);
}
}
内置 ngx-bootstrap
使用给定 id 的 ng-template 作为 candidateInfoTemplate
let focusable = document.querySelector('#candidateInfoTemplate').querySelectorAll('input,button,select,textarea,a,[tabindex]:not([tabindex="-1"])');
在打开模态弹出窗口之前发生键盘事件时抛出错误
Cannot read property 'querySelectorAll' of null
我们如何在 ngx-bootstrap
的模态弹出窗口中保持焦点?...
#candidateInfoTemplate
不是 id,它是对 <ng-template>
的引用。您可以使用 @ViewChild 装饰器访问它。
但是对于上述问题,您可以通过在带有 id
的<ng-template>
标签内添加额外的 <div>
来解决这个问题
<ng-template #candidateInfoTemplate>
<div id="candidateInfoTemplate">
...
</div>
</ng-template>
通过指定包含在 <div>
标签中的 id
属性解决了
使用 ElementRef
涉及安全风险:允许直接访问 DOM 会使您的应用程序更容易受到 XSS 攻击。
所以使用 <HTMLElement>document.querySelector()
而不是 this.elementRef.nativeElement.querySelector()
some.html
<!-- Modal Popup for Candidate Information -->
<ng-template #candidateInfoTemplate>
<div id="candidateInfoDialog" aria-modal="true">
</div>
</ng-template>
<!-- Modal Popup for Confirmation -->
<ng-template #confirmationTemplate>
<div id="confirmationDialog" aria-modal="true">
</div>
</ng-template>
some.component.ts
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
@Component({
providers: [BsModalService]
})
export class SomeComponent implements OnInit {
modalRef: BsModalRef;
config = {
backdrop: true,
ignoreBackdropClick: true,
hasAllCheckBox: false,
hasFilter: false,
hasCollapseExpand: true,
decoupleChildFromParent: true,
maxHeight: 558
};
constructor( private modalService: BsModalService) { }
@HostListener('document:keydown', ['$event'])
handleAllModalDialogs(event: any) {
this.handleTabKeyModal(event, '#candidateInfoDialog', 'input,button,select,textarea,a,[tabindex]:not([tabindex="-1"])');
this.handleTabKeyModal(event, '#confirmationDialog', 'input,button,select,textarea,a,[tabindex]:not([tabindex="-1"])');
}
handleTabKeyModal(e, modelId: string, tagsList: string) {
try {
if (e.keyCode === 9) {
const focusByID = <HTMLElement>document.querySelector(modelId);
if(focusByID != null) {
const focusable = focusByID.querySelectorAll(tagsList);
if (focusable.length) {
let first = focusable[0];
let last = focusable[focusable.length - 1];
let shift = e.shiftKey;
if (shift) {
if (e.target === first) { // shift-tab pressed on first input in dialog
(last as HTMLElement)?.focus();
e.preventDefault();
}
} else {
if (e.target === last) { // tab pressed on last input in dialog
(first as HTMLElement)?.focus();
e.preventDefault();
}
}
}
}
}
} catch (ex) {
console.log('Method: handleTabKeyModal ' + ex.message)
}
}
}