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-bind: Detect bindings in amp-live-list #7990

Merged
merged 25 commits into from Mar 20, 2017

Conversation

kmh287
Copy link
Contributor

@kmh287 kmh287 commented Mar 6, 2017

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> element

Tests 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?

@kmh287 kmh287 changed the title #7557 amp live list binding Amp-bind: Support amp-live-list Mar 6, 2017
@kmh287 kmh287 changed the title Amp-bind: Support amp-live-list Amp-bind: Detect bindings in amp-live-list Mar 7, 2017
@kmh287 kmh287 requested a review from dreamofabear March 7, 2017 22:05
@dreamofabear dreamofabear mentioned this pull request Mar 9, 2017
2 tasks
@dreamofabear
Copy link

it will apply the current scope to the entire page after adding/removing appropriate bindings

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 amp-list and amp-form, for example.

Also, is there a "flash of unbound content" as we evaluate the new bindings in the dynamic element?

@kmh287
Copy link
Contributor Author

kmh287 commented Mar 9, 2017

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.

@dreamofabear
Copy link

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

This shouldn't happen if the expression has a consistent default (we warn about this with #development=1).

If we want to maintain the invariant that the user must take a deliberate action to change the page's state

What I meant by "user intent" in my last post is that an amp-form submission may qualify as a deliberate action while amp-list and amp-live-list do not. So the best course of action may be component-dependent.

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.

@dreamofabear
Copy link

/cc @ericlindley-g

@kmh287
Copy link
Contributor Author

kmh287 commented Mar 10, 2017

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.

@kmh287
Copy link
Contributor Author

kmh287 commented Mar 13, 2017

@choumx Let me know if you have any further comments.

@kmh287
Copy link
Contributor Author

kmh287 commented Mar 13, 2017

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.

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.

Copy link
Contributor Author

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?

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.

Copy link
Contributor Author

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>

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?

Copy link
Contributor Author

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>

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?

Copy link
Contributor Author

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_;

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.

Copy link
Contributor Author

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 = []) {

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>

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -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');

Choose a reason for hiding this comment

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

?

return values[Math.round(Math.random() * (max - min))]
}

function flip() {

Choose a reason for hiding this comment

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

Ditto.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -346,16 +346,19 @@ function liveListTombstone(liveList) {
}
}


// Value is inclusive of both min and max values.
function range(min, max) {

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.

Copy link
Contributor Author

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?

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.

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>

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?

Copy link
Contributor Author

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.

@ampprojectbot
Copy link
Member

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

  • extensions/amp-bind/0.1/bind-impl.js
  • extensions/amp-bind/0.1/test/test-bind-integration.js

/to @erwinmombay @cramforce

  • build-system/server.js

/to @camelburrito @lannka

  • examples/bind/live-list.amp.html

/to @alanorozco @aghassemi @cvializ @chenshay @honeybadgerdontcare @dvoytenko @ericlindley-g @erwinmombay @Gregable @lannka @mrjoro @powdercloud @newmuis @jridgewell @kmh287 @cramforce @mkhatib @camelburrito @choumx @muxin @zhouyx

  • test/fixtures/amp-bind-integrations.html

@ampprojectbot
Copy link
Member

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

  • build-system/server.js

/to camelburrito lannka

  • examples/bind/live-list.amp.html

/to choumx kmh287

  • extensions/amp-bind/0.1/bind-impl.js
  • extensions/amp-bind/0.1/test/test-bind-integration.js

/to aghassemi alanorozco camelburrito chenshay choumx cramforce cvializ dvoytenko ericlindley-g erwinmombay gregable honeybadgerdontcare jridgewell kmh287 lannka mkhatib mrjoro muxin newmuis powdercloud zhouyx

  • test/fixtures/amp-bind-integrations.html

For any issues please file a bug at https://github.com/google/github-owners-bot/issues

@kmh287
Copy link
Contributor Author

kmh287 commented Mar 16, 2017

@choumx good to go?

@@ -346,16 +346,19 @@ function liveListTombstone(liveList) {
}
}


// Value is inclusive of both min and max values.
function range(min, max) {

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>}

Choose a reason for hiding this comment

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

@private

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

* bindings.
* @type {!Object<string, boolean>}
*/
const DYNAMIC_TAGS = {

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__.

Copy link
Contributor Author

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>

Choose a reason for hiding this comment

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

Nit: Leading whitespace.

Copy link
Contributor Author

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;

Choose a reason for hiding this comment

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

Copy link
Contributor Author

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.

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>

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');

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.

Copy link
Contributor Author

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.

Choose a reason for hiding this comment

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

Right-o. 👍

@ampprojectbot
Copy link
Member

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

  • build-system/server.js

/to camelburrito lannka

  • examples/bind/live-list.amp.html

/to choumx kmh287

  • extensions/amp-bind/0.1/bind-impl.js
  • extensions/amp-bind/0.1/test/test-bind-integration.js

/to alanorozco camelburrito chenshay choumx cramforce cvializ dvoytenko ericlindley-g erwinmombay gregable honeybadgerdontcare jridgewell kmh287 lannka mkhatib mrjoro muxin newmuis powdercloud zhouyx

  • test/fixtures/amp-bind-integrations.html

For any issues please file a bug at https://github.com/google/github-owners-bot/issues

Copy link

@dreamofabear dreamofabear left a 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');

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;

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.

@ampprojectbot
Copy link
Member

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

  • build-system/server.js

/to camelburrito lannka

  • examples/bind/live-list.amp.html

/to choumx kmh287

  • extensions/amp-bind/0.1/bind-impl.js
  • extensions/amp-bind/0.1/test/test-bind-integration.js

/to alanorozco camelburrito chenshay choumx cvializ ericlindley-g erwinmombay gregable honeybadgerdontcare jridgewell kmh287 lannka mkhatib mrjoro muxin newmuis powdercloud zhouyx

  • test/fixtures/amp-bind-integrations.html

For any issues please file a bug at https://github.com/google/github-owners-bot/issues

@kmh287
Copy link
Contributor Author

kmh287 commented Mar 17, 2017

Waiting for testing fixes in #8010

@kmh287 kmh287 mentioned this pull request Mar 20, 2017
@kmh287 kmh287 merged commit d040812 into ampproject:master Mar 20, 2017
jpettitt pushed a commit to jpettitt/amphtml that referenced this pull request Mar 21, 2017
*/
const DYNAMIC_TAGS = map({
'TEMPLATE': true,
'AMP-LIVE-LIST': true,
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cramforce

👍, I'm in the middle of implementing a similar alternative strategy here

mrjoro pushed a commit to mrjoro/amphtml that referenced this pull request Apr 28, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants