Configuring Funnelback for instant search

Managed by | Updated .

Background

This article details the steps involved in configuring Funnelback to provide an instant search. Instant search is where the search results display updates as you type the query.

See an example of a working Funnelback instant search: http://showcase.funnelback.com/s/search.html?collection=showcase-instant-search

Process

1. Create a pre process hook script to expand query:

hook_pre_process.groovy
// Add partial query support to padre
// reads the query CGI parameter and creates a disjunctive query based on the suggestions returned from padre-qs, injected into the system query parameter

// Imports required for access to the padre suggest service
import java.io.File;
import java.util.List;
import com.funnelback.dataapi.connector.padre.PadreConnector;
import com.funnelback.dataapi.connector.padre.suggest.Suggestion;
import com.funnelback.dataapi.connector.padre.suggest.Suggestion.ActionType;
import com.funnelback.dataapi.connector.padre.suggest.Suggestion.DisplayType;
// Imports required to enable logging
//import org.apache.logging.log4j.LogManager;
//import org.apache.logging.log4j.Logger;
//Logger logger = LogManager.getLogger("partial query expander")


def q = transaction.question
if (q.collection.configuration.value(["partial_query_enabled"])) {
    // Convert a partial query into a set of query terms
    // Maximum number of query terms to expand partial query to - read from collection.cfg partial_query_expansion_index parameter.
    // eg. partial_query=com might expand to query=[commerce commercial common computing]
    def partial_query_expansion_index = 5
    if ((q.collection.configuration.value(["partial_query_expansion_index"]) != null) && (q.collection.configuration.value(["partial_query_expansion_index"]).isInteger())) {
      partial_query_expansion_index = q.collection.configuration.value(["partial_query_expansion_index"])
    }

    if (q.query != null) {
                File searchHome = new File("/opt/funnelback")
                File indexStem = new File(q.collection.configuration.value(["collection_root"]) + File.separator + "live" + File.separator + "idx","index")

                // NOTE: CONSTRUCTOR HAS CHANGED post v14.2 and requires searchHome as the first param
                List<Suggestion> suggestions = new PadreConnector(searchHome,indexStem)
                  .suggest(q.query)
                  .suggestionCount(partial_query_expansion_index)
                  .fetch();
				// Use this instead for v14.2 and earlier
                /* List<Suggestion> suggestions = new PadreConnector(indexStem)
                  .suggest(q.query)
                  .suggestionCount(partial_query_expansion_index)
                  .fetch(); */

                // build the expanded query from the list of suggestions
                def expanded_query = ""
                suggestions.each {
                        expanded_query += '"'+it.key+'" '
                }

                // set the query to the expanded set of query terms ORed together
                if (expanded_query != "") {
                                q.additionalParameters["s"] = ["["+expanded_query+"]"]
                }
    }
}

2. Add collection.cfg options:

collection.cfg
# Option to enable the hook script code above
partial_query_enabled=true
# optional parameter to control number of suggestions to expand the query to (def = 5)
#partial_query_expansion_index=5
# disable query completion
query_completion_enabled=false

3. Add the following JS to the web resources folder ($SEARCH_HOME/conf/$COLLECTION_NAME/_default/web/jquery.fb.instantSearch.js)

_default/web/jquery.fb.instantSearch.js
(function($) {
    $.widget('fb.instantSearch', {
        options: {
            autocomplete : false,
            form         : 'results-instant',
            length       : 3,
            updatedSel   : '#search-results-instant',
        },
        autocomplete: function(val) { // turn on|off autocomplete dropdown
            if (typeof(val) != 'undefined') this.options.autocomplete = val;
            if (this.element.is(':ui-autocomplete')) this.element.autocomplete(this.options.autocomplete ? 'enable' : 'disable');
            return this.options.autocomplete;
        },
        callUpdate: function() { // call ajax request and update chunk of page based on return html response
            var that = this, form = that.form();
            $.get(form.attr('action'), that._setParameter(form.serialize(), 'form', that.options.form)).done(function(data) {
                $(that.options.updatedSel).html(data);
                var url = that._setParameter(document.location.search, 'query', that.element.val());
                window.history.pushState({html:data, pageTitle:that.element.val()}, '', url); // update query parameter in browser address bar URL
            });
        },
        form: function() { // get parent form of element
            return this.element.closest('form');
        },
        _create: function() {
            if (!$(this.options.updatedSel).length) return;
            var that = this;
            that.autocomplete();
            that.element.keyup(function(e) {
                if ($(this).val().length < that.options.length) return;
                that.callUpdate();
            });
        },
        _setParameter: function(str, key, val) { // add or update parameter
            var regex = new RegExp('([?&]' + key + ')=([^#&]*)', 'g');
            return str.match(regex) ? str.replace(regex, '$1=' + val) : str + '&' + key + '=' + val;
        }
    });
})(jQuery);

4. Modify the search template to call the instant search function from the in-page Javascript block (eg. after query completion code).  Note: the length parameter controls the minimum length of the partial query used to trigger the instant search

simple.ftl
// Instant search
jQuery("input.query").instantSearch({
    length : '<@s.cfg>query_completion.length</@s.cfg>'
});

5. Include a #search-results-instant div in the html page code - note this should be included even in the s.InitialFormOnly section.

simple.ftl
<@s.InitialFormOnly>
<div id="search-results-instant" />
</@s.InitialFormOnly>

<@s.AfterSearchOnly>

 <div id="search-results-instant" class="row" data-ng-show="isDisplayed('results')">

    ...

  </div>
</@s.AfterSearchOnly>
Was this artcle helpful?

Tags
Type: Keywords:
Features:
XML
Frontend > Modern UI Frontend > Hook scripts Frontend > Auto-completion

Comments