Skip to content

Commit

Permalink
add RPAL to accessibility list, phetsims/joist#355
Browse files Browse the repository at this point in the history
  • Loading branch information
zepumph committed Oct 20, 2017
1 parent 6dae1ab commit 9a9a4a7
Showing 1 changed file with 343 additions and 0 deletions.
343 changes: 343 additions & 0 deletions reactants-products-and-leftovers-a11y-view.html
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'>&#10092;assertive update&#10093;</p>
<p class='alert_copy' id="assertive-element-copy"></p>
</div>
<div id='polite-element-container'>
<p class='alert'>&#10092;polite update&#10093;</p>
<p class='alert_copy' id="polite-element-copy"></p>
</div>
<div id='assertive-alert-element-container'>
<p class='alert'>&#10092;assertive alert update&#10093;</p>
<p class='alert_copy' id="assertive-alert-element-copy"></p>
</div>
<div id='polite-alert-element-container'>
<p class='alert'>&#10092;polite status update&#10093;</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>

0 comments on commit 9a9a4a7

Please sign in to comment.