Loading...

How to add search to your Jekyll website

how-to jekyll
Ram Patra Published on November 29, 2019
Image placeholder

Jekyll is a static website generator that takes your plain text files and converts them to a beautiful website/blog.

People mostly use markdown files to write content and then use Jekyll to convert these markdown (.md) files to Html pages. Now, as there are no database nor backend application servers, there are limited ways to add a search feature to such a website. To be more specific, we have the following options:

  1. Google Custom Search
  2. Algolia Search Widget
  3. Custom search with the help of Javascript (may use 3rd party js libraries too)

In this blog, we will discuss the 3rd option and if you want to know why I went with this option and not the other two you can another post which describes how I made my blog.

There are two main steps in this option:

First, we need to have a file that will have details of all our posts. This file will basically act as our database. The RSS xml file can be used but the RSS feed file generated by jekyll-feed plugin contains only the recent 10 posts. This number is hardcoded into the plugin and is not configurable. Therefore, we have two options here, first, fork jekyll-feed plugin which I’ve already done, second, we need to explicitly generate a file that would contain metadata about all the posts. As there are no backend servers nor databases, we would ideally do the search on the client-side, i.e, with the help of Javascript. Therefore, it would be prudent to generate a json file which would contain the details of all our posts. We can then use this json file in our javascript code to search. Sounds simple? Cool, let’s see how we can create the json file.

---
layout: null
---
[
{% for post in site.posts %}
    {
        "title" : "{{ post.title }}",
        "url" : "{{ post.url }}",
        "date" : "{{ post.date | date: "%b %d, %Y" }}",
        "content" : "{{ post.content | strip | strip_html | strip_newlines | replace: '"', '' | slice: 0, 600 }}"
    }{% if forloop.last %}{% else %},{% endif %}
    {% endfor %}
]

File: site.json

Place the above file in the root directory of your project. This will create a json array with all the metadata which we can use in our search method. You can even see the site.json file of my website.

Second, implement the search method in Javascript. We basically add an event listener and once our required event is triggered we perform the search. This can be a button click event or a keyup event. I chose the latter because I wanted to refresh the results as the user types, just like how Google Search works.

var posts = []; // will hold the json array from your site.json file

$("body").on("click", "[data-action]", function (e) {

    e.preventDefault();
    
    var $this = $(this);
    var action = $this.data('action');
    var target = $this.data('target');
    
    if (action === 'omnisearch-open') {
        var $searchFormEl = $(target).find('.form-control');
    
        search($searchFormEl.val()); // to handle cases where the search field isn't empty, i.e, the user searched for something earlier
    
        $searchFormEl.keyup(function (e) { // refresh results while user is typing
            e.preventDefault();
    
            search($searchFormEl.val());
        });
    }

});

function search(searchStr) {
	fetchSiteJson(searchCallback(searchStr));
}

function searchCallback(searchStr) {
    return function () {
        var options = { 		// initialize options for fuse.js
            shouldSort: true,
            threshold: 0.4,
            location: 0,
            distance: 100,
            maxPatternLength: 32,
            minMatchCharLength: 1,
            keys: [
                {
                    name: "title",
                    weight: 0.3		// give title more importance
                },
                {
                    name: "content",
                    weight: 0.4
                }
            ]
        };

        // initialize fuse.js library
        var fuse = new Fuse(posts, options);
        var results = fuse.search(searchStr); // invoke search method in fuse.js library

        if (searchStr.length === 0) {
            updateResults(posts.slice(0, 5), true); // if there are no search results, show some suggestions
        } else {
            updateResults(results, false);
        }
    }
}

In the above code, if you see I’ve used fuse.js library to perform the search. You can simply do a string match in javascript and filter the results but what’s the fun in that, right. Therefore, I compared various client-side search libraries and even used lunr.js at first but later learned that it lacked the Edit distance algorithm which I think is important to be in a search library. The Edit distance algorithm basically takes care of typos and shows the closest results. So, I went with fuse.js and it wasn’t only because of that but fuse.js also has great documentation, is easier to use, does the job well, and last but not least, is the most trending on the market now.

It is simply two lines of code to use the fuse.js library. You initialize the library new Fuse(posts, options); and then invoke the search method fuse.search(searchStr);.

Below is the code to update the search results on the UI. This code snippet is taken from this website. You can see how it looks by hitting the search button at the top. Now, this will be different based on your design but I’ve shared it here so that you at least get an idea of what’s involved.

function updateResults(results, isSuggestion) {
	var resultsHtml = '';

    results.forEach(function (res) {
        resultsHtml += '<li>' +
            '<a class="list-link" href="' + res.url + '">' +
            '<i class="search-icon" data-feather="' + (isSuggestion ? 'clock' : 'search') + '"></i> ' +
            '<span>' + res.title + '</span><small> on ' + res.date + '</small>' +
            '</a>' +
            '</li>';
    });
    
    var resultsWrapperHtml = '<h6 class="heading">Search ' + (isSuggestion ? 'Suggestions' : 'Results') + '</h6>' +
        '<div class="row">' +
        '<div class="col-sm-12">' +
        '<ul class="list-unstyled mb-0">' + resultsHtml + '</ul></div></div>';
    
    // insert the result html into the page
    $('.omnisearch-suggestions').html(resultsWrapperHtml);
    
    // render feather icons
    feather.replace({class: 'search-icon', width: '1em', height: '1em'});

}

Lastly, below is the code to load the site.json file and get the metadata into an array. If you’re wondering what are those $ signs everywhere are then please read about jQuery framework. This is the only dependency in my code which you can easily get rid of as I am not doing anything complex here.

function fetchSiteJson(callback) {
    if (posts.length === 0) {
    	// fetch site.json file
    	$.get("/site.json", function (data) {
    		posts = data;
    		callback();
    	});
    } else { // we already have the posts so simply use it instead of downloading the file again
    	callback();
    }
}

The above code snippets are taken from this website. If you want to see a live demo of how everything looks and feels like then hit the search button at the top. For any queries, please feel free to drop a comment below. Cheers!

Presentify

Take your presentation to the next level.

FaceScreen

Put your face and name on your screen.

ToDoBar

Your to-dos on your menu bar.

Ram Patra Published on November 29, 2019
Image placeholder

Keep reading

If you liked this article, you may like these as well

January 5, 2022 Two simple ways to earn interest with your crypto

There are many ways you can make your crypto savings work for you but I am going to list down two of the most well-known ones that I personally use most frequently.

pc-build windows pc-fan January 29, 2022 How to control the fan speed in Windows? (for example, Antec fans on a MSI Motherboard)

In this blog post, we are going to see how to control the fan speed/RPM of Antec Prism ARGB 120mm fans on a MSI X570 Tomahawk Motherboard. Having said that, the process should be similar for other fans and motherboards.

October 17, 2024 Show your screen and your face at the same time on macOS

In today’s world of remote work and digital content creation, engaging your audience has never been more important. Whether you’re giving a presentation, conducting a tutorial, or recording a demo, adding a personal touch can make all the difference. That’s where FaceScreen comes in. This innovative macOS app allows you to open up your camera view in Picture-in-Picture (PiP) mode, enabling you to show both your screen and your face simultaneously.