Skip to content

Instantly share code, notes, and snippets.

@michaelpacker
Last active December 19, 2017 18:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save michaelpacker/a2c76de6e38d6bc55349d2836240772b to your computer and use it in GitHub Desktop.
Save michaelpacker/a2c76de6e38d6bc55349d2836240772b to your computer and use it in GitHub Desktop.
Accessible Tooltip patter (WIP)
<!DOCTYPE>
<html>
<head>
<title>Tooltip Test</title>
<style type="text/css">
/* General styles for the sake of this example */
body {
background-color: #fff;
}
h1 { padding-top:20px; }
.container {
width: 98%;
margin: 0 auto;
margin-bottom: 2em;
border-bottom:1px solid #ccc;
padding-bottom: 2em;
}
.float-right {
float:right;
width:220px;
}
.clear {
display:block;
clear:both;
}
/* Pattern specific css */
.css-stackLabel {
display: block;
}
fieldset {
margin: 0;
position: relative;
}
textarea {
margin-top:5px; /* Display */
}
.css-tooltip {
position: absolute;
display: block;
background: #cccccc; /* Display */
padding: 10px; /* Display */
max-width: 700px; /* Display */
}
.css-tooltip.css-tooltip-top {
/* use this for positioning the tooltip above */
bottom: 0;
top: auto;
}
.css-tooltip.css-tooltip-left {
left:0;
right:auto;
}
.css-tooltip.css-tooltip-top[style="visibility: visible;"]:after {
top: 100%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-color: rgba(204, 204, 204, 0);
border-top-color: #cccccc;
border-width: 8px;
margin-left: -8px;
}
/* had to remove [style="visibility: visible;"] from css definitions because jquery writes additional css that nullifys the condition */
.css-tooltip {
margin-top:10px;
margin-left:-5px;
/* adding a margin right makes sure it does not but right up against the browser side */
margin-right:10px;
}
/* Create the arrow */
.css-tooltip:after {
bottom: 100%;
left: 14px;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-color: rgba(204, 204, 204, 0);
border-bottom-color: #cccccc;
border-width: 8px;
margin-left: -8px;
}
.hx-form-horizontal {
display: inline-block; /* Established utility class */
}
</style>
</head>
<body>
<div class="container">
<p>Based on: <a href="http://pauljadam.com/demos/tooltip.html">http://pauljadam.com/demos/tooltip.html</a></p>
<p>Help text divs are in the flow of the document, but visibly hidden. This causes spacing issues. The CSS from the sample site has the position written as relative, with negative co-ordinates placing the object on the page. This still doesn't do the trick. We'll need to position the help text absolutely and use javascript to update the position when triggered.</p>
<p>This positioning would need to be able to be reliably calculated, based on the location of the label and its button. We would always need to wrap every form label/input pair in a div for this purpose.</p>
</div>
<div class="container">
<label for="example-1" class="css-stackLabel js-tooltipLabel">Text Input Field
<button id="mytooltipbutton-1" class='js-tooltipAccessor' aria-label="Help?" aria-expanded="false" aria-labelledby="mytooltipbutton-1 mytooltip-1" >?</button></label>
<input type="text" id="example-1" data-name='input' />
<div id="mytooltip-1" class='js-tooltipContent css-tooltip' role="tooltip" style="visibility: hidden;"><b>Input Field</b><br>This is the tooltip text content.</div>
</div>
<div class="container">
<label for="example-2" class="css-stackLabel js-tooltipLabel">Select List
<button id="mytooltipbutton-2" class='js-tooltipAccessor' aria-label="Help?" aria-expanded="false" aria-labelledby="mytooltipbutton-2 mytooltip-2" >?</button>
</label>
<select id="example-2" name="example-2" data-name='input-2'>
<option value='1'>Option 1</option>
<option value='2'>Option 2</option>
<option value='3'>Option 3</option>
</select>
<div id="mytooltip-2" class='js-tooltipContent css-tooltip' role="tooltip" style="visibility: hidden;"><b>Select list</b><br>This is the tooltip text content.</div>
</div>
<div class="container">
<label for="example-3" data-name='label-3' class="js-tooltipLabel"><input type="checkbox" id="example-3" name="example-3"> Single Check Box
<button id="mytooltipbutton-3" class='js-tooltipAccessor' aria-label="Help?" aria-expanded="false" aria-labelledby="mytooltipbutton-3 mytooltip-3" >?</button>
</label>
<div id="mytooltip-3" class='js-tooltipContent css-tooltip' role="tooltip" style="visibility: hidden;"><b>Single Check Box</b><br>This is the tooltip text content.</div>
</div>
<div class="container">
<fieldset>
<legend class="js-tooltipLabel js-tooltipUmbrella">Some check list w/Single Help <button id="mytooltipbutton-4" class='js-tooltipAccessor' aria-label="Help?" aria-expanded="false" aria-labelledby="mytooltipbutton-4 mytooltip-4" >?</button></legend>
<label for="check-1"><input type="checkbox" id="check-1" name="checklist-1"> Check box 1
</label>
<label for="check-2"><input type="checkbox" id="check-2" name="checklist-1"> Check box 2
</label>
<label for="check-3"><input type="checkbox" id="check-3" name="checklist-1"> Check box 3
</label>
<div id="mytooltip-4" class='js-tooltipContent css-tooltip' role="tooltip" style="visibility: hidden;"><b>Single Check Box</b><br>This is the tooltip text content.</div>
</fieldset>
<p>Selection of the appropriate attributes would need to be adjusted for this structure. The JS relies on specific elements being in specific locations. The JS may need to be rewritten to be smarter than it is right now.</p>
</div>
<div class="container">
<fieldset>
<legend class="js-tooltipLabel js-tooltipUmbrella">Some radio list w/Single Help <button id="mytooltipbutton-5" class='js-tooltipAccessor' aria-label="Help?" aria-expanded="false" aria-labelledby="mytooltipbutton-5 mytooltip-5" >?</button></legend>
<label for="radio-1"><input type="radio" id="radio-1" name="radiolist-1"> Check box 1
</label>
<label for="radio-2"><input type="radio" id="radio-2" name="radiolist-1"> Check box 2
</label>
<label for="radio-3"><input type="radio" id="radio-3" name="radiolist-1"> Check box 3
</label>
<div id="mytooltip-5" class='js-tooltipContent css-tooltip' role="tooltip" style="visibility: hidden;"><b>Single Check Box</b><br>This is the tooltip text content.</div>
</fieldset>
<p>Selection of the appropriate attributes would need to be adjusted for this structure. The JS relies on specific elements being in specific locations. The JS may need to be rewritten to be smarter than it is right now.</p>
</div>
<div class="container">
<fieldset>
<legend>Some check list w/Multiple Help</legend>
<div class="hx-form-group hx-form-horizontal">
<label for="check-1b" class="js-tooltipLabel"><input type="checkbox" id="check-1b" name="checklist-2"> Check box 1 <button id="mytooltipbutton-6" class='js-tooltipAccessor' aria-label="Help?" aria-expanded="false" aria-labelledby="mytooltipbutton-6 mytooltip-6" >?</button>
</label>
<div id="mytooltip-6" class='js-tooltipContent css-tooltip' role="tooltip" style="visibility: hidden;"><b>Single Check Box</b><br>This is the tooltip text content for the first checkbox.</div>
</div>
<div class="hx-form-group hx-form-horizontal">
<label for="check-2b" class="js-tooltipLabel"><input type="checkbox" id="check-2b" name="checklist-2"> Check box 2 <button id="mytooltipbutton-7" class='js-tooltipAccessor' aria-label="Help?" aria-expanded="false" aria-labelledby="mytooltipbutton-7 mytooltip-7" >?</button>
</label>
<div id="mytooltip-7" class='js-tooltipContent css-tooltip' role="tooltip" style="visibility: hidden;"><b>Single Check Box</b><br>This is the tooltip text content for the second check box.</div>
</div>
<div class="hx-form-group hx-form-horizontal">
<label for="check-3b" class="js-tooltipLabel"><input type="checkbox" id="check-3b" name="checklist-2"> Check box 3 <button id="mytooltipbutton-8" class='js-tooltipAccessor' aria-label="Help?" aria-expanded="false" aria-labelledby="mytooltipbutton-8 mytooltip-8" >?</button>
</label>
<div id="mytooltip-8" class='js-tooltipContent css-tooltip' role="tooltip" style="visibility: hidden;"><b>Single Check Box</b><br>This is the tooltip text content for the third check box.</div>
</div>
</fieldset>
</div>
<div class="container">
<label for="textarea-1" class="css-stackLabel js-tooltipLabel">Text Area
<button id="mytooltipbutton-9" class='js-tooltipAccessor' aria-label="Help?" aria-expanded="false" aria-labelledby="mytooltipbutton-9 mytooltip-9" >?</button></label>
<textarea id="textarea-1" name="textarea-1"></textarea>
<div id="mytooltip-9" class='js-tooltipContent css-tooltip' role="tooltip" style="visibility: hidden;"><b>Text Field</b><br>This is the tooltip text content.</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script type="text/javascript">
// When clicking, if the tool tip is open, close it.
// Otherwise, open the tooltip if moused over, or focused
// but also close the tooltip if moused out, loss of focus, or ESC is pressed
$(document).ready(function() {
// Set globals
var ariaElement = '';
var tooltipElement = '';
// Get the IDs of the elements we're working with:
// The button and the tool tipl container
function getElements(thisObj) {
// The aria-labeledby attribute can have multiple values
// Get those values and drop them into a usable array
var ariaValues = thisObj.attr('aria-labelledby');
var ariaValuesArray = ariaValues.split(" ");
// Get the ID of the tooltip help div.
// This should always be the first div with a specific helper class after
// the element (usually the label) where the button was interacted with.
// We give this container a class, because there's a chance the button
// is not cotnained in a label
var idValues = thisObj.closest('.js-tooltipLabel').nextAll('.js-tooltipContent').attr('id');
// The tooltip help div might have multiple values in its ID attribute, so create an array
var idValuesArray = idValues.split(" ");
// Find a match between the arrays
var commonValue = $.grep(ariaValuesArray, function(element) {
return $.inArray(element, idValuesArray ) !== -1;
});
// The commonValue is the value of the match between buttons 'aria-labelledby' and the tooltip 'ID'
// Manipulate the tooltip div with the commonValue as the ID
// The aria attribute may have multiple values, so we need to find if
// the value exists with a wild card
ariaElement = "button[aria-labelledby*='" + commonValue + "']";
tooltipElement = "div#" + commonValue;
}
function clickTooltip(thisObj) {
getElements(thisObj);
if ($(ariaElement).attr('aria-expanded') == 'true') {
// If the tool tip is open, close it
$(tooltipElement).css('visibility','hidden');
$(ariaElement).attr('aria-expanded','false');
} else {
// Show the tooltip
$(tooltipElement).css('visibility','visible');
$(ariaElement).attr('aria-expanded','true');
if ($(ariaElement).parents('legend').length == 1) {
alert('button is in a legend');
}
}
}
function openTooltip(thisObj) {
getElements(thisObj);
$(tooltipElement).css('visibility','visible');
$(ariaElement).attr('aria-expanded','true');
// If the button is in a legend element
// the tool tip likely refers to multiple fields
// place the tooltip in a more global position
// relative to the button
if ($(ariaElement).parents('.js-tooltipUmbrella').length == 1) {
// Get the height and left edge values of the button
var thisButton = $(ariaElement);
var thisButtonHeight = thisButton.outerHeight();
var thisButtonLeft = thisButton.position().left;
// Set the position of the tooltip
var positionStyles = {'left':thisButtonLeft,'top':thisButtonHeight};
$(tooltipElement).css(positionStyles);
} // This might be more useful for other implementations
// Could globalize this with a utility class instead of using legend - just choose to hook off of the button parent
}
function closeTooltip(thisObj) {
getElements(thisObj);
$(tooltipElement).css('visibility','hidden');
$(ariaElement).attr('aria-expanded','false');
}
// For keypress, we need to find out which, if any tooltips are open and close them
function closeAllTooltips() {
// Find out which button is focused
var $focused = $(':focus');
closeTooltip($focused);
}
// Determine whether or not the trigger has been clicked
$('.js-tooltipAccessor').click(function() {
$(this).data('clicked', true);
});
// Capture the events, take the appropriate action
if ($('.js-tooltipAccessor').data('clicked')) {
clickTooltip($(this));
} else {
$('.js-tooltipAccessor').mouseover(function() {
openTooltip($(this));
});
$('.js-tooltipAccessor').mouseout(function() {
closeTooltip($(this));
});
$('.js-tooltipAccessor').focus(function() {
openTooltip($(this));
});
$('.js-tooltipAccessor').blur(function() {
closeTooltip($(this));
});
$(document).keydown(function(e) {
if (e.keyCode == 27) {
closeAllTooltips();
}
});
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment