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.
<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>
如果此页面上的说明未能涵盖您的所有问题,欢迎与其他 AMP 用户取得联系,讨论您的具体用例。
前往 Stack Overflow 一项无法解释的功能?AMP 项目强烈鼓励您参与并做出贡献!我们希望您能成为我们开放源代码社区的持续参与者,但我们也欢迎您对所热衷问题做出一次性贡献。
编辑 GitHub 上的示例-
Written by @sebastianbenz