<div id="searchbox"></div>
<div id="hierarchical"></div>
<div id="hits"></div>
const customMenuRenderFn = function(renderParams, isFirstRendering) {
  const container = renderParams.widgetParams.container;
  const title = renderParams.widgetParams.title;
  const templates = renderParams.widgetParams.templates;
  const cssClasses = renderParams.widgetParams.cssClasses || '';
  const attributes = renderParams.widgetParams.attributes.map(attribute =>
    attribute.replace('.', '___')
  );

  let currentSelectedValue = null;

  function attachChangeEvent(element, refine) {
    $(container)
      .find(element)
      .on('change', event => {
        let value = event.target.value;

        if (value === '__EMPTY__') {
          value = currentSelectedValue;
        }

        refine(value);

        currentSelectedValue = value;
      });
  }

  if (isFirstRendering) {
    $(container).append(
      `<div class="ais-header">${templates.header}</div>` +
        `<select id="ais-hierarchic-root--attribute-${attributes[0]}" class="${cssClasses.select}">` +
        `<option value="__EMPTY__" class="${cssClasses.item}">Tutto</option>` +
        `</select>` +
        `<div class="ais-hierarchic-levels"></div>`
    );

    attachChangeEvent(
      `#ais-hierarchic-root--attribute-${attributes[0]}`,
      renderParams.refine
    );
  } else {
    $(container)
      .find('.ais-hierarchic-levels')
      .empty();

    updateHits(0, renderParams.items);
  }

  function generateOptionsHtml(depth, hits) {
    let nullValue = '__EMPTY__';

    if (depth > 0) {
      const rootSelect = $(`#ais-hierarchic-root--attribute-${attributes[0]}`);
      if (rootSelect.length > 0) {
        nullValue = rootSelect.val();
      }
    }

    let optionsHtml = [
      `<option value="${nullValue}" class="${cssClasses.item}" selected>Everything</option>`,
    ];

    optionsHtml = optionsHtml.concat(
      hits.map(
        item => `<option class="${cssClasses.item}" value="${item.value}" ${item.isRefined
          ? 'selected'
          : ''}>
                        ${item.label} (${item.count})
                    </option>`
      )
    );

    return optionsHtml;
  }

  function updateHits(depth, hits) {
    let element = null;
    hits.forEach(item => {
      if (item.isRefined && item.data) {
        return updateHits(depth + 1, item.data);
      }
    });


    const hierarchicLevels = $(container).find('.ais-hierarchic-levels');

    if (depth > 0) {
      element = $(hierarchicLevels).find(
        `#ais-hierarchic-levels--attribute-${attributes[depth]}`
      );

      if (element.length <= 0) {
        const originalAttributeName = attributes[depth].replace('___', '.');

        $(hierarchicLevels).append(
          `<select id="ais-hierarchic-levels--attribute-${attributes[
            depth
          ]}" ` +
            `class="${cssClasses.select}"` +
            `data-attribute="${originalAttributeName}">` +
            `</select>`
        );

        element = $(container).find(
          `#ais-hierarchic-levels--attribute-${attributes[depth]}`
        );

        attachChangeEvent(
          `#ais-hierarchic-levels--attribute-${attributes[depth]}`,
          renderParams.refine
        );
      }
    } else {
      element = $(container).find(
        `#ais-hierarchic-root--attribute-${attributes[0]}`
      );
    }

    element.html(generateOptionsHtml(depth, hits));
  }
};

const selectHierarchicalMenu = instantsearch.connectors.connectHierarchicalMenu(
  customMenuRenderFn
);

const search = instantsearch({
  appId: 'latency',
  apiKey: '6be0576ff61c053d5f9a3225e2a90f76',
  indexName: 'instant_search',
});

search.addWidget(
  instantsearch.widgets.hits({
    container: '#hits',
    templates: {
      empty: 'No results',
      item: '<em>Hit {{objectID}}</em>: {{{_highlightResult.name.value}}}',
    },
  })
);

search.addWidget(
  instantsearch.widgets.searchBox({
    container: '#searchbox',
    placeholder: 'Search for products',
  })
);

search.addWidget(
  selectHierarchicalMenu({
    limit: 50,
    container: '#hierarchical',
    attributes: ['hierarchicalCategories.lvl0', 'hierarchicalCategories.lvl1'],
    showParentLevel: false,
    templates: {
      header: 'Header',
    },
    cssClasses: {
      select: 'testing-select-class',
      item: 'testing-item-class',
    },
  })
);

search.start();

External CSS

  1. https://cdn.jsdelivr.net/npm/instantsearch.js@2.1.6/dist/instantsearch.min.css
  2. https://cdn.jsdelivr.net/npm/instantsearch.js@2.1.6/dist/instantsearch-theme-algolia.min.css

External JavaScript

  1. https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js
  2. https://cdn.jsdelivr.net/npm/instantsearch.js@2.1.6/dist/instantsearch.min.js