Working with HTML Templates

The Solr Ref Guide uses Jekyll to build the HTML version of the site.

What is Jekyll?

Jekyll is a static site generator, meaning that it takes some set of documents and produces HTML pages. It allows for templating of the pages, so each page has the same look and feel without having to code headers, footers, logos, etc., into every page.

Jekyll is an open source project written in Ruby, online at https://jekyllrb.com/.

How We Use Jekyll

The following sections describe the main features of Jekyll that you will encounter while working with the Solr Ref Guide.

Jekyll-Asciidoctor Plugin

We use a plugin for Jekyll from the Asciidoctor project to integrate Jekyll with Asciidoc formatted content. The source for the plugin is available at https://github.com/asciidoctor/jekyll-asciidoc.

This plugin allows us to use Asciidoctor-style variables with Jekyll.

_config.yml

The _config.yml is a global configuration file that drives many of the options used when building the site (particularly in our use of Jekyll).

We have template-ized _config.yml, so in our use of Jekyll you will find it in solr/solr-ref-guide/src as _config.yml.template. This allows us to define some variables during the build and use common Lucene/Solr build parameters (such as project versions).

Front Matter

Front matter for Jekyll is similar to a header that defines the title of the page, and other variables that may be helpful (or even required) when rendering the page.

Every document that will be converted to HTML must include at least the page title at the top of the page. Page titles are defined with a single equal sign (=) followed by the title that will appear at the top of the page (such as = Topic of the Page).

Many guides to Jekyll also say that defining the layout in the front matter is required. However, since we only use one layout for all pages, we have defined this as a default.

The Solr Ref Guide uses the front matter to define some custom attributes on a per-page basis:

  • page-children - ordered list of child pages, this is used to build the site navigation menu that appears to the left of each page’s content.

Other page-level elements can also be defined, such as an Asciidoctor attribute that should apply only to that page, but are not needed on a regular basis.

The format for adding any parameter to the front matter is to use colons on both sides of the parameter, followed by the value for the parameter (such as :page-toc: false).

Table of Contents

There are some optional custom attributes that can be defined in pages to affect the Table of Contents presentation in Jekyll:

toclevels
Changes how "deep" the TOC will be in terms of nested section/sub-section titles (default = 2). Example: :toclevels: 1.
page-show-toc
If this is false, then no TOCs will be generated for the page at all. The default is true, so can usually be left undefined. Example :page-show-toc: false.

Layouts

Layouts define the "look and feel" of each page. Jekyll uses Liquid for page templates.

For our implementation of Jekyll, layouts are found in solr-ref-guide/src/_layouts.

We currently use the _layouts/default.html layout for overall page structure, for almost all pages and _layouts/page.html for the page-level content. The page.html layout is inserted into the default.html layout.

The main page (index.html) of the Ref Guide uses the _layouts/home.html layout. It also still uses _layouts/page.html for the page-level content. This is done because index.html has some special formatting and rules for how to define the page.

Includes

Include files are (usually) small HTML files that are pulled into a layout when a page is being built. They are Liquid templates that define an area of the page. This allows flexibility across layouts - all pages can have the same header without duplicating code, but different pages could have different menu options.

Include files that we use define the top navigation, the page header, and the page footer.

For our implementation of Jekyll, include files are found in solr-ref-guide/src/_includes.

Data Files

Data files include data such as lists that should be included in each page. The left-hand navigation menu is an example of a data file. However, in our build, the navigation is built from the page-children hierarchies defined on parent pages.

For our implementation of Jekyll, data files are found in solr-ref-guide/src/_data.

Asciidoctor Slim Templates

Jekyll creates all of the page elements we do not define in each .adoc file: the header, footer, top nav, sidebar nav, and other parts of the page that we don’t worry about as we write the content of a page.

Asciidoctor converts the content in each .adoc file into HTML and inserts it into the Jekyll page layout we have defined (see Layouts) to make the individual HTML files that make up the Ref Guide.

While we have unlimited control over styling page content via CSS, without creating custom Asciidoctor-specific plugins or templates there is little out-of-the-box control over the elements, classes, etc. that make up the HTML pages.

In order to better support HTML5, we have customized Asciidoc’s default conversion with templates found in the _templates directory. These templates use Slim as the template engine.

Since these templates dictate the very structure of the HTML of our content, customizing these should only be attempted in rare instances and with extensive testing for unforeseen impacts.

Using Bootstrap

The HTML files include Bootstrap (v4.1.3 as of April 2020, see _includes/head.html to confirm the Bootstrap version currently being used), so all of the components of Bootstrap are available.

The design of the Ref Guide makes extensive use of Bootstrap classes to layout the page via the Liquid templates and our customized Asciidoctor templates.

When we want to use additional components of Boostrap that require specific HTML constructs, we must define those within the page content itself (using either <div> elements in the content or with Asciidoctor’s roles, discussed in the section).

Asciidoctor Roles

Asciidoctor helpfully provides a way to define custom <div> classes in .adoc files, as long as we understand how to use it.

Asciidoctor does not call these "divs" or "classes", but instead "roles". We can give any content a role - to images, content blocks (such as [source] or [NOTE], etc.), even a word in the middle of a sentence.

Because roles are so flexible, they only apply to the thing - the word, content block, image, etc. - they are directly applied to. This means that if we want an entire section of content to be given a specific role in the HTML (i.e., enclosed in a <div>), then we need to put the content in a block.

For more on Roles in Asciidoctor, see Role in the Asciidoctor User Guide.

Creating Tabbed Sections

Hopefully a little bit of background on roles is helpful to understanding the rest of what we’ll do to create a tabbed section in a page.

See the Bootstrap docs on nav tabs for details on how to use tabs and pills with Bootstrap. As a quick overview, tabs in Bootstrap are defined like this:

<ul class="nav nav-pills"> 
  <li class="active"><a data-toggle="pill" href="#sec1">Section 1</a></li>
  <li><a data-toggle="pill" href="#sect2">Section 2</a></li>
</ul>

<div class="tab-content"> 
  <div id="sect1" class="tab-pane active"> 
    <h3>Section 1</h3>
    <p>Some content.</p>
  </div>
  <div id="sect2" class="tab-pane">
    <h3>Section 2</h3>
    <p>Some other content.</p>
  </div>
</div>
1This section creates an unordered list with a line item for each tab. The data-toggle and class parameters are what tell Bootstrap how to render the content.
2Note the class defined here: <div class="tab-content">. This defines that what follows is the content that will make up the panes of our tabs. We will need to define these in our document.
3In our document, we need to delineate the separate sections of content that will make up each pane.

We have created some custom JavaScript that will do part of the above for us if we assign the proper roles to the blocks of content that we want to appear in the tab panes. To do this, we can use Asciidoctor’s block delimiters to define the tabbed content, and the content between the tab.

  1. Define an "open block" (an unformatted content block), and give it the role .dynamic-tabs. An open block is defined by two hyphens on a line before the content that goes in the block, and two hyphens on a line after the content to end the block. We give a block a role by adding a period before the role name, like this:

    [.dynamic-tabs]
    --
    The stuff we'll put in the tabs will go here.
    --
  2. Next we need to define the content for the tabs between the open block delimiters.
    1. We enclose each tab pane in another type of block, and "example" block. This allows us to include any kind of content in the block and be sure all of the various types of elements (heading, text, examples, etc.) are included in the pane.
    2. We give the example block another role, tab-pane, and we must make sure that each pane has a unique ID. We assign IDs with a hash mark (\#) followed by the ID value (#sect1).
    3. We also need to define a label for each tab. We do this by adding another role, tab-label to the content we want to appear as the name of the tab.
    4. In the end one pane will look like this:

      [example.tab-pane#sect1] 
      ==== 
      [.tab-label]*Section 1*  
      My content...
      ====
      1When we define the example block with [example], it’s followed by .tab-pane#sect1 as the class (each class separated by a period .) and the ID defined in the tab definition earlier. Those will become the classes (class="tab-pane active") and ID (id="sect1") in the resulting HTML.
      2Example blocks are delimited by 4 equal signs (====) before and after the enclosed content.
      3The words "Section 1" will appear in the HTML page as the label for this tab.
    5. Create [example.tab-pane#id] sections for each tab, until you finally end up with something that looks like this:

      [.dynamic-tabs]
      --
      [example.tab-pane#sect1]
      ====
      [.tab-label]*Section 1*
      My content...
      ====
      
      [example.tab-pane#sect2]
      ====
      [.tab-label]*Section 2*
      My content...
      ====
      --

Building the HTML Site

An Ant target ant build-site when run from the solr/solr-ref-guide directory will build the full HTML site (found in solr/build/solr-ref-guide/html-site).

This target builds the navigation for the left-hand menu, and converts all .adoc files to .html, including navigation and inter-document links.

Building the HTML has several dependencies that will need to be installed on your local machine. Review the README.adoc file in the solr/solr-ref-guide directory for specific details.

Using the Gradle build does not require any local dependencies. Simply use ./gradlew buildSite to generate the HTML files using Gradle (these will be found in solr/solr-ref-guide/build/html-site).

Build Validation

When you run ant build-site to build the HTML, several additional validations occur during that process. See solr-ref-guide/tools/CheckLinksAndAnchors.java for details of what that tool does to validate content.