spahql
Version:
A query language and data model for deep Javascript object structures.
226 lines (192 loc) • 6.46 kB
JavaScript
Nav = function() {
}
Nav.init = function(injectorCallback) {
var overview = this.buildOverview(injectorCallback);
this.addScrollListener(overview);
this.addToggleBehaviours();
this.addLinkBehaviours();
this.addHelperMarkup();
this.formatCode();
}
Nav.addHelperMarkup = function() {
$("article > pre").each(function() {
var $pre = $(this);
var $prev = $pre.prev();
$pre.wrap('<div class="pre-wrapper"></div>');
$pre = $pre.closest(".pre-wrapper");
if($prev[0] && $prev[0].nodeName.toLowerCase() == "p" && $prev.html().indexOf(":") == $prev.html().length-1) {
var wrapper = $("<div class=\"code-pair\"></div>");
$prev.before(wrapper);
var pWrapper = $("<div class=\"text-container\"></div>");
wrapper.append(pWrapper);
pWrapper.append($prev);
wrapper.append($pre);
wrapper.append("<div class=\"clearfix\"></div>")
}
});
};
Nav.formatCode = function() {
$("code").each(function() {
var $e = $(this);
var code = $e.html();
$e.html(code.replace(/\t/g, " "));
});
};
Nav.buildOverview = function(injectorCallback) {
// Build the heading index
var hTags = ["h1", "h2", "h3", "h4", "h5", "h6"];
var hSel = hTags.join(", ");
var hPrev, liPrev, target;
// target is always a LIST and we're appending LI's to it
var rootList = $("<ol class=\"h1\"></ol>");
injectorCallback(rootList);
$(hSel, $("article")).each(function(index) {
if(index == 0) {
// Important: First heading ignored as it is the page heading
}
else {
var $h = $(this);
var newLi = $("<li class=\"hidden\"><a href=\"#"+$h.attr("id")+"\">"+$h.html()+"</a><ol></ol></li>");
if(hPrev && (this.tagName == hPrev.tagName)) {
// console.log("keeping scope: "+this.tagName+" ('"+$h.html()+"')");
}
else if(!hPrev || (this.tagName > hPrev.tagName)) {
// Starting out or descending scope
// console.log("entering scope: "+this.tagName+" ('"+$h.html()+"')");
// Go to new target
target = ($("ol", (liPrev)).length > 0)? $("ol", liPrev).first() : rootList;
// Add toggler to previous target
if(liPrev && hPrev) {
liPrev.addClass("has-subtopics");
$("a", liPrev).addClass("has-subtopics");
liPrev.prepend("<a class=\"nav-toggle\" href=\"#\">+</a> ");
target.addClass(this.tagName.toLowerCase())
target.hide();
}
}
else if(this.tagName < hPrev.tagName) {
// console.log("exiting scope: "+this.tagName+" ('"+$h.html()+"')");
// Exiting scope, determine how many exits we're making
var prevT = target;
target = target.closest("ol."+this.tagName.toLowerCase());
}
// Append to current scope
target.append(newLi);
// Set scope comparison flag
hPrev = this;
liPrev = newLi;
}
});
return rootList;
}
Nav.addToggleBehaviours = function() {
// Build togglers
$("a.nav-toggle").click(function(e) {
var $a = $(this);
var $li = $a.closest("li");
if($li.hasClass("hidden")) {
Nav.expandTree($li);
$li.addClass("manual");
}
else {
$li.removeClass("manual");
Nav.collapseTree($li);
}
e.preventDefault();
return false;
});
}
Nav.expandTree = function(li) {
var $li = $(li);
$li.removeClass("hidden");
$li.addClass("shown");
var sublist = $li.children("ol");
if(sublist.children("li").length > 0) {
sublist.slideDown(250);
}
$li.children("a.nav-toggle").html("-");
}
Nav.collapseTree = function(li) {
var $li = $(li);
if($li.hasClass("manual")) return;
$li.removeClass("shown");
$li.addClass("hidden");
$li.children("ol").slideUp(250);
$li.children("a.nav-toggle").html("+");
}
Nav.addLinkBehaviours = function() {
$("a[href^='#']:not(.nav-toggle)").click(function(event) {
var dest = $(this).attr("href");
if(dest!="#") {
Nav.navigate(dest);
// Kill actual link behaviour
event.preventDefault();
return false;
}
});
}
Nav.scrollTarget = null;
Nav.navigate = function(dest) {
// Scroll main content view
Nav.scrollTarget = dest;
$.scrollTo($(dest), 500, {onAfter: function() {
document.location = document.location.toString().split("#")[0]+dest;
Nav.scrollTarget = null;
}});
}
Nav.makeDestinationCurrent = function(dest, hNav) {
var navLi = $("nav a[href='"+dest+"']").parents("li");
navLi.addClass("current");
// Collapse all nav trees that aren't a part of this
var navItems = $("li", hNav);
navItems.each(function(i) {
var $item = $(this);
// If this list item contains a link to the destination, we expand it
// Else we collapse it
if($("a[href='"+dest+"']", $item).length > 0) {
Nav.expandTree(this);
}
else {
Nav.collapseTree(this);
}
});
}
Nav.addScrollListener = function(hNav) {
$(window).scroll(function(event) {
// Find the first visible header and highlight it as current in the nav
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
// Strip current classes
$("li", hNav).removeClass("current");
// Find first visible header and if no header is visible, find closest to document top
// OR take the current async scroll target first
if(Nav.scrollTarget) {
Nav.makeDestinationCurrent(Nav.scrollTarget, hNav);
}
else {
var lastAboveFrameHeader;
$("h1:not(h1:first-child), h2, h3, h4, h5, h6", $("article")).each(function(i) {
var $elem = $(this);
var elemTop = $elem.offset().top;
var elemBottom = elemTop + $elem.height();
var aboveFrame = (elemBottom < docViewTop);
var belowFrame = (elemTop > docViewBottom);
var inFrame = (!aboveFrame && !belowFrame);
if(aboveFrame) {
// Above frame, store in fallback pointer
lastAboveFrameHeader = $elem;
}
else if(belowFrame && lastAboveFrameHeader) {
// If we went below frame, run action against last aboveFrame header
Nav.makeDestinationCurrent("#"+lastAboveFrameHeader.attr("id"), hNav);
return false;
}
else if(inFrame) {
// If in frame, this is the badger
Nav.makeDestinationCurrent("#"+$elem.attr("id"), hNav);
return false;
}
});
}
});
}