Notes
Background
The navigation hierarchy provided by Just the Docs is currently limited to three levels:
- Top-level pages with no
parent
field. - Pages whose
parent
field is the title of a level-1 page. - Pages whose
parent
field is the title of a level-2 page, and whosegrand_parent
field is the title of a top-level page.
The current implementation requires parent pages to have has_children: true
, and grand_parent
fields to be consistent with the parent
fields of their parent pages. Level-2 pages may have the same title when they have different parent
fields. And level-3 pages may have the same title when they have different parent
fields or different grand_parent
fields.
In PR #192, Eugene Kuzmenko proposed allowing the navigation to have unbounded depth, and provided a very elegant implementation (using recursive inclusion of Liquid files) with the following features:
- makes navigation recursive, which means that it can go down to an arbitrary depth
- makes the notion of grand parents/children obsolete. If a page is a child of a page, that itself is a child of another page, that automatically makes this page a grandchild.
has_children
andgrand_parent
options are removed as unnecessary- breadcrumbs are no longer dependent on main navigation setting certain variables
nav_exclude
excludes the page from any depth in the menu hierarchy
The implementation of navigation in PR #192 assumes that all pages used as parents have different titles, so that the parent
fields alone determine the hierarchy. If two parent pages happen to have the same title, the resulting navigation hierarchy includes their combined children twice. The only work-around is to change the page titles to make them all different.
It is possible to adapt the implementation from PR #192 using optional grand_parent
fields. Existing 3-level sites would not require any changes. However, it is unclear how to best to generalise grand_parent
to more then three levels, to support the kind of navigation hierarchy mentioned in the following comment on PR #192:
[…] standardize pages and subpages for easy reading (e.g. all docs for microservices have the same format and same child page names).
Current proposal
The current proposal is based on the following assumptions:
- Top-level pages cannot have the same title
- Children of the same parent cannot have the same title
- A child cannot have the same title as its parent or any of its ancestors
It implements the following features:
If no two pages on your website have the same
title
, you only need to set theparent
titles to fix the hierarchy. You can also have the sametitle
on pages that have no children, provided that they have differentparent
titles.If two parents have the same
title
, but different grandparents, you can set theirgrand_parent
titles to distinguish between their parents. Thegrand_parent
title needs to be the same as theparent
of theparent
.For resolving parents in deeper navigation structures, you can set the
ancestor
field of a page to the title of any page reachable by following successive references toparent
titles.If you want the navigation structure in different parts of your website to look the same, you can add the title of the top page of each part as the
ancestor
of all its sub-pages.Instead of using
ancestor
, you can set an arbitrary number or string as thesection_id
for each part, and add it as thein_section
field on all the descendant pages of that part.
Implementation overview
See the comments in the files referenced below for further explanation of the implementation. The regression tests include examples exploiting all the above features for disambiguating parents in recursive navigation hierarchies.
The default layout calls:
main
to output the main navigation for the current page,crumbs
to output any breadcrumb navigation at the top of the page, andtoc
to output the list of any child page navigation at the bottom of the page.
All the files that implement the navigation are in the _includes/nav/
folder. All variables assigned by the code are prefixed by nav_
(which is elided when referring to variables in the descriptions below).
- called from the default layout
- outputs the main navigation for ordinary pages and for each just-the docs collection
- called from
main
- creates the
parenthood
grouped array based on theparent
fields - if the current page is in the
pages
parameter, callspage
to locate it - calls
links
to output the navigation links
- called from
collection
- when
direct
is true: heuristic search using the directory hierarchy, may fail - when
direct
is false: exhaustive search, always succeeds - traverses the nav hierarchy top-down until it reaches the current page, then:
- uses
children
to determine the children of each node
- called from
collection
andnode
- uses
sorted
to sort the nodes - traverses the nav hierarchy top-down, outputting an HTML link for each non-excluded node using
node
orinactive_node
- uses
page_path
to test whether each node is active - uses
children
to determine the children of each node
- called from
links
- outputs the link for a node, then for its children using
links
orinactive_links
- called from
node
andinactive_node
- uses
sorted
to sort the nodes - traverses the nav hierarchy top-down, outputting an HTML link for each non-excluded node using
inactive_node
- uses
children
to determine the children of each node
- called from
inactive_links
- outputs the link for a node, then for its children using
inactive_links
- called from the default layout with parameter
page_ancestors
- outputs HTML for the given array of pages
- called from the default layout with parameter
page_children
- outputs HTML for the given array of pages
- called from
page
andlinks
with an array of potential child pages - sets
children
to those pages with matchinggrand_parent
,section
, andancestor
fields
- called from
collection
with an unsorted array of pages - sets
sorted
to the result of sorting the array using thenav_order
fields
- called from the default layout
- outputs diagnostics at the bottom of all pages on
localhost
- activated by configuration setting
nav_debug: "..."
- requires a plugin, so debug not available on GitHub Pages
Efficiency
The local build time for a moderate-sized site (150 pages, average branching: 5, average depth: 3) on a MacBook Air is about 30 seconds using Jekyll 4.1.1, and about 45 seconds using Jekyll 3.8.5. Building the current theme documentation pages with the regression tests takes about 15 seconds with Jekyll 4.1.1, and 25 seconds with Jekyl 3.8.5.
The performance of the implementation has been optimised by:
- using
jekyll-include-cache
(v0.2.1) to cache the navigation links for inactive top-level pages - avoiding unnecessary sorting of large arrays of pages
- using heuristics to guide the search for the current page in the navigation hierarchy
- separating searching for the location of a page from outputting the links for it
Suggestions for further optimisations are welcome!