Commit 5b5b4eb4 by Christine Lytwynec

Merge pull request #11393 from edx/clytwynec/ac-238

Improve accessibility for dashboard course settings dropdown
parents 2c788133 5ef52633
......@@ -461,7 +461,7 @@ class AccountSettingsA11yTest(AccountSettingsTestMixin, WebAppTest):
self.visit_account_settings_page()
self.account_settings_page.a11y_audit.config.set_rules({
'ignore': [
'link-href', # TODO: AC-233, AC-238
'link-href', # TODO: AC-233
],
})
self.account_settings_page.a11y_audit.check_for_accessibility_errors()
......@@ -232,11 +232,4 @@ class LmsDashboardA11yTest(BaseLmsDashboardTest):
"""
course_listings = self.dashboard_page.get_course_listings()
self.assertEqual(len(course_listings), 1)
self.dashboard_page.a11y_audit.config.set_rules({
"ignore": [
'link-href', # TODO: AC-238
],
})
self.dashboard_page.a11y_audit.check_for_accessibility_errors()
var edx = edx || {};
(function ($) {
'use strict';
edx.dashboard = edx.dashboard || {};
edx.dashboard.dropdown = {};
// Generate the properties object to be passed along with business intelligence events.
edx.dashboard.dropdown.toggleCourseActionsDropdownMenu = function (event) {
// define variables for code legibility
var dashboardIndex = $(event.currentTarget).data().dashboardIndex,
dropdown = $('#actions-dropdown-' + dashboardIndex),
dropdownButton = $('#actions-dropdown-link-' + dashboardIndex),
ariaExpandedState = (dropdownButton.attr('aria-expanded') === 'true'),
menuItems = dropdown.find('a');
var catchKeyPress = function(object, event) {
// get currently focused item
var focusedItem = $(':focus');
// get the index of the currently focused item
var focusedItemIndex = menuItems.index(focusedItem);
// var to store next focused item index
var itemToFocusIndex;
// if space or escape key pressed
if ( event.which === 32 || event.which === 27) {
dropdownButton.click();
event.preventDefault();
}
// if up arrow key pressed or shift+tab
else if (event.which === 38 || (event.which === 9 && event.shiftKey)) {
// if first item go to last
if (focusedItemIndex === 0 || focusedItemIndex === -1) {
menuItems.last().focus();
} else {
itemToFocusIndex = focusedItemIndex - 1;
menuItems.get(itemToFocusIndex).focus();
}
event.preventDefault();
}
// if down arrow key pressed or tab key
else if (event.which === 40 || event.which === 9) {
// if last item go to first
if (focusedItemIndex === menuItems.length - 1 || focusedItemIndex === -1) {
menuItems.first().focus();
} else {
itemToFocusIndex = focusedItemIndex + 1;
menuItems.get(itemToFocusIndex).focus();
}
event.preventDefault();
}
};
// Toggle the visibility control for the selected element and set the focus
dropdown.toggleClass('is-visible');
if (dropdown.hasClass('is-visible')) {
dropdown.attr('tabindex', -1);
dropdown.focus();
} else {
dropdown.removeAttr('tabindex');
dropdownButton.focus();
}
// Inform the ARIA framework that the dropdown has been expanded
dropdownButton.attr('aria-expanded', !ariaExpandedState);
//catch keypresses when inside dropdownMenu (we want to catch spacebar;
// escape; up arrow or shift+tab; and down arrow or tab)
dropdown.on('keydown', function(event){
catchKeyPress($(this), event);
});
};
edx.dashboard.dropdown.bindToggleButtons = function() {
$('.action-more').bind(
'click',
edx.dashboard.dropdown.toggleCourseActionsDropdownMenu
);
};
$(document).ready(function() {
edx.dashboard.dropdown.bindToggleButtons();
});
})(jQuery);
......@@ -37,8 +37,6 @@
notifications.focus();
}
$('.action-more').bind('click', toggleCourseActionsDropdown);
// Track clicks of the upgrade button. The `trackLink` method is a helper that makes
// a `track` call whenever a bound link is clicked. Usually the page would change before
// `track` had time to execute; `trackLink` inserts a small timeout to give the `track`
......@@ -79,33 +77,6 @@
return properties;
}
function toggleCourseActionsDropdownInternal(element) {
var dashboard_index = element.data('dashboard-index');
// Toggle the visibility control for the selected element and set the focus
var dropdown_selector = 'div#actions-dropdown-' + dashboard_index;
var dropdown = $(dropdown_selector);
dropdown.toggleClass('is-visible');
if (dropdown.hasClass('is-visible')) {
dropdown.attr('tabindex', -1);
} else {
dropdown.removeAttr('tabindex');
}
// Inform the ARIA framework that the dropdown has been expanded
var anchor_selector = 'a#actions-dropdown-link-' + dashboard_index;
var anchor = $(anchor_selector);
var aria_expanded_state = (anchor.attr('aria-expanded') === 'true');
anchor.attr('aria-expanded', !aria_expanded_state);
}
function toggleCourseActionsDropdown(event) {
toggleCourseActionsDropdownInternal($(this));
// Suppress the actual click event from the browser
event.preventDefault();
}
$("#failed-verification-button-dismiss").click(function() {
$.ajax({
url: urls.verifyToggleBannerFailedOff,
......@@ -128,7 +99,7 @@
if($(event.target).data("optout") === "False") {
$("#receive_emails").prop('checked', true);
}
toggleCourseActionsDropdownInternal(element);
edx.dashboard.dropdown.toggleCourseActionsDropdownMenu(event);
});
$(".action-unenroll").click(function(event) {
......@@ -144,7 +115,7 @@
}, true));
$('#refund-info').html( element.data("refund-info") );
$("#unenroll_course_id").val( element.data("course-id") );
toggleCourseActionsDropdownInternal(element);
edx.dashboard.dropdown.toggleCourseActionsDropdownMenu(event);
});
$('#unenroll_form').on('ajax:complete', function(event, xhr) {
......@@ -179,7 +150,6 @@
return false;
});
$(".action-email-settings").each(function(index){
$(this).attr("id", "email-settings-" + index);
// a bit of a hack, but gets the unique selector for the modal trigger
......
<style>
.actions-dropdown { display: none; }
.actions-dropdown.is-visible { display: block; }
</style>
<a class="cover" data-course-key="edX/DemoX/Demo_Course" href="courses/edX/DemoX/Demo_Course/info">
<img alt="DemoX" class="course-image" src="/path/to/image.jpg">
</a>
......@@ -21,12 +25,13 @@
<a href="#" class="verified-info" data-course-key="edX/DemoX/Demo_Course"></a>
<div class="course-container">
<div class="label-xseries-association">
<i class="xseries-icon"></i>
<p class="message-copy">XSeries Program Course</p>
</div>
<article class="course honor">
<article class="course honor">
<section class="details">
<div class="wrapper-course-image" aria-hidden="true">
<a class="cover">
......@@ -48,11 +53,103 @@
<div class="wrapper-course-actions">
<div class="course-actions">
<div class="wrapper-action-more" data-course-key="course-v1:DelftX+CTB3365DWx+1T2016">
<a href="#actions-dropdown-2" class="action action-more" id="actions-dropdown-link-2" aria-haspopup="true" aria-expanded="false" data-course-number="CTB3365DWx" data-course-name="Introduction to Drinking Water Treatment" data-dashboard-index="2">
<span class="sr">Course options dropdown</span>
<button type='button'class="action action-more" id="actions-dropdown-link-1" aria-haspopup="true" aria-expanded="false" aria-controls="actions-dropdown-1" data-course-number="CTB3365DWx" data-course-name="Introduction to Drinking Water Treatment" data-dashboard-index="1">
<span class="sr">
Course options for Introduction to Drinking Water Treatment
</span>
<i class="fa fa-cog" aria-hidden="true"></i>
</button>
<div class="actions-dropdown" id="actions-dropdown-1" tabindex="-1">
<ul class="actions-dropdown-list" id="actions-dropdown-list-1" aria-label="Available Actions" role="menu">
<li class="actions-item" id="actions-item-unenroll-1">
<a href="#unenroll-modal" class="action action-unenroll" rel="leanModal" data-course-id="course-v1:DelftX+CTB3365DWx+1T2016" data-course-number="CTB3365DWx" data-course-name="Introduction to Drinking Water Treatment" data-dashboard-index="1" data-track-info="Are you sure you want to unenroll from %(course_name)s (%(course_number)s)?" id="unenroll-1">Unenroll</a>
</li>
<li class="actions-item" id="actions-item-email-settings-1">
<a href="#email-settings-modal" class="action action-email-settings" rel="leanModal" data-course-id="course-v1:DelftX+CTB3365DWx+1T2016" data-course-number="CTB3365DWx" data-dashboard-index="1" data-optout="False" id="email-settings-1">Email Settings</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</section>
<footer class="wrapper-messages-primary">
<ul class="messages-list">
<div class="message message-upsell has-actions is-shown">
<div class="wrapper-extended">
<p class="message-copy" align="justify">
<b class="message-copy-bold">Pursue a Certificate of Achievement to highlight the knowledge and skills you gain in this course.</b>
<br>
It's official. It's easily shareable. It's a proven motivator to complete the course.
<br>
<a href="https://www.edx.org/verified-certificate" class="verified-info" data-course-key="course-v1:DelftX+CTB3365DWx+1T2016">Learn more about the verified Certificate of Achievement</a>.
</p>
<div class="action-upgrade-container">
<a class="action action-upgrade" href="/verify_student/upgrade/course-v1:DelftX+CTB3365DWx+1T2016/" data-course-id="course-v1:DelftX+CTB3365DWx+1T2016" data-user="Anon">
<i class="action-upgrade-icon"></i>
<span class="wrapper-copy">
<span class="copy" id="upgrade-to-verified">Upgrade to Verified</span>
</span>
</a>
<div class="actions-dropdown" id="actions-dropdown-2" aria-label="Additional Actions Menu">
</div>
</div>
</div>
<div class="message message-status is-shown credit-message">
<div class="xseries-action">
<div class="message-copy xseries-msg">
<p><b class="message-copy-bold">XSeries Program: Interested in more courses in this subject?</b></p>
<p></p>
<p class="message-copy">
This course is 1 of 3 courses in the <a href="https://www.edx.org/xseries/water-management">Water Management</a> XSeries.
</p>
</div>
<a class="btn xseries-border-btn" href="https://www.edx.org/xseries/water-management" target="_blank"
data-program-id="xseries007">
<i class="action-xseries-icon"></i>
<span>View XSeries Details</span>
</a>
</div>
</div>
</ul>
</footer>
</article>
</div>
<div class="course-container">
<div class="label-xseries-association">
<i class="xseries-icon"></i>
<p class="message-copy">XSeries Program Course</p>
</div>
<article class="course honor">
<section class="details">
<div class="wrapper-course-image" aria-hidden="true">
<a class="cover">
<img src="/asset-v1:DelftX+CTB3365DWx+1T2016+type@asset+block@Delftx_water_course_image2.jpg" class="course-image" alt="CTB3365DWx Introduction to Drinking Water Treatment Cover Image">
</a>
<span class="sts-enrollment" title="You're enrolled as an honor code student">
<span class="label">Enrolled as: </span>
<div class="sts-enrollment-value">Honor Code</div>
</span>
</div>
<div class="wrapper-course-details">
<h3 class="course-title"><span>Introduction to Drinking Water Treatment</span></h3>
<div class="course-info">
<span class="info-university">DelftX - </span>
<span class="info-course-id">CTB3365DWx</span>
<span class="info-date-block" data-tooltip="Hi">Starts - Tuesday at 12pm UTC</span>
</div>
<div class="wrapper-course-actions">
<div class="course-actions">
<div class="wrapper-action-more" data-course-key="course-v1:DelftX+CTB3365DWx+1T2016">
<button type='button'class="action action-more" id="actions-dropdown-link-2" aria-haspopup="true" aria-expanded="false" aria-controls="actions-dropdown-2" data-course-number="CTB3365DWx" data-course-name="Introduction to Drinking Water Treatment" data-dashboard-index="2">
<span class="sr">
Course options for Introduction to Drinking Water Treatment
</span>
<i class="fa fa-cog" aria-hidden="true"></i>
</button>
<div class="actions-dropdown" id="actions-dropdown-2" tabindex="-1">
<ul class="actions-dropdown-list" id="actions-dropdown-list-2" aria-label="Available Actions" role="menu">
<li class="actions-item" id="actions-item-unenroll-2">
<a href="#unenroll-modal" class="action action-unenroll" rel="leanModal" data-course-id="course-v1:DelftX+CTB3365DWx+1T2016" data-course-number="CTB3365DWx" data-course-name="Introduction to Drinking Water Treatment" data-dashboard-index="2" data-track-info="Are you sure you want to unenroll from %(course_name)s (%(course_number)s)?" id="unenroll-2">Unenroll</a>
......@@ -87,7 +184,7 @@
</a>
</div>
</div>
</div>
</div>
<div class="message message-status is-shown credit-message">
<div class="xseries-action">
<div class="message-copy xseries-msg">
......@@ -96,7 +193,7 @@
This course is 1 of 3 courses in the <a href="https://www.edx.org/xseries/water-management">Water Management</a> XSeries.
</p>
</div>
<a class="btn xseries-border-btn" href="https://www.edx.org/xseries/water-management" target="_blank"
data-program-id="xseries007">
<i class="action-xseries-icon"></i>
......
define(['js/dashboard/dropdown', 'jquery.simulate'],
function() {
'use strict';
var keys = $.simulate.keyCode,
toggleButtonSelector = '#actions-dropdown-link-2',
dropdownSelector = '#actions-dropdown-2',
dropdownItemSelector = '#actions-dropdown-2 li a',
clickToggleButton = function() {
$(toggleButtonSelector).click();
},
verifyDropdownVisible = function() {
expect($(dropdownSelector)).toBeVisible();
},
verifyDropdownNotVisible = function() {
expect($(dropdownSelector)).not.toBeVisible();
},
waitForElementToBeFocused = function(element, desc) {
// This is being used instead of toBeFocused which is flaky
waitsFor(
function () {
return element === document.activeElement;
},
desc + ' element to have focus',
500
);
},
openDropDownMenu = function() {
verifyDropdownNotVisible();
clickToggleButton();
verifyDropdownVisible();
},
keydown = function(keyInfo) {
$(document.activeElement).simulate("keydown", keyInfo);
};
describe("edx.dashboard.dropdown.toggleCourseActionsDropdownMenu", function() {
beforeEach(function() {
loadFixtures('js/fixtures/dashboard/dashboard.html');
window.edx.dashboard.dropdown.bindToggleButtons();
});
it("Clicking the .action-more button toggles the menu", function() {
verifyDropdownNotVisible();
clickToggleButton();
verifyDropdownVisible();
clickToggleButton();
verifyDropdownNotVisible();
});
it("ESCAPE will close dropdown and return focus to the button", function() {
openDropDownMenu();
keydown({ keyCode: keys.ESCAPE });
verifyDropdownNotVisible();
waitForElementToBeFocused($(toggleButtonSelector)[0], "button");
});
it("SPACE will close dropdown and return focus to the button", function() {
openDropDownMenu();
keydown({ keyCode: keys.SPACE });
verifyDropdownNotVisible();
waitForElementToBeFocused($(toggleButtonSelector)[0], "button");
});
describe("Focus is trapped when navigating with", function() {
it("TAB key", function() {
openDropDownMenu();
keydown({ keyCode: keys.TAB });
waitForElementToBeFocused($(dropdownItemSelector)[0], "first");
});
it("DOWN key", function() {
openDropDownMenu();
keydown({ keyCode: keys.DOWN });
waitForElementToBeFocused($(dropdownItemSelector)[0], "first");
});
it("TAB key + SHIFT key", function() {
openDropDownMenu();
keydown({ keyCode: keys.TAB, shiftKey: true });
waitForElementToBeFocused($(dropdownItemSelector)[1], "last");
});
it("UP key", function() {
openDropDownMenu();
keydown({ keyCode: keys.UP });
waitForElementToBeFocused($(dropdownItemSelector)[1], "last");
});
});
});
}
);
......@@ -317,6 +317,10 @@
exports: 'js/dashboard/donation',
deps: ['jquery', 'underscore', 'gettext']
},
'js/dashboard/dropdown.js': {
exports: 'js/dashboard/dropdown',
deps: ['jquery']
},
'js/shoppingcart/shoppingcart.js': {
exports: 'js/shoppingcart/shoppingcart',
deps: ['jquery', 'underscore', 'gettext']
......@@ -649,6 +653,7 @@
'lms/include/js/spec/views/notification_spec.js',
'lms/include/js/spec/views/file_uploader_spec.js',
'lms/include/js/spec/dashboard/donation.js',
'lms/include/js/spec/dashboard/dropdown_spec.js',
'lms/include/js/spec/dashboard/track_events_spec.js',
'lms/include/js/spec/groups/views/cohorts_spec.js',
'lms/include/js/spec/shoppingcart/shoppingcart_spec.js',
......
......@@ -38,6 +38,7 @@ lib_paths:
- xmodule_js/common_static/js/vendor/requirejs/text.js
- xmodule_js/common_static/js/vendor/jquery.min.js
- xmodule_js/common_static/js/vendor/jquery-ui.min.js
- xmodule_js/common_static/js/vendor/jquery.simulate.js
- xmodule_js/common_static/js/vendor/jquery.cookie.js
- xmodule_js/common_static/js/vendor/jquery.timeago.js
- xmodule_js/common_static/js/vendor/flot/jquery.flot.js
......
......@@ -474,6 +474,16 @@
position: relative;
@include float(right);
.action-more {
@include font-size(14);
box-shadow: none;
background: $white;
background-image: none;
color: $gray;
line-height: 16px;
text-shadow: none;
}
.actions-dropdown {
@extend %ui-no-list;
@extend %ui-depth1;
......
......@@ -178,11 +178,14 @@ from student.helpers import (
% endif
% endif
<div class="wrapper-action-more" data-course-key="${enrollment.course_id}">
<a href="#actions-dropdown-${dashboard_index}" class="action action-more" id="actions-dropdown-link-${dashboard_index}" aria-haspopup="true" aria-expanded="false" data-course-number="${course_overview.number | h}" data-course-name="${course_overview.display_name_with_default_escaped | h}" data-dashboard-index="${dashboard_index}">
<span class="sr">${_('Course options dropdown')}</span>
<button type="button" class="action action-more" id="actions-dropdown-link-${dashboard_index}" aria-haspopup="true" aria-expanded="false" aria-controls="actions-dropdown-${dashboard_index}" data-course-number="${course_overview.number | h}" data-course-name="${course_overview.display_name_with_default_escaped | h}" data-dashboard-index="${dashboard_index}">
<span class="sr">${_('Course options for')}</span>
<span class="sr">&nbsp;
${course_overview.display_name_with_default_escaped}
</span>
<i class="fa fa-cog" aria-hidden="true"></i>
</a>
<div class="actions-dropdown" id="actions-dropdown-${dashboard_index}" aria-label="${_('Additional Actions Menu')}">
</button>
<div class="actions-dropdown" id="actions-dropdown-${dashboard_index}" tabindex="-1">
<ul class="actions-dropdown-list" id="actions-dropdown-list-${dashboard_index}" aria-label="${_('Available Actions')}" role="menu">
% if can_unenroll:
<li class="actions-item" id="actions-item-unenroll-${dashboard_index}">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment