Problem/Motivation
If you use hook_contextual_links_view_alter to make a link use ajax via a modal it does not work.
This is because Drupal.behaviors.AJAX.attach is fired before the contextual links are dynamically rendered to the page.
I found this in #2729413: Open "Configure Blocks" contextual links in a Modal with a simplified (quick edit) form
That issue contains a small fix to block.js but is probably not the right place for it as it would only solve it for blocks.
This problem will probably also affect the new issue #2762505: Introduce "outside in" quick edit pattern to core which introduces offcanvas tray pattern that will use contextual links and ajax.
Proposed resolution
Abstract the code to a helper function
Call it again after contextual links are attached.
Remaining tasks
N/A
User interface changes
N/A, except things work now
API changes
N/A
Data model changes
N/A
| Comment | File | Size | Author |
|---|---|---|---|
| #40 | 2764931-contextual-40.patch | 15.64 KB | tim.plunkett |
| #34 | 2764931-contextual-34.patch | 15.59 KB | tim.plunkett |
| #34 | 2764931-contextual-34-interdiff.txt | 5.67 KB | tim.plunkett |
| #29 | 2764931-29.patch | 14.99 KB | tedbow |
| #29 | interdiff-26-29.txt | 673 bytes | tedbow |
Comments
Comment #2
tedbowHere is patch that simply adds a test module that provides a contextual link that also uses Ajax. The link will not actually use Ajax for now because of the bug.
Will work on patch that fixes it.
Comment #3
tedbowOk here is a patch that actually fixes the problem.
Comment #4
tedbowWhoops!
Comment #7
nod_Improved fix. Haven't tested but works in theory.
Comment #8
tedbow@nod_ nice solution! I guess this could also solve other problems where contextual links don't work with other behaviors too!
Right now there is test module but no test that actually uses it. I manually enabled and tested with the test module.
The probably is there is no JS test for contextual links(even with ajax. I tried before and didn't have success writing a test.
Do we write the contextual links tests for basic links and ajax in this issue?
Comment #9
nod_Guess we should, but I just want that to get in so that the offcanvas don't duplicate code from ajax.js.
Comment #10
tedbowI created #2821724: Create Javascript Tests for Contextual Links
There is patch that tests contextual links. After that is committed it should be easy to adapt the test to also test ajax.
Postponing so we get that done first.
Comment #11
wim leersI'd argue the problem is in
outside_in.js. This code is problematic:That code should look at how Quick Edit does it:
core/modules/quickedit/js/views/ContextualLinkView.jsetc.That intercepts the click event on the "Quick Edit" contextual link and then does whatever it wants. For example, in the case of Outside In: open a modal dialog. At some point, Quick Edit even had dynamic link text, like "Stop quick edit" after Quick Editing was started.
The HTML inside links in Contextual Links are not like the rest of the page.
So I'm not convinced this change is actually desirable.
Comment #13
tedbow@Wim Leers thanks for review. I lost track of this issue.
This is patch is starting from scratch but more similar to the approach in #3 then #7
I trust you know the Contextual module much better than. So I think this from #9
So I this from #9 is actually a bigger change than is actually needed to solve the problem of Contextual links not working with Ajax. This is basically also how Settings Tray is solving the problem.
As I said in #10
Of course now that I think about solving other problem also might introduce other problems.
If there is code using the attach behaviours functionality that does not get invoked currently on Contextual links that code would all of sudden get invoked on contextual link if we went with the solution in #7.
This could definitely cause unforeseen problem. With the lack of functional Javascript testing core I could see problems not being caught in the current tests. And of course this could also be considered a BC break.
So this patch explicitly only bind ajax links when contextual links added.
Since for links to work ajax you have explicitly add the use-ajax class then I would say would not accidentally cause any links to all of sudden be ajax links that should not be.
There was other logic outside_in.es6.js that had to be changed because it was relying on behaviours being attached to contextual links.
I have consulted the logic into new function prepareAjaxLinks().
It needs to be called twice:
on attach because it needs to work with regular links. This need will be removed in #2844261: Allow dialog links to specify a renderer in addition to a dialog type
Then on the drupalContextualLinkAdded event. This need will be remain but the logic prepareAjaxLinks() will be simpler after #2844261
Comment #14
tedbowComment #15
wim leersI think I like this better. Will need review from a JS maintainer, not from me though. This is now firmly in the land of
ajax system, notcontextual.js.P.S.: this reminds me of the fact that all Drupal behaviors are attached in alphabetical order — rather than in asset library dependency order. There's an issue for that somewhere.
Comment #16
GrandmaGlassesRopeManComment #17
GrandmaGlassesRopeManMissing `type` in JSDoc.
Arrow function.
Camel case
Set this when you create the object.
Use `/** ... */` for multiline comments.
You'll end up getting rid of `$(this)` with the arrow function. Don't wrap it every time you need to use it.
Don't we get rid of this logic in #2844261: Allow dialog links to specify a renderer in addition to a dialog type?
This is confusing. Why would we not have an `instance`?
Use `|| false`
No `in`.
Spacing.
Needs a comment.
Comment #18
tedbow1. fixed
2. fixed
3. fixed
4. Fixed more properties to object creation that aren't conditional
5. fixed
6. fixed
7, 8, 9, 10, 11, . Yes this logic will all be changed in #2844261: Allow dialog links to specify a renderer in addition to a dialog type. For that reason and also because all the logic in prepareAjaxLinks() is preexisting code that is simply wrapped in a new function I would say changes inside prepareAjaxLinks() are out of scope.
12. Fixed
Comment #20
droplet commented1. Should we put it on ajax.js or dialog.ajax.js ?
2. Shouldn't reuse the new helper for `$('.use-ajax-submit').once('ajax').each(function () {`
About the outside_in, I think we need a quick fix first, it affected the performance. reattached about 9 times on default homepage:
https://www.drupal.org/node/2901667#comment-12226496
Comment #21
tim.plunkettThis blocks Layout builder.
Comment #22
tedbowRe-rolled
Comment #23
tim.plunkett#20.1
Despite the presence of these dialog properties, this is generic non-dialog code. It should stay in here.
#20.2
No, since this is only about contextual links, not buttons
I think all we need now are tests?
Comment #24
tedbowI think yes just tests.
Here is test for clicking an ajax dialog.
I had to use
hook_page_attachments_alter()to attach thecore/drupal.dialog.ajaxIs there a better way to do this? I tried
hook_contextual_links_view_alter(and left in patch) but this doesn't work. Is it too late?Comment #25
droplet commentedShould it pass HTMLElement instead? It seems handier for a globally shared script used for different places and does not include jQuery in advanced usage. (e.g. code split might not include jQuery in exact file)
It's some quick and extracts the code to a function but I'm not sure if it's good to bound jQuery.once inside in a new API. Usually, in the coding, we expected calling a function will re-fresh all states again. thoughts?
(Considered sometimes we will not replace the whole .use-ajax HTML completely and re-bound to a new behavior)
This patch might violate Drupal commit policy, Per Scope Per Issue. If we split it, I think the ajax part need not a test and commit that straightly. [committers call :)]
Comment #26
tim.plunkettAgreed with the first part about HtmlElement, that makes sense to me.
Moving away from
.use-ajaxand.once()seems out of scope to me.The removal of
Drupal.attachBehaviors(data.$el[0]);in settings_tray.es6.js has to happen in this issue, since it is a direct workaround for this bug.Not sure of the prepareAjaxLinks() change, assigning back to @tedbow for that.
Also, this has tests now!
Comment #27
droplet commentedI thought it's not out of scope or we should split the ajax part into a new issue (in favor of not out of scope :S )
1. We're exporting a new public API. If we apply it to D8.4 and released, we cannot make a change to it anymore (usually)
2. Adding API to resolve a single bug in a non-private project and throw all potential problem away, it's always a bad idea.
3. Nothing complicated, we only have to make a simple decision:
Plan A:
bindAjaxLink -> no jQuery.once
bindAjaxLinks -> has jQuery.once, re-use bindAjaxLink internal
Plan B:
bindAjaxLinks -> Remove no jQuery.once, handle it in your module
4.
current bindAjaxLinks in patch forced you to pass the parent scope
jQuery(target).parent(). It's a very bad programming design.Comment #28
tedbowThe logic in
prepareAjaxLinks()was moved from insideDrupal.behaviors.toggleEditMode.attachbecause the fix is no longer attaching behaviors to ajax links as they are added as the fix in settings_tray.js(previously outside_in.js) was . So if it stayed where it was it would not longer be invoked. Before #2844261: Allow dialog links to specify a renderer in addition to a dialog type this logic was called in 2 different places so it needed to be in a function. NowprepareAjaxLinks()is only called once but since we have to move it I think it make more sense still to put it into function.Comment #29
tedbowWhen I rerolled in #22 it was after #2844261: Allow dialog links to specify a renderer in addition to a dialog type. I remove logic dealing with
This logic actually got removed from the patch in #22 it was committed in #2844261: Allow dialog links to specify a renderer in addition to a dialog type instead of #2844261: Allow dialog links to specify a renderer in addition to a dialog type which is still RTBC.
So this todo needs to be removed as this is already removed from the this patch
Comment #30
phenaproximaShould this be an arrow function? I ask because arrow functions bind to the lexical scope, and I'm wondering if that might not have undesirable side effects further down the line. (But, I am not a JavaScript samurai, so I defer to the JS experts on this one.)
Should there be any safeguards against these data not being present (explicit checks and/or default values)?
Same here -- what if the element has no ID?
Additionally, it might be ever so slightly more performant to simply use the native ajaxLink.id property.
This should probably use the // comment style.
It's possible to reach this call without elementSettings.url and elementSettings.event. Will Drupal.ajax() be OK with that, or will bizarre side effects occur?
There's an extra 'q' at the end of this line.
A possible micro-optimization here: You could use instance.element.getAttribute() instead, to reduce the overall reliance on jQuery.
Probably out of scope, but once again I'm wondering -- what if the .settings-tray-editable parent has no ID?
Comment #31
GrandmaGlassesRopeMan@phenaproxima
- #30.1 - It shouldn't be an issue. Especially since we aren't using
this.- #30.2/3/8 - Maybe.
If there are no
$linkElementitems, then it won't really matter. However, if the data attribute is not present, it will returnundefined- #30.4 - It follows our multi-line comment style, https://github.com/airbnb/javascript#comments
- #30.7 - 👍
Comment #32
dawehner❓Naively I would have expected that this event would have been added on the contextual links site.
it feels wrong that ajax.js knows abut contextual links. Otherwise we could at least add some documentation why this is needed here.
Can we wait on some element (the contextual links) to appear?
$x
Is there a reason we could not use
Object.assignor...?Comment #33
GrandmaGlassesRopeMan@dawehner
About #32.4,
Object.assign()requires a polyfill and using the spread operator would require another Babel plugin.Comment #34
tim.plunkett#30
1,4) Addressed in #31
2,3,5,7,8) These are all the original code, we're just moving it
6) Fixed!
#32
1) Very good point
2) There is probably a more specific element to wait for, but this is the same assertion used in the rest of this test method.
3) Fixed :)
4) Addressed in #33
Comment #35
tedbowWent through this and it looks like the reviews from @phenaproxima in #30 and @dawehner in #32 have been addressed.
So this looks good to me. Won't RTBC because I worked on this patch a bunch.
Comment #36
GrandmaGlassesRopeMan🤘 All the JavaScript changes look good. 🤘
Comment #37
dawehnerWell, let's hope we don't run into new random failures ...
Comment #38
tim.plunkettassertWaitOnAjaxRequest() should be more than sufficient, even if it is not very clear/semantic.
I opened #2915524: Make non-Settings Tray helper methods out of SettingsTrayJavascriptTestBase, and use them instead of assertWaitOnAjaxRequest() as a task to fix this.
Comment #40
tim.plunkettConflicted with some eslint cleanup. Start the clock over on RTBC...
Comment #42
tim.plunkett"CI Job missing"
Comment #44
webchickOK, since this is a blocker to Blocks + Layouts and since the JS here is copy/pasta, going to "be bold" and commit this.
It does look like we could do with a follow-up for these existential questions:
Committed and pushed to 8.5.x. Tried to cherry-pick to 8.4.x as well, but ran into conflicts.
Comment #45
alexpottJust to note that this is not as per the evolving JS coding standards and introduced a couple of regressions against #2914710: JS codestyle: padded-blocks in 8.5.x. Filed #2917303: JS codestyle: padded-blocks revisited to address this.
Comment #47
Anonymous (not verified) commentedIt was fixed for 8.5.x. Unfortunately, the time for 8.4 expired.