/* $Id: autonav.js 8494 2008-05-01 02:46:37Z a1155544 $ AutoNav object Hooks into the DOM to collapse and reopen the list nav to indicate current position, building the crumb along the way. */ function AutoNav() { this.display_nav = true; // id of the nav root node this.nav_root_id = "nav-container"; // LIs with this class at the top of the nav will have their links prepended to the crumb this.parent_class = "nav-parent"; this.crumb_root_id = "crumb-container"; this.crumb_breaker = " > "; this.current_page_id = "autonav-current-page"; this.page_alternatives = ["index.html", "index.htm"]; this.page_alts_string = this.buildAlternatives(this.page_alternatives, false); this.page_alts_regexp = new RegExp(this.page_alts_string+"(?:(?:\\?|#).*)?$", "i"); // these are defined in the funcs that use them this.url_match_regexp = null; this.url_host_regexp = null; this.url_pathname_regexp = null; this.url_clean_regexp = null; //console.debug("AutoNav constructed"); } // builds an alternating regex source from the given array // if capt is false, the expression won't capture AutoNav.prototype.buildAlternatives = function(alternatives, capt) { var str = capt ? "(" : "(?:"; for (var i=0; i 0) str += "|"; str += alternatives[i]; } str += ")"; return str; }; // given two lists, returns the index of the first element that differs AutoNav.prototype.findDiffInLists = function(data1, data2, fromIdx) { if (fromIdx == null) fromIdx = 0; if (data1 instanceof Array && data2 instanceof Array) for (var i = fromIdx; i < data1.length; i++) if (data1[i] != data2[i]) return i; return null; }; // iterate nextSibling until we get a DOM object AutoNav.prototype.nextObject = function(node) { var elem = node.nextSibling; while (elem && elem.innerHTML == null) { elem = elem.nextSibling; } return elem; }; // return the url given, with common implicit names (index.html) removed AutoNav.prototype.urlClean = function(url) { if (this.url_clean_regexp == null) this.url_clean_regexp = new RegExp("^(.*/)"+this.page_alts_string); if (url.match("/\/$/")) { return url; } if (url.match(this.url_clean_regexp)) { return RegExp.$1; } return url; }; // return the pathname component of a url // if collapse is true, removes common implicit names like index.html AutoNav.prototype.urlPathname = function(url, collapse) { if (this.url_pathname_regexp == null) this.url_pathname_regexp = new RegExp("\/\/[^\/]+([^?#]*).*?$"); if (url.match(this.url_pathname_regexp)) { if (collapse) { var pathname = RegExp.$1; return pathname.replace(this.page_alts_regexp, ""); } else { return RegExp.$1; } } return url; }; // return the host component of a url AutoNav.prototype.urlHost = function(url) { if (this.url_host_regexp == null) this.url_host_regexp = new RegExp("\/\/([^\/]+).*$"); if (url.match(this.url_host_regexp)) { return RegExp.$1; } else if (url.match(/^([^\/]+).*$/)) { return RegExp.$1; } return null; }; // relaxed matching to allow for implicit index pages // returns boolean indicating match AutoNav.prototype.urlMatch = function(url, target) { if (this.url_match_regexp == null) this.url_match_regexp = new RegExp("^(.*/)"+this.page_alts_string+"$"); if (url == target) { return true; } else { if (url.match(this.url_match_regexp) && RegExp.$1 == target) { return true; } } return false; }; // what it says (if tag is null will search for tagless nodes) AutoNav.prototype.firstChildByTagName = function(node, tag) { if (!node) return false; if (tag) tag = tag.toUpperCase(); var child = node.firstChild; if (child) { do { if (child.tagName) { if (child.tagName.toUpperCase() == tag) { return child; } } else if (! tag) { return child; } } while (child = child.nextSibling); } return null; }; // walk from the current node to the top level of the nav, returning // an array of 'parent' links. If expand is true, expand ULs appropriately AutoNav.prototype.walkNavToRoot = function(current, expand) { var path = []; while (current.parentNode.parentNode && current.parentNode.parentNode.id != this.nav_root_id) { if (current.tagName == 'UL') { if (expand) { // Navs may be malformed with mutliple ULs in one level, so compensate var parentChildren = current.parentNode.childNodes; for (var i = 0, len = parentChildren.length; i < len; i++) { if (parentChildren[i].tagName == 'UL') parentChildren[i].style.display = ""; } } } else if (current.tagName == 'LI') { path.push(this.firstChildByTagName(current, 'A')); } current = current.parentNode; } if (current.tagName == 'LI') { path.push(this.firstChildByTagName(current, 'A')); } return path; }; // find the closest match in the nav by choosing from the given array, // that which has a parent that most closely matches the path components // of the current page AutoNav.prototype.findClosestNavLinkByStructure = function(links) { var pathlist = window.location.pathname.split(/\//); var best_link; var best_score = 0; for (var i=0; i max) { max = score; //console.debug("pathname %d,%d=> %s, %d",i,j,current_url,max); } } if (max > best_score) { best_link = links[i]; best_score = max; } //console.debug("findClosestNavLinkByStructure() %s => %d",link.text,path.length); } return best_link; }; // try various strategies to obtain the best link in the nav for this page AutoNav.prototype.findClosestNavLink = function(links) { //console.group("findClosestNavLink()"); var i; var pathname = window.location.pathname; var href = window.location.href; var host = window.location.host; if (this.current_page_id) { // look for an id set by the page var id_elem = document.getElementById(this.current_page_id); if (id_elem) { // get the id var id = id_elem.innerHTML; if (id) { for (i=0; i 0) { if (closest.length == 1) { return closest[0]; } else { // fall back to examining link parentage var closest_link = this.findClosestNavLinkByStructure(closest); //console.debug("best link by structure: %o(%s)", closest_link, closest_link.text); return closest_link; } } // fall back to closest match var best_links = []; var best_score = 0; var path = pathname.split(/\//); for (i=0; i= path.length) { var dec = (linkpath.length - path.length); //console.debug("decrementing score from %d by %d on %o(%s)", current_score, dec, current_link, current_link.text); current_score -= dec; break; } if (linkpath[j] == path[j]) { current_score++; //console.debug("link %s matched %s for score %d", current_href, path[j], current_score); } } if (current_score == best_score) { //console.debug("adding link %o(%s) with score %s", current_link, current_link.text, current_score); best_links.push(current_link); } else if (current_score > best_score) { //console.debug("ditching previous in favour of %o(%s)", current_link, current_link.text); best_score = current_score; best_links = [current_link]; } } var bestest_link; var bestest_pathname; if (best_links.length > 0) { for (var i=0; i "+bestest_pathname); continue; } var current_pathname = this.urlPathname(best_links[i].href, true); //console.debug("this.urlPathname("+best_links[i].href+", true) => "+current_pathname); if (current_pathname.length < bestest_pathname.length) { bestest_link = best_links[i]; bestsest_pathname = current_pathname; } } //console.info("bestest link: %o(%s)", bestest_link, bestest_link.text); } else { //console.warn("best link: null"); } //console.groupEnd(); return bestest_link; }; // sets display to none on all descendant UL tags AutoNav.prototype.collapseMenu = function(node) { if (!document.getElementById) return false; if (!node) node = this.root; if (!node) return false; var nodes = node.getElementsByTagName('UL'); for (var i=0; i=0; i--) { path.push(prepend[i]); } path.reverse(); return path; }; // creates breadcrumbs from the nav path generated by expandMenu AutoNav.prototype.makeCrumbs = function(path, node) { if (!path) return false; if (!node) node = document.getElementById(this.crumb_root_id); if (!node) return false; // clear any crumb present var nodes = node.getElementsByTagName('UL'); for (var i=0; i 0) { var breaker = document.createElement('li'); breaker.innerHTML = this.crumb_breaker; ul_elem.appendChild(breaker); } var li_elem = document.createElement('li'); li_elem.setAttribute('class', 'crumb'); var a_elem = document.createElement('a'); a_elem.setAttribute('href', path[i].href); a_elem.innerHTML = path[i].innerHTML; li_elem.appendChild(a_elem); ul_elem.appendChild(li_elem); } node.appendChild(ul_elem); }; // finds the nav, shows the children, expands the menu, leaves crumbs AutoNav.prototype.process = function() { this.root = document.getElementById(this.nav_root_id); if (this.root) { if (this.display_nav) { var children = this.root.childNodes; for (var i=0; i\n' + ' #nav-container { display: hidden; }\n' + ' #crumb-container { display: block; }\n' + ''); }; (new AutoNav()).runOnReady();