190 lines
7.4 KiB
JavaScript
190 lines
7.4 KiB
JavaScript
/*
|
|
* Expandable list implementation.
|
|
* by David Lindquist <first name><at><last name><dot><net>
|
|
* See:
|
|
* http://www.gazingus.org/html/DOM-Scripted_Lists_Revisited.html
|
|
* Modifies lists so that sublists can be hidden and shown by means of
|
|
* a switch. The switch is a node inserted into the DOM tree as the
|
|
* first child of the list item containing the sublist.
|
|
*/
|
|
|
|
// The script will only be applied to lists containing this class name,
|
|
// e.g.: <ul class="foo expandable">...</ul>.
|
|
var CLASS_NAME = "expandable";
|
|
|
|
// This value is the assumed initial display style for a sublist when it cannot
|
|
// be determined by other means. See below.
|
|
var DEFAULT_DISPLAY = "none";
|
|
|
|
// The namespace to use when using this script in an XML context.
|
|
var XMLNS = "http://www.w3.org/1999/xhtml";
|
|
|
|
// The beginning of the title text for the switch when the sublist is collapsed.
|
|
var CLOSED_PREFIX = "Expand list: ";
|
|
|
|
// The beginning of the title text for the switch when the sublist is expanded.
|
|
var OPENED_PREFIX = "Collapse list: ";
|
|
|
|
/******************************************************************************/
|
|
|
|
// Returns a copy of a string with leading and trailing whitespace removed.
|
|
String.prototype.trim = function() {
|
|
return this.replace(/^\s+/, "").replace(/\s+$/, "");
|
|
}
|
|
|
|
// Walks the DOM tree starting at a given root element. Returns an
|
|
// array of nodes of the specified type and conforming to the criteria
|
|
// of the given filter function. The filter should return a boolean.
|
|
function getNodesByType(root, type, filter) {
|
|
var node = root;
|
|
var nodes = [];
|
|
var next;
|
|
|
|
while (node != null) {
|
|
if (node.hasChildNodes())
|
|
node = node.firstChild;
|
|
else if (node != root && null != (next = node.nextSibling))
|
|
node = next;
|
|
else {
|
|
next = null;
|
|
for ( ; node != root; node = node.parentNode) {
|
|
next = node.nextSibling;
|
|
if (next != null) break;
|
|
}
|
|
node = next;
|
|
}
|
|
if (node != null && node.nodeType == type && filter(node))
|
|
nodes.push(node);
|
|
}
|
|
return nodes;
|
|
}
|
|
|
|
// Simulates the innerText property of IE and other browsers.
|
|
// Mozilla/Firefox need this.
|
|
function getInnerText(node) {
|
|
if (node == null || node.nodeType != 1)
|
|
return;
|
|
var text = "";
|
|
var textnodes = getNodesByType(node, 3, function() { return true; });
|
|
for (var i = 0; i < textnodes.length; i++)
|
|
text += textnodes[i].data;
|
|
return text;
|
|
}
|
|
|
|
function initExpandableLists() {
|
|
if (!document.getElementsByTagName) return;
|
|
|
|
// Top-level function to accommodate browsers that do not register
|
|
// a click event when a link is activated by the keyboard.
|
|
switchNode = function(id) {
|
|
var node = document.getElementById(id);
|
|
if (node && /^switch /.test(node.className)) node.onclick();
|
|
}
|
|
|
|
// Top-level function to be assigned as the event handler for the
|
|
// switch. This could have been bound to the handler as a closure,
|
|
// but closures are associated with memory leak problems in IE.
|
|
actuate = function() {
|
|
var sublist = this.parentNode.getElementsByTagName("ul")[0] ||
|
|
this.parentNode.getElementsByTagName("ol")[0];
|
|
if (sublist.style.display == "block") {
|
|
sublist.style.display = "none";
|
|
this.firstChild.data = "+";
|
|
this.className = "switch off";
|
|
this.title = this.title.replace(OPENED_PREFIX, CLOSED_PREFIX);
|
|
} else {
|
|
sublist.style.display = "block";
|
|
this.firstChild.data = "-";
|
|
this.className = "switch on";
|
|
this.title = this.title.replace(CLOSED_PREFIX, OPENED_PREFIX);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Create switch node from which the others will be cloned.
|
|
if (typeof document.createElementNS == "function")
|
|
var template = document.createElementNS(XMLNS, "a");
|
|
else
|
|
var template = document.createElement("a");
|
|
template.appendChild(document.createTextNode(" "));
|
|
|
|
var list, i = 0, j = 0;
|
|
var pattern = new RegExp("(^| )" + CLASS_NAME + "( |$)");
|
|
|
|
while ((list = document.getElementsByTagName("ul")[i++]) ||
|
|
(list = document.getElementsByTagName("ol")[j++]))
|
|
{
|
|
// Only lists with the given class name are processed.
|
|
if (pattern.test(list.className) == false) continue;
|
|
|
|
var item, k = 0;
|
|
while ((item = list.getElementsByTagName("li")[k++])) {
|
|
var sublist = item.getElementsByTagName("ul")[0] ||
|
|
item.getElementsByTagName("ol")[0];
|
|
// No sublist under this list item. Skip it.
|
|
if (sublist == null) continue;
|
|
|
|
// Attempt to determine initial display style of the
|
|
// sublist so the proper symbol is used.
|
|
var symbol;
|
|
switch (sublist.style.display) {
|
|
case "none" : symbol = "+"; break;
|
|
case "block": symbol = "-"; break;
|
|
default:
|
|
var display = DEFAULT_DISPLAY;
|
|
if (sublist.currentStyle) {
|
|
display = sublist.currentStyle.display;
|
|
} else if (document.defaultView &&
|
|
document.defaultView.getComputedStyle &&
|
|
document.defaultView.getComputedStyle(sublist, ""))
|
|
{
|
|
var view = document.defaultView;
|
|
var computed = view.getComputedStyle(sublist, "");
|
|
display = computed.getPropertyValue("display");
|
|
}
|
|
symbol = (display == "none") ? "+" : "-";
|
|
// Explicitly set the display style to make sure it is
|
|
// set for the next read. If it is somehow the empty
|
|
// string, use the default value from the (X)HTML DTD.
|
|
sublist.style.display = display || "block";
|
|
break;
|
|
}
|
|
|
|
// This bit attempts to extract some text from the first
|
|
// child node of the list item to append to the title
|
|
// attribute of the switch.
|
|
var child = item.firstChild;
|
|
var text = "";
|
|
while (child) {
|
|
if (child.nodeType == 3 && "" != child.data.trim()) {
|
|
text = child.data;
|
|
break;
|
|
} else if (child.nodeType == 1 &&
|
|
!/^[ou]l$/i.test(child.tagName))
|
|
{
|
|
text = child.innerText || getInnerText(child);
|
|
break;
|
|
}
|
|
child = child.nextSibling;
|
|
}
|
|
|
|
var actuator = template.cloneNode(true);
|
|
// a reasonably unique ID
|
|
var uid = "switch" + i + "-" + j + "-" + k;
|
|
actuator.id = uid;
|
|
actuator.href = "javascript:switchNode('" + uid + "')";
|
|
actuator.className = "switch " + ((symbol == "+") ? "off" : "on");
|
|
actuator.title = ((symbol == "+")
|
|
? CLOSED_PREFIX : OPENED_PREFIX) + text.trim();
|
|
actuator.firstChild.data = symbol;
|
|
actuator.onclick = actuate;
|
|
item.insertBefore(actuator, item.firstChild);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Props to Simon Willison:
|
|
// http://simon.incutio.com/archive/2004/05/26/addLoadEvent
|
|
var oldhandler = window.onload;
|
|
window.onload = (typeof oldhandler == "function")
|
|
? function() { oldhandler(); initExpandableLists(); } : initExpandableLists;
|