Skip to content

Instantly share code, notes, and snippets.

@remy
Created April 18, 2010 22:28
Show Gist options
  • Save remy/370590 to your computer and use it in GitHub Desktop.
Save remy/370590 to your computer and use it in GitHub Desktop.
Add <details> support - includes stylesheet
/**
* Note that this script is intended to be included at the *end* of the document, before </body>
*/
(function (window, document) {
if ('open' in document.createElement('details')) return;
// made global by myself to be reused elsewhere
var addEvent = (function () {
if (document.addEventListener) {
return function (el, type, fn) {
if (el && el.nodeName || el === window) {
el.addEventListener(type, fn, false);
} else if (el && el.length) {
for (var i = 0; i < el.length; i++) {
addEvent(el[i], type, fn);
}
}
};
} else {
return function (el, type, fn) {
if (el && el.nodeName || el === window) {
el.attachEvent('on' + type, function () { return fn.call(el, window.event); });
} else if (el && el.length) {
for (var i = 0; i < el.length; i++) {
addEvent(el[i], type, fn);
}
}
};
}
})();
/** details support - typically in it's own script */
// find the first /real/ node
function firstNode(source) {
var node = null;
if (source.firstChild.nodeName != "#text") {
return source.firstChild;
} else {
source = source.firstChild;
do {
source = source.nextSibling;
} while (source && source.nodeName == '#text');
return source || null;
}
}
function isSummary(el) {
var nn = el.nodeName.toUpperCase();
if (nn == 'DETAILS') {
return false;
} else if (nn == 'SUMMARY') {
return true;
} else {
return isSummary(el.parentNode);
}
}
function toggleDetails(event) {
// more sigh - need to check the clicked object
var keypress = event.type == 'keypress',
target = event.target || event.srcElement;
if (keypress || isSummary(target)) {
if (keypress) {
// if it's a keypress, make sure it was enter or space
keypress = event.which || event.keyCode;
if (keypress == 32 || keypress == 13) {
// all's good, go ahead and toggle
} else {
return;
}
}
var open = this.getAttribute('open');
if (open === null) {
this.setAttribute('open', 'open');
} else {
this.removeAttribute('open');
}
// this.className = open ? 'open' : ''; // Lame
// trigger reflow (required in IE - sometimes in Safari too)
setTimeout(function () {
document.body.className = document.body.className;
}, 13);
if (keypress) {
event.preventDefault && event.preventDefault();
return false;
}
}
}
function addStyle() {
var style = document.createElement('style'),
head = document.getElementsByTagName('head')[0],
key = style.innerText === undefined ? 'textContent' : 'innerText';
var rules = ['details{display: block;}','details > *{display: none;}','details.open > *{display: block;}','details[open] > *{display: block;}','details > summary:first-child{display: block;cursor: pointer;}','details[open]{display: block;}'];
i = rules.length;
style[key] = rules.join("\n");
head.insertBefore(style, head.firstChild);
}
var details = document.getElementsByTagName('details'),
wrapper,
i = details.length,
j,
first = null,
label = document.createElement('summary');
label.appendChild(document.createTextNode('Details'));
while (i--) {
first = firstNode(details[i]);
if (first != null && first.nodeName.toUpperCase() == 'SUMMARY') {
// we've found that there's a details label already
} else {
// first = label.cloneNode(true); // cloned nodes weren't picking up styles in IE - random
first = document.createElement('summary');
first.appendChild(document.createTextNode('Details'));
if (details[i].firstChild) {
details[i].insertBefore(first, details[i].firstChild);
} else {
details[i].appendChild(first);
}
}
// this feels *really* nasty, but we can't target details :text in css :(
j = details[i].childNodes.length;
while (j--) {
if (details[i].childNodes[j].nodeName === '#text' && (details[i].childNodes[j].nodeValue||'').replace(/\s/g, '').length) {
wrapper = document.createElement('text');
wrapper.appendChild(details[i].childNodes[j]);
details[i].insertBefore(wrapper, details[i].childNodes[j]);
}
}
first.legend = true;
first.tabIndex = 0;
}
// trigger details in case this being used on it's own
document.createElement('details');
addEvent(details, 'click', toggleDetails);
addEvent(details, 'keypress', toggleDetails);
addStyle();
})(window, document);
@chris-morgan
Copy link

chris-morgan commented Feb 10, 2014

I implemented Element.prototype.open in https://gist.github.com/chris-morgan/8909427. Its browser support, however, will be limited to those supporting Object.defineProperty correctly, which is IE9 (http://kangax.github.io/es5-compat-table/#Object.defineProperty suggests IE8 won't work but I don't know).

Copy link

ghost commented Nov 22, 2014

I added a check so that it will work for nested details tags in https://gist.github.com/kgmstwo/53e189d87b41ac7743ee.

@jlgrall
Copy link

jlgrall commented Aug 22, 2016

Doesn't work if there is a form inside the details: all space and enter keys are intercepted and blocked by this polyfill.

You may also get in trouble if in your details, you have any buttons or other active elements that you want to activate with space or enter keys.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment