Skip to content

Instantly share code, notes, and snippets.

@richshea
Created February 23, 2018 16:01
Show Gist options
  • Save richshea/4bad5cf308540e3ede93c20706883b34 to your computer and use it in GitHub Desktop.
Save richshea/4bad5cf308540e3ede93c20706883b34 to your computer and use it in GitHub Desktop.
Brandeis Custom Javascript for Accessibility Fixes
(function() {
/* Load jQuery for use in accessibility fixing code below */
var jquerymini = document.createElement("script");
jquerymini.type = "text/javascript";
jquerymini.async = true;
jquerymini.src = "https://code.jquery.com/jquery-2.2.4.min.js";
var c = document.getElementsByTagName("script")[0];
c.parentNode.insertBefore(jquerymini, c);
})();
(function () {
"use strict";
'use strict';
var app = angular.module('viewCustom', ['angularLoad']);
/* Fix accessibility */
/* remove skip to results, skip to facets, and skip to make menu links */
app.controller('prmSkipToAfterController', ['$filter', function ($filter) {
//console.time('Fix SkipTo');
//remove skipTo Links that do not work well with screen readers because they use page scrolling instead of internal page linking
//Get access to the service used by the skip to directive to create links
var myContainer = this.parentCtrl.skipToService
//access to the list of link codes for the page
var searchLinks = myContainer.skipLinksObject["exploreMain.search"];
//use custom filters to remove the codes for everything but skip to search.
myContainer.skipLinksObject["exploreMain.search"] = $filter('filter')($filter('filter')($filter('filter')(searchLinks, '!mainResults'), '!facets'), '!mainMenu');
myContainer.skipLinksObject["exploreMain.jsearch"] = $filter('filter')($filter('filter')($filter('filter')(searchLinks, '!mainResults'), '!facets'), '!mainMenu');
myContainer.skipLinksObject["exploreMain.favorites"] = $filter('filter')($filter('filter')($filter('filter')(searchLinks, '!mainResults'), '!favoritesLabels'), '!mainMenu');
myContainer.skipLinksObject["exploreMain.citationTrails"] = $filter('filter')($filter('filter')(searchLinks, '!mainResults'), '!mainMenu');
//myContainer.skipLinksObject["account"] = $filter('filter')($filter('filter')($filter('filter')(searchLinks, '!backtosearch'), '!accountoverview'), '!banner);
//console.timeEnd('Fix SkipTo');
}])
/* Add a consistent h1 heading at the beginning of every page for accessibility purposes */
app.component('prmSkipToAfter', {
bindings: { parentCtrl: '<' },
controller: 'prmSkipToAfterController',
template: '<h1 class="accessible-brandeis" >Brandeis OneSearch</h1>'
});
/*General Accessibility controller to run jQuery functions to change element attributes, mainly aria-labels, roles, and alts. This controller is run on several components */
app.controller('AccessibilityController', ['$timeout', function ($timeout) {
function fixAccessibility () {
//console.time("fixAccessibility");
// Make tabs selector accessible
//md-select: adding the button role makes voiceover direct the use to click to access the menu
//md-option: You have to click it to select anything so it's a button
$('md-select, md-option').not('[role|=\'button\']').attr('role','button');
//adding the listbox role improves voiceover navigation
//$('#select_container_3 md-content').attr('role','listbox');
$('md-content').not('[role|=\'listbox\']').attr('role','listbox');
//This adds a meaningful option name to the scope options
$('#select_container_3 md-option').attr('aria-label', function () {
return 'Search in '+this.children[0].children[0].innerHTML;
});
//This adds a meaningful message for the default search. Without this it just says "select search tab" with no mention of what is selected
$('[aria-label|=\'select search tab\']').attr('aria-label','select search tab Library Catalog');
//Add alt-text to logo link
$('prm-logo #banner a').not('[aria-label|=\'Link to home page. Use this link to return to the simple search page\']').attr('aria-label','Link to home page. Use this link to return to the simple search page');
//Fix main menu aria-labels, both inset and overlay
$('[aria-label|=\'Contact Us\'],[translate*=\'mainmenu.label.contact_us\']').attr('aria-label','Contact Us Button. Clicking this button will open a new window and link to a form to contact Brandeis Library Staff.');
$('[aria-label|=\'Find Journals\'],[translate*=\'mainmenu.label.journalsearch\']').attr('aria-label','Find Journals Button. Clicking this button will navigate to a new OneSearch page for finding journal records held by Brandeis Library.');
$('[aria-label|=\'ILL\'],[translate*=\'mainmenu.label.ill\']').attr('aria-label','ILL Button. Clicking this button will open a new window and link to a Brandeis library guide to Inter-Library Loan.');
$('[aria-label|=\'LTS Home\'],[translate*=\'mainmenu.label.lts_home\']').attr('aria-label','LTS Home Button. Clicking this button will open a new window and link to the Brandeis Library home page');
$('[aria-label|=\'Browse\'],[translate*=\'mainmenu.label.browse\']').attr('aria-label','Browse Button. Clicking this button will navigate to a new OneSearch page with Browse search functionality.');
$('#more-links-button').attr('aria-label','More Options Button. Clicking this button will open an overlay containing additional Main Menu options.');
//Fix sign in and sign out button labels depending on is the user is logged in or not
if ($('.user-name').html() == "Sign In ") {
$('[aria-label|=\'Open user actions menu\']').attr('aria-label','You are currently using OneSearch as a Guest user. Click to open a two button sign in menu with options to sign in and access your library account');
$('[aria-label|=\'Go To My Account\']').attr('aria-label','If you sign in this button will navigate to a new OneSearch page with your library account information. Since you are not logged in this button will navigate to a new OneSearch page with the login options.');
} else {
$('[aria-label|=\'Open user actions menu\']').attr('aria-label','You are currently logged in as '+$('.user-name').html()+'. Click to open a two button sign in menu with options to sign out and access your library account');
$('[aria-label|=\'Go To My Account\']').attr('aria-label','Since you have already signed in, this button will navigate to a new OneSearch page with your library account information. If you were not logged in, this button would navigate to a new OneSearch page with the login options.');
}
$('prm-user-area [aria-label|=\'Sign in\']').attr('aria-label','Click to navigate to a new OneSearch page with the login options. Move focus to another element in this page or use the escape key to close this two-button sign in menu.');
$('prm-user-area [aria-label|=\'Sign out\']').attr('aria-label','Click to sign out of OneSearch and navigate to the OneSearch home page. Move focus to another element in this page or use the escape key to close this two-button sign in menu.');
//Add alt and aria-label attributes to material type icons
$('[alt|=\'Multiple Versions Image\'],.fallback-img').attr({"alt": "Book Icon", "aria-label": "Book Icon"});
/* This element was causing this accessibility error: "This element's role is "presentation" but contains child elements with semantic meaning." */
$('md-virtual-repeat-container').removeAttr('role');
//This element was missing an aria-label
$('input[ng-model|=\'row.searchQuery\']').each( function (idx, element) {
if ($(element).attr('aria-label') == undefined) $(element).attr('aria-label', 'Search Query Text');
});
//this element was missing an aria-label
$('button.switch-to-simple').each( function (idx, element) {
if ( $(element).attr('aria-label') == undefined) {
$(element).attr('aria-label', 'Switch to Simple Search');
}
});
//this element was missing an aria-label
$('[ng-click|=\'$ctrl.switchAdvancedSearch()\']').each( function (idx, element) {
if ( $(element).attr('aria-label') == undefined) {
$(element).attr('aria-label', 'Switch to Advanced Search');
}
});
//iframes need a title element to be wcag 2.0 AA compliant. The alma get it mashup does not have one
$('.mashup-iframe').not('[title|=\'Request and Access and Loan Information\']').attr('title','Request and Access and Loan Information');
//This submit input that exists specifically for accessibility purposes does not have an aria-label
$('input.accessible-only[type|=\'submit\']').not('[aria-label|=\'Submit Find Journals Search\']').attr('aria-label','Submit Find Journals Search');
//This h1 for accessibility is not present on all pages, and it is replaced by the one added to the prmSkipTpAfter template elsewhere in this file.
$('h1.accessible-only').remove();
//The submit button on the Browse page should be type submit instead of type button for accessibility purposes
$('.submit-button').not('[type|=\'submit\']').attr('type','submit');
//The title is undefined on the browse search page.
if ($('#primoExploreTitle').innerHTML == undefined) { document.title = "Brandeis Library OneSearch"; }
//The previous page button has no aria-label on the browse search result page for the first page of results
if ($('div.counter-prev a').attr('aria-label') == undefined || $('div.counter-prev a').attr('aria-label') == "") {
$('div.counter-prev a').attr('aria-label', 'Go to Previous Page');
}
/* Code in progress to be improved by consultation with Web Accessibility Specialist
$('#mainResults').not('[role|=\'list\']').attr('role','list');
$('prm-brief-result-container').not('[role|=\'listitem\']').attr('role','listitem');
console.timeEnd("fixAccessibility");
*/
}
//Run ASAP and then again in 3 second to make sure. This whole function take between 5 and 12 ms, and runs two or 3 times per page load, so about 6 times total.
$timeout(fixAccessibility,10);
$timeout(fixAccessibility,3000);
}]);
app.component('prmAdvancedSearchAfter', {
controller: 'AccessibilityController'
});
app.component('prmPageNavMenuAfter', {
controller: 'AccessibilityController'
});
app.component('prmSearchAfter', {
controller: 'AccessibilityController'
});
app.component('prmBrowseSearchAfter', {
controller: 'AccessibilityController'
});
/* specific controller to run jQuery functions to change IDs and an image alt attributes,
run only on prmIconAfter. prmIconAfter directives are probably the last directives to be loaded
because they are nested the most deeply in the page. Since prmIconAfter appears so often and this
code is run many times on the search results page, it has been optimized for speed */
app.controller('prmIconAfterController', ['$timeout', function ($timeout) {
function fixAccessibility () {
var duplicateIds = ["select_value_label", "chevron-up", "briefResultMoreOptionsButton"];
/* This is identical to the code block directly below, but it runs much slower
console.time('jquery');
$.each(duplicateIds, function(idx, element) { $('[id*=\''+element+'\']').each( function (idx, element) {
var myID = $(element).attr('id');
var myElements = $('[id|=\''+myID+'\']');
if (myElements.length > 1) {
$.each(myElements, function(idx, element) { if (idx >0) $(element).attr('id',$(element).attr('id')+'_'+idx); } );
}
})});
console.timeEnd('jquery'); */
/* This code finds duplicate ids based on the array defined above and changes their ids to be distinct. This is done to fix accessibility errors according to the WCAG 2.0 AA standards */
//console.time('minimal jquery');
for (var i=duplicateIds.length-1; i>-1; i--) {
var myElements = $('[id*=\''+duplicateIds[i]+'\']');
for (var j=myElements.length-1; j>-1; j--) {
//AGRS removed the lines below to make it run faster, even though it might change IDs that are unique. This broadness is created in the definition of myElements above because it uses *= which is a contains comparison. This is necessary because the select_value_label_* id elements change the numeric suffix randomly.
//var myID = myElements[j].getAttribute('id');
myElements[j].setAttribute('id', duplicateIds[i]+'_'+j);
/* var myIdenticalElements = $('[id|=\''+myID+'\']');
for (var k=myIdenticalElements.length-1; k>0; k--) {
myIdenticalElements[k].setAttribute('id', myID+'_'+k);
} */
}
}
//console.timeEnd('minimal jquery');
//console.time('beacon fix');
//This code adds an alt tag to the image in order to pass accessibility standards
$('img[src*=\'https://beacon01.alma.exlibrisgroup.com\']').not('[alt|=\'No content image, please ignore\']').attr('alt','No content image, please ignore');
//console.timeEnd('beacon fix');
}
//This controller code is run every time an icon is loaded, so it only needs to be run initiated once in the controller.
$timeout(fixAccessibility,10);
}]);
app.component('prmIconAfter', {
controller: 'prmIconAfterController'
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment