API Reference / InstantSearch.js Widgets / menuSelect
Apr. 24, 2019

menuSelect

Widget signature
instantsearch.widgets.menuSelect({
  container: string|HTMLElement,
  attribute: string,
  // Optional parameters
  limit: number,
  sortBy: string[]|function,
  templates: object,
  cssClasses: object,
  transformItems: function,
});

About this widget #

The menuSelect widget allows a user to select a single value to refine inside a select element.

Requirements#

The attribute provided to the widget must be added in attributes for faceting, either on the dashboard or using attributesForFaceting with the API.

Examples #

1
2
3
4
instantsearch.widgets.menuSelect({
  container: '#menu-select',
  attribute: 'brand',
});

Options #

container #
type: string|HTMLElement
Required

The CSS Selector or HTMLElement to insert the widget into.

Edit
1
2
3
4
instantsearch.widgets.menuSelect({
  // ...
  container: '#menu-select',
});
attribute #
type: string
Required

The name of the attribute in the record.

Edit
1
2
3
4
instantsearch.widgets.menuSelect({
  // ...
  attribute: 'brand',
});
limit #
type: number
default: 10
Optional

The maximum amount of values to display.

Edit
1
2
3
4
instantsearch.widgets.menuSelect({
  // ...
  limit: 20,
});
sortBy #
type: string[]|function
default: ["name:asc"]
Optional

How to sort refinements. Must be one or more of the following strings:

  • "count:asc"
  • "count:desc"
  • "name:asc"
  • "name:desc"
  • "isRefined"

It’s also possible to give a function, which receives items two by two, like JavaScript’s Array.sort.

Edit
1
2
3
4
instantsearch.widgets.menuSelect({
  // ...
  sortBy: ['isRefined'],
});
templates #
type: object
Optional

The templates to use for the widget.

Edit
1
2
3
4
5
6
instantsearch.widgets.menuSelect({
  // ...
  templates: {
    // ...
  },
});
cssClasses #
type: object
default: {}
Optional

The CSS classes to override.

  • root: the root element of the widget.
  • noRefinementRoot: the root element if there are no refinements.
  • select: the select element.
  • option: the option elements of the select.
Edit
1
2
3
4
5
6
7
8
9
10
instantsearch.widgets.menuSelect({
  // ...
  cssClasses: {
    root: 'MyCustomMenuSelect',
    select: [
      'MyCustomMenuSelectElement',
      'MyCustomMenuSelectElement--subclass'
    ],
  },
});
transformItems #
type: function
default: x => x
Optional

Receives the items, and is called before displaying them. Should return a new array with the same shape as the original array. Useful for mapping over the items to transform, and remove or reorder them.

Edit
1
2
3
4
5
6
7
8
9
instantsearch.widgets.menuSelect({
  // ...
  transformItems(items) {
    return items.map(item => ({
      ...item,
      label: item.label.toUpperCase(),
    }));
  },
});

Templates #

item #
type: string|function
Optional

The template to customize each option element. It exposes:

  • label: string: the label to display in the option.
  • value: string: the value for the option.
  • count: number: the number of hits that match this value.
  • isRefined: boolean: indicates if it’s the current refined value.
Edit
1
2
3
4
5
6
7
instantsearch.widgets.menuSelect({
  // ...
  templates: {
    item:
      '{{label}} ({{#helpers.formatNumber}}{{count}}{{/helpers.formatNumber}})',
  },
});
defaultOption #
type: string|function
Optional

The template to customize the first option of the select.

Edit
1
2
3
4
5
6
instantsearch.widgets.menuSelect({
  // ...
  templates: {
    defaultOption: 'See all',
  },
});

Customize the UI - connectMenu#

If you want to create your own UI of the menuSelect widget, you can use connectors.

This connector is also used to build other widgets: Menu

It’s a 3-step process:

// 1. Create a render function
const renderMenuSelect = (renderOptions, isFirstRender) => {
  // Rendering logic
};

// 2. Create the custom widget
const customMenuSelect = instantsearch.connectors.connectMenu(
  renderMenuSelect
);

// 3. Instantiate
search.addWidget(
  customMenuSelect({
    // instance params
  })
);

Create a render function#

This rendering function is called before the first search (init lifecycle step) and each time results come back from Algolia (render lifecycle step).

const renderMenuSelect = (renderOptions, isFirstRender) => {
  const {
    object[] items,
    boolean canRefine,
    function refine,
    object widgetParams,
  } = renderOptions;

  if (isFirstRender) {
    // Do some initial rendering and bind events
  }

  // Render the widget
}

If SEO is critical to your search page, your custom HTML markup needs to be parsable:

  • use plain <a> tags with href attributes for search engines bots to follow them,
  • use semantic markup with structured data when relevant, and test it.

Refer to our SEO checklist for building SEO-ready search experiences.

Rendering options #

items #
type: object[]

The elements that can be refined for the current search results. With each item:

  • value: string: the value of the menu item.
  • label: string: the label of the menu item.
  • count: number: the number of matched results after a refinement is applied.
  • isRefined: boolean: indicates if the refinement is applied.
1
2
3
4
5
6
7
8
9
10
11
const renderMenuSelect = (renderOptions, isFirstRender) => {
  const { items } = renderOptions;

  document.querySelector('#menu-select').innerHTML = `
    <select>
      ${items
        .map(item => `<option value="${item.value}">${item.label}</option>`)
        .join('')}
    </select>
  `;
};
canRefine #
type: boolean

Returns true if a refinement can be applied.

1
2
3
4
5
6
7
8
9
10
11
const renderMenuSelect = (renderOptions, isFirstRender) => {
  const { items, canRefine } = renderOptions;

  document.querySelector('#menu-select').innerHTML = `
    <select ${!canRefine ? 'disabled' : ''}>
      ${items
        .map(item => `<option value="${item.value}">${item.label}</option>`)
        .join('')}
    </select>
  `;
};
refine #
type: function

Sets the refinement and triggers a search.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const renderMenuSelect = (renderOptions, isFirstRender) => {
  const { items, refine } = renderOptions;
  const container = document.querySelector('#menu-select');

  if (isFirstRender) {
    const select = document.createElement('select');

    select.addEventListener('change', event => {
      refine(event.target.value);
    });

    container.appendChild(select);
  }

  container.querySelector('select').innerHTML = `
    <option value="">See all</option>
    ${items
      .map(
        item =>
          `<option
            value="${item.value}"
            ${item.isRefined ? 'selected' : ''}
          >
            ${item.label}
          </option>`
      )
      .join('')}
  `;
};
widgetParams #
type: object

All original widget options forwarded to the render function.

1
2
3
4
5
6
7
8
9
10
11
12
13
const renderMenuSelect = (renderOptions, isFirstRender) => {
  const { widgetParams } = renderOptions;

  widgetParams.container.innerHTML = '...';
};

// ...

search.addWidget(
  customMenuSelect({
    container: document.querySelector('#menu-select'),
  })
);

Create and instantiate the custom widget#

We first create custom widgets from our rendering function, then we instantiate them. When doing that, there are two types of parameters you can give:

  • Instance parameters: they are predefined parameters that you can use to configure the behavior of Algolia.
  • Your own parameters: to make the custom widget generic.

Both instance and custom parameters are available in connector.widgetParams, inside the renderFunction.

const customMenuSelect = instantsearch.connectors.connectMenu(
  renderMenuSelect
);

search.addWidget(
  customMenuSelect({
    attribute: string,
    // Optional parameters
    limit: number,
    sortBy: string[]|function,
    transformItems: function,
  })
);

Instance options #

attribute #
type: string
Required

The name of the attribute in the record.

Edit
1
2
3
customMenuSelect({
  attribute: 'brand',
});
limit #
type: number
default: 10
Optional

The maximum amount of values to display.

Edit
1
2
3
4
customMenuSelect({
  // ...
  limit: 20,
});
sortBy #
type: string[]|function
default: ["isRefined", "name:asc"]
Optional

How to sort refinements. Must be one or more of the following strings:

  • "count:asc"
  • "count:desc"
  • "name:asc"
  • "name:desc"
  • "isRefined"

It’s also possible to give a function, which receives items two by two, like JavaScript’s Array.sort.

Edit
1
2
3
4
customMenuSelect({
  // ...
  sortBy: ['name:asc'],
});
transformItems #
type: function
default: x => x
Optional

Receives the items, and is called before displaying them. Should return a new array with the same shape as the original array. Useful for mapping over the items to transform, and remove or reorder them.

Edit
1
2
3
4
5
6
7
8
9
customMenuSelect({
  // ...
  transformItems(items) {
    return items.map(item => ({
      ...item,
      label: item.label.toUpperCase(),
    }));
  },
});

Full example#

Edit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Create the render function
const renderMenuSelect = (renderOptions, isFirstRender) => {
  const { items, canRefine, refine, widgetParams } = renderOptions;

  if (isFirstRender) {
    const select = document.createElement('select');

    select.addEventListener('change', event => {
      refine(event.target.value);
    });

    widgetParams.container.appendChild(select);
  }

  const select = widgetParams.container.querySelector('select');

  select.disabled = !canRefine;

  select.innerHTML = `
    <option value="">See all</option>
    ${items
      .map(
        item =>
          `<option
            value="${item.value}"
            ${item.isRefined ? 'selected' : ''}
          >
            ${item.label}
          </option>`
      )
      .join('')}
  `;
};

// Create the custom widget
const customMenuSelect = instantsearch.connectors.connectMenu(renderMenuSelect);

// Instantiate the custom widget
search.addWidget(
  customMenuSelect({
    container: document.querySelector('#menu-select'),
    attribute: 'brand',
  })
);

HTML output#

1
2
3
4
5
6
7
8
<div class="ais-MenuSelect">
  <select class="ais-MenuSelect-select">
    <option class="ais-Menu-option">
      Apple (50)
    </option>
    <!-- more items -->
  </select>
</div>

Did you find this page helpful?