-
Notifications
You must be signed in to change notification settings - Fork 340
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor Details and Button components #761
Changes from 4 commits
7e3df17
70b54e0
575a1fa
cf90626
b872a54
9b1b275
a2ba8e2
7ec56b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,18 +8,17 @@ | |
* the 'polyfill' will be automatically initialised | ||
*/ | ||
import '../../vendor/polyfills/Function/prototype/bind' | ||
import { addEvent, removeEvent, charCode, preventDefault } from '../../common' | ||
import '../../vendor/polyfills/Event' // addEventListener and event.target normaliziation | ||
import { generateUniqueID } from '../../common.js' | ||
|
||
var KEY_ENTER = 13 | ||
var KEY_SPACE = 32 | ||
|
||
// Create a flag to know if the browser supports navtive details | ||
var NATIVE_DETAILS = typeof document.createElement('details').open === 'boolean' | ||
|
||
function Details () { | ||
// Create a flag so we can prevent the initialisation | ||
// function firing from both DOMContentLoaded and window.onload | ||
this.INITIALISED = false | ||
function Details ($module) { | ||
this.$module = $module | ||
} | ||
|
||
/** | ||
|
@@ -28,13 +27,14 @@ function Details () { | |
* @param {function} callback function | ||
*/ | ||
Details.prototype.handleKeyDown = function (node, callback) { | ||
addEvent(node, 'keypress', function (event, target) { | ||
node.addEventListener('keypress', function (event) { | ||
var target = event.target | ||
// When the key gets pressed - check if it is enter or space | ||
if (charCode(event) === KEY_ENTER || charCode(event) === KEY_SPACE) { | ||
if (event.keyCode === KEY_ENTER || event.keyCode === KEY_SPACE) { | ||
if (target.nodeName.toLowerCase() === 'summary') { | ||
// Prevent space from scrolling the page | ||
// and enter from submitting a form | ||
preventDefault(event) | ||
event.preventDefault() | ||
// Click to let the click event do all the necessary action | ||
if (target.click) { | ||
target.click() | ||
|
@@ -47,15 +47,17 @@ Details.prototype.handleKeyDown = function (node, callback) { | |
}) | ||
|
||
// Prevent keyup to prevent clicking twice in Firefox when using space key | ||
addEvent(node, 'keyup', function (event, target) { | ||
if (charCode(event) === KEY_SPACE) { | ||
node.addEventListener('keyup', function (event) { | ||
var target = event.target | ||
if (event.keyCode === KEY_SPACE) { | ||
if (target.nodeName.toLowerCase() === 'summary') { | ||
preventDefault(event) | ||
event.preventDefault() | ||
} | ||
} | ||
}) | ||
|
||
addEvent(node, 'click', function (event, target) { | ||
node.addEventListener('click', function (event) { | ||
var target = event.target | ||
callback(event, target) | ||
}) | ||
} | ||
|
@@ -76,81 +78,65 @@ Details.prototype.getAncestor = function (node, match) { | |
return node | ||
} | ||
|
||
/** | ||
* Initialise the script on a list of details elements in a container | ||
* @param {object} list of details elements | ||
* @param {string} container where to look for details elements | ||
*/ | ||
Details.prototype.initDetails = function (list, container) { | ||
container = container || document.body | ||
// If this has already happened, just return | ||
// else set the flag so it doesn't happen again | ||
if (this.INITIALISED) { | ||
Details.prototype.init = function () { | ||
var $module = this.$module | ||
|
||
if (!$module) { | ||
return | ||
} | ||
this.INITIALISED = true | ||
// Get the collection of details elements, but if that's empty | ||
// then we don't need to bother with the rest of the scripting | ||
if ((list = container.getElementsByTagName('details')).length === 0) { | ||
|
||
var details = $module | ||
|
||
// Save shortcuts to the inner summary and content elements | ||
$module.__summary = $module.getElementsByTagName('summary').item(0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a blocker and I know they were already there, but we're using double underscores only on this component when storing references. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Forget about that, you already changed it in the next commit. |
||
$module.__content = $module.getElementsByTagName('div').item(0) | ||
|
||
// If <details> doesn't have a <summary> and a <div> representing the content | ||
// it means the required HTML structure is not met so the script will stop | ||
if (!$module.__summary || !$module.__content) { | ||
return | ||
} | ||
// else iterate through them to apply their initial state | ||
var n = list.length | ||
var i = 0 | ||
for (i; i < n; i++) { | ||
var details = list[i] | ||
|
||
// Save shortcuts to the inner summary and content elements | ||
details.__summary = details.getElementsByTagName('summary').item(0) | ||
details.__content = details.getElementsByTagName('div').item(0) | ||
|
||
// If <details> doesn't have a <summary> and a <div> representing the content | ||
// it means the required HTML structure is not met so the script will stop | ||
if (!details.__summary || !details.__content) { | ||
return | ||
} | ||
|
||
// If the content doesn't have an ID, assign it one now | ||
// which we'll need for the summary's aria-controls assignment | ||
if (!details.__content.id) { | ||
details.__content.id = 'details-content-' + i | ||
} | ||
// If the content doesn't have an ID, assign it one now | ||
// which we'll need for the summary's aria-controls assignment | ||
if (!$module.__content.id) { | ||
$module.__content.id = 'details-content-' + generateUniqueID() | ||
} | ||
|
||
// Add ARIA role="group" to details | ||
details.setAttribute('role', 'group') | ||
// Add ARIA role="group" to details | ||
$module.setAttribute('role', 'group') | ||
|
||
// Add role=button to summary | ||
details.__summary.setAttribute('role', 'button') | ||
// Add role=button to summary | ||
$module.__summary.setAttribute('role', 'button') | ||
|
||
// Add aria-controls | ||
details.__summary.setAttribute('aria-controls', details.__content.id) | ||
// Add aria-controls | ||
$module.__summary.setAttribute('aria-controls', $module.__content.id) | ||
|
||
// Set tabIndex so the summary is keyboard accessible for non-native elements | ||
// http://www.saliences.com/browserBugs/tabIndex.html | ||
if (!NATIVE_DETAILS) { | ||
details.__summary.tabIndex = 0 | ||
} | ||
// Set tabIndex so the summary is keyboard accessible for non-native elements | ||
// http://www.saliences.com/browserBugs/tabIndex.html | ||
if (!NATIVE_DETAILS) { | ||
$module.__summary.tabIndex = 0 | ||
} | ||
|
||
// Detect initial open state | ||
var openAttr = details.getAttribute('open') !== null | ||
if (openAttr === true) { | ||
details.__summary.setAttribute('aria-expanded', 'true') | ||
details.__content.setAttribute('aria-hidden', 'false') | ||
} else { | ||
details.__summary.setAttribute('aria-expanded', 'false') | ||
details.__content.setAttribute('aria-hidden', 'true') | ||
if (!NATIVE_DETAILS) { | ||
details.__content.style.display = 'none' | ||
} | ||
// Detect initial open state | ||
var openAttr = $module.getAttribute('open') !== null | ||
if (openAttr === true) { | ||
$module.__summary.setAttribute('aria-expanded', 'true') | ||
$module.__content.setAttribute('aria-hidden', 'false') | ||
} else { | ||
$module.__summary.setAttribute('aria-expanded', 'false') | ||
$module.__content.setAttribute('aria-hidden', 'true') | ||
if (!NATIVE_DETAILS) { | ||
$module.__content.style.display = 'none' | ||
} | ||
|
||
// Create a circular reference from the summary back to its | ||
// parent details element, for convenience in the click handler | ||
details.__summary.__details = details | ||
} | ||
|
||
// Create a circular reference from the summary back to its | ||
// parent details element, for convenience in the click handler | ||
$module.__summary.__details = details | ||
|
||
// Bind an event to handle summary elements | ||
this.handleKeyDown(container, function (event, summary) { | ||
this.handleKeyDown($module, function (event, summary) { | ||
if (!(summary = this.getAncestor(summary, 'summary'))) { | ||
return true | ||
} | ||
|
@@ -187,19 +173,9 @@ Details.prototype.stateChange = function (summary) { | |
* @param {object} node element | ||
*/ | ||
Details.prototype.destroy = function (node) { | ||
removeEvent(node, 'click') | ||
} | ||
|
||
/** | ||
* Initialise an event listener for DOMContentLoaded at document level | ||
* and load at window level | ||
* | ||
* If the first one fires it will set a flag to block the second one | ||
* but if it's not supported then the second one will fire | ||
*/ | ||
Details.prototype.init = function () { | ||
addEvent(document, 'DOMContentLoaded', this.initDetails.bind(this)) | ||
addEvent(window, 'load', this.initDetails.bind(this)) | ||
node.removeEventListener('keypress') | ||
node.removeEventListener('keyup') | ||
node.removeEventListener('click') | ||
} | ||
|
||
export default Details |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a simpler version of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be a bit of an overkill for what we need, but I get is trying to follow rfc4122, so it should be ok.