API Reference / InstantSearch.js / numericMenu
Signature
instantsearch.widgets.numericMenu({
  container: string|HTMLElement,
  attribute: string,
  items: object[],
  // Optional parameters
  templates: object,
  cssClasses: object,
  transformItems: function,
});

About this widget # A

The numericMenu widget displays a list of numeric filters in a list. Those numeric filters are pre-configured when creating the widget.

Requirements#

The value provided to the attribute option must be an attribute which is a number in the index, not a string.

Examples # A

1
2
3
4
5
6
7
8
9
10
instantsearch.widgets.numericMenu({
  container: '#numeric-menu',
  attribute: 'price',
  items: [
    { label: 'All' },
    { label: 'Less than 500$', end: 500 },
    { label: 'Between 500$ - 1000$', start: 500, end: 1000 },
    { label: 'More than 1000$', start: 1000 },
  ],
});

Options # A

container #
type: string|HTMLElement
Required

The CSS Selector or HTMLElement to insert the widget into.

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

The name of the attribute in the record.

1
2
3
4
instantsearch.widgets.numericMenu({
  // ...
  attribute: 'price',
});
items #
type: object[]
Required

A list of all the options to display, with:

  • label: string: label of the option.
  • start: number: lower bound of the option (>=).
  • end: number: higher bound of the option (<=).
1
2
3
4
5
6
7
8
9
instantsearch.widgets.numericMenu({
  // ...
  items: [
    { label: 'All' },
    { label: 'Less than 500$', end: 500 },
    { label: 'Between 500$ - 1000$', start: 500, end: 1000 },
    { label: 'More than 1000$', start: 1000 },
  ],
});
templates #
type: object
Optional

The templates to use for the widget.

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

The CSS classes to override.

  • root: the root element of the widget.
  • noRefinementRoot: container class without results.
  • list: the list of results.
  • item: the list items.
  • selectedItem: the selected item in the list.
  • label: the label of each item.
  • labelText: the text element of each item.
  • radio: the radio button of each item.
1
2
3
4
5
6
7
8
9
10
instantsearch.widgets.numericMenu({
  // ...
  cssClasses: {
    root: 'MyCustomNumericMenu',
    list: [
      'MyCustomNumericMenuList',
      'MyCustomNumericMenuList--subclass',
    ],
  },
});
transformItems #
type: function
default: items => items
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 transforming, removing, or reordering items.

In addition, the full results data is available, which includes all regular response parameters, as well as parameters from the helper (for example disjunctiveFacetsRefinements).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
instantsearch.widgets.numericMenu({
  // ...
  transformItems(items) {
    return items.map(item => ({
      ...item,
      label: item.label.toUpperCase(),
    }));
  },
});

/* or, combined with results */
instantsearch.widgets.numericMenu({
  // ...
  transformItems(items, { results }) {
    return items.map(item => ({
      ...item,
      label: item.isRefined && results
        ? `${item.label} (${results.nbHits} hits)`
        : item.label,
    }));
  },
});

Templates # A

item #
type: string|function
Optional

The template for each item. It exposes:

  • attribute: string: the name of the attribute.
  • label: string: the label for the option.
  • value: string: the encoded URL of the bounds object with a {start, end} form. This value can be used verbatim in the webpage and can be read by refine directly. If you want to inspect the value, you can do JSON.parse(window.decodeURI(value)) to get the object.
  • isRefined: boolean: whether the refinement is selected.
  • url: string: the URL with the applied refinement.
  • cssClasses: object: the CSS classes provided to the widget.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
instantsearch.widgets.numericMenu({
  // ...
  templates: {
    item: `
      <label class="{{cssClasses.label}}">
        <input
          type="radio"
          class="{{cssClasses.radio}}"
          name="{{attribute}}"
          {{#isRefined}} checked{{/isRefined}}
        />
        <span class="{{cssClasses.labelText}}">
          {{label}}
        </span>
      </label>`,
  },
});

HTML output# A

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
<div class="ais-NumericMenu">
  <ul class="ais-NumericMenu-list">
    <li class="ais-NumericMenu-item ais-NumericMenu-item--selected">
      <label class="ais-NumericMenu-label">
        <input
          class="ais-NumericMenu-radio"
          type="radio"
          name="NumericMenu"
          checked
        />
        <span class="ais-NumericMenu-labelText">All</span>
      </label>
    </li>
    <li class="ais-NumericMenu-item">
      <label class="ais-NumericMenu-label">
        <input
          class="ais-NumericMenu-radio"
          type="radio"
          name="NumericMenu"
        />
        <span class="ais-NumericMenu-labelText">Less than 500</span>
      </label>
    </li>
  </ul>
</div>

Customize the UI with connectNumericMenu# A

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

It’s a 3-step process:

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

// 2. Create the custom widget
const customNumericMenu = instantsearch.connectors.connectNumericMenu(
  renderNumericMenu
);

// 3. Instantiate
search.addWidgets([
  customNumericMenu({
    // 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 renderNumericMenu = (renderOptions, isFirstRender) => {
  const {
    object[] items,
    boolean hasNoResults,
    function refine,
    function sendEvent,
    function createURL,
    object widgetParams,
  } = renderOptions;

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

  // Render the widget
}

Rendering options #

items #
type: object[]

The list of available options, with each option:

  • label: string: the label for the option.
  • value: string: the encoded URL of the bounds object with the {start, end} form. This value can be used verbatim in the webpage and can be read by refine directly. If you want to inspect the value, you can do JSON.parse(window.decodeURI(value)) to get the object.
  • isRefined: boolean: whether the refinement is selected.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const renderNumericMenu = (renderOptions, isFirstRender) => {
  const { items } = renderOptions;

  document.querySelector('#numeric-menu').innerHTML = `
    <ul>
      ${items
        .map(
          item => `
            <li>
              <label>
                <input
                  type="radio"
                  name="price"
                  value="${item.value}"
                  ${item.isRefined ? 'checked' : ''}
                />
                ${item.label}
              </label>
            </li>`
        )
        .join('')}
    </ul>
  `;
};
hasNoResults #
type: boolean

Whether the search has results.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const renderNumericMenu = (renderOptions, isFirstRender) => {
  const { items, hasNoResults } = renderOptions;

  document.querySelector('#numeric-menu').innerHTML = `
    <ul ${hasNoResults ? 'hidden' : ''}>
      ${items
        .map(
          item => `
            <li>
              <label>
                <input
                  type="radio"
                  name="price"
                  value="${item.value}"
                  ${item.isRefined ? 'checked' : ''}
                />
                ${item.label}
              </label>
            </li>`
        )
        .join('')}
    </ul>
  `;
};
refine #
type: function

Sets the selected value and triggers a new 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
30
31
32
const renderNumericMenu = (renderOptions, isFirstRender) => {
  const { items, refine } = renderOptions;

  const container = document.querySelector('#numeric-menu');

  container.innerHTML = `
    <ul>
      ${items
        .map(
          item => `
            <li>
              <label>
                <input
                  type="radio"
                  name="price"
                  value="${item.value}"
                  ${item.isRefined ? 'checked' : ''}
                />
                ${item.label}
              </label>
            </li>`
        )
        .join('')}
    </ul>
  `;

  [...container.querySelectorAll('input')].forEach(element => {
    element.addEventListener('change', event => {
      refine(event.currentTarget.value);
    });
  });
};
sendEvent #
type: (eventType, facetValue) => void

The function to send click events. The click event is automatically sent when refine is called. You can learn more about the insights middleware.

  • eventType: 'click'
  • facetValue: string
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// For example,
sendEvent('click', [10, 20]);

/*
  A payload like the following will be sent to the `insights` middleware.
  {
    eventType: 'click',
    insightsMethod: 'clickedFilters',
    payload: {
      eventName: 'Filter Applied',
      filters: ['numerics<=20', 'numerics>=10'],
      index: '',
    },
    widgetType: 'ais.numericMenu',
  }
*/
createURL #
type: function

Generates a URL for the next state.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const renderNumericMenu = (renderOptions, isFirstRender) => {
  const { items, createURL } = renderOptions;

  document.querySelector('#numeric-menu').innerHTML = `
    <ul>
      ${items
        .map(
          item => `
            <li>
              <a href="${createURL(item.value)}">${item.label}</a>
            </li>`
        )
        .join('')}
    </ul>
  `;
};
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
14
const renderNumericMenu = (renderOptions, isFirstRender) => {
  const { widgetParams } = renderOptions;

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

// ...

search.addWidgets([
  customNumericMenu({
    // ...
    container: document.querySelector('#numeric-menu'),
  })
]);

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 customNumericMenu = instantsearch.connectors.connectNumericMenu(
  renderNumericMenu
);

search.addWidgets([
  customNumericMenu({
    attribute: string,
    items: object[],
    // Optional parameters
    transformItems: function,
  })
]);

Instance options #

attribute #
type: string
Required

The name of the attribute in the record.

1
2
3
4
customNumericMenu({
  // ...
  attribute: 'price',
});
items #
type: object[]
Required

A list of all the options to display, with:

  • label: string: label of the option.
  • start: string: lower bound of the option (>=).
  • end: string: higher bound of the option (<=).
1
2
3
4
5
6
7
8
9
customNumericMenu({
  // ...
  items: [
    { label: 'All' },
    { label: 'Less than 500$', end: 500 },
    { label: 'Between 500$ - 1000$', start: 500, end: 1000 },
    { label: 'More than 1000$', start: 1000 },
  ],
});
transformItems #
type: function
default: items => items
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 transforming, removing, or reordering items.

In addition, the full results data is available, which includes all regular response parameters, as well as parameters from the helper (for example disjunctiveFacetsRefinements).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
customNumericMenu({
  // ...
  transformItems(items) {
    return items.map(item => ({
      ...item,
      label: item.label.toUpperCase(),
    }));
  },
});

/* or, combined with results */
customNumericMenu({
  // ...
  transformItems(items, { results }) {
    return items.map(item => ({
      ...item,
      label: item.isRefined && results
        ? `${item.label} (${results.nbHits} hits)`
        : item.label,
    }));
  },
});

Full example#

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
45
46
47
48
49
50
// Create the render function
const renderNumericMenu = (renderOptions, isFirstRender) => {
  const { items, hasNoResults, refine, widgetParams } = renderOptions;

  widgetParams.container.innerHTML = `
    <ul ${hasNoResults ? 'hidden' : ''}>
      ${items
        .map(
          item => `
            <li>
              <label>
                <input
                  type="radio"
                  name="${widgetParams.attribute}"
                  value="${item.value}"
                  ${item.isRefined ? 'checked' : ''}
                />
                ${item.label}
              </label>
            </li>`
        )
        .join('')}
    </ul>
  `;

  [...widgetParams.container.querySelectorAll('input')].forEach(element => {
    element.addEventListener('change', event => {
      refine(event.currentTarget.value);
    });
  });
};

// Create the custom widget
const customNumericMenu = instantsearch.connectors.connectNumericMenu(
  renderNumericMenu
);

// Instantiate the custom widget
search.addWidgets([
  customNumericMenu({
    container: document.querySelector('#numeric-menu'),
    attribute: 'price',
    items: [
      { label: 'All' },
      { label: 'Less than 500$', end: 500 },
      { label: 'Between 500$ - 1000$', start: 500, end: 1000 },
      { label: 'More than 1000$', start: 1000 },
    ],
  })
]);
Did you find this page helpful?