Skip to Content

autocompleteSearch()

Run an AJAX autocomplete search using jQuery UI.

JavaScript February 11, 2021

Usage

JavaScript
nebula.autocompleteSearch(element, types)

Parameters

element
(Required) (Object (or String)) The jQuery object (or selector) of the search input field
Default: None

types
(Optional) (String) A comma-separated string of post types to include
Default: 'any'

Request or provide clarification »

Examples

Include only posts and pages in search.

JavaScript
nebula.loadJS(nebula.site.resources.scripts.nebula_jquery_ui, 'jquery-ui').then(function(){ //Ensure jQuery-UI JS is loaded
    jQuery('#example-input').on('keyup paste', function(){ //Add the listener
        nebula.autocompleteSearch(jQuery(this), 'post,page');
    });
});

nebula.loadCSS(nebula.site.resources.styles.nebula_jquery_ui); //Load the jQuery-UI CSS too

Search all post types by simply using the WordPress core "s" class.

HTML
<input class="s" type="text" placeholder="Search this site..." />

Customize the post types in an HTML form without needing to re-write any JavaScript

HTML
<input class="search" type="text" data-types="post,page" />

Additional Notes

For an easier implementation, you could simply add the class s to the search input. See autocompleteSearchListeners().

On keypress or paste, this function triggers nebula_autocomplete_search_start.

On autocomplete success, nebula_autocomplete_search_success is triggered.

When search results are available, nebula_autocomplete_search_results is triggered, otherwise nebula_autocomplete_search_no_results is triggered if no results are available.

On AJAX error, this function triggers nebula_autocomplete_search_error.

Source File

Located in /assets/js/modules/search.js on line 131.

No Hooks

This function does not have any filters or actions available. Request one?
JavaScript
nebula.autocompleteSearch = function(element, types = ''){
    if ( typeof element === 'string' ){
        element = jQuery(element);
    }

    if ( types && Array.isArray(types) ){
        types = types.join(','); //Convert an array to to a comma-separated string
    }

    nebula.dom.document.trigger('nebula_autocomplete_search_start', element);
    nebula.timer('(Nebula) Autocomplete Search', 'start');
    nebula.timer('(Nebula) Autocomplete Response', 'start');

    if ( element.val().trim().length ){
        if ( element.val().trim().length >= 2 ){ //This checks the length for animation but the minlength (below) handles it for autocomplete
            //Add "searching" class for custom Nebula styled forms
            element.closest('form').addClass('searching');
            setTimeout(function(){
                element.closest('form').removeClass('searching');
            }, 10_000);

            //Swap magnifying glass on Bootstrap input-group
            element.closest('.input-group').find('.fa-search').removeClass('fa-search').addClass('fa-spin fa-spinner');
        } else {
            element.closest('form').removeClass('searching');
            element.closest('.input-group').find('.fa-spin').removeClass('fa-spin fa-spinner').addClass('fa-search');
        }

        let typesQuery = '';
        if ( types ){
            typesQuery = '&types=' + types;
        }

        if ( typeof element.autocomplete !== 'function' ){
            nebula.help('nebula.autocompleteSearch requires jQuery UI. Load that library before calling this function', '/functions/autocompletesearch/');
            return false;
        }

        element.autocomplete({ //jQuery UI dependent
            position: {
                my: 'left top-2px',
                at: 'left bottom',
                collision: 'flip',
            },
            source: async function(request, sourceResponse){
                let searchResults = nebula.memoize('get', 'autocomplete search (' + request.term.toLowerCase() + ') [' + typesQuery + ']'); //Try from stored memory first

                if ( !searchResults ){
                    var fetchResponse = await fetch(nebula.site.home_url + '/wp-json/nebula/v2/autocomplete_search?term=' + request.term + typesQuery, {importance: 'high'}).then(function(fetchResponse){
                        return fetchResponse.json();
                    }).then(function(fetchData){
                        searchResults = nebula.memoize('set', 'autocomplete search (' + request.term.toLowerCase() + ') [' + typesQuery + ']', fetchData); //Add to stored memory
                    }).catch(function(error){
                        nebula.dom.document.trigger('nebula_autocomplete_search_error', request.term);
                        nebula.debounce(function(){
                            ga('send', 'exception', {'exDescription': '(JS) Autocomplete AJAX error: ' + error, 'exFatal': false});
                            nebula.crm('event', 'Autocomplete Search AJAX Error');
                        }, 1500, 'autocomplete error buffer');
                        element.closest('form').removeClass('searching');
                        element.closest('.input-group').find('.fa-spin').removeClass('fa-spin fa-spinner').addClass('fa-search');
                    });
                }

                nebula.dom.document.trigger('nebula_autocomplete_search_success', searchResults);
                ga('set', nebula.analytics.metrics.autocompleteSearches, 1);

                var noSearchResults = ' (No Results)'; //Prep the string

                if ( searchResults ){
                    nebula.dom.document.trigger('nebula_autocomplete_search_results', searchResults);
                    nebula.prefetch(searchResults[0].link);

                    jQuery.each(searchResults, function(index, value){
                        value.label = value.label.replaceAll(/&#038;/g, '\&');
                    });

                    noSearchResults = '';
                } else {
                    nebula.dom.document.trigger('nebula_autocomplete_search_no_results');
                }

                nebula.debounce(function(){
                    let thisEvent = {
                        category: 'Internal Search',
                        action: 'Autocomplete Search' + noSearchResults, //GA4 name: "search"
                        request: request,
                        term: request.term.toLowerCase(),
                        noResults: ( noSearchResults )? true : false,
                    };

                    nebula.dom.document.trigger('nebula_event', thisEvent);
                    ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.term);
                    if ( typeof fbq === 'function' ){fbq('track', 'Search', {search_string: thisEvent.term});}
                    if ( typeof clarity === 'function' ){clarity('set', thisEvent.category, thisEvent.term);}
                    nebula.crm('identify', {internal_search: thisEvent.term});
                }, 1500, 'autocomplete success buffer');

                ga('send', 'timing', 'Autocomplete Search', 'Server Response', Math.round(nebula.timer('(Nebula) Autocomplete Search', 'lap')), 'Each search until server results');

                sourceResponse(searchResults); //Respond to the jQuery UI Autocomplete now

                element.closest('form').removeClass('searching').addClass('autocompleted');
                element.closest('.input-group').find('.fa-spin').removeClass('fa-spin fa-spinner').addClass('fa-search');
            },
            focus: function(event, ui){
                event.preventDefault(); //Prevent input value from changing.
            },
            select: function(event, ui){
                let thisEvent = {
                    category: 'Internal Search',
                    action: 'Autocomplete Click', //GA4 name: "select_content"
                    ui: ui,
                    label: ui.item.label,
                    external: ( typeof ui.item.external !== 'undefined' )? true : false,
                };

                ga('set', nebula.analytics.metrics.autocompleteSearchClicks, 1);
                nebula.dom.document.trigger('nebula_autocomplete_search_selected', thisEvent.ui);
                nebula.dom.document.trigger('nebula_event', thisEvent);
                ga('send', 'event', thisEvent.category, thisEvent.action, thisEvent.label);
                ga('send', 'timing', 'Autocomplete Search', 'Until Navigation', Math.round(nebula.timer('(Nebula) Autocomplete Search', 'end')), 'From first initial search until navigation');

                if ( thisEvent.external ){
                    window.open(ui.item.link, '_blank');
                } else {
                    window.location.href = ui.item.link;
                }
            },
            open: function(){
                element.closest('form').addClass('autocompleted');
                let heroAutoCompleteDropdown = jQuery('.form-identifier-nebula-hero-search');
                heroAutoCompleteDropdown.css('max-width', element.outerWidth());
            },
            close: function(){
                element.closest('form').removeClass('autocompleted');
            },
            minLength: 3, //Require at least 3 characters
        }).data('ui-autocomplete')._renderItem = function(ul, item){
            let thisSimilarity = ( typeof item.similarity !== 'undefined' )? item.similarity.toFixed(1) + '% Match' : '';
            let listItem = jQuery("<li class='" + item.classes + "' title='" + thisSimilarity + "'></li>").data("item.autocomplete", item).append("<a href='" + item.link + "'> " + item.label.replaceAll(/\\/g, '') + "</a>").appendTo(ul);
            return listItem;
        };

        let thisFormIdentifier = element.closest('form').attr('id') || element.closest('form').attr('name') || element.closest('form').attr('class');
        element.autocomplete('widget').addClass('form-identifier-' + thisFormIdentifier);
    }
};

Override

To override or disable this JavaScript function, simply redeclare it with the exact same function name.

JavaScript
nebula.autocompleteSearch = function(element, types){
    //Write your own code here, leave it blank, or return false.
}