Updated: Comment #0
Problem/Motivation
For a long, long time now, in-place editing of custom blocks has been broken. I don't even remember what it looks or feels like.
In looking for the root cause, it turns out I'm responsible myself :) Together with effulgentsia, I worked on #1972514: Impossible to set attributes for all entities. In #14 of that issue, I remarked that effulgentsia's changes in the reroll of #12 in that issue contained a flaw. I failed to ever correct it… and therefor in-place editing of blocks is broken.
Yes, that means it probably never ever worked in Drupal 8 core. Yes, I'm ashamed because that implies that I probably never tested it again on blocks, only on terms. I suspect we were working within the Drupal 8 branch of the Spark distribution back then, in which we had a different core patch, and hence it worked there, hence I didn't notice. No excuses though, that's just craptastic of me.
Proposed resolution
Actually make it work like intended. I said in #1972514-14:
Sadly, your changes broke the blocks case. In my patch, I was setting
$variables['attributes']. In your patch, that became$variables['content_attributes']. A subtle, but very important difference.
(Yes, I even explicitly said that this broke the blocks case.)
Remaining tasks
None.
User interface changes
None. (Besides that in-place editing of custom blocks will work again.)
API changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #35 | block_attributes-2171269-35.patch | 17.04 KB | internetdevels |
| #6 | interdiff.txt | 3.06 KB | wim leers |
| #6 | block_attributes-2171269-6.patch | 5.29 KB | wim leers |
Comments
Comment #1
wim leersComment #2
effulgentsia commentedWhy is it important for the attributes to be on the outer element?
I don't get that. The "custom block" entity is rendered inside block.html.twig's {{ content }}. Therefore, the entity's container can be either something internal to that (for example, if we chose to have a custom_block.html.twig file as we do for all our other content entities), or any wrapper around that provided by block.html.twig. Why is the nearest wrapper not sufficient, and what is it about the outer wrapper that makes things magically work?
Comment #3
wim leersEach entity should have its own contextual region, because we want contextual actions (in the form of links) on each entity. In order to get the contextual actions on the entity, the "boundaries" of the entity should match the "boundaries" of the contextual region. In HTML/DOM, that means they need to be the same DOM element.
And that's where the problem arises in the case of blocks/custom blocks the way it's implemented today: a custom block is also a block, therefor a custom block entity is also a block entity. The block entity is rendered, but it in turn renders the custom block entity. Unfortunately, the custom block entity is rendered as a child of the block entity. If we look at the template:
Then the total template represents the block entity, yet only the
{{ content }}part and its surrounding div renders the custom block entity, plus the custom block's title is extracted to print as the{{ label }}.Consequently, the overall template gets marked as a contextual region, yet the custom block is only rendered as a subset of it. Hence, they're not rendered as the same DOM element — in other words: the custom block is NOT a contextual region!
(And the only reason the contextual region shows up on the overall template and not just the custom block part of the template, is because there also is special code to deal with that: see the bottom part of
_block_get_renderable_region():// If there are any nested contextual links, move them to the top level..)This affects Edit's
data-attributes, because they get set on the custom block part of the template ({{ content_attributes }})instead of the overall template ({{ attributes }}).That is inconsistent with how attributes are handled elsewhere, e.g. in the node template:
A
data-attribute set by Edit on a node entity will get set on the top-level{{ attributes }}, not on the child-level{{ content_attributes }}.In other words, this code promises to set attributes on all entities in a consistent, unified way (that's why we worked on #1972514: Impossible to set attributes for all entities in the first place, that was the whole point!), yet due to the way that Block module incorrectly handles things, that promise is a lie:
yields
and
Which is the problem.
Comment #4
effulgentsia commentedYeah. That's the heart of the problem. How about this then?
Since this is pretty brittle stuff, can you also add a test?
Comment #5
effulgentsia commentedOr maybe even this?
Comment #6
wim leersI veto #5.
The whole point is that Edit module can deal with all entities in a uniform way. The patch in #5 violates that.
And what if another module wants to do something similarly generic to all entities? It'd have to apply this same hack.
The approach in #4 is fine by me. I considered that one too. It feels a little bit cleaner.
Finally: yes, I agree; this needs test coverage. Added.
Comment #7
effulgentsia commentedMoved the test from custom_block to block, some other code around, and documented the reasoning in BlockViewBuilder, since someone will surely bang their head on this again.
Comment #8
tim.plunkettI read through the patch twice and nothing jumps out to me as "the line that fixes the bug", but that's possibly because the test and test plugin are moved. Can someone upload a "test only" version so we can easily see what is failing?
Comment #9
effulgentsia commentedThis is the essential change in the patch. The rest is just some code reorganization to improve clarity and put related things together.
And this is the essential test for it, obscured by the rename of the test and supporting files from "HtmlId" to "Html", since it's no longer just about the ID (actually, even in HEAD, the test tests more than just ID).
Comment #11
effulgentsia commentedAnd to sum up #9, essentially, we're changing the rendering of the #attributes that are attached to the render array returned by BlockPluginInterface::build() from being rendered on the DIV that wraps just the {{ content }} in block.html.twig to the DIV that wraps the entire block.
The code comment in #9 tries to explain this, but I'm wondering if this is sound reasoning both for core, and for modules like Panels.
The alternative was #5, but Wim disagrees with only doing it for Edit module attributes (see #6).
Comment #12
tim.plunkettThis threw me off. When is $key not the entity ID?
Comment #13
effulgentsia commentedAlways. See HEAD's BlockViewBuilder::view() and HEAD's EntityViewBuilder::view().
Comment #14
effulgentsia commentedMinor fixes, plus uploading just the tests and just the non-test code separately, in addition to combined.
Comment #17
wim leersThe first failure is a mystery to me. I don't know blocks/CMI well enough to fix that. I fixed the second. The third is pretty tricky, and I think it's related to your changes in
BlockViewBuilder. I don't have time to dive into those.I don't think you ran tests locally?
Comment #19
effulgentsia commentedThe first is due to a change in the base class of the test block. The second is due to #contextual_links being merged to the top-level element earlier in the pipeline. This should fix both.
Comment #20
wim leersGreen and works as advertised! I can't RTBC this though.
tim.plunkett, do you have any more questions/objections? :)
Comment #21
tim.plunkettMy only worry is that this used to be NestedArray::mergeDeep, and now its a +=. Should we switch to that just to be safe?
Comment #22
joelpittet@tim.plunkett
It looks to be initialized to an empty array before the union so I don't think there is a case that needs the deepMerge. Maybe I'm wrong?
Comment #23
tim.plunkettIf
$content['#contextual_links']contains anything other than 'block', it won't be merged in.Comment #24
eclipsegc commentedSo I think what's being said here is "Let the specific implementation handle attributes and contextual links as it pleases." If that's what you're proposing, then I am ++. Robustness of new implementations (and thus, overridability and/or implementation specific alterations) is definitely the goal. I'd argue that this probably gives panels et. al. more room to do what they want, not less.
Eclipse
Comment #25
effulgentsia commentedThanks for the feedback @EclipseGc, but not quite. There's no functional change in this patch with respect to contextual_links: only a movement of the code from _block_get_renderable_region() to inside BlockViewBuilder, so that does give Panels more room, since it can swap out BlockViewBuilder.
With respect to #attributes though, they key functional change is that in HEAD, the $build array returned by the plugin is presumed to describe the content of the block only, and therefore the #attributes is rendered on the nearest wrapper DIV of that content. It also means that the Block entity and the Block plugin have different elements they can each separately set #attributes on, and each gets rendered on a different DIV. But with this patch, while the plugin is still free to set #attributes on the $build that it returns, the meaning of that property is changed to now be assumed to be attributes of the entire block, not just its content. Which means the Block entity takes those attributes and moves them to its own element.
So in a way, it's taking some control *away* from the plugin (it's saying that while the plugin can have a say in what the attributes are, it can't have a say about what DIV element they get rendered on). In a way, that was also true in HEAD given the code in template_preprocess_block(), but that arguably was its own bug that could have been fixed with minimal effort, but with this patch, the lack of control over where is being instituted by design.
Comment #26
effulgentsia commentedOn the other hand, you could look at it as more control, in that with the patch, the plugin is able to return attributes for the top-level block DIV, whereas in HEAD, it can't.
Comment #27
eclipsegc commentedYeah, still think I'm ok with this.
Eclipse
Comment #28
effulgentsia commentedThat's exactly right. By moving the merge from template_preprocess_block() to BlockViewBuilder, we go from not knowing the initial state to knowing it, so can use the simpler union operator.
Huh? The new code is doing a
+operation, same as the equivalent code removed from _block_get_renderable_region(). Possibly you meant the opposite: that if the plugin returns a 'block' key in #contextual_links, then that won't get merged in? That would be true, but that's the same as HEAD, and I think ok, since block.module owns that namespace.Thanks for the detailed reviews, Tim. Can I assume that since you're focusing on that, that you're ok with the basic premise of #25?
Comment #29
tim.plunkettOh yeah, I'm +1 to the whole patch and what you outlined in #25. I'm just focusing on minutiae that might bite us later, like += vs mergeDeep.
But the mergeDeep was on #attributes, which as you said, is now initialized as empty. So I think we're okay.
Comment #30
wim leers#27 + #29 = RTBC? :)
Comment #31
tim.plunkettYep, I think we're good here.
Comment #32
alexpottComment #33
snig commentedre-rolled
Comment #35
internetdevels commentedfixed previous re-roll
Comment #36
internetdevels commentedchanged status
Comment #38
snig commented35: block_attributes-2171269-35.patch queued for re-testing.
Comment #39
wim leersBack to RTBC per #31.
Comment #40
catchCommitted/pushed to 8.x, thanks!
Comment #41
wim leersHurray, thanks :)
Comment #43
effulgentsia commentedBoth of the above hunks (the code removed and the code added by this patch) result in #2486267-10: Attributes of a block content are applied to block itself (well, prior to this patch, the attributes were moved to a different
<div>, but still the same point that it got moved away from the<form>). If anyone here has input on how to resolve that, please comment on that issue. Thanks!Comment #44
effulgentsia commentedRe, the unintentional version change in #43: I filed a d.o. issue: #2772953: 8.0.x is missing from Drupal core's issue queue as a "version" option for past issues.
Comment #45
drumm