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
✨ Built-in see-more for amp-list
#13796
Conversation
Sync'd w/ the group again, and the current naming proposed in the ITI looks great, so we can proceed with that version. Thanks! |
@ericlindley-g wrt naming (and in the spirit of bikeshedding), since we're always providing a url to the next page in the response, I think it actually makes sense to swap things back a bit:
or
The |
@cpapazian my 2 cents: I find |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cpapazian Thanks, this is a pretty good initial PR. Few requests also heads up that there is another biggish change to amp-list
#13782 that will cause conflicts with this.
extensions/amp-list/0.1/amp-list.js
Outdated
this.nextSrc_ = null; | ||
|
||
/** @private {?float} */ | ||
this.pageOnScrollRatio_ = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some of the variable names may need to changed after bikeshedding, so I won't nit pick on them for now.
extensions/amp-list/0.1/amp-list.js
Outdated
@@ -173,19 +188,23 @@ export class AmpList extends AMP.BaseElement { | |||
* @return {!Promise} | |||
* @private | |||
*/ | |||
fetchList_() { | |||
fetchList_(append) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jsDoc for append
. Since it is optional, let's name it opt_append
so js doc would be {boolean=} opt_append
extensions/amp-list/0.1/amp-list.js
Outdated
if (this.element.hasAttribute('single-item')) { | ||
user().assert(typeof items !== 'undefined' , | ||
'Response must contain an array or object at "%s". %s', | ||
itemsExpr, this.element); | ||
if (!isArray(items)) { | ||
items = [items]; | ||
} | ||
} else if (this.element.hasAttribute('next-page')) { | ||
const nextExpr = this.element.getAttribute('next-page'); | ||
this.nextSrc_ = getValueForExpr(result, nextExpr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to do assertHttpsUrl
on nextSrc_
.
Ideally we do a better error message if it is not specified, so a null assert before doing assertHttpsUrl
would be nice. (e.g. user().assert(this.nextSrc_, "JSON response must specify a ....")
extensions/amp-list/0.1/amp-list.js
Outdated
@@ -72,6 +81,9 @@ export class AmpList extends AMP.BaseElement { | |||
|
|||
/** @const @private {string} */ | |||
this.initialSrc_ = element.getAttribute('src'); | |||
|
|||
/** @private {?string} */ | |||
this.nextSrc_ = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: loadMoreSrc_
? helps make it a bit more clear what internal variables are tied to what feature.
extensions/amp-list/0.1/amp-list.js
Outdated
if (this.element.hasAttribute('single-item')) { | ||
user().assert(typeof items !== 'undefined' , | ||
'Response must contain an array or object at "%s". %s', | ||
itemsExpr, this.element); | ||
if (!isArray(items)) { | ||
items = [items]; | ||
} | ||
} else if (this.element.hasAttribute('load-more')) { | ||
const nextExpr = this.element.getAttribute( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest expecting load-more-src
as the default key. WDYT? (it is verbose, but having load-more
as prefix for everything related to it creates an easier to understand API IMO)
extensions/amp-list/0.1/amp-list.js
Outdated
} | ||
}); | ||
} | ||
|
||
setupNextPage_() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto naming: all nextPage -> loadMore
extensions/amp-list/0.1/amp-list.js
Outdated
const overflow = this.getOverflowElement(); | ||
const triggerOnScroll = this.element.getAttribute('load-more') === 'auto'; | ||
if (!overflow && !triggerOnScroll) { | ||
user().warn(TAG, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will make this user.error() (also something we can prevent using validation rules)
extensions/amp-list/0.1/amp-list.js
Outdated
overflow.onclick = null; | ||
} | ||
|
||
if (this.positionObserver_) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I don't see a benefit to turning this off just for the duration of the fetch.
extensions/amp-list/0.1/amp-list.js
Outdated
} | ||
|
||
this.element.setAttribute('src', this.nextSrc_); | ||
this.nextSrc = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nextSrc_
extensions/amp-list/0.1/amp-list.js
Outdated
return batchFetchJsonFor(ampdoc, this.element, '.', policy); | ||
} | ||
|
||
maybeInstallPositionObserver_() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
basic jsdoc (@private
)
3f8f522
to
7b6003f
Compare
@aghassemi @choumx I'm just getting back to this. I fixed the naming and jsdoc over the weekend. I'm not an expert at jsdoc, so I added some explicit type annotations that may not be needed (the I also added a few tests, Other outstanding issues:
Thanks! |
@cpapazian I believe appending to |
0ab89dc
to
d37ae0a
Compare
@aghassemi I think this is getting close. Just a few outstanding questions ...
thanks! |
@cpapazian 2- Yes, essentially any DOM manipulation that does not happen synchronously during 3A- Before writing tests, we also need to put an experiment flag around this feature (as we do with most new features in AMP). This involves adding a new entry in 3B- Regarding testing strategy, ideally existing tests just pass without modifications, then we can add a separate 4- Agreed. |
aae9082
to
9d21c27
Compare
Having some trouble with tests after rebasing. It looks like changing the I had initially set this up this way to avoid a race condition between the overflow logic in the parent and |
@@ -252,9 +254,6 @@ describes.realWin('amp-list component', { | |||
element.setAttribute('src', 'https://new.com/list.json'); | |||
list.mutatedAttributesCallback({'src': 'https://new.com/list.json'}); | |||
}); | |||
listMock.expects('toggleLoading').withExactArgs(false).once(); | |||
listMock.expects('togglePlaceholder').withExactArgs(false).once(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these listMock
s were set twice: once in expectFetchAndRender
and once in the test itself, but they are only called once. when rendered_()
returns a promise (measurePromise
), these conditions fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking great!
css/amp.css
Outdated
@@ -543,6 +543,23 @@ i-amphtml-scroll-container.amp-active, i-amp-scroll-container.amp-active { | |||
visibility: visible; | |||
} | |||
|
|||
amp-list [overflow] > .i-amphtml-loader { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may break existing cases where another amp-element
exists inside inside overflow
by hiding their loading icon.
I believe should should do two things (for this and the roles below)
1- do amp-list[load-more] as the prefix to control all this behaviour to load more.
2- use direct child selector for .i-amphtml-loader
so we don't hide other loading indicators from whatever else might be in [overflow]
extensions/amp-list/0.1/amp-list.js
Outdated
this.loadMoreSrc_ = null; | ||
|
||
/** @private {?Element|undefined} */ | ||
this.loadMoreOverflow_ = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we normally do null
instead of undefined
as default then type can be just {?Element}
extensions/amp-list/0.1/amp-list.js
Outdated
if (this.loadMoreOverflow_) { | ||
this.loadMoreOverflow_.classList.toggle( | ||
'amp-visible', !!this.loadMoreSrc_); | ||
this.loadMoreOverflow_.onclick = () => this.loadMoreCallback_(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
listen(elem, 'click', cb)
would be preferred (listen
can be imported from event-helper.js
)
extensions/amp-list/0.1/amp-list.js
Outdated
* @private | ||
*/ | ||
getLoadMoreLoadingElement_() { | ||
if (this.loadMoreLoadingElement_ === undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh I see that you are using the undefined
vs null
for lazy querying. Honestly I don't mind this happening in buildCallback
(if isLoadMoreEnabled) rather than doing it lazily (buildCallback
runs during prerender mode and is meant for querying things you may need later anyway)
@@ -316,6 +313,112 @@ describes.realWin('amp-list component', { | |||
}); | |||
}); | |||
|
|||
describe('load more', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another type of test to consider if these start to become too fragile/hacky, is integration tests using describes.integration
. For those tests, essentially you provide some valid AMP HTML/CSS and they run as if it is a really AMP page, then you can use normal DOM API to wait for stuff and verify. So more blackbox testing closer to something you would normally do with WebDriver. Nothing can be mocked.
If you search for describes.integration
you can find some good examples. The test-position-observer
one for example actually scrolls the page and then asserts stuff.
526160f
to
36318ad
Compare
63c1ed9
to
9a6ec8f
Compare
with auto load-more
9a6ec8f
to
5b995a2
Compare
@cpapazian is this something you are working on actively? To reduce the stale PRs I will close this for now since there hasn't been action on this in 4 months. Please reopen this if you intend to work on this. |
@cpapazian @nainar I've been the hold up for this PR. |
Rebasing and testing this. |
8f1a0cd
to
43b4d1a
Compare
@aghassemi can you please review this again? Merged master and fixed the overflow element issue by using a new overflow element instead of the existing one. I've tested and regression tested most of amp-list except for the SSR template changes from @alabiaga on which I have very little context. I've fixed the relevant tests, but I'm not 100% confident that I didn't break anything for SSR. @alabiaga could you review this as well? Would appreciate it if you could test the SSR functionality or give guidance on testing. =) |
@cathyxz this feature should just remain unsupported for SSR until we decide whether we want to enable this for AMP4Email which SSR is mainly for. So we are safe there were no changes to load more in the condition that ssr is enabled. Code changes in ssr path looks good to me. |
43b4d1a
to
57003dc
Compare
Yup, you're absolutely correct on that end. This is not a feature for SSR, and we just need to know that it doesn't accidentally break SSR. Thanks a lot @alabiaga ! |
57003dc
to
92a8a39
Compare
@@ -137,24 +137,24 @@ describes.realWin('amp-list component', { | |||
}); | |||
|
|||
it('should fetch and render', () => { | |||
const items = [{title: 'Title1'}]; | |||
const fetched = {'items': [{title: 'Title1'}]}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this change is a function of how the test works, and does not imply a change in code logic, but a second opinion here would be helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems fine to me. I can see how this is the result of fetch_
not taking expr
anymore and the implications of it with the mocking. Also looks "more correct" than the old version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the sanity check! I pulled this out to a constant so that in case we ever have to change this again, we don't have to type it 30 times in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had reviewed this before and focused on the new changes, testing and looking for regression potentials. LGTM.
extensions/amp-list/0.1/amp-list.js
Outdated
* @private | ||
*/ | ||
fetch_(opt_refresh = false, opt_itemsExpr) { | ||
const expr = opt_itemsExpr || '.'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any place where fetch
is called with opt_itemsExpr
set
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! This is possibly an error. Let me look into it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, this is actually legacy code that was made unnecessary because we pulled out the call to getValueForExpr(data, itemsExpr)
and put that in the parent in fetchList_
per:
fetch = this.fetch_(opt_refresh).then(data => {
let items = getValueForExpr(data, itemsExpr);
I think this is actually okay and we can safely remove the opt_itemsExpr
parameter (which I did).
@@ -137,24 +137,24 @@ describes.realWin('amp-list component', { | |||
}); | |||
|
|||
it('should fetch and render', () => { | |||
const items = [{title: 'Title1'}]; | |||
const fetched = {'items': [{title: 'Title1'}]}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems fine to me. I can see how this is the result of fetch_
not taking expr
anymore and the implications of it with the mocking. Also looks "more correct" than the old version.
extensions/amp-list/0.1/amp-list.js
Outdated
// If there's nothing currently being rendered, schedule a render pass. | ||
if (!this.renderItems_) { | ||
this.renderPass_.schedule(); | ||
} | ||
this.renderItems_ = {data, resolver, rejecter}; | ||
|
||
this.renderItems_ = {data, opt_append, resolver, rejecter}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitL it's not really opt
at this point, just append
would do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Thank you!
visibility: hidden; | ||
} | ||
|
||
amp-list[load-more] > [load-more-loading].amp-visible, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick, alphabetize
amp-list[load-more] > [load-more-button].amp-visible,
amp-list[load-more] > [load-more-failed].amp-visible,
amp-list[load-more] > [load-more-loading].amp-visible,
here and below
* implement paging for amp-list with auto load-more * Address comments * Pull out literals to constants * Fix missed constant conversion
Implements #13575
First pass. Outstanding issues/questions:
onFocus event prevents additional overflow interactions from working reliably. see: Click events don't always fire from overflow div #13774
amp-list
to indicate various loading states