API Reference / InstantSearch.js Widgets / numericMenu
Apr. 11, 2019

numericMenu

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

About this widget

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

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

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: 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.

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

Templates

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 or not 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>`,
  },
});

Customize the UI - connectNumericMenu

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.addWidget(
  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 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 or not 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 or not 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);
    });
  });
};
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.addWidget(
  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.addWidget(
  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: 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.

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

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.addWidget(
  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 },
    ],
  })
);

HTML output

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>

Did you find this page helpful?