๐ŸŽ‰ Try the public beta of the new docs site at algolia.com/doc-beta! ๐ŸŽ‰
UI libraries / InstantSearch.js / Widgets
Signature
ratingMenu({
  container: string|HTMLElement,
  attribute: string,
  // Optional parameters
  max: number,
  templates: object,
  cssClasses: object,
});
Import
1
import { ratingMenu } from 'instantsearch.js/es/widgets';

About this widget

The RatingMenu widget lets the user refine search results by clicking on stars. The stars are based on the selected attribute.

Requirements

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

The attribute values must be integers, not strings nor floats. If your dataset includes float values, the widget returns only exact numerical matches. For example, if a user selects โ€œ4 [stars] & Upโ€, we only return records with values such as 4 or 5, not records with values like 4.5 or 4.7. If your attribute is a float, we recommend indexing a new attribute with the rounded integer value to use in this widget.

Examples

1
2
3
4
ratingMenu({
  container: '#rating-menu',
  attribute: 'rating',
});

Options

Parameter Description
container
type: string|HTMLElement
Required

The CSS Selector or HTMLElement to insert the widget into.

1
2
3
4
ratingMenu({
  // ...
  container: '#rating-menu',
});
attribute
type: string
Required

The name of the attribute in the record.

1
2
3
4
ratingMenu({
  // ...
  attribute: 'rating',
});
max
type: number
default: 5
Optional

The maximum value for the rating. This value is exclusive, which means the number of stars will be the provided value, minus one.

1
2
3
4
ratingMenu({
  // ...
  max: 4,
});
templates
type: object
Optional

The templates to use for the widget.

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

The CSS classes you can 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.
  • disabledItem: the disabled item in the list.
  • starIcon: the default class of each star (when using the default template).
  • fullStarIcon: the class of each full star (when using the default template).
  • emptyStarIcon: the class of each empty star (when using the default template).
  • label: the label of each item.
  • count: the count of each item.
1
2
3
4
5
6
7
8
9
10
ratingMenu({
  // ...
  cssClasses: {
    root: 'MyCustomRatingMenu',
    list: [
      'MyCustomRatingMenuList',
      'MyCustomRatingMenuList--subclass',
    ],
  },
});

Templates

You can customize parts of the widgetโ€™s UI using the Templates API.

Every template provides an html function you can use as a tagged template. Using html lets you safely provide templates as an HTML string. It works directly in the browser without a build step. See Templating your UI for more information.

The html function is available starting from v4.46.0.

Parameter Description
item
type: string|function
Optional

The template used for displaying each item, with:

  • count: number: the number of results that match this refinement.
  • isRefined: boolean: whether the refinement is selected.
  • name: string: the name corresponding to the number of stars.
  • value: string: the number of stars with a string form.
  • url: string: the value of the URL with the applied refinement.
  • labels: object: the value of the other templates.
  • cssClasses: object: the CSS classes provided to the widget.
  • stars: boolean[]: the list of stars to generate with:
    • true: represents a filled star
    • false: represents an empty star
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
ratingMenu({
  // ...
  templates: {
    item(data, { html }) {
      return html`
        <a
          href="${data.url}"
          class="${data.cssClasses.link}"
          aria-label="${data.name} & up">
          ${data.stars
            .map(
              isFilled => html`
            <svg
              class="${data.cssClasses.starIcon} ${
                isFilled
                  ? data.cssClasses.fullStarIcon
                  : data.cssClasses.emptyStarIcon
              }"
              aria-hidden="true"
              width="24"
              height="24"
            >
              <use xlink:href="#${
                isFilled
                  ? 'ais-RatingMenu-starSymbol'
                  : 'ais-RatingMenu-starEmptySymbol'
              }" />
            </svg>
          `)}
          <span class="${data.cssClasses.label}">& Up</span>
          <span class="${data.cssClasses.count}">${data.count}</span>
        </a>
      `;
    },
  },
});
{{/count}} {{^count}} <div class="{{cssClasses.link}}" aria-label="{{value}} & up" disabled> {{/count}} {{#stars}} <svg class="{{cssClasses.starIcon}} {{#.}}{{cssClasses.fullStarIcon}}{{/.}}{{^.}}{{cssClasses.emptyStarIcon}}{{/.}}" aria-hidden="true" width="24" height="24" > {{#.}}<use xlink:href=https://www.algolia.com/doc/api-reference/widgets/rating-menu/js/"#ais-RatingMenu-starSymbol"></use>{{/.}} {{^.}}<use xlink:href=https://www.algolia.com/doc/api-reference/widgets/rating-menu/js/"#ais-RatingMenu-starEmptySymbol"></use>{{/.}} </svg> {{/stars}} <span class="{{cssClasses.label}}">&amp; Up</span> {{#count}} <span class="{{cssClasses.count}}">{{#helpers.formatNumber}}{{count}}{{/helpers.formatNumber}}</span> {{/count}} {{#count}} </a> {{/count}} {{^count}} </div> {{/count}} `, }, });" class="snippet-body hidden">
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
// String-based templates are deprecated, use functions instead.
ratingMenu({
  // ...
  templates: {
    item: `
      {{#count}}
        <a class="{{cssClasses.link}}" aria-label="{{value}} & up" href="{{url}}">
      {{/count}}
      {{^count}}
        <div class="{{cssClasses.link}}" aria-label="{{value}} & up" disabled>
      {{/count}}
      {{#stars}}
        <svg
          class="{{cssClasses.starIcon}} {{#.}}{{cssClasses.fullStarIcon}}{{/.}}{{^.}}{{cssClasses.emptyStarIcon}}{{/.}}"
          aria-hidden="true"
          width="24"
          height="24"
        >
          {{#.}}<use xlink:href="#ais-RatingMenu-starSymbol"></use>{{/.}}
          {{^.}}<use xlink:href="#ais-RatingMenu-starEmptySymbol"></use>{{/.}}
        </svg>
      {{/stars}}
      <span class="{{cssClasses.label}}">&amp; Up</span>
      {{#count}}
        <span class="{{cssClasses.count}}">{{#helpers.formatNumber}}{{count}}{{/helpers.formatNumber}}</span>
      {{/count}}
      {{#count}}
        </a>
      {{/count}}
      {{^count}}
        </div>
      {{/count}}
    `,
  },
});

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<div class="ais-RatingMenu">
  <svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
    <symbol id="ais-RatingMenu-starSymbol" viewBox="0 0 24 24">
      <path d="M12 .288l2.833 8.718h9.167l-7.417 5.389 2.833 8.718-7.416-5.388-7.417 5.388 2.833-8.718-7.416-5.389h9.167z"/>
    </symbol>
    <symbol id="ais-RatingMenu-starEmptySymbol" viewBox="0 0 24 24">
      <path d="M12 6.76l1.379 4.246h4.465l-3.612 2.625 1.379 4.246-3.611-2.625-3.612 2.625 1.379-4.246-3.612-2.625h4.465l1.38-4.246zm0-6.472l-2.833 8.718h-9.167l7.416 5.389-2.833 8.718 7.417-5.388 7.416 5.388-2.833-8.718 7.417-5.389h-9.167l-2.833-8.718z"/>
    </symbol>
  </svg>
  <ul class="ais-RatingMenu-list">
    <li class="ais-RatingMenu-item ais-RatingMenu-item--disabled">
      <div class="ais-RatingMenu-link" aria-label="5 & up" disabled>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <span class="ais-RatingMenu-label" aria-hidden="true">& Up</span>
        <span class="ais-RatingMenu-count">2,300</span>
      </div>
    </li>
    <li class="ais-RatingMenu-item ais-RatingMenu-item--selected">
      <a class="ais-RatingMenu-link" aria-label="4 & up" href="#">
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--empty" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starEmptySymbol"></use></svg>
        <span class="ais-RatingMenu-label" aria-hidden="true">& Up</span>
        <span class="ais-RatingMenu-count">2,300</span>
      </a>
    </li>
    <li class="ais-RatingMenu-item">
      <a class="ais-RatingMenu-link" aria-label="3 & up" href="#">
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--empty" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starEmptySymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--empty" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starEmptySymbol"></use></svg>
        <span class="ais-RatingMenu-label" aria-hidden="true">& Up</span>
        <span class="ais-RatingMenu-count">1,750</span>
      </a>
    </li>
  </ul>
</div>

Customize the UI with connectRatingMenu

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

To use connectRatingMenu, you can import it with the declaration relevant to how you installed InstantSearch.js.

1
import { connectRatingMenu } from 'instantsearch.js/es/connectors';

Then itโ€™s a 3-step process:

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

// 2. Create the custom widget
const customRatingMenu = connectRatingMenu(
  renderRatingMenu
);

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

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

  // Render the widget
}

Rendering options

Parameter Description
items
type: object[]

This list of stars to display, with:

  • count: number: the number of results that match this refinement.
  • isRefined: boolean: whether the refinement is selected.
  • name: string: the name corresponding to the number of stars.
  • value: string: the number of stars with a string form.
  • stars: boolean[]: the list of stars to generate with:
    • true: represents a filled star
    • false: represents an empty star
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const renderRatingMenu = (renderOptions, isFirstRender) => {
  const { items } = renderOptions;

  document.querySelector('#rating-menu').innerHTML = `
    <ul>
      ${items
        .map(
          item =>
            `<li>
              <a href="#">
                ${item.stars.map(isFilled => (isFilled ? 'โ˜…' : 'โ˜†')).join('')}
                <span>&amp; Up</span>
                <span>${item.count}</span>
              </a>
            </li>`
        )
        .join('')}
    </ul>
  `;
};
canRefine
type: boolean
Required

Indicates if search state can be refined.

1
2
3
4
5
6
7
8
const renderRatingMenu = (renderOptions, isFirstRender) => {
  const { canRefine } = renderOptions;

  if (!canRefine) {
    document.querySelector('#rating-menu').innerHTML = '';
    return;
  }
};
refine
type: function

Selects a rating to filter the results. Takes the value of an item as parameter.

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
const renderRatingMenu = (renderOptions, isFirstRender) => {
  const { items, refine } = renderOptions;

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

  if (isFirstRender) {
    container.appendChild(document.createElement('ul'));

    return;
  }

  container.querySelector('ul').innerHTML = items
    .map(
      item =>
        `<li>
          <a
            href="#"
            data-value="${item.value}"
            style="font-weight: ${item.isRefined ? 'bold' : ''}"
          >
            ${item.stars.map(isFilled => (isFilled ? 'โ˜…' : 'โ˜†')).join('')}
            <span>&amp; Up</span>
            <span>${item.count}</span>
          </a>
        </li>`
    )
    .join('');

  [...container.querySelectorAll('a')].forEach(element => {
    element.addEventListener('click', event => {
      event.preventDefault();
      refine(event.currentTarget.dataset.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', 3);

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

Generates a URL for the next state. Takes the value of an item as parameter.

<span>${item.name} &amp; Up</span> <span>${item.count}</span> </a> </li>` ) .join('')} </ul> `; }; " class="snippet-body ">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const renderRatingMenu = (renderOptions, isFirstRender) => {
  const { items, createURL } = renderOptions;

  document.querySelector('#rating-menu').innerHTML = `
    <ul>
      ${items
        .map(
          item =>
            `<li>
              <a href="${createURL(item.value)}">
                <span>${item.name} &amp; Up</span>
                <span>${item.count}</span>
              </a>
            </li>`
        )
        .join('')}
    </ul>
  `;
};

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 customRatingMenu = connectRatingMenu(
  renderRatingMenu
);

search.addWidgets([
  customRatingMenu({
    attribute: string,
    // Optional parameters
    max: number,
  })
]);

Instance options

Parameter Description
attribute
type: string
Required

The name of the attribute in the record.

1
2
3
customRatingMenu({
  attribute: 'rating',
});
max
type: number
default: 5
Optional

The maximum value for the rating.

1
2
3
4
customRatingMenu({
  // ...
  max: 4,
});

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
// Create the render function
const renderRatingMenu = (renderOptions, isFirstRender) => {
  const { items, refine, createURL, widgetParams } = renderOptions;

  if (isFirstRender) {
    widgetParams.container.appendChild(document.createElement('ul'));

    return;
  }

  widgetParams.container.querySelector('ul').innerHTML = items
    .map(
      item =>
        `<li>
          <a
            href="${createURL(item.value)}"
            data-value="${item.value}"
            style="font-weight: ${item.isRefined ? 'bold' : ''}"
          >
            ${item.stars.map(isFilled => (isFilled ? 'โ˜…' : 'โ˜†')).join('')}
            <span>&amp; Up</span>
            <span>${item.count}</span>
          </a>
        </li>`
    )
    .join('');

  [...widgetParams.container.querySelectorAll('a')].forEach(element => {
    element.addEventListener('click', event => {
      event.preventDefault();
      refine(event.currentTarget.dataset.value);
    });
  });
};

// Create the custom widget
const customRatingMenu = connectRatingMenu(
  renderRatingMenu
);

// Instantiate the custom widget
search.addWidgets([
  customRatingMenu({
    container: document.querySelector('#rating-menu'),
    attribute: 'rating',
  })
]);
Did you find this page helpful?