AMP
  • websites

Favorite Button

Introduction

This sample demonstrates how to implement a favorite/like/bookmark button in AMP. Our implementation:

  • shows the correct icon based on whether the user already liked an item or not. This works if the AMP is served from an AMP Cache or the original origin.
  • shows a placeholder while the current state is loaded asynchronously.
  • falls back to the original state and displays an error message if the request fails, for example when the user is offline.

Setup

We use the amp-list component to dynamically render the initial state of the favorite button.

<script async custom-element="amp-list" src="https://cdn.ampproject.org/v0/amp-list-0.1.js"></script>

The mustache component is required by amp-list.

<script async custom-template="amp-mustache" src="https://cdn.ampproject.org/v0/amp-mustache-0.2.js"></script>

We need amp-form to submit the favorite request.

<script async custom-element="amp-form" src="https://cdn.ampproject.org/v0/amp-form-0.1.js"></script>

amp-bind enables us to dynamically change the button state when we submit the form.

<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>

Managing state

We initialize the button state from a JSON endpoint using amp-state. As we use cookies to identify the user, we need to add the credentials="include" attribute.

<amp-state id="favorite" credentials="include" src="https://amp.dev/documentation/examples/interactivity-dynamic-content/favorite_button/favorite">
</amp-state>

A simple favorite button

The button is embedded inside an amp-list, which enables us to dynamically render the button based on whether the user already liked something or not. Inside the template we use mustache's implicit iterator . to access the boolean value that is returned by our /favorite endpoint: {{#.}}heart-fill{{/.}}.

We also declare a placeholder icon inside the amp-list using the placeholder attribute which is shown while the amp-list is loading.

There are a few things that are happening when the user presses the favorite button:

  • The form sends a toggle request when the user presses the button.
  • We implement an optimistic UX that will instantly update the button state when the button is pressed.
  • If the form submission fails (submit-error:...), we revert the favorite state to the original version and show an error message (favorite-failed-message.show).
  • We hide any existing error messages (favorite-failed-message.hide).
<form class="favorite-button" method="post" action-xhr="https://amp.dev/documentation/examples/interactivity-dynamic-content/favorite_button/favorite" target="_top" on="submit:AMP.setState({
                    favorite: !favorite
                 }),
                 favorite-failed-message.hide;
          submit-error:AMP.setState({
                    favorite: !favorite
                 }),
                 favorite-failed-message.show">
  <amp-list width="56" height="56" credentials="include" items="." single-item src="https://amp.dev/documentation/examples/interactivity-dynamic-content/favorite_button/favorite" binding="always">
    <template type="amp-mustache">
      <input type="submit" class="{{#.}}heart-fill{{/.}}{{^.}}heart-border{{/.}}" [class]="favorite ? 'heart-fill' : 'heart-border'" value aria-label="Favorite Toggle">
    </template>
    <div placeholder>
      <input type="submit" disabled class="heart-loading" value aria-label="favorite placeholder">
    </div>
  </amp-list>
</form>

A simple snackbar that we show when the form submission fails.

<div id="favorite-failed-message" hidden>Error: Could not favorite.
  <div on="tap:favorite-failed-message.hide" tabindex="0" role="button">CLOSE</div>
</div>

A favorite button with counter

This is a more sophisticated version of the previous sample that also includes the number of favorites. Our JSON endpoint returns two values: value and count.

<amp-state id="favoriteWithCount" credentials="include" src="https://amp.dev/documentation/examples/interactivity-dynamic-content/favorite_button/favorite-with-count">
</amp-state>

The implementation is similar to the previous sample, but also updates the count when the button is clicked

AMP.setState({
  ...,
  count: favoriteWithCount.count + (favoriteWithCount.value ? -1 : 1)
})

We use a temporary variable previousFavoriteWithCount to store the previous value in order to be able to revert the button state in case the form submission fails.

0
<form class="favorite-button" method="post" action-xhr="https://amp.dev/documentation/examples/interactivity-dynamic-content/favorite_button/favorite-with-count" target="_top" on="submit:AMP.setState({
               previousFavoriteWithCount: favoriteWithCount,
               favoriteWithCount: {
                 value: !favoriteWithCount.value,
                 count: favoriteWithCount.count + (favoriteWithCount.value ? -1 : 1),
               }
             }),
             favorite-failed-message.hide;
         submit-error:AMP.setState({
               value: !favoriteWithCount.value,
               favoriteWithCount: previousFavoriteWithCount.count
             }),
             favorite-failed-message.show">
  <amp-list width="200" height="56" credentials="include" items="." single-item noloading src="https://amp.dev/documentation/examples/interactivity-dynamic-content/favorite_button/favorite-with-count" binding="always">
    <template type="amp-mustache">
      <div class="favorite-container">
        <input type="submit" class="{{#value}}heart-fill{{/value}}{{^value}}heart-border{{/value}}" [class]="favoriteWithCount.value ? 'heart-fill' : 'heart-border'" value aria-label="Favorite Toggle">
        <div class="favorite-count" [text]="favoriteWithCount.count ? favoriteWithCount.count : ''">{{count}}</div>
      </div>
    </template>
    <div placeholder>
      <div class="favorite-container">
        <input type="submit" disabled class="heart-loading" value aria-label="favorite placeholder">
        <div class="favorite-count loading">0</div>
      </div>
    </div>
  </amp-list>
</form>
Precisa de explicações mais detalhadas?

Se as explicações nesta página não respondem a todas as suas perguntas, entre em contato com outros usuários de AMP para discutir seu caso de uso específico.

Ir para o Stack Overflow
Falta explicar algum recurso?

O projeto AMP incentiva fortemente sua participação e contribuições! Esperamos que você se torne um participante assíduo de nossa comunidade de código aberto, mas também agradecemos contribuições pontuais para problemas que você tenha particular interesse.

Editar amostra no GitHub