From e336dddbfb7064d73fe44f87b5a34b0b28305d35 Mon Sep 17 00:00:00 2001
From: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Date: Tue, 9 Jun 2020 14:10:53 +0100
Subject: [PATCH] Bug 22807: Add 'Skip to content' option

This patch adds a 'Skip to content' link to the header bar which will
only appear upon the first use of 'tab' to navigate after any fresh page
load in the OPAC.

Test plan
1/ Load any page in the OPAC
2/ Hit the `tab` key
3/ Note the new 'Skip to main content' link appears at the top left of
the screen.
4/ Hit `Enter` or Click the button
5/ Note the page scrolls to the area of the page that has the first block
   containing a .maincontent class.
6/ Note that the next available focusable element after the first .maincontent
   block has been given focus.
7/ Note that the link has been hidden
---
 .../bootstrap/en/includes/masthead.inc        |  1 +
 koha-tmpl/opac-tmpl/bootstrap/js/global.js    | 46 +++++++++++++++++++
 2 files changed, 47 insertions(+)

diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc
index 88dd9a9255..8de7ec60a5 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc
@@ -12,6 +12,7 @@
         <div class="navbar navbar-inverse navbar-static-top">
             <div class="navbar-inner">
                 <div class="container-fluid">
+                    <button id="scrolltocontent" class="sr-only sr-only-focusable pull-left nav btn btn-danger" type="button">Skip to main content</button>
                     <h1 id="logo">
                         <a class="brand" href="/cgi-bin/koha/opac-main.pl">
                             [% IF ( LibraryNameTitle ) %]
diff --git a/koha-tmpl/opac-tmpl/bootstrap/js/global.js b/koha-tmpl/opac-tmpl/bootstrap/js/global.js
index 76ce7b5cb0..5eb956b700 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/js/global.js
+++ b/koha-tmpl/opac-tmpl/bootstrap/js/global.js
@@ -91,3 +91,49 @@ function confirmModal(message, title, yes_label, no_label, callback) {
     $("#bootstrap-confirm-box-modal-cancel").text( no_label || 'Cancel' );
     $("#bootstrap-confirm-box-modal").modal('show');
 }
+
+//Add jQuery :focusable selector
+(function($) {
+    function visible(element) {
+        return $.expr.filters.visible(element) && !$(element).parents().addBack().filter(function() {
+            return $.css(this, 'visibility') === 'hidden';
+        }).length;
+    }
+
+    function focusable(element, isTabIndexNotNaN) {
+        var map, mapName, img, nodeName = element.nodeName.toLowerCase();
+        if ('area' === nodeName) {
+            map = element.parentNode;
+            mapName = map.name;
+            if (!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') {
+                return false;
+            }
+            img = $('img[usemap=#' + mapName + ']')[0];
+            return !!img && visible(img);
+        }
+        return (/input|select|textarea|button|object/.test(nodeName) ?
+                !element.disabled :
+                'a' === nodeName ?
+                element.href || isTabIndexNotNaN :
+                isTabIndexNotNaN) &&
+            // the element and all of its ancestors must be visible
+            visible(element);
+    }
+
+    $.extend($.expr[':'], {
+        focusable: function(element) {
+            return focusable(element, !isNaN($.attr(element, 'tabindex')));
+        }
+    });
+})(jQuery);
+
+$("#scrolltocontent").click(function() {
+    var content = $(".maincontent");
+    if (content.length > 0) {
+        $('html,body').animate({
+                scrollTop: content.first().offset().top
+            },
+        'slow');
+        content.first().find(':focusable').eq(0).focus();
+    }
+});
-- 
2.20.1