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

✨PWA: Expose amp-bind getState and setState #25011

Merged
merged 15 commits into from Oct 16, 2019
Merged

✨PWA: Expose amp-bind getState and setState #25011

merged 15 commits into from Oct 16, 2019

Conversation

mattwomple
Copy link
Member

@mattwomple mattwomple commented Oct 10, 2019

Allow PWAs to access amp-bind methods through the shadowDoc instance created by AMP.attachShadowDoc():

  • shadowDoc.getState(expr) - Get an amp-bind state from the AMP document using a JSON expression string
  • shadowDoc.setState(state) - Deep merge an object or expression into the AMP document's global amp-bind state

Fixes #24982

Expose shadowDoc.getState and shadowDoc.setState methods for PWAs.
@mattwomple
Copy link
Member Author

mattwomple commented Oct 10, 2019

@choumx Initial thoughts welcome. I'm actually blocked on testing .setState because this promise seems to hang (at least in localhost testing):

const evaluatePromise = this.ww_('bind.evaluateBindings', [this.state_]);

Oh, I see, ww.js isn't lazy building: GET http://localhost:8000/dist/ww.js net::ERR_EMPTY_RESPONSE

src/runtime.js Outdated
* a copy of the value of a state
*/
getState: name => {
return Services.bindForDocOrNull(shadowRoot).then(bind => {
Copy link
Member Author

Choose a reason for hiding this comment

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

For pages that do not use amp-bind, this promise never resolves. I don't think it's a problem, but wanted to make sure this received consideration.

Choose a reason for hiding this comment

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

Hm, it should resolve with null on pages that are missing the extension script in the doc head.

src/runtime.js Outdated
* @return {Promise} - Resolves after state and history have been updated
*/
setState: state => {
return Services.bindForDocOrNull(shadowRoot).then(bind => {
Copy link
Member Author

Choose a reason for hiding this comment

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

same here

@mattwomple mattwomple changed the title [WIP] ✨PWA: Expose getState and setState ✨PWA: Expose amp-bind getState and setState Oct 11, 2019
@mattwomple mattwomple marked this pull request as ready for review October 11, 2019 09:13
src/runtime.js Outdated
*/
getState: name => {
return Services.bindForDocOrNull(shadowRoot).then(bind => {
return bind && bind.getStateCopy(name);
Copy link
Member Author

Choose a reason for hiding this comment

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

This should probably be changed to:
return bind ? bind.getStateCopy(name) : Promise.resolve();

return Services.bindForDocOrNull(shadowRoot).then(bind => {
return (
(bind && bind.setStateAndUpdateHistory(state)) || Promise.resolve()
);
Copy link
Member Author

Choose a reason for hiding this comment

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

This would be more readable as:
return bind ? bind.setStateAndUpdateHistory(state) : Promise.resolve();

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.

Thanks for taking this on!

@@ -94,6 +94,8 @@ Both `AMP.attachShadowDoc` and `AMP.attachShadowDocAsStream` return a `ShadowDoc
- `shadowDoc.setVisibilityState()` - changes the visibility state of the AMP document.
- `shadowDoc.postMessage()` and `shadowDoc.onMessage()` - can be used to message with the AMP document.
- `shadowDoc.close()` - closes the AMP document and frees the resources.
- `shadowDoc.bind.getState(expr)` - Get an `amp-bind` state from the AMP document using expression syntax
- `shadowDoc.bind.setState(obj)` - Deep merge an object into the AMP document's global `amp-bind` state

Choose a reason for hiding this comment

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

IMO it's fine to not namespace this under .bind since we don't namespace the on="tap:AMP.setState(...)" action either.

@@ -94,6 +94,8 @@ Both `AMP.attachShadowDoc` and `AMP.attachShadowDocAsStream` return a `ShadowDoc
- `shadowDoc.setVisibilityState()` - changes the visibility state of the AMP document.
- `shadowDoc.postMessage()` and `shadowDoc.onMessage()` - can be used to message with the AMP document.
- `shadowDoc.close()` - closes the AMP document and frees the resources.
- `shadowDoc.bind.getState(expr)` - Get an `amp-bind` state from the AMP document using expression syntax

Choose a reason for hiding this comment

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

Let's mention that expr is a JSON expression string e.g. "foo.bar".

* @param {string} expr
* @return {(?JsonObject|string|undefined)}
*/
getStateCopy(expr) {

Choose a reason for hiding this comment

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

Nit: Let's just call this getState(). There shouldn't be a use case for returning a non-copy.

// Support expressions
if (typeof state === 'string') {
// Emulate UIEvent 'click'
return this.pushStateWithExpression(

Choose a reason for hiding this comment

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

A bit confusing if this API can both "set" and "push" state. I think it's worth separating into two APIs.

Though for the sake of keeping this PR small, can we remove this case?

Copy link
Member Author

Choose a reason for hiding this comment

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

To make sure I understand, are you suggesting that we do not update history with the shadowDoc.setState() method?

Choose a reason for hiding this comment

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

I mean removing this case where passing a string calls "push" instead of "set". The former appends a new entry onto the history stack.

Choose a reason for hiding this comment

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

Or maybe you just meant to call setStateWithExpression here?

this.history_.replace(data);
}
});
return this.setStatePromise_;

Choose a reason for hiding this comment

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

Would be nice to de-dupe this code snippet with setStateWithExpression. Feel free to create a new private helper method or just drop a TODO(choumx) here for me.

@mattwomple
Copy link
Member Author

Thank you for the review! I believe all review comments are resolved in 1a1582e.

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.

Looks good overall.

/cc @dvoytenko FYI a new shadow API for amp-bind.

src/runtime.js Outdated
// Emulate UIEvent 'click'
return bind.setStateWithExpression(
/** @type {string} */ (state),
/** @type {!JsonObject} */ ({event: 1})

Choose a reason for hiding this comment

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

Is {event: 1} useful here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I haven't dug too deep into how the scope parameter is used, but at least an empty object needs to be passed. I opted to mimic the object that is passed by a single click event here.

Choose a reason for hiding this comment

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

Empty object will suffice then. :)

It's used for things like on="submit-success: AMP.setState({foo: event.response})", but there's no real "event context" in this case.

Copy link
Contributor

@dvoytenko dvoytenko left a comment

Choose a reason for hiding this comment

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

API LGTM

@dreamofabear dreamofabear merged commit 9b45ef8 into ampproject:master Oct 16, 2019
@dreamofabear
Copy link

Great contribution!

joshuarrrr pushed a commit to Parsely/amphtml that referenced this pull request Oct 22, 2019
* PWA: Expose getState and setState

Expose shadowDoc.getState and shadowDoc.setState methods for PWAs.

* PWA: Remove inconsistent return value for setState

* Minor cleanup

* Fix check-types

* Add micro documentation for PWA (get/set)State

* Support PWA setState with expression syntax

* Fixup types

* Review updates and add tests

* Fix check-types

* Fix empty string return value from getState

* Don't limit return type of getState

* Fix unit test

* Do not emulate event in setState

* Really fix unit test
@mattwomple mattwomple deleted the patch-3 branch December 13, 2019 21:34
micajuine-ho pushed a commit to micajuine-ho/amphtml that referenced this pull request Dec 27, 2019
* PWA: Expose getState and setState

Expose shadowDoc.getState and shadowDoc.setState methods for PWAs.

* PWA: Remove inconsistent return value for setState

* Minor cleanup

* Fix check-types

* Add micro documentation for PWA (get/set)State

* Support PWA setState with expression syntax

* Fixup types

* Review updates and add tests

* Fix check-types

* Fix empty string return value from getState

* Don't limit return type of getState

* Fix unit test

* Do not emulate event in setState

* Really fix unit test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Expose getState and setState in a more "official" way in PWAs
4 participants