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-bind: Detect bindings in amp-live-list #7990
Amp-bind: Detect bindings in amp-live-list #7990
Conversation
If we do evaluate, we should probably only evaluate the bindings within the dynamic element rather than the entire page. It's interesting since the user intent is slightly different between Also, is there a "flash of unbound content" as we evaluate the new bindings in the dynamic element? |
I'm concerned about evaluating certain parts of the page since that opens us up to potential issues where parts of the page are out of sync. Even if we did do that, consider a scenario where we have a p tag outside the live list, and an identical tag (with the same bindings) inside an amp-live-list item that is added to the page 30 seconds after page load. If the user doesn't take any action to change the page state, the p tag in the live-list item will evaluate with null, whereas the p tag outside will be with whatever default was set. If we want to maintain the invariant that the user must take a deliberate action to change the page's state (as in amp-state), then I think we should only evaluate new bindings if the user has already taken one such action. EDIT: To answer your second question, yes there is. |
This shouldn't happen if the expression has a consistent default (we warn about this with
What I meant by "user intent" in my last post is that an To unblock, let's avoid evaluation of new bindings completely for now. Then let's fix the FOUC and then figure out the best scheme for evaluation for each component. |
/cc @ericlindley-g |
Okay, sounds like a good way forward. re: templates, I don't think that the user submitting a form before any bindings have evaluated should cause an evaluation for all bindings on the page either. |
@choumx Let me know if you have any further comments. |
Continues #7369 |
elementToObserve = element.parentElement; | ||
} else if (tagName === 'AMP-LIVE-LIST') { | ||
// All elements in AMP-LIVE-LIST are children of a <div> with an | ||
// `items` attribute. |
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.
dom.js#childElementByAttr
. Found that by looking at the implementation of amp-live-list.js
.
I wonder if we should delegate this to the dynamic element itself, similar to mutatedAttributesCallback
.
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. Delegate deciding which element should be observed?
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.
Yea, since that's an implementation detail of the element. Not necessary for this PR but something to think about.
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.
@@ -17,6 +17,7 @@ | |||
<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 src="/dist/amp.js"></script> | |||
<script custom-element="amp-carousel" src="/dist/v0/amp-carousel-0.1.js"></script> | |||
<script custom-element="amp-live-list" src="https://cdn.ampproject.org/v0/amp-live-list-0.1.js"></script> |
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.
Use the /dist/v0/
URL?
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.
<button update id="liveListUpdateButton" on="tap:liveList.update"></button> | ||
<div items id="liveListItems"> | ||
<div id="liveListItem1" data-sort-time="123456789"> | ||
<p [text]=liveListText>unbound</p> |
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.
Does liveListText
need to be wrapped in quotes?
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.
Apparently not, but it should be.
const liveListItem1 = fixture.doc.getElementById('liveListItem1'); | ||
expect(liveListItem1.firstElementChild.textContent).to.equal('unbound'); | ||
|
||
const impl = liveList.implementation_; |
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.
Move duplicated set up code into a beforeEach
method.
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.
There are only two lines of setup. I'm not sure extracting them is worth it.
@@ -162,6 +162,73 @@ describe.configure().retryOnSaucelabs().run('integration amp-bind', function() { | |||
}); | |||
}); | |||
|
|||
describe('amp-live-list integration', () => { | |||
function createFromServer(childAttrs = []) { |
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 see this is copied from test-amp-live-list.js
. Generally not a good idea to dupe code, and besides many parts of it is unused here (updateTime
, tombstone
).
You're only calling this method once so you can probably remove it and replace it with a single innerHTML = ...
line.
@@ -0,0 +1,257 @@ | |||
<!doctype html> |
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.
Please remove unnecessary pieces of this example page so that just the essential bind/live-list parts remain.
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.
build-system/server.js
Outdated
@@ -413,6 +468,17 @@ app.use('/examples/live-blog(-non-floating-button)?.amp.(min.|max.)?html', | |||
next(); | |||
}); | |||
|
|||
app.use('/examples/bind/live-list.amp.(min.|max.)?html', | |||
function(req, res, next) { | |||
console.log('test'); |
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.
?
build-system/server.js
Outdated
return values[Math.round(Math.random() * (max - min))] | ||
} | ||
|
||
function flip() { |
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.
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.
build-system/server.js
Outdated
@@ -346,16 +346,19 @@ function liveListTombstone(liveList) { | |||
} | |||
} | |||
|
|||
|
|||
// Value is inclusive of both min and max values. | |||
function range(min, max) { |
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.
Please briefly document this method. Not your fault but the implementation is pretty inscrutable.
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. There has to be a simpler way to do this, right?
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.
Yea, I'm not sure what the use is creating the values
array in the first place.
build-system/server.js
Outdated
data-param-text="Hello world" | ||
data-param-href="https://example.com/?ref=URL" | ||
data-param-app_id="145634995501895"></amp-social-share> | ||
<amp-social-share type="twitter"></amp-social-share> |
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.
Is amp-social-share
necessary here? Where did this markup originally come from?
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.
No, I was trying to make this as close to the non-bind live-list example as possible.
Hi, ampproject bot here! Here are a list of the owners that can approve your files. You may leave an issue comment stating "ampprojectbot retry!" to force me to re-evaluate this Pull Request's status /to @cramforce @dvoytenko
/to @camelburrito @lannka
/to @alanorozco @aghassemi @cvializ @chenshay @honeybadgerdontcare @dvoytenko @ericlindley-g @erwinmombay @Gregable @lannka @mrjoro @powdercloud @newmuis @jridgewell @kmh287 @cramforce @mkhatib @camelburrito @choumx @muxin @zhouyx
|
Hi, ampproject bot here! Here are a list of the owners that can approve your files. You may leave an issue comment stating "@ampprojectbot retry!" to force me to re-evaluate this Pull Request's status /to cramforce erwinmombay
/to camelburrito lannka
/to choumx kmh287
/to aghassemi alanorozco camelburrito chenshay choumx cramforce cvializ dvoytenko ericlindley-g erwinmombay gregable honeybadgerdontcare jridgewell kmh287 lannka mkhatib mrjoro muxin newmuis powdercloud zhouyx
For any issues please file a bug at https://github.com/google/github-owners-bot/issues |
@choumx good to go? |
build-system/server.js
Outdated
@@ -346,16 +346,19 @@ function liveListTombstone(liveList) { | |||
} | |||
} | |||
|
|||
|
|||
// Value is inclusive of both min and max values. | |||
function range(min, max) { |
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.
Yea, I'm not sure what the use is creating the values
array in the first place.
@@ -40,6 +41,16 @@ const TAG = 'amp-bind'; | |||
const AMP_CSS_RE = /^(i?-)?amp(html)?-/; | |||
|
|||
/** | |||
* Tags under which bind should observe mutaitons to detect added/removed | |||
* bindings. | |||
* @type {!Object<string, boolean>} |
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.
@private
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
extensions/amp-bind/0.1/bind-impl.js
Outdated
* bindings. | ||
* @type {!Object<string, boolean>} | ||
*/ | ||
const DYNAMIC_TAGS = { |
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.
Wrap in map()
to prevent matching on things like __proto__
.
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 idea.
@@ -83,6 +84,17 @@ | |||
[controls]="videoControls"> | |||
</amp-video> | |||
|
|||
<p> AMP-LIVE-LIST</p> |
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: Leading whitespace.
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
impl.update(update); | ||
fixture.doc.getElementById('liveListUpdateButton').click(); | ||
|
||
let liveListItem2; |
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.
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.
They're not before and after. There should be two live list items in the list by the end of the test.
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.
Sorry I meant "after" the live list update. Maybe you can ID one live list item "pizza" and another "bacon" to go with the theme of the example... up to you though.
<div id="live-blog-item-${now}" data-sort-time="${now}"> | ||
<div class="article-body"> | ||
${body} | ||
<p> As you can see, bacon is far superior to <b><span [text]='favoriteFood'>everything!</span></b>!</p> |
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.
😄
var numOfParagraphs = range(1, 2); | ||
var body = Array.apply(null, Array(numOfParagraphs)).map(x => { | ||
return `<p>${bacon(range(50, 90))}</p>`; | ||
}).join('\n'); |
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 content generation doesn't seem necessary for your test.
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.
It's necessary for the example to work.
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.
Right-o. 👍
Hi, ampproject bot here! Here are a list of the owners that can approve your files. You may leave an issue comment stating "@ampprojectbot retry!" to force me to re-evaluate this Pull Request's status /to cramforce erwinmombay
/to camelburrito lannka
/to choumx kmh287
/to alanorozco camelburrito chenshay choumx cramforce cvializ dvoytenko ericlindley-g erwinmombay gregable honeybadgerdontcare jridgewell kmh287 lannka mkhatib mrjoro muxin newmuis powdercloud zhouyx
For any issues please file a bug at https://github.com/google/github-owners-bot/issues |
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 work! Sorry for being a bit strict on the last round of code review. I might've been hangry.
var numOfParagraphs = range(1, 2); | ||
var body = Array.apply(null, Array(numOfParagraphs)).map(x => { | ||
return `<p>${bacon(range(50, 90))}</p>`; | ||
}).join('\n'); |
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.
Right-o. 👍
impl.update(update); | ||
fixture.doc.getElementById('liveListUpdateButton').click(); | ||
|
||
let liveListItem2; |
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.
Sorry I meant "after" the live list update. Maybe you can ID one live list item "pizza" and another "bacon" to go with the theme of the example... up to you though.
Hi, ampproject bot here! Here are a list of the owners that can approve your files. You may leave an issue comment stating "@ampprojectbot retry!" to force me to re-evaluate this Pull Request's status /to cramforce erwinmombay
/to camelburrito lannka
/to choumx kmh287
/to alanorozco camelburrito chenshay choumx cvializ ericlindley-g erwinmombay gregable honeybadgerdontcare jridgewell kmh287 lannka mkhatib mrjoro muxin newmuis powdercloud zhouyx
For any issues please file a bug at https://github.com/google/github-owners-bot/issues |
Waiting for testing fixes in #8010 |
*/ | ||
const DYNAMIC_TAGS = map({ | ||
'TEMPLATE': true, | ||
'AMP-LIVE-LIST': true, |
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.
Why don't we instead have these components emit something like
this.dispatchCustomEvent('dom:changed-subtree', {root: element})
And listen to that from here?
That would not require changing AMP bind as more such components come.
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'm in the middle of implementing a similar alternative strategy here
This PR adds support for detecting new bind attributes in amp-live-list items. To do this, bind will observe changes to the children of the required
<div items>
element under any<amp-live-list>
elementTests for this and an example have also been added.
Fixes #7557
@choumx The example highlights an interesting problem with these dynamic elements. Right now, when bind detects mutations on dynamic elements, it will apply the current scope to the entire page after adding/removing appropriate bindings. If a bind attribute is bound to a variable that hasn't been initialized, it will be set to 'null' at this point. One thing we could do to prevent this is we could prevent applying the current scope when dynamic elements change if setState hasn't otherwise been called. What are your thoughts?