Refactor code for multiple indices search with autocomplete.js

I’m looking to bridge multiple indices leveraging autocomplete.js. I found this tutorial very helpful. My issue is what happens when I have a lot more that two indices to search?

Currently in our project we’ll have over 30 different indices that will need to be searched. Obviously, simply copy 'n paste code over-and-over again is horrible thing to do, but I can’t figure out any other way to make this work then just doing that.

Is there another way of doing things that would normalize my code?

Here is an example of it. Just imagine that there are another 28 indices in this example. You can see that it’s out of control quickly.

var client = algoliasearch('9G2RUKPPGE', '8860a74c330efaf0119818fcdd800126');
var SPR     = client.initIndex('dev-SPR');
var SWG_SPR = client.initIndex('dev-SWG_SPR');

//initialize autocomplete on search input (ID selector must match)
$('#aa-search-input').autocomplete({ hint: false }, [
    {
        source: $.fn.autocomplete.sources.hits(SPR, {
            hitsPerPage: 15
        }),
        displayKey: 'name',
        //hash of templates used when rendering dataset
        templates: {
            //'suggestion' templating function used to render a single suggestion
            suggestion: function(suggestion) {
                const markup = `
                    <div class="row">
                        <div class="col-xs-1 col-sm-1 col-md-1 nopadding">
                            <img src="${suggestion.image}" alt="" class="algolia-thumb">
                        </div>
                        <div class="col-xs-11 col-sm-11 col-md-11">
                            <div class="row">
                                <div class="col-xs-6 col-sm-8 col-md-8">
                                    <span>${suggestion._highlightResult.code.value}</span>
                                </div>
                                <div class="col-xs-6 col-sm-4 col-md-4">
                                    <span>Available Qty: ${suggestion.quantityAvailable.toLocaleString()}</span>
                                </div>
                            </div>
                            <div class="row hidden-xs">
                                <div class="col">
                                    <span>${suggestion.description}</span>
                                </div>
                            </div>
                        </div>
                    </div>`;

                return '<div class="algolia-result">' + markup + '</div>';
            },
            empty: function(options) {
                return '<div class="algolia-result"><span>No results were found with your current selection.</span></div>';
            },
        }
    },
    {
        source: $.fn.autocomplete.sources.hits(SWG_SPR, {
            hitsPerPage: 15
        }),
        displayKey: 'name',
        //hash of templates used when rendering dataset
        templates: {
            //'suggestion' templating function used to render a single suggestion
            suggestion: function(suggestion) {
                const markup = `
                    <div class="row">
                        <div class="col-xs-1 col-sm-1 col-md-1 nopadding">
                            <img src="${suggestion.image}" alt="" class="algolia-thumb">
                        </div>
                        <div class="col-xs-11 col-sm-11 col-md-11">
                            <div class="row">
                                <div class="col-xs-6 col-sm-8 col-md-8">
                                    <span>${suggestion._highlightResult.code.value}</span>
                                </div>
                                <div class="col-xs-6 col-sm-4 col-md-4">
                                    <span>Available Qty: ${suggestion.quantityAvailable.toLocaleString()}</span>
                                </div>
                            </div>
                            <div class="row hidden-xs">
                                <div class="col">
                                    <span>${suggestion.description}</span>
                                </div>
                            </div>
                        </div>
                    </div>`;

                return '<div class="algolia-result">' + markup + '</div>';
            },
            empty: function(options) {
                return '<div class="algolia-result"><span>No results were found with your current selection.</span></div>';
            },
        }
    }
]).on('autocomplete:selected', function(event, suggestion, dataset) {
    window.location.href = window.location.origin + '/' + suggestion.url
});

30 indexes! that’s indeed quite a lot to keep track of here.

If they all have the same options, what I’d do is make a function like makeTemplates

and then in the options do templates: makeTemplates()

Also it would probably be useful if you had an array of the index names, and then do something like this:

const indices = [SPR, 'etc.'];

const sources = indices.map(function(indexName) {
  return {
    // source would be edited with the custom source from the other question
    source: $.fn.autocomplete.sources.hits(indexName, {
      hitsPerPage: 15,
    }),
    displayKey: 'name',
    templates: {
      suggestion: function(suggestion) {
        const markup = `
          <div class="row">
              <div class="col-xs-1 col-sm-1 col-md-1 nopadding">
                  <img src="${suggestion.image}" alt="" class="algolia-thumb">
              </div>
              <div class="col-xs-11 col-sm-11 col-md-11">
                  <div class="row">
                      <div class="col-xs-6 col-sm-8 col-md-8">
                          <span>${suggestion._highlightResult.code.value}</span>
                      </div>
                      <div class="col-xs-6 col-sm-4 col-md-4">
                          <span>Available Qty: ${suggestion.quantityAvailable.toLocaleString()}</span>
                      </div>
                  </div>
                  <div class="row hidden-xs">
                      <div class="col">
                          <span>${suggestion.description}</span>
                      </div>
                  </div>
              </div>
          </div>`;

        return `<div class="algolia-result">${markup}</div>`;
      },
      empty: function(options) {
        return `
        <div class="algolia-result"><span> No results were found with your current selection.</span></div>`;
      },
    },
  };
});

Then you can use that array instead of the fully written array.

Hope that makes sense!

@haroen
Your function does make sense, thanks! I took your code and applied it to mine, however I’m getting an error. Obviously, things won’t work as a simple cut and paste. There’s got to be a little bit of manipulation that needs to take place. I get that and will continue to work on the error on my side.

One thing that I don’t see is where you do your initIndex function to access the object? Perhaps this is contributing to my ‘Cannot read property ‘_ua’ of undefined’ error message.

Take a look at my JSFiddle for how I’m attempting to do it.

//Search for 'BR6340-02-0.250-00' > Success
//Search for 'blah' > Failure
    
    var client = algoliasearch('9G2RUKPPGE', '8860a74c330efaf0119818fcdd800126');
        
    const indices = ['SPR', 'SWG_SPR'];
    
    const sources = indices.map(function(indexName) {
    return {
      // source would be edited with the custom source from the other question
      source: $.fn.autocomplete.sources.hits(indexName, {
        hitsPerPage: 15,
      }),
      displayKey: 'name',
      templates: {
        suggestion: function(suggestion) {
          const markup = `
            <div class="row">
                <div class="col-xs-1 col-sm-1 col-md-1 nopadding">
                    <img src="${suggestion.image}" alt="" class="algolia-thumb">
                </div>
                <div class="col-xs-11 col-sm-11 col-md-11">
                    <div class="row">
                        <div class="col-xs-6 col-sm-8 col-md-8">
                            <span>${suggestion._highlightResult.code.value}</span>
                        </div>
                        <div class="col-xs-6 col-sm-4 col-md-4">
                            <span>Available Qty: ${suggestion.quantityAvailable.toLocaleString()}</span>
                        </div>
                    </div>
                    <div class="row hidden-xs">
                        <div class="col">
                            <span>${suggestion.description}</span>
                        </div>
                    </div>
                </div>
            </div>`;

          return `<div class="algolia-result">${markup}</div>`;
        },
        empty: function(options) {
          return `
          <div class="algolia-result"><span> No results were found with your current selection.</span></div>`;
        },
      },
    };
  });

@haroen mentioned ‘source from other question’. The other question is here.

1 Like

oh yes you’re right, I forgot that in, but in your map as well, instead of passing indexName as first argument to the source; you should pass client.initIndex(indexName).

@haroen,

Thanks so much for working through this problem with me. Having so many indices is really a daunting task to accomplish. After reviewing my code and seeing that I was the reason for things not correctly functioning, I decided to look at your first post again.

Below is another crack at the JS that’s not working. I believe that the issue is because of the indices.map function that’s adding the .empty callout on every source. So, if the user searches for ‘BR6340-02-0.250-00’ a successful result is returned but I also see a ‘No results found…’ message for the indices where that value isn’t found. The result is a bad UX.

What do you think can be done about this?

As usual, this JSFiddle shows a working example.

//Search for BR6340-02-0.250 > Success
//Search for blah > Failure

var client = algoliasearch('9G2RUKPPGE', '8860a74c330efaf0119818fcdd800126');

const indices = [
  ['SPR', 'Spacers'],
  ['SWG_SPR', 'Swage Spacers'],
  ['FF_STDF', 'Female-Female Standoffs'],
  ['CHF', 'Chassis Fasteners'],
  ['BSM', 'Ball Stud Males'],
  ['BSF', 'Ball Stud Females']
];

const sources = indices.map(function(indexKeyValue) {
  var index = client.initIndex(indexKeyValue[0]);

  return {
// source would be edited with the custom source from the other question
source: $.fn.autocomplete.sources.hits(index, {
  hitsPerPage: 15,
}),
displayKey: 'code',
templates: {
  suggestion: function(suggestion) {
    const markup = `
      <div class="row">
          <div class="col-xs-1 col-sm-1 col-md-1 nopadding">
              <img src="${suggestion.image}" alt="" class="algolia-thumb">
          </div>
          <div class="col-xs-11 col-sm-11 col-md-11">
              <div class="row">
                  <div class="col-xs-6 col-sm-8 col-md-8">
                      <span>${suggestion._highlightResult.code.value}</span>
                  </div>
                  <div class="col-xs-6 col-sm-4 col-md-4">
                      <span>Available Qty: ${suggestion.quantityAvailable.toLocaleString()}</span>
                  </div>
              </div>
              <div class="row hidden-xs">
                  <div class="col-sm-12 col-md-12 col-lg-12 col-xl-12">
                      <span>${suggestion.description}</span>
                  </div>
              </div>
          </div>
      </div>`;

    return `<div class="algolia-result">${markup}</div>`;
  },
  empty: function(options) {
    return `<div class="algolia-result"><span> No results were found with your current selection.</span></div>`;
  },
},
  };
});

$('#aa-search-input').autocomplete({
hint: true,
debug: true
  },
  sources
).on('autocomplete:selected', function(event, suggestion, dataset) {
  window.location.href = window.location.origin + '/' + suggestion.url
});

please sent me the working code for multiple indicies

Hi @jnichols

If I understand correctly you’ve managed to correctly use the multi index using a function to factorize the redundant code but you have an issue with the display of the no-results? What would be a correct display? Do you want to hide the no-results message if some datasources yield some results?

Have you considered aggregating your indices into a single one?

By the way, I took the liberty to fork your example and add the missing dependencies to make it work. http://jsfiddle.net/rtkhw7o0/

@Bobylito,

Thanks for looking at my situation. The correct display would be that of two different ways.

If there is a successful result found then the code should return the single result. As you can see below I’m getting an empty message where that value is not found in all other indices. However, that value was found in a single index.
22 AM

If there are no results for all indices then a single empty message should be returned. FYI, I obtained this example by commenting out all of my indices except for only one.
05 AM

I also need to say thanks for adding the dependencies on my previous JSFiddle. My source fiddle that I’m testing on is private but wanted to make a copy for others to review. I just forgot to add the dependencies.

Working with multiple indices generally means that you have heterogeneous types of records. This means that each index would have its own specificity, and therefore you should customize the “no result” message to give more context.

If you want something that is more aggregated because the difference is of little value, you can simplify everything by aggregating your data in a single index. Also it will be more efficient, as you would only do one request rather than one per index.

@Bobylito - Incorporating long-term planning for my corporation, it makes the best sense to go ahead and have separate indices for the records that I need Algolia to host. We have future plans of releasing different search functionality throughout our site using Algolia. I do believe that Algolia’s documentation indicates that if there are different records they should be in a different index.

I would like to show different error message results based upon the indices. However, trying to make things just work and function meeting a release date I need to figure out something.

Ultimately having the solution in place to show an error message for all indices where a result isn’t found is our goal. Currently what I have in place won’t work for production and would require me to show ZERO error messages. I think that would be a bad UX with the user.

Below is an example of what we currently have when a successful record is found

This is an example when no part number is found.
12 AM

Current working JSFiddle is found here: http://jsfiddle.net/jandk4014/0bjkv873/3/

@Bobylito @haroen

For what it’s worth I was able to figure out the issue. Oddly enough the answer came from an old GitHub ticket. When I first stumbled upon the option of an event “autocomplete:empty” that lead to my Google search. That search lead me to ticket 102 where that user showed how to apply a “global” template.

I’ve updated a JSFiddle to show it’s functionality.

2 Likes