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

Add registerAnchorMutator to navigation service #17522

Merged
merged 14 commits into from Aug 20, 2018
Merged

Add registerAnchorMutator to navigation service #17522

merged 14 commits into from Aug 20, 2018

Conversation

alabiaga
Copy link
Contributor

@alabiaga alabiaga commented Aug 15, 2018

add a means of registering a location mutator. An achor mutator modifies the anchor object on the onclick event, executing based on it's priority. The number 1 being of highest priority.

e.g.
given location href is http://www.ampproject.org

navigation.registerAnchorMutator((location) => { location.href += '&first=1'; return location; }, 1); navigation.registerAnchorMutator((location) => { location.href += '&second=2'; return location; }, 2);

will result in a location href inline update evaluating to, http://www.ampproject.org&first=1&second=2

Address #16719

@alabiaga
Copy link
Contributor Author

cc\ @calebcordry

Caleb, let me know if API makes sense and fits your needs.

*/
registerLinkRule(callback, priority) {
user().assert(!this.isLinkRulePriorityUsed_(priority),
'Rule with same priority is already in use.');
Copy link
Member

Choose a reason for hiding this comment

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

I think maybe we should allow more than one rule with the same priority. If not, every time a new use case is introduced we will have to add another entry to the enum.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

right. currently..any number is open for anyone to use. I initially thought of reserving numbers for each extension or use case but as you mentioned, I see no issue with allowing some rules to have the same priority or maybe have a concept of no priority and it can be executed in any order..left for last. As discussed from the meeting we need to prioritize the linker URL transformations vs the skimlinks URL transformations. If so then perhaps, reserve a number now for the linker link rule priority? and make priority optional.

Choose a reason for hiding this comment

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

I think we need priority uniqueness. Otherwise, isn't the order of invocation for handlers with the same priority indeterminate?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

right..I think we should have priority uniqueness but was concerned with users who want to register an anchor mutation and didn't care for priority, for example if simply adding a queryParam with no concern of where it's placed. But we can start with reserving a priority for users of this and change it if users in the future show similar concerns.

@calebcordry When using the API, just define a number and I can follow with a separate PR with the priority enum, eslint rules and/or proper js annotations to ensure that users define and reserve a priority number in an enum.

Copy link
Member

Choose a reason for hiding this comment

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

Agree, I was thinking that people may not care specific order just that one group executes before the other. I guess we can cross that bridge when when see more than two use cases 🙂.

@alabiaga Sounds good.

* @return {!Location}
*/
transform(location) {
return this.callback_(location);
Copy link
Member

Choose a reason for hiding this comment

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

We talked about using element here. Location seems fine to me, but is there any downside you can thing think of @lannka @choumx ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, I don't remember the use case for why we needed to use the element here and thought that narrowing it to the scope of the location object would be ok. Lets see what @lannka @choumx say. Thanks for the feedback.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think element is more flexible so that we can

  1. read attributes for extra configurations later we might have
  2. keep the original URL as an attribute of the element, and can be accessed by any following mutators

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sounds good. made the change.

@calebcordry
Copy link
Member

Thanks for getting this prioritized! 👍

@alabiaga alabiaga changed the title [WIP] Add registerLinkRule to navigation service [WIP] Add registerLocationMutator to navigation service Aug 15, 2018
@@ -103,6 +103,16 @@ class AmpVideo extends AMP.BaseElement {
}
}

/**
Copy link
Contributor

Choose a reason for hiding this comment

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

are these change from a bad merge?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, this along with base-element.js

* @return {!Location}
*/
transform(location) {
return this.callback_(location);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think element is more flexible so that we can

  1. read attributes for extra configurations later we might have
  2. keep the original URL as an attribute of the element, and can be accessed by any following mutators

@alabiaga alabiaga changed the title [WIP] Add registerLocationMutator to navigation service Add registerLocationMutator to navigation service Aug 16, 2018
@alabiaga alabiaga changed the title Add registerLocationMutator to navigation service Add registerAnchorMutator to navigation service Aug 16, 2018
* @return {!Element}
*/
transform(el) {
return this.callback_(el);
Copy link
Contributor

Choose a reason for hiding this comment

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

this method might not need to return anything. unless you want to create a new Element instance, which I think is unnecessary and error-prone

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yea you're right. removed.

registerAnchorMutator(callback, priority) {
user().assert(!this.isAnchorMutatorPriorityUsed_(priority),
'Mutator with same priority is already in use.');
this.registeredAnchorMutators_.push(new AnchorMutator(callback, priority));
Copy link
Contributor

@lannka lannka Aug 16, 2018

Choose a reason for hiding this comment

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

instead of "check then push", you can do one scan, and splice in.
that will also save you from later sorting

@@ -124,6 +124,9 @@ export class Navigation {
* @private {?Array<string>}
*/
this.a2aFeatures_ = null;

/** @const @private {!Array<!AnchorMutator>} */
this.registeredAnchorMutators_ = [];

Choose a reason for hiding this comment

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

Nit: this.anchorMutators_

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.

const am = new AnchorMutator(callback, priority);
if (this.registeredAnchorMutators_.length) {
this.registeredAnchorMutators_.splice(
this.findIndexToInsert(this.registeredAnchorMutators_, am), 0, am);

Choose a reason for hiding this comment

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

A simpler option is to use a sparse array and simply do:

user().assert(!this.anchorMutators[priority]);
this.anchorMutators[priority] = callback;

Then just add a null check when you iterate over this.anchorMutators_.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is definitely simpler and as discussed offline, I am not familiar with the performance of sparse arrays. Lets do this solution and follow up with research on what would be more performant. it also looks like forEach skips the empty cells so null check is not necessary.

@@ -124,6 +124,9 @@ export class Navigation {
* @private {?Array<string>}
*/
this.a2aFeatures_ = null;

/** @const @private {!Array<!AnchorMutator>} */

Choose a reason for hiding this comment

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

ClickListener?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From your comment on using sparse arrays and the code sample..I take it that you wanted !Array and just do away with the AnchorMutator object.

@alabiaga
Copy link
Contributor Author

Submitting for Caleb to use. Thanks all. Will fix any that will arise from his usage.

@alabiaga alabiaga merged commit 12cb19c into ampproject:master Aug 20, 2018
const transformedTarget = target;
this.anchorMutators_.forEach(callback => {
callback(target);
location = this.parseUrl_(target.href);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need to parseUrl every time?

@@ -253,8 +256,15 @@ export class Navigation {
return;
}

// Handle anchor transformations.
const transformedTarget = target;
Copy link
Contributor

Choose a reason for hiding this comment

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

This variable seems unnecessary?

registerAnchorMutator(callback, priority) {
user().assert(!this.anchorMutators_[priority],
'Mutator with same priority is already in use.');
this.anchorMutators_[priority] = callback;
Copy link
Contributor

Choose a reason for hiding this comment

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

This will make sparse arrays, which will cause forEach to be very slow. Use a binary-search to find the proper location, and use Array.p.splice to insert it instead.

Choose a reason for hiding this comment

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

#17522 (comment) 😛

I think this is fine since N is tiny, but if you really want you could use our existing PriorityQueue class that I wrote.

Copy link
Contributor

Choose a reason for hiding this comment

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

N doesn't matter here, only priority. Given that we can't register the same priority class twice, I expect we'll see odd numbers like 100, 200, meaning we'll be iterating 200 items to only find 2.

Choose a reason for hiding this comment

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

I meant N == array.length.

I expect we'll see odd numbers like 100, 200, meaning we'll be iterating 200 items to only find 2.

We can just as easily not do that by comment and example.

Enriqe pushed a commit to Enriqe/amphtml that referenced this pull request Nov 28, 2018
* fix test

* fixes

* change name as per choumx comment offline

* fix

* revert

* name change

* return element instead of location object

* revert url.js changes

* address lannka@ comments

* fix check types

* address comments

* increase bundle size

* address choumx comments

* fix presubmit error
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.

None yet

6 participants