Where to Discuss?

Local Group

Preface

Goal: Javascript Search in Hugo, using lunrjs


1: Prepare

Reading

Source

I respect copyright. The javascript code below, copied and pasted from:

Although both are intented to be used with Jekyll, the Javascript part reamin the same with Hugo.

I also made a slight modification, and a little enhancement. But of course the logic for lunr part remain the same.

Artefacts

This is the list of the Arteftacfts that we require.

Edit Artefact:

  • themes/tutor-06/layouts/partials/site-scripts.html

  • themes/tutor-06/layouts/_default/baseof.html

  • themes/tutor-06/layouts/partials/site-header.html

New Artefact:

  • themes/tutor-06/layouts/search/single.html

  • content/pages/search.md

  • themes/tutor-06/static/js/jquery.min.js

  • themes/tutor-06/static/js/lunr-0.7.1.min.js

  • themes/tutor-06/static/js/search-lunr-0.7.1.js


2: Search Page

The search page consist of this two artefacts:

  • themes/tutor-06/layouts/search/single.html

  • content/pages/search.md

Layout: Single

First we need to create newly layout for search

{{ define "main" }}
<div class="p-2">
  <form action="get" id="site_search">
    <label for="search_box">Search</label>
    <input type="text" id="search_box" name="query">
    <input type="submit" value="search">
  </form>

  <ul id="search_results" class="list-unstyled"></ul>
</div>
{{ end }}
+++
type  = "search"
title = "Search Blog"
+++

Server Output: Browser

Open in your favorite browser. You should see, search form in search page.

  • http://localhost:1313/pages/search/

Hugo Search: No Javascript

We do not use javascript yet.


3: Navigation Bar

Consider have a look at our ancient artefact, that exist from the very beginning of the tutorial.

We need to update navigation Bar. Change the form part.

      <form class="form-inline mt-2 mt-md-0" action="/pages/search/" method="get">
        <input class="form-control mr-sm-2" type="text" name="q"
          placeholder="Search" aria-label="Search">
        <button class="btn btn-outline-light my-2 my-sm-0" 
          type="submit">Search</button>
      </form>

Server Output: Browser

Open in your favorite browser. You should see, search form in navigation bar.

  • http://localhost:1313/

Hugo Search: Navigation Bar

What does it do ?

If you type a word to be searched, such as for example time, and click the search button. The page will be redirect to

  • http://localhost:1313/pages/search/?q=time

4: Javascript Layout

Riddle ?

We need to forward the url request such as:

  • http://localhost:1313/pages/search/?q=time

To the form in search page, using javacript. So the form will show the word time.

We need to put a specific javascript in this serach page.

Layout: baseof

How do we manage javascript for specific page ?

We should have look at our ancient artefact. All we need is to add block.

{{ partial "site-scripts.html" . }}
{{ block "custom-javascript" . }}{{ end }}

The complete code is here

<!DOCTYPE html>
<html lang="en">
{{ partial "site-head.html" . }}
<body>
{{/* partial "service-google-analytics.html" . */}}
{{ partial "site-header.html" . }}

  <div class="container-fluid maxwidth">

    <div class="row layout-base">
      {{- block "main" . }}
      {{ .Content }}
      {{- end }}

      {{- block "aside" . }}{{- end }}
    </div><!-- .row -->

  </div><!-- .container-fluid -->

{{ partial "site-footer.html" . }}
{{ partial "site-scripts.html" . }}
{{ block "custom-javascript" . }}{{ end }}
</body>
</html>

Layout: Single

And apply the block in layout.

{{ define "main" }}
<div class="p-2">
  <form action="get" id="site_search">
    <label for="search_box">Search</label>
    <input type="text" id="search_box" name="query">
    <input type="submit" value="search">
  </form>

  <ul id="search_results" class="list-unstyled"></ul>
</div>
{{ end }}

{{ define "custom-javascript" }}
    <script src="{{ "js/search-form-example.js" | relURL }}"></script>
{{ end }}

Javascript: Example

And the content of the Javascript example is:

jQuery(function() {
  // Get search results if q parameter is set in querystring
  if (getParameterByName('q')) {
      var query = decodeURIComponent(getParameterByName('q'));
      $("#search_box").val(query);
      
      window.data.then(function(loaded_data){
        $('#site_search').trigger('submit'); 
      });
  }

});

 /* ==========================================================================
    Helper functions
    ========================================================================== */

/**
 * Gets query string parameter - taken from 
 * http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
 * @param {String} name 
 * @return {String} parameter value
 */
function getParameterByName(name) {
    var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
    return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}

Server Output: Browser

Open in your favorite browser. You should see, search form respond to url query.

  • http://localhost:1313/pages/search/?q=time

Hugo Search: Search Form JQuery


5: JQuery Requirement

Our lunr search require JQuery, instead of just JQuery Slim.

Download JQuery

And put in JS directory under static assets.

  • themes/tutor-06/static/js/jquery.min.js

Partial: Javascript

It is just an empty template.

    <!-- Bootstrap core JavaScript
    ================================================== -->
    <!-- Placed at the end of the document so the pages load faster -->
    <script src="{{ "js/jquery.min.js" | relURL }}"></script>
    <script src="{{ "dist/js/bootstrap.min.js" | relURL }}"></script>
    <script src="{{ "js/prism.js" | relURL }}"></script>

6: Search Data Source

Remember our last JSON source ? Consider refresh our last tutorial.

Server Output: Browser

Now you can see JSON output in your favorite browser.

  • http://localhost:1313/pages/archives/index.json

This will produce something similar as shown below:

{
  
    "/quotes/2018/09/13/mothers-no-crying-in-baseball/": {
      ...
    },
    "/quotes/2018/09/07/julian-baker-something/": {
      "title": "Julian Baker - Something",
      "content": "I knew I was wasting my time. Keep myself awake at night.",
      "url": "http://localhost:1313/quotes/2018/09/07/julian-baker-something/",
      "author": "epsi",
      "category": "quotes"
    },
    ...
}

Modern browser automatically format, the JSON output in nice view collapsable view.

Hugo: JSON Content Type


This is the main component.

Download lunrjs.

I haven’t update my skill lately. I’m still using the old lunrjs code.

And put it in JS directory.

  • themes/tutor-06/static/js/lunr-0.7.1.min.js

Layout: Single

And apply the block in layout.

{{ define "custom-javascript" }}
    <script src="{{ "js/lunr-0.7.1.min.js" | relURL }}"></script>
    <script src="{{ "js/search-data-example.js" | relURL }}"></script>
{{ end }}

Javascript: Loading Lunr

And the content of the Javascript example is:

jQuery(function() {

  // Initalize lunr with the fields it will be searching on. I've given title
  // a boost of 10 to indicate matches on this field are more important.
  window.idx = lunr(function () {
    this.ref('id');
    this.field('title');
    this.field('content', { boost: 10 });
    this.field('author');
    this.field('category');
  });

  // Download the data from the JSON file we generated
  window.data = $.getJSON('/pages/archives/index.json');

  // Wait for the data to load and add it to lunr
  window.data.then(function(loaded_data){
    $.each(loaded_data, function(index, value){
      window.idx.add(
        $.extend({ "id": index }, value)
      );
    });
  });

  // Event when the form is submitted
  $("#site_search").submit(function(event){
      event.preventDefault();
      var query = $("#search_box").val(); // Get the value for the text field
      var results = window.idx.search(query); // Get lunr to perform a search
      display_search_results(results); // Hand the results off to be displayed
  });

  function display_search_results(results) {
    console.log(results);
  }

});

Notice the data source is that we call

window.data = $.getJSON('/pages/archives/index.json');

Server Output: Browser

Open in your favorite browser. Put any text in search form, and click the search button. You should see, console contain array, as respond to button click.

  • http://localhost:1313/pages/search/

Hugo Search: lunr Data in Console

In json object, this would be as below:

[
  {
    "ref": "/quotes/2018/09/07/julian-baker-something/",
    "score": 0.1154962866960379
  }
]

8: Showing The Result

We are almost finished.

Javascript: Loading Lunr

Showing the result is simply changing the display_search_results.

  function display_search_results(results) {
    var $search_results = $("#search_results");

    // Wait for data to load
    window.data.then(function(loaded_data) {

      // Are there any results?
      if (results.length) {
        $search_results.empty(); // Clear any old results

        // Iterate over the results
        results.forEach(function(result) {
          var item = loaded_data[result.ref];

          // Build a snippet of HTML for this result
          var appendString = '<li><a href="' + item.url + '">' + item.title + '</a></li>';

          // Add it to the results
          $search_results.append(appendString);
        });
      } else {
        $search_results.html('<li>No results found</li>');
      }
    });
  }

Layout: Single

Since we change the javascript name again, we must also update the block layout in this artefact.

{{ define "main" }}
<div class="p-2">
  <form action="get" id="site_search">
    <label for="search_box">Search</label>
    <input type="text" id="search_box" name="query">
    <input type="submit" value="search">
  </form>

  <ul id="search_results" class="list-unstyled"></ul>
</div>
{{ end }}

{{ define "custom-javascript" }}
    <script src="{{ "js/lunr-0.7.1.min.js" | relURL }}"></script>
    <script src="{{ "js/search-lunr-0.7.1.js" | relURL }}"></script>
{{ end }}

Server Output: Browser

Open in your favorite browser. Put any text in search form, and click the search button. You should see, the html result. as text in browser, as respond to button click.

  • http://localhost:1313/pages/search/

Hugo Search: lunr Result


9: Summary

Now it is about the right time to put it all together.

// http://rayhightower.com/blog/2016/01/04/how-to-make-lunrjs-jekyll-work-together/
// https://learn.cloudcannon.com/jekyll/jekyll-search-using-lunr-js/

jQuery(function() {

  // Initalize lunr with the fields it will be searching on. I've given title
  // a boost of 10 to indicate matches on this field are more important.
  window.idx = lunr(function () {
    this.ref('id');
    this.field('title');
    this.field('content', { boost: 10 });
    this.field('author');
    this.field('category');
  });

  // Download the data from the JSON file we generated
  window.data = $.getJSON('/pages/archives/index.json');

  // Wait for the data to load and add it to lunr
  window.data.then(function(loaded_data){
    $.each(loaded_data, function(index, value){
      window.idx.add(
        $.extend({ "id": index }, value)
      );
    });
  });

  function display_search_results(results) {
    var $search_results = $("#search_results");

    // Wait for data to load
    window.data.then(function(loaded_data) {

      // Are there any results?
      if (results.length) {
        $search_results.empty(); // Clear any old results

        // Iterate over the results
        results.forEach(function(result) {
          var item = loaded_data[result.ref];

          // Build a snippet of HTML for this result
          var appendString = '<li><a href="' + item.url + '">' + item.title + '</a></li>';

          // Add it to the results
          $search_results.append(appendString);
        });
      } else {
        $search_results.html('<li>No results found</li>');
      }
    });
  }

  // Event when the form is submitted
  $("#site_search").submit(function(event){
      event.preventDefault();
      var query = $("#search_box").val(); // Get the value for the text field
      var results = window.idx.search(query); // Get lunr to perform a search
      display_search_results(results); // Hand the results off to be displayed
  });

  // Get search results if q parameter is set in querystring
  if (getParameterByName('q')) {
      var query = decodeURIComponent(getParameterByName('q'));
      $("#search_box").val(query);
      
      window.data.then(function(loaded_data){
        $('#site_search').trigger('submit'); 
      });
  }

});

 /* ==========================================================================
    Helper functions
    ========================================================================== */

/**
 * Gets query string parameter - taken from 
 * http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
 * @param {String} name 
 * @return {String} parameter value
 */
function getParameterByName(name) {
    var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
    return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}

What is Next ?

Feel sleepy ? Although it is a good idea to finish your tutorial. Just like this sleepy cat, you may sleep anytime you want. Have a break for a while, feed your pet, and resume our tutorial.

adorable cat

There is this our last article, how to make your blog site live. This is actually, the first article that I made, before I start the whole tutorial. Consider continue reading [ Git Pages - Deploying ].

Thank you for reading.

Farewell. We shall meet again.