Problem/Motivation
In Layout Builder, contextual links are used to trigger edit/remove operations on blocks. If a block contains content that also has contextual links, for example a View or a Content Entity, the page can become cluttered, making it difficult to discern which contextual links are for Layout Builder, and which are for other operations.
Proposed resolution
Remove contextual links not related to Layout Builder inside the blocks on the Layout Builder configuration pages.
Remaining tasks
Review the patch.
User interface changes
Users will not longer be able to use other non-Layout Builder contextual links within Layout Builder.
API changes
None.
Data model changes
None.
Comments
Comment #2
samuel.mortensonComment #3
tedbowThis didn't seem to work completely because the button and links would still should up.
So here is another try at CSS solution but I don't think we will be able to fix on this level.
Because:
displayproperty directly on the element which will override our CSS.Comment #4
tim.plunkettIn chat Sam suggested that instead of hiding the ones we don't care about, that we always show all the ones we DO care about.
Sorta like if they clicked the "Edit" link in toolbar, but just for LB. Almost like a toolbar mode would do...
Comment #5
tedbowI think the problem with #4 is that we would still have to solve the problem of keyboard navigation. Someone using the keyboard would still tab through them I think.
Also the contextual Drupal.announce message would be wrong about the number of contextual links unless we changed that.
Here is patch that uses the JS event
drupalContextualLinkAddedto remove non layout builder contextual links that are inside of an element with[data-layout-block-uuid]Comment #7
tedbowwhoops.
Also adding test only patch to prove the test would fail
Comment #9
jibranLet's store data.$el and data.$el.attr('') in local vars.
I think we prefer .closest() instead of .parents().
Comment #10
tedbow@jibran thanks for the review!
1. fixed
2.
closest()will pick up the element itself. We could change to use it and change to> 1but this seems more readable. Is there reason we should switch?This comment is copied will create a new 1.
Comment #11
jibranSee #1400310: Use of .parents() is really .closest() for #10.2.
Comment #12
tedbowChanging to closest.
All of these should be single quotes
Comment #13
tim.plunkettComment #14
tedbowExtra blank line
Comment #15
gun_dose commentedI noticed that in Drupal 8.6.4 there are no contextual links at blocks, that placed into layout. I suppose, contextual links should be removed only when you edit layout, but when you just view this, contextual links should be present.
Comment #16
bnjmnmThis currently-unused node should either be removed or the tests should include confirming contextual links are intact on node view. I'd prefer adding the test, although one could argue its out of scope since the contextual-link-removing JS is only loaded when editing layouts.
Comment #17
tedbowThe contextual links that should show up on the node view(and do from my testing in 8.7.x) are the contextual links inside the blocks note the contextual links at the block level.
For instance if node gets rendered inside the layout builder either through a view that is placed or an entity referenced field that displays the node those contextual links to edit/quick edit the node do show up. Also the "edit view" contextual link will not show up.
But placing a field block or the "Powered by Drupal" block you will not get a contextual link for the placed block on the node view. You have to edit the layout to alter those settings.
In the case of the where the defaults layout for the content type was rendering the node if we offered the contextual link on the block level this would be editing for all of the content(if we could do it).
Comment #19
tedbowTest passed locally for me. I think this is a failure because we need to wait for the off-canvas dialog to close. Also should wait after clicking "Save dialog"
Comment #21
tedbowComment #22
bnjmnmRe-re-reviewed the patch and it looks good. The test coverage is good, but I did some additional manual testing just to be sure
drupalContextualLinkAddedjs event, and monitored the JS console to confirm no errors were present.Comment #24
tedbowIt looks like there was random test failure https://www.drupal.org/pift-ci-job/1181203 for #19
The error happened here clicking "Save Layout".
So 2 things happen in
\Drupal\layout_builder\Controller\LayoutRebuildTrait::rebuildAndCloseWe need to wait for both the off-canvas to close and for the new layout to be rebuilt. Checking for the new View to be rendered means the Layout has been rebuilt.
Added this wait.
This patch just runs this test 25x.
Comment #26
tedbowre #24
I was wrong we were already waiting for this inside
assertCorrectContextualLinks()with
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '.block-views-blocktest-block-view-block-2'));Adding another wait.
Comment #28
bnjmnmI reproduced the inconsistent test failures on a local install DrupalCI, and was able to fix it by disabling CSS transitions for
.dialog-off-canvas-main-canvas. If this patch works with official-Testbot, I'll provide a cleaner implementation + interdiff.Comment #29
bnjmnmLooks like Settings Tray already had a module that almostaddressed removing css transitions. Had to add an additional
transition: none !important;for it to override the transition css forI added this module to every Layout Builder test using Settings Tray
Also added
@filedoc to layout_builder.es6.js for JS standards.Interdiff from #19 as the changes that followed were related to solving the intermittent test failures, and are not included in this patch.
Comment #30
tedbow@bnjmnm re #28
Nice work!
I don't think we should be including this test module from another module in our tests. Especially since that module isn't involved in test at all.
I think we should duplicate the test module to
layout_builder_test_cssand add a todo to #2901792: Disable all animations in Javascript testing which has a module to remove animations in general.Also we should not be adding the test module to any other tests because they are not currently failing in head. We need to keep this issue scoped to just the tested need.
In addition with the next patch could we get a x25 patch that is exactly the same except without the new
layout_builder_test_css. To prove on drupalci that is the fix.Comment #31
bnjmnmCreated LB specific module for disabling animations + minor JS code standard fix.
There are diffs for the changes in my previous patch + the last patch where it was RTBC
Comment #32
bnjmnmComment #34
phenaproximaSo here is my big question...what if this runs _after_ contextual links have been initialized? Will it apply retroactively? Is there any way to test that?
Comment #35
bnjmnm@phenaproxima -- the
drupalContextualLinkAddedevent is triggered as part of the initialization process for each contextual link, so it this listener should always run during initialization, never after.It is, of course, possible for this event to run many times after initial pageload. It will also happen any time an AJAX request returns an item with contextual links. This scenario is covered in the existing tests when adding blocks to the layout triggers a rebuild, which includes a re-triggering of
drupalContextualLinkAdded. I think the tests can be enhanced to demonstrate these additional calls have no unwanted effects, which I'll take care of.If I'm misinterpeting #34 definitely let me know so I can incorporate that into the expanded tests.
Comment #36
bnjmnmLocally, the contextual links tests are passing without the fix applied. Adding a test-only patch to confirm this is happening outside of my dev environment before I go too far down the rabbit hole.
Comment #37
bnjmnmComment #38
bnjmnmThe test-only on #36 is passing, so setting this back to needs work.
Comment #39
bnjmnmThe earlier "should fail" tests were likely failing because of the other intermittent test failure issues. The views block in the test was not generating a contextual link. This was addressed with the following:
Also discussed #35 with @phenaproxima and confirmed his concern is not an issue as the even listener is not inside a behavior.
A x25 test is included because of the intermittent failures that were happening with earlier patches.
Comment #41
tedbow@bnjmnm great work! RTBC!
Comment #42
knyshuk.vova commentedThe patch looks good and applies successfully. +1 for RTBC.
Comment #43
lauriii.startsWithisn't supported by IE 11 which we still support.I'm wondering if this should be applied globally in all JavaScript tests. Definitely not in the scope of this issue but we should open follow-up to explore the idea.
Comment #44
tedbowre #43.2
There is an existing issue #2901792: Disable all animations in Javascript testing
I renamed this issue to the patch that is on there
Comment #45
bnjmnmChanged to use
substr()@todoto removelayout_builder_test_css_transitionsif #2901792: Disable all animations in Javascript testing is completed.Comment #46
tedbowThis was changed to button in #3004536: Move the Layout Builder UI into an entity form for better integration with other content authoring modules and core features so the test needs to be updated. Also now the "l" is not capitalized.
Otherwise it looks good. I manually tested in again and it does remove the contextual links
Comment #47
bnjmnmFixed tests per #46
Comment #50
tedbowNow that I look at this again, why do we have the nested if? We could just more this condition to the outer diff.
Otherwise looks good
Comment #51
bnjmnmRe #50
Good catch - no reason for the nested if anymore. Refactored in the patch.
Comment #52
tedbowOk looks good!
Comment #53
lauriiiWould it make sense to add test coverage to ensure that nested contextual links are being removed? As far as I can tell, this is supported by the current solution but it isn't covered by the tests.
Comment #54
tedbowBecause we have added the View block which should have the contextual link for editing the View and the Nodes within the View this proves the contextual links have been removed.
Comment #55
tedbowCreated this issue #3034800: Allow modules to prevent certain Contextual links from being render as a follow up after chatting with @lauriii
I am not adding a todo because it is likely that the new issue will just determine the way we did it here is just fine. But we should explore if a new way should be created.
Comment #57
bnjmnmReroll. The smaller size compared to #51 is because the
layout_builder_test_css_transitionsmodule is now in HEADComment #58
tedbowsetting to Needs Work to run tests
Comment #59
tedbowReroll looks good! ☄️
Comment #60
xjmComment #61
lauriiiI'm concerned about the fact that this patch completely ignores updating
Drupal.contextual.collection. For example,Drupal.contextualToolbar.StateModel.countContextualLinksprovides false information about the contextual link count on the page. We also can't really make predictions on what kind of custom code there might be built on top of contextual.Could we look into a solution that would remove the placeholders from the markup before contextual links get initialized? This way the items would never even get created into
Drupal.contextual.collection.Comment #62
bnjmnmFrom #61
This patch is an entirely new approach, most of what remains is the test (which was also expanded). This is definitely a first try so I won't be surprised if there are more elegant ways to accomplish certain things such as string matching on the current route.
Some explanations of what I did
['#contextual_links']from render arrays.ViewsBlockBaseso->setShowAdminLinks(FALSE)can be called on the view before buildlayout_builder_entity_view_alteras they aren't available in arrays when inside a block plugin.layout_builder_uicache context since entity Contextual links are rendered when viewing layouts, but not when they're in the Layout Builder UI.Comment #64
phenaproximaNit: $value is not actually used.
"entity" is misspelled here.
Again, chalk this up to my non-understanding of cache contexts, but why does this need to implement CalculatedCacheContextInterface? As far as I can tell, the only real difference is the presence of $parameter in getContext(), but it's not used in this implementation.
Nit: The outer parentheses are superfluous.
If Views is not installed, will this cause a fatal error (will ViewsBlock be autoloadable)? It might be useful to call class_exists() too.
Nit: $value is not used.
Nit: There's a superfluous blank line here.
Comment #65
bnjmnmThanks @phenaproxima - addressed the nits and a few responses to your items
#64.3
. Turns out it doesn't -- was just force of habit so I'm glad you spotted that.
#64.5
Confirmed via manual testing & checking logs that this code results in no errors or unwelcome events if Views isn't installed.
Comment #67
wim leersLanguage nit: "if … " but no "then".
🤔 Why do we need to use
NestedArrayinstead ofunset()?Language nit: "for if"
Could use an
// @see layout_builder_entity_view_alter()?This 100% depends on the matched route. So
routeis the parent cache context.As described on https://www.drupal.org/docs/8/api/cache-api/cache-contexts, that means this cache context should be
route.layout_builder_ui, notlayout_builder_ui.(Because: if something is already varying by
route, then there is no more need to also vary bylayout_builder_ui.)🐫 s/UI/Ui/
// @see \Drupal\block\BlockViewBuilder::preRender()may be appropriate.NestedArray?I'd like to see this assert the presence of the cache context on these responses.
s/layout builder/Layout Builder/
This is done in many places, but not everywhere.
🤔 "that includes FOO contextual link" -> "that includes the FOO contextual link"
👍
Except: let's do:
That way, this test is not coupled to that current specific count, which will likely change in the future.
Language nit: "are correct on the canonical entity route"
Hm, it just happens to be 4? This may need to change in the future. How about
assertNotEmpty()?Why another different implementation of this if
looks much simpler and already exists in several places in HEAD?
Comment #68
andrewmacpherson commentedRelated: the contextual links which are related to layout builder should be visible all the time, not just on hover focus.
Comment #69
bnjmnmRe #67
I was erring on the side of caution in case there were contextual placeholders nested in the array - did not have a specific situation that this addressed. Since this stood out enough to be asked about, I tested a range of scenarios and found no evidence that it was necessary and changed it to
unset()@seeaddedNice, this has been changed
I removed this block entirely -- looks like this was an artifact of earlier attempts to fix the issue as is no longer necessary
AssertPageCacheContextsAndTagsTrait. Here I could check the page cache context, but still wasn't sure how to check it for the entity, which is what gets the context. Before moving into yet another type of test, I thought I'd see if providing a failing test would be sufficient. 3002608-69-cache-context-test-x10-FAIL comments the addition of the cache context, andContextualLinksTestfails as a result. It is not a cache context assertion, but it does demonstrate that this context is required for contextual links to work properly with Layout Builder. (if there's a good way to assert this I'm overlooking, point me in the right direction and I'll take care of it)waitForNoElementtest from #2892440: Provide helper test method to wait for an element to be removed from the page. I switched this out for the more compact version that is more commonly used, as it's just as effective for this particular test with fewer lines of code.Comment #71
wim leersCarefully checked each:
FunctionalJavascripttests cannot assert response headers! Note thatAssertPageCacheContextsAndTagsTraitis being added even though it's not being used.So, yes,
\Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTraitonly works forBrowserTestBase, because it predatesBrowserTestBase: it still usesWebTestBase'sdrupalGetHeader(). But … AFAICT this would work just fine:Or, with a simple change even
$this->assertCacheContext('route.layout_builder_ui');would work fine:Or am I missing something?
Comment #72
tim.plunkett#71.7
It's not supported.
Is this important enough that we write a second non-JS Functional test for this assertion?
Comment #73
wim leers#72: Wow, you're right:
and apparently the API is even explicitly designed to allow any of the web drivers to throw this exception to indicate the driver doesn't support it. Makes the interface nearly meaningless 😔 But of course that's not this issue's fault.
@bnjmnm's explanation in #69.7 indeed justifies not having an explicit assertion for this: he convincingly shows that the test coverage already proves that the cache context is resulting in the necessary variations (otherwise the tests fail), and hence there is no pressing need to assert the presence of this cache context on the response.
I think that
route.is_layout_builder_uiwould be even clearer. Per https://www.drupal.org/docs/8/api/cache-api/cache-contexts, that'd be consistent withurl.path.is_frontanduser.is_super_user. But I don't feel strongly about that.This is the only thing I don't think I can RTBC myself. I think that ideally Layout Builder wouldn't need to do something special for one particular block (and then even expand that block plugin's API surface); the block should instead be able to react to the context it's being displayed in.
I defer to a Views maintainer to sign off on this, and ideally also a Block module maintainer. Fortunately, those can be one and the same person: @tim.plunkett :) Assigning to him.
Comment #74
tim.plunkettResponding to #73.3
Please make this whole comment an @todo pointing to #3027653: Allow block and layout plugins to determine if they are being previewed, which is required to move this code out of here to where it belongs
@bnjmnm, your call on 73.2
Comment #75
bnjmnm#72.2 renamed the cache context.
#73.3 addressed by adding the @todos per the instruction in #74
Comment #76
tim.plunkettThanks!
This should still use // style comments, and the R in revisit should be capitalized.
Also I think showAdminLinks should get the full
\Drupal\views\ViewExecutable::$showAdminLinks()Comment #77
bnjmnmRe: #76 That was some choice JS/PHP vertigo. Good opportunity to notice the comment would be improved by using
\Drupal\views\ViewExecutable::$showAdminLinks(), though!Comment #78
wim leers👌
Comment #79
wim leersFor #74.
Comment #80
xjmThis patch changes only PHP and YAML, so not a frontend issue in its current state. :)
Comment #81
andypostThe same thing needed for views ui #3039248: Deprecate views_ui_contextual_links_suppress(), views_ui_contextual_links_suppress_push(), views_ui_contextual_links_suppress_pop()
Comment #82
xjmThis is rather... gnomic. I looked for other examples in core, but unless I've missed something there are no other examples? Where is this label used?
Comment #83
tim.plunkettAFAICS that method is literally never called in core. (
\Drupal\Core\Cache\Context\CalculatedCacheContextInterface::getLabel())We have another one of these cache context classes in LB,
\Drupal\layout_builder\Cache\LayoutBuilderIsActiveCacheContexthas the following:Which is super not helpful.
Comment #84
wim leers#82: The
url.path.is_frontcache context has a similar label. And Tim is right, this string is not visible anywhere in the UI; this label only exists for a hypothetical future contributed module where a site builder can manually associate cache contexts and/or for a debugging/auditing contrib module such as https://www.drupal.org/project/renderviz.Comment #85
xjmNonetheless, can we make it less odd? Simply "Layout Builder user interface"?
Comment #86
xjmHmm I also don't think this is doing the right thing. Wim's example in IsFrontPathCacheContext returns either
is_front.1oris_front.0. Sort-of descriptive string machine names. This is returning simply a1or0with no namespace -- also not great for debugging. :)Comment #88
tim.plunkett@effulgentsia suggested this be
route.name.is_layout_builder_uiShould be
? '1' : '0'Comment #89
wim leersroute.namecache context indeed existed, but I couldn't find it. It must've been late :| My bad, I'm sorry!I think we can do something similar here. No functional difference, but it may help people doing deep dives on caching problems.
Comment #90
bnjmnmRenamed cache context label per #85
Renamed cache context id per #88.1
Addressed #88.2 + #86 by using the suggestion in #89
Comment #91
tim.plunkettGreat, thanks!
Comment #92
wim leers#90 triggers a minor bit of OCD, fixed that :)
Confirming RTBC.
Comment #93
xjmThanks @Wim Leers et al; that reduces the cache context confusion.
I manually tested this and it's a definite usability improvement. One out-of-scope thing it made me notice is that the contextual links outside the Layout Builder (on "site chrome" blocks) are still somewhat confusing and still a way to accidentally leave the page (because they also contain the same "configure block" as an option, but that takes you to the block UI rather than the LB tray). Do we have a separate issue to discuss removing interactions for the rest of the page, similar to what we do with node previews?
So this comment is no longer exactly valid; the linked issue has been rescoped and has nothing to do with local tasks. However, I checked with @tim.plunkett and he pointed out that all our tests are still navigating to the node page first, and then clicking the actual layout tab.
So we should do one of:
However that should be done in a separate followup since it is everywhere. Tagging "Needs followup" for that.
This comment is confusing. Reading both this and the protected method it calls, I can't tell what's "when the Layout Builder is replaced via AJAX". ?
Shouldn't the inline comment here be above those preceding three lines?
Comment #94
xjmComment #95
tim.plunkett#3028191: When using Layout Builder, remove contextual links for blocks outside of the current layout is for #93.1, it has this issue linked as a related issue
Comment #96
bnjmnmCreated #3042089: Update Layout Builder functional javascript tests now that local tasks are not required by the UI. for #93.2
Comment #97
bnjmnm#93 item 2 ) For this particular test, the local tasks block was not necessary at all that + the @todo was removed and I created #3042089: Update Layout Builder functional javascript tests now that local tasks are not required by the UI. to follow up on this in the other tests.
#93 items 3 & 4 ) updated/moved comments.
Comment #98
tim.plunkettMuch clearer, thanks @bnjmnm! This fully addresses #93
Comment #99
xjmSaving issue credit.
Comment #102
xjmCommitted and pushed to 8.8.x and 8.7.x. Thanks!