-
Notifications
You must be signed in to change notification settings - Fork 138
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve navigation accessibility and usability (#544)
* Navbar: Make more consistent, remove unnecessary nav element * Use updated navbar, move filters closer to filtered content * Rename "navbar" to "navigation" For improved clarity and to avoid tying design to naming * Fix indentation * Use hidden attribute instead of display none * Simplify for loops * Rearrange script * Use generic names for generic functions * Remove filter submit button Is wired up and working, but doesn't feel like it because results update without using it * Update filter HTML * Remove excess article element * Remove extra namespace list status It's been folded into the filter template * First pass at filter status updates * Remove listener for now non-existent button * Handle case where filter starts non-empty * Don't use input type=search * Fewer announcements, handle same announcements back to back * Add explanatory comments * Filter script: Consolidate editing outputs * Update form hide if no filterable entries found * Filter.js: Consolidate variables, update comments, test HTML presence * Convert filter.js to module, extract common utilities * Add main.js with setJavascriptAvailable function * Handle no JavaScript situation * Make filter context consistent across pages * Only add filter if more than 1 entry * Hide Glossary link when it's not on page * Update filter styling * Hide "Limit results" link if only namespace Co-Authored-By: Andrew Suderman <[email protected]> * go fmt fixes * Update tests * Update tests Co-authored-by: Andrew Suderman <[email protected]>
- Loading branch information
1 parent
af32fc5
commit 89c2c93
Showing
16 changed files
with
304 additions
and
187 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,60 +1,124 @@ | ||
(function () { | ||
const formId = "js-filter-form"; | ||
const containerId = "js-filter-container"; | ||
import { | ||
showElement, | ||
hideElement | ||
} from "./utilities.js"; | ||
|
||
const form = document.getElementById(formId); | ||
const filterInput = form?.querySelector("input[type='search']"); | ||
const form = document.getElementById("js-filter-form"); | ||
const container = document.getElementById("js-filter-container"); | ||
|
||
const container = document.getElementById(containerId); | ||
const potentialResults = container?.querySelectorAll("[data-filter]"); | ||
const numPotentialResults = potentialResults?.length; | ||
/* | ||
These lookups simultaneously test that certain elements and attributes | ||
required for accessibility are present | ||
*/ | ||
const filterInput = form?.querySelector("input[type='text']"); | ||
const potentialResults = container?.querySelectorAll("[data-filter]"); | ||
|
||
function showFilterResult(result) { | ||
result.style.removeProperty("display"); | ||
} | ||
const outputVisual = form?.querySelector("output[aria-hidden]"); | ||
const outputPolite = form?.querySelector("output[aria-live='polite']"); | ||
const outputAlert = form?.querySelector("output[role='alert']"); | ||
|
||
function hideFilterResult(result) { | ||
result.style.display = "none"; | ||
} | ||
let statusDelay = null; | ||
|
||
// Test that all expected HTML is present | ||
if (!form) { | ||
console.error("Could not find filter form"); | ||
} else if (!filterInput) { | ||
hideElement(form); | ||
console.error("Could not find filter input element, removed filter form"); | ||
} else if (!container) { | ||
hideElement(form); | ||
console.error("Could not find filter results container, removed filter form"); | ||
} else if (!outputVisual || !outputPolite || !outputAlert) { | ||
hideElement(form); | ||
console.error("Could not find all filter output elements, removed filter form"); | ||
} else if (potentialResults.length === 0) { | ||
hideElement(form); | ||
console.error("No filterable entries found, removed filter form"); | ||
} else { | ||
// HTML was successfully set up, wire in JS | ||
filterInput.addEventListener("input", runFilter); | ||
|
||
// Handle case where input value doesn't start empty (such as on page refresh) | ||
runFilter(); | ||
} | ||
|
||
function updateResults() { | ||
let filterTerm = filterInput.value; | ||
function runFilter() { | ||
updateResults(); | ||
updateStatus(); | ||
} | ||
|
||
if (filterTerm) { | ||
let regex = new RegExp(`${ filterTerm.trim().replace(/\s/g, "|") }`, "i"); | ||
function updateResults() { | ||
let filterTerm = filterInput.value; | ||
|
||
for (let i = 0; i < numPotentialResults; i++) { | ||
let result = potentialResults[i]; | ||
let filterWithin = result.dataset.filter; | ||
if (filterTerm) { | ||
let regex = new RegExp(`${ filterTerm.trim().replace(/\s/g, "|") }`, "i"); | ||
|
||
if (regex.test(filterWithin)) { | ||
showFilterResult(result); | ||
} else { | ||
hideFilterResult(result); | ||
} | ||
for (const result of potentialResults) { | ||
if (regex.test(result.dataset.filter)) { | ||
showElement(result); | ||
} else { | ||
hideElement(result); | ||
} | ||
} else { | ||
clearFilter(); | ||
} | ||
} else { | ||
clearFilter(); | ||
} | ||
} | ||
|
||
function clearFilter() { | ||
for (let i = 0; i < numPotentialResults; i++) { | ||
showFilterResult(potentialResults[i]); | ||
} | ||
function clearFilter() { | ||
for (const result of potentialResults) { | ||
showElement(result); | ||
} | ||
} | ||
|
||
if (form && container) { | ||
if (numPotentialResults === 0) { | ||
form.style.display = "none"; | ||
console.error("No filterable entries found, removed filter form"); | ||
} else { | ||
filterInput.addEventListener("input", updateResults); | ||
|
||
form.addEventListener("submit", function(event) { | ||
event.preventDefault(); | ||
updateResults(); | ||
}) | ||
} | ||
function updateStatus() { | ||
const numResults = container?.querySelectorAll("[data-filter]:not([hidden])").length; | ||
|
||
let message, type; | ||
|
||
if (!filterInput.value) { | ||
message = `${potentialResults.length} namespaces found`; | ||
type = "polite"; | ||
} else if (numResults === 0) { | ||
message = "No namespaces match filter"; | ||
type = "alert"; | ||
} else { | ||
message = `Showing ${numResults} out of ${potentialResults.length} namespaces`; | ||
type = "polite"; | ||
} | ||
|
||
changeStatusMessage(message, type); | ||
} | ||
|
||
function changeStatusMessage(message, type = "polite") { | ||
if (statusDelay) { | ||
window.clearTimeout(statusDelay); | ||
} | ||
})(); | ||
|
||
outputVisual.textContent = message; | ||
outputPolite.textContent = ""; | ||
outputAlert.textContent = ""; | ||
|
||
/* | ||
If you don't clear the content, then repeats of the same message aren't announced. | ||
There must be a time gap between clearing and injecting new content for this to work. | ||
Delay also: | ||
- Helps make spoken announcements less disruptive by generating fewer of them | ||
- Gives the screen reader a chance to finish announcing what's been typed, which will otherwise talk over these announcements (in MacOS/VoiceOver at least) | ||
*/ | ||
statusDelay = window.setTimeout(() => { | ||
switch (type) { | ||
case "polite": | ||
outputPolite.textContent = message; | ||
outputAlert.textContent = ""; | ||
break; | ||
case "alert": | ||
outputPolite.textContent = ""; | ||
outputAlert.textContent = message; | ||
break; | ||
default: | ||
outputPolite.textContent = "Error: There was a problem with the filter."; | ||
outputAlert.textContent = ""; | ||
} | ||
}, 1000); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// For scripts that should always be run on every page | ||
|
||
import { setJavascriptAvailable } from "./utilities.js"; | ||
|
||
setJavascriptAvailable(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
function setJavascriptAvailable() { | ||
document.body.dataset.javascriptAvailable = true; | ||
} | ||
|
||
function showElement(element) { | ||
element.removeAttribute("hidden"); | ||
} | ||
|
||
function hideElement(element) { | ||
element.setAttribute("hidden", ""); | ||
} | ||
|
||
export { | ||
setJavascriptAvailable, | ||
showElement, | ||
hideElement | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.