Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[amp-list-load-more] documentation, demo, bugfix, and ui tweaks #19399

Merged
merged 17 commits into from
Nov 28, 2018
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions build-system/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,33 @@ app.get('/dist/ww(.max)?.js', (req, res) => {
});
});

app.get('/infinite-scroll', function(req, res) {
const query = {req};
cathyxz marked this conversation as resolved.
Show resolved Hide resolved
const results = [];
const numberOfItems = query['items'] ? query['items'] : 10;
const pagesLeft = query['left'] ? query['left'] : 1;

if (pagesLeft == 0) {
res.json({items: []});
}

for (let i = 0; i < numberOfItems; i++) {
const imageUrl = 'http://picsum.photos/200?' +
Math.floor(Math.random() * Math.floor(50));
const r = {
'title': 'Item ' + i,
imageUrl,
'price': i + 0.99,
};
results.push(r);
}

const nextUrl = '/infinite-scroll?items=' +
numberOfItems + '&left=' + JSON.stringify(pagesLeft - 1);
res.json({'items': results, 'next': nextUrl});
});


/**
* Autosuggest endpoint
*/
Expand Down
61 changes: 50 additions & 11 deletions extensions/amp-list/0.1/amp-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,6 @@ export class AmpList extends AMP.BaseElement {
this.loadMoreLoadingElement_ = null;
/** @private {?Element} */
this.loadMoreFailedElement_ = null;
/** @private {?Element} */
this.loadMoreOverflowElement_ = null;
/** @private {?../../../src/service/position-observer/position-observer-impl.PositionObserver} */
this.positionObserver_ = null;

Expand Down Expand Up @@ -184,9 +182,9 @@ export class AmpList extends AMP.BaseElement {
});

if (this.loadMoreEnabled_) {
this.getLoadMoreOverflowElement_();
this.getLoadMoreOverflow_();
this.getLoadMoreLoadingElement_();
if (!this.loadMoreLoadingElement_) {
if (!this.loadMoreLoadingOverlay_) {
this.getLoadMoreLoadingOverlay_();
}
this.getLoadMoreFailedElement_();
Expand All @@ -197,12 +195,12 @@ export class AmpList extends AMP.BaseElement {
* @private
* @return {!Element|null}
*/
getLoadMoreOverflowElement_() {
getLoadMoreOverflow_() {
cathyxz marked this conversation as resolved.
Show resolved Hide resolved
if (!this.loadMoreOverflow_) {
this.loadMoreOverflow_ = childElementByAttr(
this.element, 'load-more-button');
}
return this.loadMoreOverflowElement_;
return this.loadMoreOverflow_;
}

/** @override */
Expand Down Expand Up @@ -373,13 +371,32 @@ export class AmpList extends AMP.BaseElement {
items = items.slice(0, maxLen);
}
return this.scheduleRender_(/** @type {!Array} */(items), !!opt_append);
}, error => {
throw user().createError('Error fetching amp-list', error);
});
}
return fetch.catch(error => this.showFallback_(error));

cathyxz marked this conversation as resolved.
Show resolved Hide resolved
return fetch.catch(error => {
if (opt_append) {
this.handleLoadMoreFailed_();
} else {
this.showFallback_(error);
}
});
}

/**
* When the fetch fails, we should show the "load-failed" element if
* one exists, otherwise show the overflow element that triggers a new
* fetch on click.
* @private
*/
handleLoadMoreFailed_() {
if (this.loadMoreFailedElement_) {
this.setLoadMoreFailed_();
} else {
// TODO (#14060): implement reload for append after failed load
this.setLoadMoreReload_();
}
}
/**
* Proxies the template rendering to the viewer.
* @param {boolean} refresh
Expand Down Expand Up @@ -692,6 +709,29 @@ export class AmpList extends AMP.BaseElement {
}
}

/**
* @private
*/
setLoadMoreReload_() {
if (this.loadMoreOverflow_) {
this.mutateElement(() => {
this.loadMoreOverflow_.classList.toggle('amp-visible', true);
listen(this.loadMoreOverflow_, 'click',
cathyxz marked this conversation as resolved.
Show resolved Hide resolved
() => this.loadMoreReloadCallback_());
cathyxz marked this conversation as resolved.
Show resolved Hide resolved
});
}
}

/**
* @return {!Promise}
* @private
*/
loadMoreReloadCallback_() {
this.toggleLoadMoreLoading_(true);
return this.fetchList_(/* opt_append */ true)
.then(() => this.toggleLoadMoreLoading_(false));
}

/**
* @private
*/
Expand All @@ -706,7 +746,6 @@ export class AmpList extends AMP.BaseElement {
this.loadMoreSrc_ = null;
this.toggleLoadMoreLoading_(true);
return this.fetchList_(/* opt_append */ true)
.catch(() => this.setLoadMoreFailed_())
.then(() => this.toggleLoadMoreLoading_(false));
}

Expand Down Expand Up @@ -805,7 +844,7 @@ export class AmpList extends AMP.BaseElement {
this.positionObserver_.observe(this.container_,
PositionObserverFidelity.LOW,
({positionRect, viewportRect}) => {
const ratio = 1.5;
const ratio = 3;
cathyxz marked this conversation as resolved.
Show resolved Hide resolved
if (this.loadMoreSrc_ &&
positionRect.bottom < ratio * viewportRect.bottom) {
this.loadMoreCallback_();
Expand Down
33 changes: 31 additions & 2 deletions extensions/amp-list/amp-list.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ Here is how we styled the content fetched:
amp-list div[role="list"] {
display: grid;
grid-gap: 0.5em;
}
}
```

## Behavior

The request is always made from the client, even if the document was served from the AMP Cache. Loading is triggered using normal AMP rules depending on how far the element is from
Expand Down Expand Up @@ -261,6 +261,35 @@ We recommend using `binding="no"` or `binding="refresh"` for faster performance.

If `binding` attribute is not provided, default is `always`.

## Experimental: Infinite Scroll (amp-list-load-more)
cathyxz marked this conversation as resolved.
Show resolved Hide resolved
We've introduced an experiment called `amp-list-load-more` as an implementation for pagination and infinite scroll in amp-list. This is an experimental feature, and final APIs may change.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove comma between "experimental feature, and final"


### Attributes
#### load-more (mandatory)
Adding this attribute will allow amp-list (with no value) to show a “load-more” button at the end of the amp-list. The value of this attribute can be set to “auto” to trigger automatic loading more elements three viewports down for an infinite scroll effect.

#### load-more-bookmark (mandatory)
This attribute specifies an attribute in the returned data that will give the url of the next items to load. E.g. In the following sample payload, we would specify `load-more-bookmark=”next”`.

```
{ “items”: [], “next”: “https://url.to.load” }
cathyxz marked this conversation as resolved.
Show resolved Hide resolved
```

### Additional children of `<amp-list>`
`<amp-list>` with the `load-more` attribute expects the following additional child elements:

#### load-more-button (mandatory)
An element containing the `load-more-button` attribute. Clicking on this button will trigger a fetch to load more elements from the url contained in the field of the data returned corresponding to the `load-more-bookmark` attribute.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We call it an element and a button. Should the element be <button> or can it be any element used as a button? If there are only specific types of elements that can be used we should list them.


In the case of `load-more=”auto”`, or infinite scroll, this button will show up if the user has reached the end of the list but the contents are still loading.

#### load-more-failed (optional)
An element containing the `load-more-failed` attribute. This element will be displayed at the bottom of the `<amp-list>` if loading failed. If this element is not provided, the `load-more-button` element will be displayed and clicking on it will result in an attempt to re-fetch data from the last (failed) url.

#### .amp-load-more-loading (css class)
This class is applied to the element with the `load-more-button` attribute while the data is loading. This can be used to tweak the visual appearance of the load-more-button (e.g. show a loader) when the `<amp-list>` is in the middle of loading data.


##### common attributes

This element includes [common attributes](https://www.ampproject.org/docs/reference/common_attributes) extended to AMP components.
Expand Down
89 changes: 89 additions & 0 deletions test/manual/amp-list/infinite-scroll-1.amp.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<!doctype html>
<html ⚡>
<head>
<meta charset="utf-8">
<title>AMP #0</title>
<link rel="canonical" href="amps.html" >
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<link href='https://fonts.googleapis.com/css?family=Questrial' rel='stylesheet' type='text/css'>
<style amp-custom>
body {
font-family: 'Questrial', Arial;
}
article {
display: block;
margin: 8px;
}
amp-list {
border: 1px solid green;
}
[load-more-button], [load-more-failed], [load-more-loading] {
background: gray;
color: #fff;
cursor: pointer;
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
padding: 16px;
}

.amp-load-more-loading {
background: red;
}
.story-entry {
padding: 40px;
border: 1px solid gray;
text-align: center;
border-radius: 8px;

}

div[role="list"] {
display: flex;
flex-wrap: wrap;
flex-direction: row;
align-items: flex-start;
justify-content: center;
}

</style>
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
<script async src="https://cdn.ampproject.org/v0.js"></script>
<script async custom-element="amp-list" src="https://cdn.ampproject.org/v0/amp-list-0.1.js"></script>
<script async custom-template="amp-mustache" src="https://cdn.ampproject.org/v0/amp-mustache-latest.js"></script>
</head>
<body>
<article>
<h1>AMP List</h1>
<p>This is a flexbox amp-list with tiles, something like an infinite image gallery.</p>

<amp-list width="auto" height="200"
layout="fixed-height"
src="/infinite-scroll?items=5&left=3"
load-more="auto"
load-more-bookmark="next">
<template type="amp-mustache">
<div class="story-entry">
<amp-img src="{{imageUrl}}" width="160" height="160"></amp-img>
<div><a href="/link.html">{{title}}</a></div>
</div>
</template>
<div load-more-button>
SEE MORE
</div>
<div load-more-failed>
LOAD FAILED
</div>
<div fallback>
FALLBACK
</div>
<div load-more-loading>
LOADING
</div>
</amp-list>

</article>
</body>
</html>
86 changes: 86 additions & 0 deletions test/manual/amp-list/infinite-scroll-2.amp.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<!doctype html>
<html ⚡>
<head>
<meta charset="utf-8">
<title>AMP #0</title>
<link rel="canonical" href="amps.html" >
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<link href='https://fonts.googleapis.com/css?family=Questrial' rel='stylesheet' type='text/css'>
<style amp-custom>
body {
font-family: 'Questrial', Arial;
}
article {
display: block;
margin: 8px;
}
amp-list {
border: 1px solid green;
}
[load-more-button], [load-more-failed] {
background: gray;
color: #fff;
cursor: pointer;
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
padding: 16px;
}

.amp-load-more-loading {
background: red;
}
.story-entry {
padding: 40px;
border: 1px solid gray;
text-align: center;
border-radius: 8px;

}

div[role="list"] {
display: flex;
flex-wrap: wrap;
flex-direction: row;
align-items: flex-start;
justify-content: center;
}

</style>
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
<script async src="https://cdn.ampproject.org/v0.js"></script>
<script async custom-element="amp-list" src="https://cdn.ampproject.org/v0/amp-list-0.1.js"></script>
<script async custom-template="amp-mustache" src="https://cdn.ampproject.org/v0/amp-mustache-latest.js"></script>
</head>
<body>
<article>
<h1>AMP List</h1>
<p>This is a flexbox amp-list with tiles, something like an infinite image gallery.</p>

<amp-list width="auto" height="200"
layout="fixed-height"
src="/infinite-scroll?items=5&left=3"
load-more="auto"
load-more-bookmark="next">
<template type="amp-mustache">
<div class="story-entry">
<amp-img src="{{imageUrl}}" width="160" height="160"></amp-img>
<div><a href="/link.html">{{title}}</a></div>
</div>
</template>
<div overflow>
OVERFLOW
</div>
<div load-more-button>
SEE MORE
</div>
<div fallback>
FALLBACK
</div>
</amp-list>

</article>
</body>
</html>