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

ratingMenu

Widget signature
instantsearch.widgets.ratingMenu({
  container: string|HTMLElement,
  attribute: string,
  // Optional parameters
  max: number,
  templates: object,
  cssClasses: object,
});

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 added in attributes for faceting, either on the dashboard or using attributesForFaceting with the API. The values inside the attribute must be numbers, not strings.

Examples

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

Options

container
type: string|HTMLElement
Required

The CSS Selector or HTMLElement to insert the widget into.

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

The name of the attribute in the record.

1
2
3
4
instantsearch.widgets.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
instantsearch.widgets.ratingMenu({
  // ...
  max: 4,
});
templates
type: object
Optional

The templates to use for the widget.

1
2
3
4
5
6
instantsearch.widgets.ratingMenu({
  // ...
  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.
  • 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
instantsearch.widgets.ratingMenu({
  // ...
  cssClasses: {
    root: 'MyCustomRatingMenu',
    list: [
      'MyCustomRatingMenuList',
      'MyCustomRatingMenuList--subclass',
    ],
  },
});

Templates

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

Customize the UI - connectRatingMenu

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

It’s a 3-step process:

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

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

// 3. Instantiate
search.addWidget(
  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 hasNoResults,
    function refine,
    function createURL,
  } = renderOptions;

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

  // Render the widget
}

Rendering options

items
type: object[]

This list of stars to display, with:

  • count: number: the number of results that match this refinement.
  • isRefined: boolean: whether or not 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>
  `;
};
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
const renderRatingMenu = (renderOptions, isFirstRender) => {
  const { items, hasNoResults } = renderOptions;

  document.querySelector('#rating-menu').innerHTML = `
    <ul ${hasNoResults ? 'hidden' : ''}>
      ${items
        .map(
          item =>
            `<li>
              <a href="#">
                ${item.stars.map(isFilled => (isFilled ? '' : '')).join('')}
                <span>&amp; Up</span>
                <span>${item.count}</span>
              </a>
            </li>`
        )
        .join('')}
    </ul>
  `;
};
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);
    });
  });
};
createURL
type: function

Generates a URL for the next state. 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
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 = instantsearch.connectors.connectRatingMenu(
  renderRatingMenu
);

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

Instance options

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 = instantsearch.connectors.connectRatingMenu(
  renderRatingMenu
);

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

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>

Did you find this page helpful?