Yet Another Collapser.js

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

  1. 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.

  2. Use text as collapsing/expanding controls (widgets?):
    $c = $('<span class="collapser">↧</span>');
  3. Handle clicks on controls:
    $c.click(function() {$(this).next().toggle();});
  4. Insert a control before the collapsible:
    var $o = $(this);
    $o.before(c);
  5. Render as initially collapsed if HTML element marked class="collapsed":
    if ($o.hasClass("collapsed"))
    	$o.toggle(false);
  6. 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

  1. 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;
    });
  2. 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}
  3. 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?

  4. Make selector a (global) constant so can tweak which elements to collapse:
    window.COLLAPSIBLES = "ol,ul,xmp,pre,blockquote";
  5. Markup to override behavior — add this class to prevent element from collapsing:
    $("li").children(COLLAPSIBLES).filter(":not(.collapser-ignore)").each(...);
  6. Configurable roots for selectors?
    $(ROOTS).children(COLLAPSIBLES) ...

    But, nicer if used as a plugin — just apply on things:

    $(".entry-content ").collapser({collapsibles: "ol"});
  7. Toggle the arrow direction:
    .collapser::before{content:"↥"}
    .collapser.collapsed::before{content:"↧"}
    $(this).toggleClass("collapsed").nextAll(COLLAPSIBLES).filter(":not(.collapser-ignore)").first().toggle();
  8. jQuery plugin: see CoffeeScript below.
  9. Run on document.ready automatically?

CoffeeScript

  1. How to encapsulate as jQuery plugin in CoffeeScript? See Cookbook for the short version, MiniBoilerplate for the long one.

Bookmarklet

WordPress

  1. Integrating into this site’s theme, Stencil?
  2. WP plugin?

Prior art?

  1. jQuery Collapse: complicated plugin, ~4KiB.



Comments are closed.