MDL-69588 accessibility: Address review points

This commit is contained in:
Mathew May 2021-08-10 10:29:26 +08:00
parent 1ce2bb0007
commit 1f6bcd7eae
21 changed files with 281 additions and 46 deletions

View File

@ -35,6 +35,7 @@ Feature: Display badges
And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
And I press "Award badge"
# Check badge details are displayed.
And I navigate to "Badges > Manage badges" in site administration
And I follow "Testing system badge"
And I follow "Recipients (1)"
When I click on "View issued badge" "link" in the "Student 1" "table_row"
@ -59,6 +60,7 @@ Feature: Display badges
And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
And I press "Award badge"
# Check badge details are displayed.
And I navigate to "Badges > Manage badges" in site administration
And I follow "Testing system badge"
And I follow "Recipients (1)"
When I click on "View issued badge" "link" in the "Student 1" "table_row"
@ -107,6 +109,7 @@ Feature: Display badges
And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
And I press "Award badge"
# Check "Expires" date is displayed.
And I navigate to "Badges > Manage badges" in site administration
And I follow "Testing system badge"
And I follow "Recipients (1)"
And I click on "View issued badge" "link" in the "Student 1" "table_row"
@ -130,6 +133,7 @@ Feature: Display badges
# Wait 1 second to guarantee the badge is expired.
And I wait "1" seconds
# Check "Expired" date is displayed.
And I navigate to "Badges > Manage badges" in site administration
And I follow "Testing system badge"
And I follow "Recipients (1)"
And I click on "View issued badge" "link" in the "Student 1" "table_row"

View File

@ -1739,6 +1739,7 @@ $string['rename'] = 'Rename';
$string['renamefileto'] = 'Rename <b>{$a}</b> to';
$string['report'] = 'Report';
$string['reports'] = 'Reports';
$string['reporttype'] = 'Report type';
$string['repositories'] = 'Repositories';
$string['requestcourse'] = 'Request a course';
$string['requestedby'] = 'Requested by';

View File

@ -0,0 +1,2 @@
define ("core/keyboard_navigation",["exports","core/key_codes"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var c={menuitem:"[role=\"menuitem\"]",menu:"[role=\"menu\"]"},d=null,e=function(a,b){if(null!==a){return a}else{return b}},f=function(a){var e=a.srcElement,f=a.currentTarget.firstElementChild,k=j(a.currentTarget);if(e.classList.contains("dropdown-item")){if(a.keyCode===b.arrowRight||a.keyCode===b.arrowLeft){a.preventDefault();if(null!==d){d.parentElement.click()}}if(a.keyCode===b.space||a.keyCode===b.enter){a.preventDefault();Array.prototype.forEach.call(e.closest(".dropdown-menu").children,function(a){a.querySelector(c.menuitem).classList.remove("active")});if(!e.parentElement.classList.contains("dropdown")){e.click()}}}else{if(a.keyCode===b.arrowRight){a.preventDefault();g(e,f)}if(a.keyCode===b.arrowLeft){a.preventDefault();h(e,k)}if(a.keyCode===b.arrowUp||a.keyCode===b.arrowDown){d=e;a.preventDefault()}if(a.keyCode===b.home){a.preventDefault();i(f)}if(a.keyCode===b.end){a.preventDefault();i(k)}if(a.keyCode===b.space||a.keyCode===b.enter){a.preventDefault();if(!e.parentElement.classList.contains("dropdown")){e.click()}}}};a.default=function(a){a.removeEventListener("keydown",f);a.addEventListener("keydown",f)};var g=function(a,b){var d=a.parentElement.nextElementSibling,f=e(d,b),g=f.querySelector(c.menuitem);g.focus()},h=function(a,b){var d=a.parentElement.previousElementSibling,f=e(d,b),g=f.querySelector(c.menuitem);g.focus()},i=function(a){a.querySelector(c.menuitem).focus()},j=function(a){var b=a.lastElementChild;if(!b.classList.contains("d-none")){return a.lastElementChild}else{var c=Array.prototype.map.call(a.children,function(a){return a}).reverse(),d=c.filter(function(a){if(!a.classList.contains("d-none")){return a}});if(0!==d.length){return d[0]}else{return a.firstElementChild}}};return a.default});
//# sourceMappingURL=keyboard_navigation.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
define ("core/moremenu",["exports","jquery"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c={regions:{moredropdown:"[data-region=\"moredropdown\"]",morebutton:"[data-region=\"morebutton\"]"},classes:{dropdownitem:"dropdown-item",dropdownmoremenu:"dropdownmoremenu",dropdowntoggle:"dropdown-toggle",hidden:"d-none",active:"active",nav:"nav",navlink:"nav-link",observed:"observed"},attributes:{menu:"[role=\"menu\"]"}},d=function(a){var b=a.parentNode.offsetHeight+1,g=a.querySelector(c.regions.moredropdown),h=a.querySelector(c.regions.morebutton);if(a.offsetHeight>b){h.classList.remove(c.classes.hidden);var i=Array.from(a.children).reverse();i.forEach(function(d){if(!d.classList.contains(c.classes.dropdownmoremenu)){if(a.offsetHeight>b){var f=a.removeChild(d);e(a,f,!0)}}})}else{if("children"in g){var j=Array.from(g.children);j.forEach(function(c){if(a.offsetHeight<b&&"true"!==c.dataset.forceintomoremenu){var d=g.removeChild(c);f(a,d)}});if(0===j.length){h.classList.add(c.classes.hidden)}}if(a.offsetHeight>b){d(a)}}a.parentNode.classList.add(c.classes.observed)},e=function(a,b){var d=2<arguments.length&&arguments[2]!==void 0?arguments[2]:!1,e=a.querySelector(c.regions.moredropdown),f=a.querySelector("."+c.classes.dropdowntoggle),g=b.querySelector("."+c.classes.navlink);b.setAttribute("prev-role",b.getAttribute("role"));b.setAttribute("role","menuitem");if(g.classList.contains(c.classes.active)){f.classList.add(c.classes.active)}g.classList.remove(c.classes.navlink);g.classList.add(c.classes.dropdownitem);if(d){e.prepend(b)}else{e.append(b)}},f=function(a,b){var d=a.querySelector(c.regions.morebutton),e=a.querySelector("."+c.classes.dropdowntoggle),f=b.querySelector("."+c.classes.dropdownitem);b.setAttribute("role",b.getAttribute("prev-role"));if(f){var g=f.getAttribute("role");if("menuitem"===g){f.removeAttribute("role")}}if(f.classList.contains(c.classes.active)){e.classList.remove(c.classes.active)}f.classList.remove(c.classes.dropdownitem);f.classList.add(c.classes.navlink);a.insertBefore(b,d)},g=function(a){if("children"in a){var f=a.querySelector(c.regions.morebutton),g=Array.from(a.children);g.forEach(function(b){if(!b.classList.contains(c.classes.dropdownmoremenu)&&"true"===b.dataset.forceintomoremenu){e(a,b,!1);if(f.classList.contains(c.classes.hidden)){f.classList.remove(c.classes.hidden)}}})}d(a);window.addEventListener("resize",function(){d(a)});var h=function(a){var b=a.target.parentNode.querySelector(c.attributes.menu);if(b){b.classList.toggle("show")}a.stopPropagation()};(0,b.default)("."+c.classes.dropdownmoremenu).on("show.bs.dropdown",function(){var b=a.querySelector(c.regions.moredropdown);b.querySelectorAll(".dropdown").forEach(function(a){a.removeEventListener("click",h,!0);a.addEventListener("click",h,!0)})})};a.default=g;return a.default});
define ("core/moremenu",["exports","jquery","core/keyboard_navigation"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}var f={regions:{moredropdown:"[data-region=\"moredropdown\"]",morebutton:"[data-region=\"morebutton\"]"},classes:{dropdownitem:"dropdown-item",dropdownmoremenu:"dropdownmoremenu",dropdowntoggle:"dropdown-toggle",hidden:"d-none",active:"active",nav:"nav",navlink:"nav-link",observed:"observed"},attributes:{menu:"[role=\"menu\"]"}},g=function(a){var b=a.parentNode.offsetHeight+1,c=a.querySelector(f.regions.moredropdown),d=a.querySelector(f.regions.morebutton);if(a.offsetHeight>b){d.classList.remove(f.classes.hidden);var e=Array.from(a.children).reverse();e.forEach(function(c){if(!c.classList.contains(f.classes.dropdownmoremenu)){if(a.offsetHeight>b){var d=a.removeChild(c);h(a,d,!0)}}})}else{if("children"in c){var j=Array.from(c.children);j.forEach(function(d){if(a.offsetHeight<b&&"true"!==d.dataset.forceintomoremenu){var e=c.removeChild(d);i(a,e)}});if(0===j.length){d.classList.add(f.classes.hidden)}}if(a.offsetHeight>b){g(a)}}a.parentNode.classList.add(f.classes.observed)},h=function(a,b){var c=2<arguments.length&&arguments[2]!==void 0?arguments[2]:!1,d=a.querySelector(f.regions.moredropdown),e=a.querySelector("."+f.classes.dropdowntoggle),g=b.querySelector("."+f.classes.navlink);b.setAttribute("prev-role",b.getAttribute("role"));b.setAttribute("role","menuitem");if(g.classList.contains(f.classes.active)){e.classList.add(f.classes.active)}g.classList.remove(f.classes.navlink);g.classList.add(f.classes.dropdownitem);if(c){d.prepend(b)}else{d.append(b)}},i=function(a,b){var c=a.querySelector(f.regions.morebutton),d=a.querySelector("."+f.classes.dropdowntoggle),e=b.querySelector("."+f.classes.dropdownitem);b.setAttribute("role",b.getAttribute("prev-role"));if(e){var g=e.getAttribute("role");if("menuitem"===g){e.removeAttribute("role")}}if(e.classList.contains(f.classes.active)){d.classList.remove(f.classes.active)}e.classList.remove(f.classes.dropdownitem);e.classList.add(f.classes.navlink);a.insertBefore(b,c)},j=function(a){a.firstElementChild.querySelector("[role=\"menuitem\"]").setAttribute("tabindex","0");if("children"in a){var d=a.querySelector(f.regions.morebutton),e=Array.from(a.children);e.forEach(function(b){if(!b.classList.contains(f.classes.dropdownmoremenu)&&"true"===b.dataset.forceintomoremenu){h(a,b,!1);if(d.classList.contains(f.classes.hidden)){d.classList.remove(f.classes.hidden)}}})}g(a);(0,c.default)(a);window.addEventListener("resize",function(){g(a);(0,c.default)(a)});var i=function(a){var b=a.target.parentNode.querySelector(f.attributes.menu);if(b){b.classList.toggle("show")}a.stopPropagation()};(0,b.default)("."+f.classes.dropdownmoremenu).on("show.bs.dropdown",function(){var b=a.querySelector(f.regions.moredropdown);b.querySelectorAll(".dropdown").forEach(function(a){a.removeEventListener("click",i,!0);a.addEventListener("click",i,!0)})})};a.default=j;return a.default});
//# sourceMappingURL=moremenu.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
define ("core/usermenu",["exports","jquery"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c={userMenu:".usermenu",userMenuCarousel:".usermenu #usermenu-carousel",userMenuCarouselItem:".usermenu #usermenu-carousel .carousel-item",userMenuCarouselItemActive:".usermenu #usermenu-carousel .carousel-item.active",userMenuCarouselNavigationLink:".usermenu #usermenu-carousel .carousel-navigation-link"},d=function(){var a=document.querySelector(c.userMenu);(0,b.default)(c.userMenu).on("shown.bs.dropdown",function(){var b=document.querySelector(c.userMenuCarouselItemActive);b.focus();a.querySelectorAll(c.userMenuCarouselItem).forEach(function(a){if(!a.classList.contains("active")){a.style.width=b.offsetWidth+"px";a.style.height=b.offsetHeight+"px"}})});a.addEventListener("click",function(d){if(d.target.matches(c.userMenuCarouselNavigationLink)){d.stopPropagation();var e=d.target.dataset.carouselTargetId,f=a.querySelector("#"+e),g=Array.from(f.parentNode.children).indexOf(f);(0,b.default)(c.userMenuCarousel).carousel(g)}});(0,b.default)(c.userMenu).on("hide.bs.dropdown",function(){(0,b.default)(c.userMenuCarousel).carousel(0)});(0,b.default)(c.userMenuCarousel).on("slid.bs.carousel",function(){var b=a.querySelector(c.userMenuCarouselItemActive);b.focus()})};a.default={init:function init(){d()}};return a.default});
define ("core/usermenu",["exports","jquery","core/key_codes"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var d={userMenu:".usermenu",userMenuCarousel:".usermenu #usermenu-carousel",userMenuCarouselItem:".usermenu #usermenu-carousel .carousel-item",userMenuCarouselItemActive:".usermenu #usermenu-carousel .carousel-item.active",userMenuCarouselNavigationLink:".usermenu #usermenu-carousel .carousel-navigation-link"},e=function(){var a=document.querySelector(d.userMenu);(0,b.default)(d.userMenu).on("shown.bs.dropdown",function(){var b=document.querySelector(d.userMenuCarouselItemActive);b.focus();a.querySelectorAll(d.userMenuCarouselItem).forEach(function(a){if(!a.classList.contains("active")){a.style.width=b.offsetWidth+"px";a.style.height=b.offsetHeight+"px"}})});a.addEventListener("click",function(a){if(a.target.matches(d.userMenuCarouselNavigationLink)){f(a)}});a.addEventListener("keydown",function(a){if((a.keyCode===c.space||a.keyCode===c.enter)&&a.target.matches(d.userMenuCarouselNavigationLink)){a.preventDefault();f(a)}});var f=function(c){c.stopPropagation();var e=c.target.dataset.carouselTargetId,f=a.querySelector("#"+e),g=Array.from(f.parentNode.children).indexOf(f);(0,b.default)(d.userMenuCarousel).carousel(g)};(0,b.default)(d.userMenu).on("hide.bs.dropdown",function(){(0,b.default)(d.userMenuCarousel).carousel(0)});(0,b.default)(d.userMenuCarousel).on("slid.bs.carousel",function(){var b=a.querySelector(d.userMenuCarouselItemActive);b.focus()})};a.default={init:function init(){e()}};return a.default});
//# sourceMappingURL=usermenu.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,194 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Keyboard initialization for a given html node.
*
* @module core/keyboard_navigation
* @package core
* @copyright 2021 Moodle
* @author Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {space, enter, arrowRight, arrowLeft, arrowDown, arrowUp, home, end} from 'core/key_codes';
const SELECTORS = {
'menuitem': '[role="menuitem"]',
'menu': '[role="menu"]'
};
let openDropdownNode = null;
/**
* Small helper function to check if a given node is null or not.
*
* @param {HTMLElement|null} item The node that we want to compare.
* @param {HTMLElement} fallback Either the first node or final node that can be focused on.
* @return {HTMLElement}
*/
const clickErrorHandler = (item, fallback) => {
if (item !== null) {
return item;
} else {
return fallback;
}
};
/**
* Defined event handling so we can remove listeners on nodes on resize etc.
*
* @param {event} e The triggering element and key presses etc.
*/
const listenerEvents = e => {
const src = e.srcElement;
const firstNode = e.currentTarget.firstElementChild;
const lastNode = findUsableLastNode(e.currentTarget);
// Handling for dropdown escapes.
// A bulk of the handling is already done by aria.js just add polish.
if (src.classList.contains('dropdown-item')) {
if (e.keyCode === arrowRight ||
e.keyCode === arrowLeft) {
e.preventDefault();
if (openDropdownNode !== null) {
openDropdownNode.parentElement.click();
}
}
if (e.keyCode === space ||
e.keyCode === enter) {
e.preventDefault();
// Remove active class from any other dropdown elements.
Array.prototype.forEach.call(src.closest('.dropdown-menu').children, node => {
node.querySelector(SELECTORS.menuitem).classList.remove('active');
});
if (!src.parentElement.classList.contains('dropdown')) {
src.click();
}
}
} else {
if (e.keyCode === arrowRight) {
e.preventDefault();
setFocusNext(src, firstNode);
}
if (e.keyCode === arrowLeft) {
e.preventDefault();
setFocusPrev(src, lastNode);
}
// Let aria.js handle the dropdowns.
if (e.keyCode === arrowUp ||
e.keyCode === arrowDown) {
openDropdownNode = src;
e.preventDefault();
}
if (e.keyCode === home) {
e.preventDefault();
setFocusHomeEnd(firstNode);
}
if (e.keyCode === end) {
e.preventDefault();
setFocusHomeEnd(lastNode);
}
if (e.keyCode === space ||
e.keyCode === enter) {
e.preventDefault();
// Aria.js handles dropdowns etc.
if (!src.parentElement.classList.contains('dropdown')) {
src.click();
}
}
}
};
/**
* The initial entry point that a given module can pass a HTMLElement.
*
* @param {HTMLElement} elementRoot The menu to add handlers upon.
*/
export default elementRoot => {
elementRoot.removeEventListener('keydown', listenerEvents);
elementRoot.addEventListener('keydown', listenerEvents);
};
/**
* Handle the focusing to the next element in the dropdown.
*
* @param {HTMLElement|null} currentNode The node that we want to take action on.
* @param {HTMLElement} firstNode The backup node to focus as a last resort.
*/
const setFocusNext = (currentNode, firstNode) => {
const nextListItem = currentNode.parentElement.nextElementSibling;
const nodeToSelect = clickErrorHandler(nextListItem, firstNode);
const menuItem = nodeToSelect.querySelector(SELECTORS.menuitem);
menuItem.focus();
};
/**
* Handle the focusing to the previous element in the dropdown.
*
* @param {HTMLElement|null} currentNode The node that we want to take action on.
* @param {HTMLElement} lastNode The backup node to focus as a last resort.
*/
const setFocusPrev = (currentNode, lastNode) => {
const nextListItem = currentNode.parentElement.previousElementSibling;
const nodeToSelect = clickErrorHandler(nextListItem, lastNode);
const menuItem = nodeToSelect.querySelector(SELECTORS.menuitem);
menuItem.focus();
};
/**
* Focus on either the start or end of a nav list.
*
* @param {HTMLElement} node The element to focus on.
*/
const setFocusHomeEnd = node => {
node.querySelector(SELECTORS.menuitem).focus();
};
/**
* We need to look within the menu to find a last node we can add focus to.
*
* @param {HTMLElement} elementRoot Menu to find a final child node within.
* @return {HTMLElement}
*/
const findUsableLastNode = elementRoot => {
const lastNode = elementRoot.lastElementChild;
// An example is the more menu existing but hidden on the page for the time being.
if (!lastNode.classList.contains('d-none')) {
return elementRoot.lastElementChild;
} else {
// Cast the HTMLCollection & reverse it.
const extractedNodes = Array.prototype.map.call(elementRoot.children, node => {
return node;
}).reverse();
// Get rid of any nodes we can not set focus on.
const nodesToUse = extractedNodes.filter((node => {
if (!node.classList.contains('d-none')) {
return node;
}
}));
// If we find no elements we can set focus on, fall back to the absolute first element.
if (nodesToUse.length !== 0) {
return nodesToUse[0];
} else {
return elementRoot.firstElementChild;
}
}
};

View File

@ -24,6 +24,7 @@
*/
import $ from 'jquery';
import keyboard_navigation from "core/keyboard_navigation";
/**
* Moremenu selectors.
*/
@ -176,6 +177,7 @@ const moveOutOfMoreDropdown = (menu, navNode) => {
* @param {HTMLElement} menu The navbar moremenu.
*/
export default menu => {
menu.firstElementChild.querySelector('[role="menuitem"]').setAttribute('tabindex', '0');
// Pre-populate the "more" dropdown menu with navigation nodes which are set to be displayed in this menu
// by default at all times.
if ('children' in menu) {
@ -196,10 +198,12 @@ export default menu => {
}
// Populate the more dropdown menu with additional nodes if necessary, depending on the current screen size.
autoCollapse(menu);
keyboard_navigation(menu);
// When the screen size changes make sure the menu still fits.
window.addEventListener('resize', () => {
autoCollapse(menu);
keyboard_navigation(menu);
});
const toggledropdown = e => {

View File

@ -24,6 +24,7 @@
*/
import $ from 'jquery';
import {space, enter} from 'core/key_codes';
/**
* User menu constants.
@ -65,21 +66,41 @@ const registerEventListeners = () => {
// Handle click event on the carousel navigation (control) links in the user menu.
if (e.target.matches(selectors.userMenuCarouselNavigationLink)) {
// By default the user menu dropdown element closes on a click event. This behaviour is not desirable
// as we need to be able to navigate through the carousel items (submenus of the user menu) within the
// user menu. Therefore, we need to prevent the propagation of this event and then manually call the
// carousel transition.
e.stopPropagation();
// The id of the targeted carousel item.
const targetedCarouselItemId = e.target.dataset.carouselTargetId;
const targetedCarouselItem = userMenu.querySelector('#' + targetedCarouselItemId);
// Get the position (index) of the targeted carousel item within the parent container element.
const index = Array.from(targetedCarouselItem.parentNode.children).indexOf(targetedCarouselItem);
// Navigate to the targeted carousel item.
$(selectors.userMenuCarousel).carousel(index);
carouselManagement(e);
}
});
userMenu.addEventListener('keydown', e => {
// Handle keydown event on the carousel navigation (control) links in the user menu.
if ((e.keyCode === space ||
e.keyCode === enter) &&
e.target.matches(selectors.userMenuCarouselNavigationLink)) {
e.preventDefault();
carouselManagement(e);
}
});
/**
* We do the same actions here even if the caller was a click or button press.
*
* @param {Event} e The triggering element and key presses etc.
*/
const carouselManagement = e => {
// By default the user menu dropdown element closes on a click event. This behaviour is not desirable
// as we need to be able to navigate through the carousel items (submenus of the user menu) within the
// user menu. Therefore, we need to prevent the propagation of this event and then manually call the
// carousel transition.
e.stopPropagation();
// The id of the targeted carousel item.
const targetedCarouselItemId = e.target.dataset.carouselTargetId;
const targetedCarouselItem = userMenu.querySelector('#' + targetedCarouselItemId);
// Get the position (index) of the targeted carousel item within the parent container element.
const index = Array.from(targetedCarouselItem.parentNode.children).indexOf(targetedCarouselItem);
// Navigate to the targeted carousel item.
$(selectors.userMenuCarousel).carousel(index);
};
// Handle the 'hide.bs.dropdown' event (Fired when the dropdown menu is being closed).
$(selectors.userMenu).on('hide.bs.dropdown', () => {
// Reset the state once the user menu dropdown is closed and return back to the first (main) carousel item

View File

@ -61,7 +61,7 @@ class report_helper {
if (!empty($menu)) {
$select = new url_select($menu, $activeurl, null, 'choosecoursereport');
$select->set_label(get_string('report'), ['class' => 'accesshide']);
$select->set_label(get_string('reporttype'), ['class' => 'accesshide']);
$select->attributes['style'] = "margin-bottom: 1.5rem";
$select->class .= " mb-4";
echo $OUTPUT->render($select);

View File

@ -45,7 +45,7 @@
}
}}
<nav class="moremenu">
<ul id="moremenu-{{ uniqid }}" {{#tabs}}role="tablist" {{/tabs}}{{^tabs}}role="menu" {{/tabs}}class="nav more-nav {{navbarstyle}}">
<ul id="moremenu-{{ uniqid }}" role="menubar" class="nav more-nav {{navbarstyle}}">
{{#nodecollection}}
{{#children}}
{{> core/moremenu_children}}
@ -54,8 +54,8 @@
{{#nodearray}}
{{> core/moremenu_children}}
{{/nodearray}}
<li class="nav-item dropdown dropdownmoremenu d-none" data-region="morebutton">
<a class="dropdown-toggle nav-link" href="#" id="moremenu-dropdown{{ uniqid }}" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<li role="none" class="nav-item dropdown dropdownmoremenu d-none" data-region="morebutton">
<a class="dropdown-toggle nav-link" href="#" id="moremenu-dropdown{{ uniqid }}" role="menuitem" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" tabindex="-1">
{{#str}}moremenu, core{{/str}}
</a>
<ul class="dropdown-menu dropdown-menu-right" data-region="moredropdown" aria-labelledby="moremenu-dropdown{{ uniqid }}" role="menu">

View File

@ -31,30 +31,31 @@
}
}}
{{#haschildren}}
<li class="dropdown nav-item" role="menuitem" data-forceintomoremenu="{{#forceintomoremenu}}true{{/forceintomoremenu}}{{^forceintomoremenu}}false{{/forceintomoremenu}}">
<a class="dropdown-toggle nav-link" id="drop-down-{{uniqid}}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" href="#" {{#title}}title="{{{title}}}"{{/title}} aria-controls="drop-down-menu-{{uniqid}}">
<li class="dropdown nav-item" role="none" data-forceintomoremenu="{{#forceintomoremenu}}true{{/forceintomoremenu}}{{^forceintomoremenu}}false{{/forceintomoremenu}}">
<a class="dropdown-toggle nav-link" id="drop-down-{{uniqid}}" role="menuitem" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" href="#" aria-controls="drop-down-menu-{{uniqid}}" tabindex="-1">
{{{text}}}
</a>
<div class="dropdown-menu" role="menu" id="drop-down-menu-{{uniqid}}" aria-labelledby="drop-down-{{uniqid}}">
{{#children}}
{{^divider}}
<a class="dropdown-item" role="menuitem" href="{{{url}}}" {{#title}}title="{{{title}}}"{{/title}}>{{{text}}}</a>
<a class="dropdown-item" role="menuitem" href="{{{url}}}">{{{text}}}</a>
{{/divider}}
{{#divider}}
<div class="dropdown-divider" role="presentation"></div>
<div class="dropdown-divider"></div>
{{/divider}}
{{/children}}
</div>
</li>
{{/haschildren}}
{{^haschildren}}
<li class="nav-item" role="{{#tab}}tab{{/tab}}{{^tab}}menuitem{{/tab}}" data-forceintomoremenu="{{#forceintomoremenu}}true{{/forceintomoremenu}}{{^forceintomoremenu}}false{{/forceintomoremenu}}">
<li class="nav-item" role="none" data-forceintomoremenu="{{#forceintomoremenu}}true{{/forceintomoremenu}}{{^forceintomoremenu}}false{{/forceintomoremenu}}">
{{#tab}}
<a class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" href="{{tab}}" data-toggle="tab" aria-selected="false" tabindex="-1">{{{text}}}
<a role="menuitem" class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" href="{{tab}}" data-toggle="tab" aria-selected="false" tabindex="-1">
{{{text}}}
</a>
{{/tab}}
{{^tab}}
<a class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" href="{{{url}}}{{{action}}}" {{#isactive}}aria-current="true"{{/isactive}}>
<a role="menuitem" class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" href="{{{url}}}{{{action}}}" {{#isactive}}aria-current="true"{{/isactive}} tabindex="-1">
{{{text}}}
</a>
{{/tab}}

View File

@ -80,5 +80,5 @@
{{title}}
</a>
{{/submenulink}}
{{#divider}}<div class="dropdown-divider" role="presentation"></div>{{/divider}}
{{#divider}}<div class="dropdown-divider"></div>{{/divider}}
{{/items}}

View File

@ -41,7 +41,7 @@
}}
{{#items}}
{{#link}}
<a href="{{{url}}}" class="dropdown-item pl-5" role="menuitem" title="{{title}}" {{#isactive}}aria-current="true"{{/isactive}}>
<a href="{{{url}}}" class="dropdown-item pl-5" role="menuitem" {{#isactive}}aria-current="true"{{/isactive}}>
{{text}}
</a>
{{/link}}

View File

@ -70,10 +70,10 @@
</span>
<b class="caret"></b>
</a>
<div role="menu" aria-labelledby="user-menu-toggle" id="user-action-menu" class="dropdown-menu dropdown-menu-right">
<div aria-label="{{#str}}user{{/str}}" id="user-action-menu" class="dropdown-menu dropdown-menu-right">
<div id="usermenu-carousel" class="carousel slide" data-touch="false" data-interval="false" data-keyboard="false">
<div class="carousel-inner">
<div id="carousel-item-main" class="carousel-item active" tabindex="-1" aria-label="{{#str}}usermenu{{/str}}">
<div id="carousel-item-main" class="carousel-item active" role="menu" tabindex="-1" aria-label="{{#str}}usermenu{{/str}}">
{{> core/user_action_menu_items }}
</div>
{{#submenus}}

View File

@ -939,7 +939,14 @@ class behat_navigation extends behat_base {
if ($parentnodes) {
$tabname = behat_context_helper::escape($parentnodes[0]);
$tabxpath = '//ul[@role=\'tablist\']/li/a[contains(normalize-space(.), ' . $tabname . ')]';
if ($node = $this->getSession()->getPage()->find('xpath', $tabxpath)) {
$menubarxpath = '//ul[@role=\'menubar\']/li/a[contains(normalize-space(.), ' . $tabname . ')]';
$linkname = behat_context_helper::escape(get_string('moremenu'));
$menubarmorexpath = '//ul[@role=\'menubar\']/li/a[contains(normalize-space(.), ' . $linkname . ')]';
$tabnode = $this->getSession()->getPage()->find('xpath', $tabxpath);
$menunode = $this->getSession()->getPage()->find('xpath', $menubarxpath);
$menubuttons = $this->getSession()->getPage()->findAll('xpath', $menubarmorexpath);
if ($tabnode || $menunode) {
$node = is_object($tabnode) ? $tabnode : $menunode;
if ($this->running_javascript()) {
$this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
// Click on the tab and add 'active' tab to the xpath.
@ -950,18 +957,17 @@ class behat_navigation extends behat_base {
$xpath .= '//div[@id = ' . $tabid . ']';
}
array_shift($parentnodes);
} else {
$linkname = behat_context_helper::escape(get_string('moremenu'));
$menuxpath = '//ul[@role=\'tablist\']/li/a[contains(normalize-space(.), ' . $linkname . ')]';
$morebutton = $this->getSession()->getPage()->find('xpath', $menuxpath);
if ($morebutton) {
$this->execute('behat_general::i_click_on', [$morebutton, 'NodeElement']);
$moreitemxpath = '//ul[@data-region=\'moredropdown\']/li/a[contains(normalize-space(.), ' . $tabname . ')]';
if ($morenode = $this->getSession()->getPage()->find('xpath', $moreitemxpath)) {
$this->execute('behat_general::i_click_on', [$morenode, 'NodeElement']);
$xpath .= '//div[contains(@class,\'active\')]';
array_shift($parentnodes);
}
} else if (count($menubuttons) > 0) {
try {
$this->execute('behat_general::i_click_on', [$menubuttons[1], 'NodeElement']);
} catch (Exception $e) {
$this->execute('behat_general::i_click_on', [$menubuttons[0], 'NodeElement']);
}
$moreitemxpath = '//ul[@data-region=\'moredropdown\']/li/a[contains(normalize-space(.), ' . $tabname . ')]';
if ($morenode = $this->getSession()->getPage()->find('xpath', $moreitemxpath)) {
$this->execute('behat_general::i_click_on', [$morenode, 'NodeElement']);
$xpath .= '//div[contains(@class,\'active\')]';
array_shift($parentnodes);
}
}
}

View File

@ -91,6 +91,7 @@ Feature: Saving, using and deleting feedback templates
And I press "Use this template"
And I set the field "Delete old items" to "1"
And I press "Save changes"
And I am on the "Another feedback in course 1" "feedback activity" page
And I follow "Edit questions"
And I should not see "What is your favourite subject"
And I should see "this is a multiple choice 1"

View File

@ -43,7 +43,7 @@ Feature: A teacher can choose whether glossary entries require approval
And I follow "Waiting approval"
Then I should see "(this entry is currently hidden)"
And I follow "Approve"
And I click on "Test glossary name" "link" in the "page-header" "region"
And I am on the "Test glossary name" "glossary activity" page
Then I should see "Concept definition"
And I log out
# Check that the entry can now be viewed by students.

View File

@ -53,7 +53,7 @@
<div role="menu" aria-labelledby="lang-menu-toggle" id="lang-action-menu" class="dropdown-menu dropdown-menu-right">
{{#items}}
{{#link}}
<a href="{{{url}}}" class="dropdown-item pl-5" role="menuitem" title="{{title}}" {{#isactive}}aria-current="true"{{/isactive}}>
<a href="{{{url}}}" class="dropdown-item pl-5" role="menuitem" {{#isactive}}aria-current="true"{{/isactive}}>
{{text}}
</a>
{{/link}}