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
Conversation
cc\ @calebcordry Caleb, let me know if API makes sense and fits your needs. |
src/service/navigation.js
Outdated
*/ | ||
registerLinkRule(callback, priority) { | ||
user().assert(!this.isLinkRulePriorityUsed_(priority), | ||
'Rule with same priority is already in use.'); |
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 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.
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. 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.
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 think we need priority uniqueness. Otherwise, isn't the order of invocation for handlers with the same priority indeterminate?
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..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.
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.
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.
src/service/navigation.js
Outdated
* @return {!Location} | ||
*/ | ||
transform(location) { | ||
return this.callback_(location); |
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.
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 think element is more flexible so that we can
- read attributes for extra configurations later we might have
- keep the original URL as an attribute of the element, and can be accessed by any following mutators
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.
sounds good. made the change.
Thanks for getting this prioritized! 👍 |
@@ -103,6 +103,16 @@ class AmpVideo extends AMP.BaseElement { | |||
} | |||
} | |||
|
|||
/** |
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.
are these change from a bad merge?
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.
yes, this along with base-element.js
src/service/navigation.js
Outdated
* @return {!Location} | ||
*/ | ||
transform(location) { | ||
return this.callback_(location); |
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 think element is more flexible so that we can
- read attributes for extra configurations later we might have
- keep the original URL as an attribute of the element, and can be accessed by any following mutators
src/service/navigation.js
Outdated
* @return {!Element} | ||
*/ | ||
transform(el) { | ||
return this.callback_(el); |
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 method might not need to return anything. unless you want to create a new Element instance, which I think is unnecessary and error-prone
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 you're right. removed.
src/service/navigation.js
Outdated
registerAnchorMutator(callback, priority) { | ||
user().assert(!this.isAnchorMutatorPriorityUsed_(priority), | ||
'Mutator with same priority is already in use.'); | ||
this.registeredAnchorMutators_.push(new AnchorMutator(callback, priority)); |
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.
instead of "check then push", you can do one scan, and splice in.
that will also save you from later sorting
src/service/navigation.js
Outdated
@@ -124,6 +124,9 @@ export class Navigation { | |||
* @private {?Array<string>} | |||
*/ | |||
this.a2aFeatures_ = null; | |||
|
|||
/** @const @private {!Array<!AnchorMutator>} */ | |||
this.registeredAnchorMutators_ = []; |
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: this.anchorMutators_
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.
src/service/navigation.js
Outdated
const am = new AnchorMutator(callback, priority); | ||
if (this.registeredAnchorMutators_.length) { | ||
this.registeredAnchorMutators_.splice( | ||
this.findIndexToInsert(this.registeredAnchorMutators_, am), 0, am); |
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.
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_
.
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 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.
src/service/navigation.js
Outdated
@@ -124,6 +124,9 @@ export class Navigation { | |||
* @private {?Array<string>} | |||
*/ | |||
this.a2aFeatures_ = null; | |||
|
|||
/** @const @private {!Array<!AnchorMutator>} */ |
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.
ClickListener
?
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.
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.
Submitting for Caleb to use. Thanks all. Will fix any that will arise from his usage. |
const transformedTarget = target; | ||
this.anchorMutators_.forEach(callback => { | ||
callback(target); | ||
location = this.parseUrl_(target.href); |
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 do we need to parseUrl
every time?
@@ -253,8 +256,15 @@ export class Navigation { | |||
return; | |||
} | |||
|
|||
// Handle anchor transformations. | |||
const transformedTarget = target; |
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 variable seems unnecessary?
registerAnchorMutator(callback, priority) { | ||
user().assert(!this.anchorMutators_[priority], | ||
'Mutator with same priority is already in use.'); | ||
this.anchorMutators_[priority] = callback; |
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 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.
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 think this is fine since N is tiny, but if you really want you could use our existing PriorityQueue
class that I wrote.
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.
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.
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 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.
* 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
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