Ajax/jQuery Autocomplete In Search Box (Shopify)

- 1 answer

I am looking to get autocomplete setup on my search bars within my Shopify shop- this uses Liquid and ajax

I found this tutorial, and implemented it as it says, however it is not working, nothing gets autocompleted on any search bar on the site- I think it may be quite old and was written before the updates/changes to Minimal/Shopify search functionality.

I can follow it through with Chrome dev tools, and it seems like it gets stuck where it adds the search-results list $('<ul class="search-results"></ul>').appendTo($(this)).hide();, this doesn't appear when tracing the HTML of the page. This means that when it later tries to find this list var resultsList = form.find('.search-results'); it doesn't find it, and thus cannot populate with items.

I am running the Minimal theme. The website is testing site with the search bar on the top grey header, and also on /search

A test site built by Shopify to demo this autocomplete is located [https]:// The <ul> append is already there on page load.


Doing a bit more digging, I stumbled across this error in the dev tools -

Uncaught ReferenceError: jQuery is not defined at (index):7031

Which is, you guessed it, the first line of the jQuery code below. $(function() { Any idea why the jQuery is undefined? The script is included at the bottom of my index file right before </body>, so the jquery.min.js should have loaded by then, the rest of the jQuery on the site works fine.

Form code on testing site

<form action="/search" method="get" class="site-header__search small--hide" role="search">
        {% comment %}<input type="hidden" name="type" value="product">{% endcomment %}
        <div class="site-header__search-inner">
          <label for="SiteNavSearch" class="visually-hidden">{{ '' | t }}</label>
          <input type="search" name="q" id="SiteNavSearch" placeholder="{{ '' | t }}" aria-label="{{ '' | t }}" class="site-header__search-input">

        <button type="submit" class="text-link site-header__link site-header__search-submit">
          {% include 'icon-search' %}
          <span class="icon__fallback-text">{{ '' | t }}</span>


{% layout none %}
{% capture results %}
  {% for item in search.results %}
{% assign product = item %}
  "title"    : {{ product.title | json }},
  "url"      : {{ product.url | within: product.collections.last | json }},
  "thumbnail": {{ product.featured_image.src | product_img_url: 'thumb' | json }}
{% unless forloop.last %},{% endunless %}
 {% endfor %}
{% endcapture %}
"results_count": {{ search.results_count }},
"results": [{{ results }}]


$(function() {
  // Current Ajax request.
  var currentAjaxRequest = null;

  // Grabbing all search forms on the page, and adding a .search-results list to each.
   var searchForms = 
 $('form[action="/search"]').css('position','relative').each(function() {

 // Grabbing text input.
   var input = $(this).find('input[name="q"]');

  // Adding a list for showing search results.
    var offSet = input.position().top + input.innerHeight();
    $('<ul class="search-results"></ul>').css( { 'position': 'absolute', 'left': '0px', 'top': offSet } ).appendTo($(this)).hide();    

 // Listening to keyup and change on the text field within these search forms.
   input.attr('autocomplete', 'off').bind('keyup change', function() {

// What's the search term?
  var term = $(this).val();

 // What's the search form?
  var form = $(this).closest('form');

 // What's the search URL?
  var searchURL = '/search?type=product&q=' + term;

 // What's the search results list?
  var resultsList = form.find('.search-results');

  // If that's a new term and it contains at least 3 characters.
  if (term.length > 3 && term != $(this).attr('data-old-term')) {

   // Saving old query.
    $(this).attr('data-old-term', term);

  // Killing any Ajax request that's currently being processed.
    if (currentAjaxRequest != null) currentAjaxRequest.abort();

  // Pulling results.
    currentAjaxRequest = $.getJSON(searchURL + '&view=json', function(data) {
      // Reset results.

    // If we have no results.
      if(data.results_count == 0) {

     // resultsList.html('<li><span class="title">No results.</span></li>');
        // resultsList.fadeIn(200);
      } else {

       // If we have results.
        $.each(data.results, function(index, item) {
          var link = $('<a></a>').attr('href', item.url);
          link.append('<span class="thumbnail"><img src="' + item.thumbnail + '" /></span>');
          link.append('<span class="title">' + item.title + '</span>');

       // The Ajax request will return at the most 10 results.
        // If there are more than 10, let's link to the search results page.
        if(data.results_count > 10) {
          resultsList.append('<li><span class="title"><a target="_blank" rel="nofollow noreferrer" href="' + searchURL + '">See all results (' + data.results_count + ')</a></span></li>');

 // Clicking outside makes the results disappear.
 $('body').bind('click', function(){


Here for posterity and completeness.

I finally got it working, with a few adjustments.

To fix the jQuery undefined error I replaced the first line with: window.onload = (function() {

For some reason the results list was getting a display:block from somewhere, but I couldn't find it, so using jQuery I changed this to block which made it appear. Also in this code line I modified the term.length to start ajax requests on 2, else if you typed cat/dog you would need to then type another letter, or a space to start the search.

// If that's a new term and it contains at leaade it appear. Also in this code line I modified the term.length to start ajax requests on 2, else if you typed cat/dog you would need to then type another letter, or a space to start the search.

// If that's a new term and it contains at least 2 characters. if (term.length > 2 && term != $(this).attr('data-old-term')) { $('<ul class="search-results"></ul>').css( { 'display': 'block'} )

The search results now show up, and all correct! Just needs a bit of CSS adjustments for positioning.