Long, deeply nested lists, get unwieldy. I want to interactively collapse/expand them to see an entire level without its sub-lists. This could be a nice enough — simple but not stupid — exercise in programming JavaScript, DOM, jQuery, etc. ;o)
DTSTTCPW
- Collect all elements I’d want to collapse/expand:
$("li>ol,li>ul,li>pre,li>xmp").each(...);
I only want to hide nested stuff, thus the “li>*” direct descendant selectors.
this
will be each DOM element the function is called for.$(this)
“wraps” it in a jQuery object, and we keep that to repeadely use jQuery’s methods on the element. - Use text as collapsing/expanding controls (widgets?):
$c = $('<span class="collapser">↧</span>');
- Handle clicks on controls:
$c.click(function() {$(this).next().toggle();});
- Insert a control before the collapsible:
var $o = $(this); $o.before(c);
- Render as initially collapsed if HTML element marked
class="collapsed"
:if ($o.hasClass("collapsed")) $o.toggle(false);
- Style controls, and a bit wider so easier clicking target:
.collapser{padding:0 .4em}
The complete — sweetly short — script would be something like this:
<script> $(function() { // Do this when DOM ready. $("ol,ul,pre,xmp").each(function() { // Collapse these. var $c = $("<span>↧</span>"); // Create another widget. $c.click(function() {$(this).next().toggle();}); // Show/hide when clicked. var $o = $(this); // Ref' to wrapped DOM element. $o.before($c); // Insert widget into DOM. if ($o.hasClass("collapsed")) // Initial state. $o.toggle(false); }); }); </script> <style>.collapser{padding:0 .4em}</style>
DOM traversal
Easier to find the control at the beginning of the line of list items, so let’s move it there. But, what if an item contains several collapsibles?
var $p = $o.parent(); if (! $p.children().first().is(".collapser")) // Insert control before first sibling, if $o is first collapsible. $p.prepend($c); else // Otherwise, insert just before $o. $o.before($c);
And the click handler needs to be modified to look a little farther for its collapsible:
function() { $(this).nextAll("ol,ul,pre,xmp").first().toggle(); }
Loading and dependencies
…
jQuery and tweaks
- Don’t re-create same (anonymous) handler per widget:
$(document).on("click", ".collapser", function() { // Listen to clicks on controls. $(this).nextAll("ol,ul,pre,xmp").first().toggle(); return false; });
- Move the widget’s content from HTML to CSS, so can style it, like maybe use an icon font (Webfont) instead of standard Unicode glyphs:
var $c = $('<span class="collapser"></span>');
.collapser::before{content:"↧"} .collapser:hover{background-color:lightgreen}
- Using
span
instead of anchor (a
), so mouse pointer doesn’t change, giving no indication that widget is clickable..collapser{cursor:pointer}
Maybe
button
would be more semantic? But would rather avoid browser’s (OS’s) widgets? - Make selector a (global) constant so can tweak which elements to collapse:
window.COLLAPSIBLES = "ol,ul,xmp,pre,blockquote";
- Markup to override behavior — add this class to prevent element from collapsing:
$("li").children(COLLAPSIBLES).filter(":not(.collapser-ignore)").each(...);
- Configurable roots for selectors?
$(ROOTS).children(COLLAPSIBLES) ...
But, nicer if used as a plugin — just apply on things:
$(".entry-content ").collapser({collapsibles: "ol"});
- Toggle the arrow direction:
.collapser::before{content:"↥"} .collapser.collapsed::before{content:"↧"}
$(this).toggleClass("collapsed").nextAll(COLLAPSIBLES).filter(":not(.collapser-ignore)").first().toggle();
- jQuery plugin: see CoffeeScript below.
- Run on document.ready automatically?
…
CoffeeScript
- How to encapsulate as jQuery plugin in CoffeeScript? See Cookbook for the short version, MiniBoilerplate for the long one.
…
Bookmarklet
…
WordPress
- Integrating into this site’s theme, Stencil?
- WP plugin?
Prior art?
- jQuery Collapse: complicated plugin, ~4KiB.
…