Skip to content

Instantly share code, notes, and snippets.

@addyosmani
Forked from remy/details.js
Created September 12, 2011 12:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save addyosmani/1211151 to your computer and use it in GitHub Desktop.
Save addyosmani/1211151 to your computer and use it in GitHub Desktop.
Add <details> support - includes stylesheet
/*
better details support testing
var isDetailsSupported = (function(doc) {
var el = doc.createElement('details'),
fake,
root,
diff;
if (!('open' in el)) {
return false;
}
root = doc.body || (function() {
var de = doc.documentElement;
fake = true;
return de.insertBefore(doc.createElement('body'), de.firstElementChild || de.firstChild);
}());
el.innerHTML = '<summary>a</summary>b';
el.style.display = 'block';
root.appendChild(el);
diff = el.offsetHeight;
el.open = true;
diff = diff != el.offsetHeight;
root.removeChild(el);
if (fake) {
root.parentNode.removeChild(root);
}
return diff;
}(document));
*/
/**
* 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);
@mathiasbynens
Copy link

Hey, that’s my feature test! :) Cool. So does this sans-jQuery fallback work in IE now, or what?

@addyosmani
Copy link
Author

It is your feature test indeed! :) I'm currently holding out on updating Remy's fallback to work cross-browser as I wasn't sure whether he'd done further work on it since. Pinged him but waiting on a reply :) Might just go ahead and work on it eitherway!

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