Breadcrumbs Component in Harp

So far I have written up two blog posts on the Harp static site generator. The first is Getting Started with Harp which provides a quick overview of all the main features of Harp and will get you going quickly. Then I wrote up Hosting Harp on Github Pages for getting your static site up for the world to see. Now that I have the setup, development, and deployment pieces out of the way I would like to start discussing some more interesting things you can do with the Harp static site generator.

In this blog post we are going to create a breadcrumbs component. We can essentially look at this from two angles. First we can consider the content that the breadcrumbs component will render and secondly we can look at the implementation that will generate the markup. The content will come in the form of _data.json and ejs files within a directory hierarchy. The implementation will come in the form of a contextual partial which will render the breadcrumbs based on the current location within the hierarchy.

The Content

An Example Page

In Harp you can have a nested directory of ejs templates which are then compiled into html pages. In each directory you can have metadata associated with that directory in the form of a json filed called _data.json. Let's look at an example. Imagine the following directory and file structure:

<project-root>/story/history/origins.ejs

In this example we have a project root with a sub directory called "story" and a sub directory within it called "history". In this lowest level sub directory we have a file called "origins.ejs". This is an html template that when rendered by harp will be available at the following url:

localhost:9000/story/history/origins.ejs

The Metadata

Each of these directories can contain metadata in the form of json in a _data.json file. Add such a json file in each of these directories with the following content:
{
  "title": "provide a title here"
}
You will want to replace the title with something specific for each directory. In the root level _data.json file update the file to look like this:

{
  "title": "provide a title here",
  "navigationTitle": "Home"
}
This will let us both provide a title for the whole site while also using the word "Home" in the breadcrumbs. Finally update the _data.json file in the "history" directory to look like this:
{
  "title": "provide a title here",
  "origins": {
    "title": "provide a title for the origins page"
  }
}
Here you can see that the title that we will use in the breadcrumbs will be found either at the _data.json of the corresponding directory or in the parent directory in a sub attribute of the json of the same name as the page. In this way you do not need a _data.json file for every page. The leaf node pages such as "origins.html" can have it's title defined in the containing directories metadata.

The Implementation

Now that we have some example content go ahead and update the origins.ejs file with the following contents:
<!DOCTYPE html>
<html>
  <head>
  </head>
  <body>
    <%- partial("../../breadcrumbs") %>
  </body>
</html>

The Breadcrumbs Partial

This will include a partial called "_breadcrumbs.ejs" from the root of the project. Let's go ahead and create this file. I am going to provide the full contents of this breadcrumbs partial and then explain how it works:
<div class="breadcrumbs">
  <a href="/"><%- public._data.home %></a>
  <% current.path.forEach((pathSegment, index, array) => { %>
    <a href="/<%- array.slice(0, index+1).join("/") %>.html">
      <%
        var currentData = array
          .slice(0, index + 1)
          .reduce((oldVal, newVal) => oldVal && oldVal[newVal] ? oldVal[newVal] : {}, public)
          ._data;
        var title = currentData ? currentData.title : false;
        var parentData = array
          .slice(0, index)
          .reduce((oldVal, newVal) => oldVal && oldVal[newVal] ? oldVal[newVal] : {}, public)
          ._data;
        let parentDefinedTitle = parentData && parentData[pathSegment]
          ? parentData[pathSegment].title
          : "";
      %>
      <%- title ? title : parentDefinedTitle %>
    </a>
  <% }); %>
</div>
Now lets look at each piece of this bread crumbs implementation.

The Home Link

This is the easiest part. This simply looks for a title attribute in the _data.json of the root directory.
<div class="breadcrumbs">
  <a href="/"><%- public._data.home %></a>
  ... 
</div>

For Every Segment in the Path

This part is again fairly straightforward. Here we are basically going to render a link for every segment in the path. So when the user comes to "localhost:9000/story/history/origins.ejs" we are going to render a link for "story", "history", and "origins".
<div class="breadcrumbs">
  <a href="/"><%- public._data.home %></a>
  <% current.path.forEach((pathSegment, index, array) => { %>
    <a href="/<%- array.slice(0, index+1).join("/") %>.html">
      <%- ... %>
    </a>
  <% }); %>
</div>

Finding The Title

This part gets more complicated. For simplicity I have separated the JavaScript from the ejs template however to see what the full file looks like look above at the "The Breadcrumbs Partial" section of this blog post.
// 1. Get the json of the current links _data.json file.
let currentData = array
  .slice(0, index + 1)
  .reduce((oldVal, newVal) =>
    oldVal && oldVal[newVal] ? oldVal[newVal] : {}, public)
  ._data;

// 2. Get the title from this _data.json file if it exists.
let title = currentData ? currentData.title : false;

// 3. Get the json of the parent _data.json file.
let parentData = array
  .slice(0, index)
  .reduce((oldVal, newVal) =>
    oldVal && oldVal[newVal] ? oldVal[newVal] : {}, public)
  ._data;

// 4. Get the title of the current page as defined in the parents _data.json file.
let parentDefinedTitle = parentData && parentData[pathSegment]
  ? parentData[pathSegment].title
  : "";

This has four steps:
  1. Get the json of the current links _data.json file.
  2. Get the title from this _data.json file if it exists.
  3. Get the json of the parent _data.json file.
  4. Get the title of the current page as defined in the parents _data.json file.
In steps 1 and 3 we slice the array of the current page down to the length of the index. In this we we take the full ["story", "history", "origins"] array and operate on it three times as follows:
  1. ["story"]
  2. ["story", "history"]
  3. ["story", "history", "origins"]
For each time through the loop we use the slice method to get the correct array and then use the reduce method to step through the metadata that we created earlier in this blog post. This metadata is stored in the "public" variable. Even though we never see this metadata in one single block of json, at render time it looks like this:
{
  "title": "Your website title",
  "navigationTitle": "Home",
  "story": {
    "_data": {
      "title": "Story"
    },
    "history": {
      "_data": {
        "title": "History",
        "origins": {
          "title": "Origins"
        }
      }
    }
  }
}


The reason that "story" and "history" have a "_data" node underneath of them and "origins" does not is because we did not provide "origins" with a _data.json file and instead placed the title for this page in the parent directories _data.json file (i.e. "history").

So this JavaScript searches uses the array of path segments in order to find the title for the specified page. Now all we need to do is add the title inside of the link as shown below. Again, to see the whole file refer above to the "The Breadcrumbs Partial" section of this blog post.
<div class="breadcrumbs">
  <a href="/"><%- public._data.home %></a>
  <% current.path.forEach((pathSegment, index, array) => { %>
    <a href="/<%- array.slice(0, index+1).join("/") %>.html">
      <%
        // The JavaScript from above goes here.
        ...
      %>
      <%- title ? title : parentDefinedTitle %>
    </a>
  <% }); %>
</div>

Now all we need to do is add some css in order for the links to be separated from one another. Go ahead and add the following css to the page:
.breadcrumbs a:not(:last-child):after {
  margin: 0 0.5rem;
  content: ">"
}
And that is it! Go ahead and open up localhost:9000/story/history/origins.ejs and you should see the following breadcrumbs:



This breadcrumbs component will work on any page in any location within Harp. It will contextually generate the breadcrumbs up to the root of the site and will pull in the titles specified for each page. It can be added to a layout, included from another partial, or added to any page on your Harp website!

Thankyou for reading and stay tuned for more blog posts about the Harp static site generator.

Comments

Popular Articles

The Vanilla Javascript Component Pattern

The Sunless Citadel: A D&D 5e Session Report

Getting Started with Harp