Reputation: 105
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.
https://help.shopify.com/themes/customization/store/enable-autocomplete-for-search-boxes
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]://search-autocomplete.myshopify.com/. The <ul>
append is already there on page load.
EDIT:
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">{{ 'general.search.placeholder' | t }}</label>
<input type="search" name="q" id="SiteNavSearch" placeholder="{{ 'general.search.placeholder' | t }}" aria-label="{{ 'general.search.placeholder' | t }}" class="site-header__search-input">
</div>
<button type="submit" class="text-link site-header__link site-header__search-submit">
{% include 'icon-search' %}
<span class="icon__fallback-text">{{ 'general.search.submit' | t }}</span>
</button>
</form>
search.json
{% 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 }}]
}
search-autocomplete.liquid
<script>
$(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.
resultsList.empty();
// If we have no results.
if(data.results_count == 0) {
// resultsList.html('<li><span class="title">No results.</span></li>');
// resultsList.fadeIn(200);
resultsList.hide();
} 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>');
link.wrap('<li></li>');
resultsList.append(link.parent());
});
// 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 href="' + searchURL + '">See all results (' + data.results_count + ')</a></span></li>');
}
resultsList.fadeIn(200);
}
});
}
});
});
// Clicking outside makes the results disappear.
$('body').bind('click', function(){
$('.search-results').hide();
});
});
</script>
Upvotes: 3
Views: 4883
Reputation: 105
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 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.
Upvotes: 1