-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add RPAL to accessibility list, phetsims/joist#355
- Loading branch information
Showing
1 changed file
with
343 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,343 @@ | ||
<!DOCTYPE HTML> | ||
<!-- Top-level HTML file for viewing, generated by 'grunt generate-a11y-view-html' --> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"/> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/> | ||
<meta name="viewport" content="initial-scale=1,user-scalable=no,maximum-scale=1"/> | ||
<meta name="apple-mobile-web-app-capable" content="yes"> | ||
<meta name="phet-sim-level" content="production"> | ||
|
||
<title>Reactants, Products and Leftovers A11y View</title> | ||
|
||
<style> | ||
html { | ||
font-family: Arial, Helvetica, sans-serif; | ||
line-height: 1.4; | ||
} | ||
|
||
body { | ||
display: block; | ||
margin: 1em auto 2em; | ||
width: 95%; | ||
background: #ccc; | ||
} | ||
|
||
div.simulation_frame { | ||
float: left; | ||
width: 47%; | ||
background: #ccc; | ||
} | ||
|
||
/* This class was found online as a way to preserve an iframe aspect ratio, see https://fettblog.eu/blog/2013/06/16/preserving-aspect-ratio-for-embedded-iframes/*/ | ||
.aspect-ratio { | ||
position: relative; | ||
width: 100%; | ||
height: 0; | ||
padding-bottom: 72%; | ||
} | ||
|
||
.aspect-ratio iframe { | ||
position: absolute; | ||
width: 100%; | ||
height: 100%; | ||
left: 0; | ||
top: 0; | ||
overflow: hidden; /* Prevents a scrollbar, and clips content. */ | ||
border: 0; | ||
} | ||
|
||
div.pdom_copy { | ||
background: #ccc; | ||
float: right; | ||
width: 50%; | ||
overflow: auto; | ||
} | ||
|
||
div.clearfix { | ||
clear: both; | ||
} | ||
|
||
p.alert { | ||
opacity: 0.6; | ||
} | ||
|
||
div.alert_copy_container { | ||
background: #e5c45b; | ||
border-radius: 1em; | ||
padding: 1em; | ||
} | ||
|
||
p.alert_copy { | ||
min-height: 2em; | ||
} | ||
|
||
div.dom_copy_container { | ||
background: #a0e1e7; | ||
border-radius: 1em; | ||
padding: 1em; | ||
} | ||
|
||
div.dom_copy_container li { | ||
margin-bottom: .5em; | ||
} | ||
|
||
div.wrapper { | ||
height: auto; | ||
width: 100%; | ||
margin-bottom: 2em; | ||
} | ||
|
||
em { | ||
background-color: #9adaa6; | ||
border-bottom: double 3px #000; /*or a border in dark green*/ | ||
} | ||
</style> | ||
|
||
</head> | ||
<body> | ||
|
||
<div class="wrapper"> | ||
<div id="intro"> | ||
<h1>Reactants, Products and Leftovers A11y View: Visual and Described content side by side</h1> | ||
<p> | ||
The accessible version of Reactants, Products and Leftovers is embedded in the iframe below. As you interact with the visual sim | ||
in the | ||
iframe with either a keyboard or mouse, the descriptions for the sim automatically update on the right in a | ||
structured Parallel Document Object Model, or PDOM. Interactive Alerts (in orange) announce key changes of what is | ||
happening during interaction, while the dynamic descriptions (in green) automatically update in the background, | ||
maintaining an up-to-date description of the state of the sim and all its objects. Static descriptions (in blue) | ||
identify regions and objects and do not change. | ||
</p> | ||
</div> | ||
<div id="content"> | ||
<div class="simulation_frame" id="simulation-frame"> | ||
<h2>Reactants, Products and Leftovers in iframe</h2> | ||
|
||
<div class="aspect-ratio"> | ||
<iframe id="iframe" allowfullscreen title="Interact with Reactants, Products and Leftovers"></iframe> | ||
</div> | ||
</div> | ||
|
||
<!--The PDOM copy will be placed in this container, along with a section just for alerts --> | ||
<div class="pdom_copy" id="pdom-copy"> | ||
<h2>Reactants, Products and Leftovers descriptions structured in a Parallel DOM</h2> | ||
<h3>Interactive alerts</h3> | ||
<div id=alert-copy-container class=alert_copy_container> | ||
<div id='assertive-element-container'> | ||
<p class='alert'>❬assertive update❭</p> | ||
<p class='alert_copy' id="assertive-element-copy"></p> | ||
</div> | ||
<div id='polite-element-container'> | ||
<p class='alert'>❬polite update❭</p> | ||
<p class='alert_copy' id="polite-element-copy"></p> | ||
</div> | ||
<div id='assertive-alert-element-container'> | ||
<p class='alert'>❬assertive alert update❭</p> | ||
<p class='alert_copy' id="assertive-alert-element-copy"></p> | ||
</div> | ||
<div id='polite-alert-element-container'> | ||
<p class='alert'>❬polite status update❭</p> | ||
<p class='alert_copy' id="polite-status-element-copy"></p> | ||
</div> | ||
</div> | ||
<h3>Static and Dynamic Descriptions</h3> | ||
<div id=dom-copy-container class="dom_copy_container"></div> | ||
</div> | ||
|
||
<!--clear the blocks after the float effect--> | ||
<div class="clearfix"></div> | ||
</div> | ||
</div> | ||
|
||
|
||
<script type="application/javascript"> | ||
|
||
// Grab all query parameters to pass to the simulation, and a couple of params that are required for this test | ||
var simulationQueryString = window.location.search; | ||
if ( simulationQueryString.indexOf( '?' ) >= 0 ) { | ||
simulationQueryString += '&'; | ||
} | ||
else { | ||
simulationQueryString += '?'; | ||
} | ||
|
||
var noPostMessage = simulationQueryString.indexOf( 'postMessageOnLoad&postMessageOnError' ) === -1; | ||
var noAccessibility = simulationQueryString.indexOf( 'accessibility' ) === -1; | ||
|
||
noPostMessage && ( simulationQueryString += 'postMessageOnLoad&postMessageOnError&' ); | ||
noAccessibility && ( simulationQueryString += 'accessibility' ); | ||
document.getElementById( 'iframe' ).setAttribute( 'src', 'reactants-products-and-leftovers_en.html' + simulationQueryString ); | ||
</script> | ||
|
||
<script type="application/javascript"> | ||
|
||
/** | ||
* Get all 'element' nodes off the parent element, placing them in an array for easy traversal. Note that this | ||
* includes all elements, even those that are 'hidden' or purely for structure. | ||
* | ||
* @param {HTMLElement} domElement - parent whose children will be linearized | ||
* @returns {HTMLElement[]} | ||
* @private | ||
*/ | ||
function getLinearDOMElements( domElement ) { | ||
|
||
// gets ALL descendant children for the element | ||
var children = domElement.getElementsByTagName( '*' ); | ||
|
||
var linearDOM = []; | ||
for ( var i = 0; i < children.length; i++ ) { | ||
|
||
// searching for the HTML element nodes (NOT Scenery nodes) | ||
if ( children[ i ].nodeType === Node.ELEMENT_NODE ) { | ||
linearDOM[ i ] = ( children[ i ] ); | ||
} | ||
} | ||
return linearDOM; | ||
} | ||
|
||
// handling messages from sims | ||
window.addEventListener( 'message', function( evt ) { | ||
var data = JSON.parse( evt.data ); | ||
|
||
// if load is successful, create a visualization of the parallel DOM | ||
if ( data.type === 'load' ) { | ||
|
||
var simFrame = document.getElementById( 'iframe' ); | ||
var innerDoc = simFrame.contentDocument || simFrame.contentWindow.document; | ||
|
||
// copy of the parallel DOM | ||
var pDOM = innerDoc.getElementsByClassName( 'accessibility' )[ 0 ]; | ||
var pDOMCopy = pDOM.cloneNode( true ); | ||
|
||
// get the alert dom elements from the iframe's pDOM | ||
var assertiveElement = innerDoc.getElementById( 'assertive' ); | ||
var politeElement = innerDoc.getElementById( 'polite' ); | ||
var assertiveAlertElement = innerDoc.getElementById( 'assertive-alert' ); | ||
var politeStatusElement = innerDoc.getElementById( 'polite-status' ); | ||
|
||
// get the alert dom elements from the pDOM copy | ||
var assertiveElementCopy = document.getElementById( 'assertive-element-copy' ); | ||
var politeElementCopy = document.getElementById( 'polite-element-copy' ); | ||
var assertiveAlertElementCopy = document.getElementById( 'assertive-alert-element-copy' ); | ||
var politeStatusElementCopy = document.getElementById( 'polite-status-element-copy' ); | ||
|
||
// strip the styling from the copied DOM elements | ||
pDOMCopy.removeAttribute( 'style' ); | ||
assertiveElementCopy.removeAttribute( 'style' ); | ||
politeElementCopy.removeAttribute( 'style' ); | ||
assertiveAlertElementCopy.removeAttribute( 'style' ); | ||
politeStatusElementCopy.removeAttribute( 'style' ); | ||
|
||
// strip style from all elements in the DOM | ||
getLinearDOMElements( pDOMCopy ).forEach( function( element ) { | ||
element.removeAttribute( 'style' ); | ||
} ); | ||
|
||
// get the parent container for the parallel DOM copy and the alert content | ||
var copyContainer = document.getElementById( 'dom-copy-container' ); | ||
var assertiveElementContainer = document.getElementById( 'assertive-element-container' ); | ||
var politeElementContainer = document.getElementById( 'polite-element-container' ); | ||
var assertiveAlertElementContainer = document.getElementById( 'assertive-alert-element-container' ); | ||
var politeStatusElementContainer = document.getElementById( 'polite-status-element-container' ); | ||
|
||
// add the copies to the outer document | ||
copyContainer.appendChild( pDOMCopy ); | ||
|
||
// remove all elements in the PDOM copy from the navigation order | ||
function removeTabIndex( rootNode ) { | ||
var allElements = rootNode.getElementsByTagName( "*" ); | ||
for ( var i = 0; i < allElements.length; i++ ) { | ||
allElements[ i ].tabIndex = '-1'; | ||
} | ||
} | ||
|
||
removeTabIndex( pDOMCopy ); | ||
|
||
function addAriaLabelsToHTML( rootNode ) { | ||
var allElements = rootNode.getElementsByTagName( "*" ); | ||
for ( var i = 0; i < allElements.length; i++ ) { | ||
var element = allElements[ i ]; | ||
|
||
if ( element.hasAttribute( 'aria-label' ) ) { | ||
|
||
// remove the style | ||
element.removeAttribute( 'style' ); | ||
|
||
if ( element.tagName.toLowerCase() === 'input' ) { | ||
|
||
// set the value of the input to be the same as the aria-label so that it is displayed in the pDOM copy | ||
element.setAttribute( 'value', element.getAttribute( 'aria-label' ) ); | ||
} | ||
else { | ||
|
||
// if not an input, then add it to the innerHTML of an element, without overriding what is already there. | ||
element.innerHTML = element.getAttribute( 'aria-label' ) + element.innerHTML; | ||
} | ||
} | ||
} | ||
} | ||
|
||
addAriaLabelsToHTML( pDOMCopy ); | ||
|
||
// whenever an element in the parallel DOM changes, we need to update its | ||
// copied element as well | ||
var domObserver = new MutationObserver( function( mutations ) { | ||
|
||
mutations.forEach( function( mutation ) { | ||
|
||
// This is extremely ineficient - every time the document changes, make a new copy, remove | ||
// the visual dom, and add a new one | ||
// TODO: Work on refining this, and only modifying the elements that change in the PDOM | ||
copyContainer.removeChild( pDOMCopy ); | ||
pDOMCopy = pDOM.cloneNode( true ); | ||
pDOMCopy.removeAttribute( 'style' ); | ||
copyContainer.appendChild( pDOMCopy ); | ||
|
||
addAriaLabelsToHTML( pDOMCopy ); | ||
|
||
// make sure that the copied html is completely out of navigation | ||
removeTabIndex( pDOMCopy ); | ||
} ); | ||
} ); | ||
|
||
// configuration of the dom observer: | ||
var config = { attributes: true, childList: true, characterData: true, subtree: true }; | ||
|
||
// pass in the target node, as well as the observer options | ||
domObserver.observe( pDOM, config ); | ||
|
||
// add mutation observers to each of the aria-live elements | ||
function addLiveObserver( container, originalElement, copiedElement ) { | ||
var liveObserver = new MutationObserver( function( mutations ) { | ||
mutations.forEach( function( mutation ) { | ||
|
||
// remove all other aria-live content | ||
assertiveElementCopy.textContent = ''; | ||
politeElementCopy.textContent = ''; | ||
assertiveAlertElementCopy.textContent = ''; | ||
politeStatusElementCopy.textContent = ''; | ||
|
||
|
||
// update the text content of the copied element to match the element in | ||
// the iframe document | ||
copiedElement.textContent = mutation.target.textContent; | ||
} ); | ||
} ); | ||
|
||
liveObserver.observe( originalElement, config ) | ||
}; | ||
|
||
addLiveObserver( assertiveElementContainer, assertiveElement, assertiveElementCopy ); | ||
addLiveObserver( politeElementContainer, politeElement, politeElementCopy ); | ||
addLiveObserver( assertiveAlertElementCopy, assertiveAlertElement, assertiveAlertElementCopy ); | ||
addLiveObserver( politeStatusElementContainer, politeStatusElement, politeStatusElementCopy ); | ||
|
||
// set focus to the loaded ifram | ||
document.getElementById( 'iframe' ).focus(); | ||
|
||
} | ||
} ); | ||
</script> | ||
</body> | ||
</html> |