Skip to content

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

Merged
dreamofabear merged 15 commits intoampproject:masterfrom
mattwomple:patch-3
Oct 16, 2019
Merged

✨PWA: Expose amp-bind getState and setState#25011
dreamofabear merged 15 commits intoampproject:masterfrom
mattwomple:patch-3

Conversation

@mattwomple
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

Copy link
Copy Markdown

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
Copy Markdown
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
Copy Markdown
Member Author

@mattwomple mattwomple Oct 14, 2019

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
Copy Markdown
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
Copy Markdown

@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!

- `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
Copy link
Copy Markdown

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.

- `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
Copy link
Copy Markdown

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) {
Copy link
Copy Markdown

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(
Copy link
Copy Markdown

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
Copy Markdown
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?

Copy link
Copy Markdown

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.

Copy link
Copy Markdown

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_;
Copy link
Copy Markdown

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
Copy Markdown
Member Author

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

Copy link
Copy Markdown

@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})
Copy link
Copy Markdown

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
Copy Markdown
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.

Copy link
Copy Markdown

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
Copy Markdown
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
Copy Markdown

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