Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Add <details> support - includes stylesheet
/* Author: Remy Sharp / @rem - adds <details> support to the browser */
(function (window, document) {
// 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'),
i = details.length,
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);
}
}
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);
// minified
(function(j,c){function m(a){if(a.firstChild.nodeName!="#text")return a.firstChild;else{a=a.firstChild;do a=a.nextSibling;while(a&&a.nodeName=="#text");return a||null}}function k(a){var b=a.nodeName.toUpperCase();return b=="DETAILS"?false:b=="SUMMARY"?true:k(a.parentNode)}function l(a){var b=a.type=="keypress",e=a.target||a.srcElement;if(b||k(e)){if(b){b=a.which||a.keyCode;if(!(b==32||b==13))return}this.getAttribute("open")===null?this.setAttribute("open","open"):this.removeAttribute("open");setTimeout(function(){c.body.className=
c.body.className},13);if(b){a.preventDefault&&a.preventDefault();return false}}}function n(){var a=c.createElement("style"),b=c.getElementsByTagName("head")[0],e=a.innerText===undefined?"textContent":"innerText",d=["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;}"];h=d.length;a[e]=d.join("\n");b.insertBefore(a,b.firstChild)}
var i=function(){return c.addEventListener?function(a,b,e){if(a&&a.nodeName||a===j)a.addEventListener(b,e,false);else if(a&&a.length)for(var d=0;d<a.length;d++)i(a[d],b,e)}:function(a,b,e){if(a&&a.nodeName||a===j)a.attachEvent("on"+b,function(){return e.call(a,j.event)});else if(a&&a.length)for(var d=0;d<a.length;d++)i(a[d],b,e)}}(),g=c.getElementsByTagName("details"),h=g.length,f=null;for(c.createElement("summary").appendChild(c.createTextNode("Details"));h--;){f=m(g[h]);if(!(f!=null&&f.nodeName.toUpperCase()==
"SUMMARY")){f=c.createElement("summary");f.appendChild(c.createTextNode("Details"));g[h].firstChild?g[h].insertBefore(f,g[h].firstChild):g[h].appendChild(f)}f.legend=true;f.tabIndex=0}c.createElement("details");i(g,"click",l);i(g,"keypress",l);n()})(window,document);

It should probably be noted that this solution doesn’t work with <details> elements that contain only text (an edge case, admittedly).

Owner

remy commented Jul 4, 2010

Hmm - it is designed to...

Owner

remy commented Jul 6, 2010

Fixed....though I think I need a shower now :-S

I like the idea of wrapping direct child text nodes in a <text> element. It’s better than wrapping them in a <div>, because that way, any CSS for details div {} will get applied. Clever!

BTW, I completely agree with your comment on line132 :)

I’ve updated the demo page for your snippet: http://jsbin.com/akadi4/2 IE8 and below seems to throw an error though (this happened before the update as well).

codepo8 commented Sep 7, 2010

Urgh testing for nodeName #text instead of nodeType?

Yaffle commented Nov 15, 2011

WTF?
why not to use event delegation ???

Owner

remy commented Nov 15, 2011

@Yaffle WTF WTF? Why WTF?

Yaffle commented Nov 15, 2011

i think it's better to use event delegation, so dynamically inserted

will be supported too, also you will be able place script at any place in document(not only before )

Owner

remy commented Nov 15, 2011

@Yaffle ... so fork the script right, instead of getting all upset. Make it better - then we'll use your script and that's better for everyone.

Yaffle commented Nov 15, 2011

@remy, ok, i will try a little later.
Also some notes:

  1. seems, "keydown" event should be used an
  2. while checking event.keyCode, event.ctrlKey, event.shiftKey, event.altKey should also be checked (some user agents may have shortcuts or something for such combinations..., so to not preventDefault action for this combinations, it's better to check for !event.ctrlKey && !event.shiftKey && !event.altKey and may be event.metaKey...)

Wow. Awesome! I came up with a very similar approach to this and was looking for a solution to the text node problem and BAM! Remy beat me to it (by like year and a half!). It's nice to be vindicated in my approach though. Thanks for that :)

Rock.

Yaffle, you can try this script (supports dynamic inclusion): https://github.com/kapitancho/logifill-details

I implemented Element.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).

kgmstwo 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 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