[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