Skip to content
This repository has been archived by the owner on May 29, 2019. It is now read-only.

Commit

Permalink
fix(modal): dynamically fetch elements
Browse files Browse the repository at this point in the history
- Dynamically fetch elements for determination of which is the first/last to tab/shift+tab to
  • Loading branch information
wesleycho committed Mar 14, 2016
1 parent bb36e40 commit 6ce9677
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 32 deletions.
55 changes: 23 additions & 32 deletions src/modal/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,6 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
};

//Modal focus behavior
var focusableElementList;
var focusIndex = 0;
var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' +
'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
'iframe, object, embed, *[tabindex], *[contenteditable=true]';
Expand Down Expand Up @@ -391,22 +389,23 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
break;
}
case 9: {
$modalStack.loadFocusElementList(modal);
var list = $modalStack.loadFocusElementList(modal);
var focusChanged = false;
if (evt.shiftKey) {
if ($modalStack.isFocusInFirstItem(evt) || $modalStack.isModalFocused(evt, modal)) {
focusChanged = $modalStack.focusLastFocusableElement();
if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) {
focusChanged = $modalStack.focusLastFocusableElement(list);
}
} else {
if ($modalStack.isFocusInLastItem(evt)) {
focusChanged = $modalStack.focusFirstFocusableElement();
if ($modalStack.isFocusInLastItem(evt, list)) {
focusChanged = $modalStack.focusFirstFocusableElement(list);
}
}

if (focusChanged) {
evt.preventDefault();
evt.stopPropagation();
}

break;
}
}
Expand Down Expand Up @@ -476,8 +475,6 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])

openedWindows.top().value.modalDomEl = angularDomEl;
openedWindows.top().value.modalOpener = modalOpener;

$modalStack.clearFocusListCache();
};

function broadcastClosing(modalWindow, resultOrReason, closing) {
Expand Down Expand Up @@ -524,16 +521,17 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
}
};

$modalStack.focusFirstFocusableElement = function() {
if (focusableElementList.length > 0) {
focusableElementList[0].focus();
$modalStack.focusFirstFocusableElement = function(list) {
if (list.length > 0) {
list[0].focus();
return true;
}
return false;
};
$modalStack.focusLastFocusableElement = function() {
if (focusableElementList.length > 0) {
focusableElementList[focusableElementList.length - 1].focus();

$modalStack.focusLastFocusableElement = function(list) {
if (list.length > 0) {
list[list.length - 1].focus();
return true;
}
return false;
Expand All @@ -549,32 +547,25 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
return false;
};

$modalStack.isFocusInFirstItem = function(evt) {
if (focusableElementList.length > 0) {
return (evt.target || evt.srcElement) === focusableElementList[0];
$modalStack.isFocusInFirstItem = function(evt, list) {
if (list.length > 0) {
return (evt.target || evt.srcElement) === list[0];
}
return false;
};

$modalStack.isFocusInLastItem = function(evt) {
if (focusableElementList.length > 0) {
return (evt.target || evt.srcElement) === focusableElementList[focusableElementList.length - 1];
$modalStack.isFocusInLastItem = function(evt, list) {
if (list.length > 0) {
return (evt.target || evt.srcElement) === list[list.length - 1];
}
return false;
};

$modalStack.clearFocusListCache = function() {
focusableElementList = [];
focusIndex = 0;
};

$modalStack.loadFocusElementList = function(modalWindow) {
if (focusableElementList === undefined || !focusableElementList.length) {
if (modalWindow) {
var modalDomE1 = modalWindow.value.modalDomEl;
if (modalDomE1 && modalDomE1.length) {
focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector);
}
if (modalWindow) {
var modalDomE1 = modalWindow.value.modalDomEl;
if (modalDomE1 && modalDomE1.length) {
return modalDomE1[0].querySelectorAll(tababbleSelector);
}
}
};
Expand Down
46 changes: 46 additions & 0 deletions src/modal/test/modal.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,52 @@ describe('$uibModal', function() {

initialPage.remove();
});

it('should change focus to next proper element when DOM changes and tab is pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();

open({
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
'<button id="tab-focus-button">Open me!</button>',
keyboard: false
});
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);

$('#tab-focus-link3').focus();
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');

$('#tab-focus-button').remove();
triggerKeyDown(angular.element(document.activeElement), 9, false);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');

initialPage.remove();
});

it('should change focus to next proper element when DOM changes and shift+tab is pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();

open({
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
'<button id="tab-focus-button">Open me!</button>',
keyboard: false
});
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);

$('#tab-focus-link1').focus();
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');

$('#tab-focus-button').remove();
triggerKeyDown(angular.element(document.activeElement), 9, true);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');

initialPage.remove();
});
});

describe('default options can be changed in a provider', function() {
Expand Down

0 comments on commit 6ce9677

Please sign in to comment.