/* * 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;