<!-- C. Alex. North-Keys -->
<!-- http://www.talisman.org/~erlkonig/@outline.js -->
<!-- Prereq: W3C standard DOM model, such as that in Mozilla -->
<!-- Incept: 2004-01-24 16:14:01 CST (Jan Sat) 1074982441 -->

<!-- Control the display of document DIV nodes with a class of "content" -->
<!--   in webpages structured as classical nested outline documents -->
<!-- Outline is always fully displayed, but content may be hidden or shown  -->

<!-- Key advantage: document nodes do not require unique ids -->

<!-- NOTE: Mark top level OLs as class="outline" if non-outlines coexist -->
<!-- Document structure prerequisites: this structure repeats/recurses:  -->
<!-- (opt+ segments may be omitted or present in a series)               -->
<!-- req   <ol><li><h2> SECTION-TITLE </h2>     -->
<!-- opt+      ... STUFF ...                                             -->
<!-- opt+         [[ RECURSE WITH THE <ol>...</ol> STRUCTURE SHOWN ]]    -->
<!-- opt+      ... STUFF ...                                             -->
<!-- req       </li>                                                     -->
<!-- req   </ol>                                                         -->

<!-- prelimary work for getting a single collection of tags of a tag -->
function GetElementsByTags(start, tags)
// argexample: ...Tags(document, ['div','ol'])
{
	var items = [];
	var i = items.length;
	for(var ti = 0 ; ti < tags.length ; ++ti) {
		var a = start.getElementsByTagName(tags[ti]);
		for(var ai = 0 ; ai < a.length ; ++ai) items[i++] = a[ai];
	}
	return items;
}

<!-- return combined lists -->
function ListsCombine(src_lists)
{
	var all = [];
	var i = all.length;
	for(var si = 0 ; si < src_lists.length ; ++si) {
		var a = src_lists[si];
		for(var ai = 0 ; ai < a.length ; ++ai) all[i++] = a[ai];
	}
	return all;
}

<!-- return whether SOUGHT is in list ITEMS -->
function InSet(sought, items)
// example: Inset('b', [ 'a', 'b', 'c' ])
{
	for(var i = 0 ; i < items.length ; ++i)
		if(sought == items[i])
			return true;
	return false;
}

<!-- collect below START all top-level elements in TAGS, pruning at each -->
function GetElementByTagsAndPrune(start, tags)
// example: GetElementByTagsAndPrune(document.body, [ 'ol', 'li' ] )
{
	var items = [];
	var i = items.length;
	for(var child = start.firstChild ; child ; child = child.nextSibling) {
		if(child.nodeType == Node.ELEMENT_NODE) {
			if(InSet(child.tagName.toLowerCase(), tags))
				items[i++] = child;  // don't recurse, we don't want nesteds
			else {
				var more = GetElementByTagsAndPrune(child, tags);
				for(var mi = 0 ; mi < more.length ; ++mi)
					items[i++] = more[mi];
	}}}
	return items;
}

<!-- report whether ITEM is a title of an LI (which should stay visible) -->
function OutlineElementIsTitle(item)
{
	return (   (item.tagName.toLowerCase() == "h2")
		    && (item.parentNode.tagName.toLowerCase() == "li")
			&& (item.parentNode.firstChild == item));
}

<!-- install onclick events into H2 elements which are LI->firstChild -->
function OutlineInit()
{
	var items = document.getElementsByTagName("h2");
	for(var i = 0 ; i < items.length ; ++i) {
		if(OutlineElementIsTitle(items[i]))
		{
			items[i].onclick = new Function("Outline(this.parentNode)");
			// might be able to add handling of 1.2.3.4 style here....(?)
		}
	}
}

<!-- hide OL_OR_UL descendents but for outlines & 1st childs which are H2s -->
function OutlineContract(ol_or_ul)
{	// lists should only contain line items, so let's get them
	var items = GetElementByTagsAndPrune(ol_or_ul, ["li"]);    // OL
	for(var ii = 0 ; ii < items.length ; ++ii) {
		var child = items[ii].firstChild;						// OL > LI
		if(   (child.nodeType == Node.ELEMENT_NODE)
		   && (child.tagName.toLowerCase() == "h2"))			// OL > LI > H2
		{
			child = child.nextSibling;				// not to be hidden
		} else {
			ol_or_ul.style.display = "none";		// noncomformant outline
		}
		for( /*prior*/ ; child ; child = child.nextSibling) {
			if(child.nodeType == Node.ELEMENT_NODE) {
				if(InSet(child.tagName.toLowerCase(), ["ol"]))
					OutlineContract(child);
				else
					child.style.display = "none";
			}
		}			
	}
}

<!-- start the hiding process on upper level OL/UL with class of "outline" -->
function OutlineContractAll(e)
{
	var ols = GetElementByTagsAndPrune(document.body, ["ol", "ul"]);
	var ols_contracted = 0;
	var oi;
	for(oi = 0 ; oi < ols.length ; ++oi) {
		if(   (ols[oi].nodeType == Node.ELEMENT_NODE)
		   && (ols[oi].className == "outline"))
		{
			OutlineContract(ols[oi]);
			++ols_contracted;
		}
	}
	if(0 == ols_contracted)	// hmm, no OL/UL marked?  Default to all toplevels.
		for(oi = 0 ; oi < ols.length ; ++oi)
			if(ols[oi].nodeType == Node.ELEMENT_NODE)
				OutlineContract(ols[oi]);
}

<!-- show all content -->
function OutlineExpandAll(e)
{
	var divs = document.body.getElementsByTagName("*");
	for(var i = 0 ; i < divs.length ; ++i)
		divs[i].style.display = "";
}

<!-- show content in immediate children -->
function OutlineExpandChildren(node)
{
	for(var child = node.firstChild ; child ; child = child.nextSibling) {
		if(child.nodeType == Node.ELEMENT_NODE)
			child.style.display = "";
		<!-- may elect to restrict the recursion from sub-lists elements -->
		OutlineExpandChildren(child);
	}
}

<!-- focus outline on the clicked node, showing context in ancesters' -->
<!--   content nodes as well as all descendent content nodes. -->
<!-- apply this function to an item via:  onclick="Outline(event)" -->
function Outline(node)
{
	OutlineContractAll(document.body);
	OutlineExpandChildren(node);
}

<!-- - - - - - - - - - - - - - - - - - eof - - - - - - - - - - - - - - - - -->
