[LON-CAPA-cvs] cvs: loncom /interface loncommon.pm lonhelpmenu.pm
raeburn
raeburn at source.lon-capa.org
Thu Nov 27 16:19:07 EST 2025
raeburn Thu Nov 27 21:19:07 2025 EDT
Modified files:
/loncom/interface lonhelpmenu.pm loncommon.pm
Log:
- WCAG 2 compliance. Access to Online Manuals and Printable Manuals items
in hoverable drop-down lists in help page banner via keyboard actions
(enter/tab) with aria-expanded attribute updated accordingly.
-------------- next part --------------
Index: loncom/interface/lonhelpmenu.pm
diff -u loncom/interface/lonhelpmenu.pm:1.50 loncom/interface/lonhelpmenu.pm:1.51
--- loncom/interface/lonhelpmenu.pm:1.50 Thu Feb 20 03:05:34 2025
+++ loncom/interface/lonhelpmenu.pm Thu Nov 27 21:19:07 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# generate frame-based help system
#
-# $Id: lonhelpmenu.pm,v 1.50 2025/02/20 03:05:34 raeburn Exp $
+# $Id: lonhelpmenu.pm,v 1.51 2025/11/27 21:19:07 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -150,6 +150,7 @@
if ($stayOnPage) {
$r->print(&Apache::loncommon::start_page('Help',$scripttag,
{'no_secondary_menu' => 1,}));
+ $r->print(&Apache::loncommon::dropdown_js('["ul#LC_secondary_menu"]'));
} else {
$r->print(&Apache::loncommon::start_page('Help',$scripttag,
{'only_body' => 1,}));
@@ -359,7 +360,7 @@
$disptarget = ' target="'.$target.'"';
}
my $menu = '<li class="LC_hoverable">'.$img.
- '<a href="'.$link.'"'.$disptarget.'>'.
+ '<a href="'.$link.'"'.$disptarget.' aria-expanded="false">'.
'<span class="LC_nobreak">'.$title.
'<span class="LC_fontsize_medium" style="font-weight:normal;">'.
' ▼</span></span></a>'.
Index: loncom/interface/loncommon.pm
diff -u loncom/interface/loncommon.pm:1.1485 loncom/interface/loncommon.pm:1.1486
--- loncom/interface/loncommon.pm:1.1485 Wed Nov 26 19:43:20 2025
+++ loncom/interface/loncommon.pm Thu Nov 27 21:19:07 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# a pile of common routines
#
-# $Id: loncommon.pm,v 1.1485 2025/11/26 19:43:20 raeburn Exp $
+# $Id: loncommon.pm,v 1.1486 2025/11/27 21:19:07 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -7028,131 +7028,7 @@
}
if (@hasdropdowns) {
my $jsarray = '["'.join('","', at hasdropdowns).'"]';
- $dropdownjs = <<ENDJS;
- const ancestorArray = $jsarray;
- for (var i = 0; i < ancestorArray.length; i++) {
- const anc = ancestorArray[i];
- var menuItems = document.querySelectorAll(anc+" li.LC_hoverable");
- let hoverTimeoutIds = [];
- let tabTimeoutIds = [];
-
- Array.prototype.forEach.call(menuItems, function(el, i){
- el.addEventListener("mouseenter", function(event){
- while (hoverTimeoutIds.length > 0) {
- const timeoutId = hoverTimeoutIds.pop();
- clearTimeout(timeoutId);
- }
- document.querySelectorAll(anc+" li.LC_hoverable.LC_open").forEach(element => {
- element.classList.remove("LC_open");
- element.querySelector('a').setAttribute('aria-expanded',"false");
- });
- if (!hasClass(this,"LC_open")) {
- this.classList.add("LC_open");
- this.querySelector('a').setAttribute('aria-expanded',"true");
- }
- });
- el.addEventListener("mouseleave", function(event) {
- const timeoutId = setTimeout(function() {
- var opennav = document.querySelector(anc+" li.LC_hoverable.LC_open");
- if (opennav) {
- opennav.classList.remove("LC_open");
- opennav.querySelector('a').setAttribute('aria-expanded',"false");
- }
- }, 500);
- hoverTimeoutIds.push(timeoutId);
- });
- el.querySelector('a').addEventListener("click", function(event){
- if (tabTimeoutIds.length) {
- while (tabTimeoutIds.length > 0) {
- const timeoutId = tabTimeoutIds.pop();
- clearTimeout(timeoutId);
- }
- }
- const anclist = getAllAncestorsWithClass(this,'LC_hoverable');
- document.querySelectorAll(anc+" li.LC_hoverable.LC_open").forEach(element => {
- if (anclist.indexOf(element) == -1) {
- element.classList.remove("LC_open");
- element.querySelector('a').setAttribute('aria-expanded',"false");
- }
- });
- if (!hasClass(this.parentNode,"LC_open")) {
- this.parentNode.classList.add("LC_open");
- this.setAttribute('aria-expanded',"true");
- } else {
- this.parentNode.classList.remove("LC_open");
- this.setAttribute('aria-expanded',"false");
- }
- event.preventDefault();
- });
-
- var links = el.querySelectorAll(anc+' li a');
- var numlinks = links.length;
- Array.prototype.forEach.call(links, function(el, i){
- el.addEventListener("focus", function(event) {
- if (tabTimeoutIds.length) {
- while (tabTimeoutIds.length > 0) {
- const timeoutId = tabTimeoutIds.pop();
- clearTimeout(timeoutId);
- }
- }
- const anclist = getAllAncestorsWithClass(event.target,'LC_hoverable');
- document.querySelectorAll(anc+" li.LC_hoverable.LC_open").forEach(element => {
- if (anclist.indexOf(element) == -1) {
- element.classList.remove("LC_open");
- element.querySelector('a').setAttribute('aria-expanded',"false");
- }
- });
- });
- el.addEventListener("blur", function(event) {
- const timeoutId = setTimeout(function () {
- var opennav = document.querySelector(anc+" li.LC_hoverable.LC_open");
- if (opennav) {
- opennav.classList.remove("LC_open");
- opennav.querySelector('a').setAttribute('aria-expanded',"false");
- }
- }, 10);
- tabTimeoutIds.push(timeoutId);
- });
- });
- });
- }
-
-ENDJS
- }
- if ($dropdownjs) {
- $bodytag .= <<ENDJS;
-
-<script type="text/javascript">
-// <![CDATA[
-document.addEventListener("DOMContentLoaded", function() {
-$dropdownjs
-});
-
-function hasClass(el, className) {
- if (el.classList) {
- return el.classList.contains(className);
- } else {
- return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className);
- }
-}
-
-function getAllAncestorsWithClass(element, className) {
- const ancestors = [];
- let currentElement = element.parentElement; // Start with the immediate parent
-
- while (currentElement) {
- if (currentElement.classList.contains(className)) {
- ancestors.push(currentElement);
- }
- currentElement = currentElement.parentElement; // Move up to the next parent
- }
- return ancestors;
-}
-
-// ]]>
-</script>
-
-ENDJS
+ $bodytag .= &dropdown_js($jsarray);
}
my $showcrstitle = 1;
@@ -7323,6 +7199,128 @@
return $bodytag;
}
+sub dropdown_js {
+ my ($jsarray) = @_;
+ return <<ENDJS;
+<script type="text/javascript">
+// <![CDATA[
+document.addEventListener("DOMContentLoaded", function() {
+ const ancestorArray = $jsarray;
+ for (var i = 0; i < ancestorArray.length; i++) {
+ const anc = ancestorArray[i];
+ var menuItems = document.querySelectorAll(anc+" li.LC_hoverable");
+ let hoverTimeoutIds = [];
+ let tabTimeoutIds = [];
+
+ Array.prototype.forEach.call(menuItems, function(el, i){
+ el.addEventListener("mouseenter", function(event){
+ while (hoverTimeoutIds.length > 0) {
+ const timeoutId = hoverTimeoutIds.pop();
+ clearTimeout(timeoutId);
+ }
+ document.querySelectorAll(anc+" li.LC_hoverable.LC_open").forEach(element => {
+ element.classList.remove("LC_open");
+ element.querySelector('a').setAttribute('aria-expanded',"false");
+ });
+ if (!hasClass(this,"LC_open")) {
+ this.classList.add("LC_open");
+ this.querySelector('a').setAttribute('aria-expanded',"true");
+ }
+ });
+ el.addEventListener("mouseleave", function(event) {
+ const timeoutId = setTimeout(function() {
+ var opennav = document.querySelector(anc+" li.LC_hoverable.LC_open");
+ if (opennav) {
+ opennav.classList.remove("LC_open");
+ opennav.querySelector('a').setAttribute('aria-expanded',"false");
+ }
+ }, 500);
+ hoverTimeoutIds.push(timeoutId);
+ });
+ el.querySelector('a').addEventListener("click", function(event){
+ if (tabTimeoutIds.length) {
+ while (tabTimeoutIds.length > 0) {
+ const timeoutId = tabTimeoutIds.pop();
+ clearTimeout(timeoutId);
+ }
+ }
+ const anclist = getAllAncestorsWithClass(this,'LC_hoverable');
+ document.querySelectorAll(anc+" li.LC_hoverable.LC_open").forEach(element => {
+ if (anclist.indexOf(element) == -1) {
+ element.classList.remove("LC_open");
+ element.querySelector('a').setAttribute('aria-expanded',"false");
+ }
+ });
+ if (!hasClass(this.parentNode,"LC_open")) {
+ this.parentNode.classList.add("LC_open");
+ this.setAttribute('aria-expanded',"true");
+ } else {
+ this.parentNode.classList.remove("LC_open");
+ this.setAttribute('aria-expanded',"false");
+ }
+ event.preventDefault();
+ });
+
+ var links = el.querySelectorAll(anc+' li a');
+ var numlinks = links.length;
+ Array.prototype.forEach.call(links, function(el, i){
+ el.addEventListener("focus", function(event) {
+ if (tabTimeoutIds.length) {
+ while (tabTimeoutIds.length > 0) {
+ const timeoutId = tabTimeoutIds.pop();
+ clearTimeout(timeoutId);
+ }
+ }
+ const anclist = getAllAncestorsWithClass(event.target,'LC_hoverable');
+ document.querySelectorAll(anc+" li.LC_hoverable.LC_open").forEach(element => {
+ if (anclist.indexOf(element) == -1) {
+ element.classList.remove("LC_open");
+ element.querySelector('a').setAttribute('aria-expanded',"false");
+ }
+ });
+ });
+ el.addEventListener("blur", function(event) {
+ const timeoutId = setTimeout(function () {
+ var opennav = document.querySelector(anc+" li.LC_hoverable.LC_open");
+ if (opennav) {
+ opennav.classList.remove("LC_open");
+ opennav.querySelector('a').setAttribute('aria-expanded',"false");
+ }
+ }, 10);
+ tabTimeoutIds.push(timeoutId);
+ });
+ });
+ });
+ }
+});
+
+function hasClass(el, className) {
+ if (el.classList) {
+ return el.classList.contains(className);
+ } else {
+ return new RegExp('(^| )' + className + '( |\$)', 'gi').test(el.className);
+ }
+}
+
+function getAllAncestorsWithClass(element, className) {
+ const ancestors = [];
+ let currentElement = element.parentElement; // Start with the immediate parent
+
+ while (currentElement) {
+ if (hasClass(currentElement, className)) {
+ ancestors.push(currentElement);
+ }
+ currentElement = currentElement.parentElement; // Move up to the next parent
+ }
+ return ancestors;
+}
+
+// ]]>
+</script>
+
+ENDJS
+}
+
sub dc_courseid_toggle {
my ($dc_info) = @_;
return ' <span id="dccidtext" class="LC_cusr_subheading LC_nobreak">'.
More information about the LON-CAPA-cvs
mailing list