ThorneLabs

Instant Search with Twitter Bootstrap, Jekyll, JSON, and jQuery

• Updated March 17, 2019


By default, the Twitter Bootstrap Search Field will use Google to search your site. This default functionality works (assuming Google has indexed your site), but it forces you out of your website to the Google search results page. With some tweaks, the search field can be wired to do whatever you want. I wanted a search field that functioned like Google Instant Search with the following features:

  • On page load, the search field is automatically focused for quick searching
  • As more characters are typed the search results becomes more filtered
  • Be able to navigate the search results with the arrow keys
  • Hide the search results if there is a left mouse click outside of the search results dropdown menu
  • If there are no search results, display “No results found”

I was able to piece all of this together using the work already done by the folks on the following websites:

The following post describes what is needed to wire up the instant search field.

The JSON File

Because Jekyll is a static-site generator, you do not have a database to query. For this whole thing to work, you need some sort of file to search through. This is where JSON comes in.

Jekyll will generate a JSON file that will contain every post with its associative metadata. This associative metadata could also be incorporated into the search, but for this example only the title and href attributes are used.

Create file search.json in your root Jekyll directory with the following contents:

---
---
[
    {% for post in site.posts %}
    {
      "title"    : "{{ post.title }}",
      "href"     : "{{ post.url }}",
      "date"     : {
         "day"   : "{{ post.date | date: "%d" }}",
         "month" : "{{ post.date | date: "%B" }}",
         "year"  : "{{ post.date | date: "%Y" }}"
      }
    }
    {% unless forloop.last %},{% endunless %}
    {% endfor %}
]

Each time you run jekyll build, a search.json file will be generated in the Jekyll _site folder containing all of your posts and their associative metadata that you can search through using jQuery.

The jQuery Code

With the JSON file created, you now need the actual code in place to parse and search through the file.

First, make sure you have already loaded the jQuery library within the head tags of your website. If you are using Twitter Bootstrap this should already be in place, but if you don’t here is an example (the missing http: in the URL is not a typo):

<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>

Second, put the following jQuery code within the head tags of your website:

<script type="text/javascript">

    function getSearchJSON()
    {
        $.getJSON("/search.json", function(e) {
            console.log("[search.json loaded for instant search]");

            $("#search_results").html("");

            searchJSON = e;
        });
    }

    function doSearch(e)
    {
        results = [];

        if (e != "")
        {
            $.each(searchJSON, function(t, n) {
                var r = n.title, i = n.title.toLowerCase(), s = n.href, o = n.date;
                i.indexOf(e)!==-1 && results.push([r, s, o])
            });

            printResults();
        }
        else
        {
            $("#search_results").html();
            results = [];
            printResults();
        }
    }

    function printResults()
    {
        var e = $("#search_results");

        e.html("");

        e.html(function() {
            if (results.length == 0)
            {
                e.append('<li style="padding-top: 3px; padding-bottom: 3px"><a style="color: #999; word-wrap: break-word; white-space: normal" href="#">No results found</a></li>');
            }
            else
            {
                $.each(results, function(t, n) {
                    e.append('<li style="padding-top: 3px; padding-bottom: 3px"><a style="color: #999; word-wrap: break-word; white-space: normal" href="' + n[1] + '">' + n[0] + '</a></li>');
                });
            }
        });
    }

    // Show the dropdown menu as long as there are characters in the text field
    function checkTextField()
    {
        // If the value of id search_input is not empty show id search_results otherwise hide it
        if ($('#search_input').val() != '')
        {
            $('#search_results').show();
        }
        else
        {
            $('#search_results').hide();
        }
    }

    // Hide the dropdown menu if there is a left mouse click outside of it
    $(document).mouseup(function (e)
    {
        var container = $("#search_results");

        // if the target of the click isn't the
        // container nor a descendant of the container
        if (!container.is(e.target) && container.has(e.target).length === 0)
        {
            container.hide();
        }
    });

    $(document).ready(function() {
        // Create the search index on page load
        getSearchJSON();

        // Continually update search results as characters are typed
        $("#search_input").keyup(function() {
            // Make search inputs are case insensitive
            var e = $(this).val().toLowerCase();

            // Do the actual search
            doSearch(e);
        });
    });

</script>

The HTML Markup

With the JSON file created and the jQuery code in place, the last piece is the HTML markup.

I rely entirely on Twitter Bootstrap for the styling of the search field and the search results dropdown menu. You will probably need to apply additional styling to incorporate the following HTML markup into your site.

In my particular case, I put the search field within the navigation of my website, but you could just as easily put it elsewhere (if you do be sure to remove the navigation tags and classes). I have truncated () the unnecessary parts of the HTML markup that only applies to my website.

<nav class="navbar navbar-default" role="navigation">
    <div class="container">
        ...
        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <div class="nav navbar-nav navbar-form dropdown">
                <!-- Search text box -->
                <input id="search_input" data-toggle="dropdown" type="text" class="form-control" placeholder="Search" autofocus="autofocus" autocomplete="off" onkeyup="checkTextField();" />
                <!-- Search results styled as a dropdown menu -->
                <ul id="search_results" class="dropdown-menu" role="menu">
                </ul>
            </div>
            ...
        </div><!-- /.navbar-collapse -->
    </div>
</nav>

Current Problems

All of the above code and markup accomplishes what I sought out to get working. However, there is one small problem with the current implementation which has to do with navigating the search results using the arrow keys.

When you use Twitter Bootstrap’s dropdown menus it provides the functionality to navigate that dropdown menu with the arrow keys. I use this functionality to navigate the search results, and it works, but the way I use it was not how it was intended to be used. The dropdown menu is intended to be activated (i.e. shown) by clicking the dropdown menu’s header link. It is not intended to be activated by typing in a text field.

Despite this, the functionality works with the only caveat of having to hit the down arrow twice to navigate the search results. This happens because the first hit of the down arrow is actually activating the dropdown menu (which is already displayed because of the checkTextField jQuery function). The second hit of the down arrow will then navigate the search results.

At some point I hope to fix this by removing the data-toggle=“dropdown” function in the input HTML tag and writing custom jQuery code to handle navigating the search results with the arrow keys.

References

If you found this post useful and would like to help support this site - and get something for yourself - sign up for any of the services listed below through the provided affiliate links. I will receive a referral payment from any of the services you sign-up for.

Get faster shipping and more with Amazon Prime: About to order something from Amazon but want to get more value out of the money you would normally pay for shipping? Sign-up for a free 30-day trial of Amazon Prime to get free two-day shipping, access to thousands of movies and TV shows, and more.

Thanks for reading and take care.