Problem/Motivation
Variation field rendering doesn't differentiate between fields without values and fields which should not render. This can lead to situations in which a product variant may have a field w/o a value and be with another variant that has a value for the same field. The intent is for these fields to render and update on ajax, but the ajax will remove the field and never replace it if a variant has no value.
In addition to this basic problem, the ajax response commands appear to be nesting divs during the ajax. I've provided an image.

Proposed resolution
The variant field rendering needs to be aware of what fields are to be rendered in a given view mode, and which are not. For those which should be rendered, a stub div with the appropriate ajax class should ALWAYS be rendered, even in the absence of data from the variant entity. This will allow ajax responses to always replace content on the page.
I've provided a very ugly hack of \Drupal\commerce_product\ProductVariationFieldRenderer::replaceRenderedFields() to demonstrate what I mean.
public function replaceRenderedFields(AjaxResponse $response, ProductVariationInterface $variation, $view_mode = 'default') {
$rendered_fields = $this->renderFields($variation, $view_mode);
foreach ($rendered_fields as $field_name => $rendered_field) {
$element = $rendered_field;
unset($element['#cache'], $element['#weight'], $element['#attributes'], $element['#ajax_replace_class']);
if (!$element) {
$rendered_field['#markup'] = '<div class="' . $rendered_field['#ajax_replace_class'] . '"></div>';
}
$response->addCommand(new ReplaceCommand('.' . $rendered_field['#ajax_replace_class'], $rendered_field));
}
}
Remaining tasks
Figure out the right way to fix this.
Fix this.
User interface changes
The UI will work more predictably.
API changes
None
Data model changes
Likely none. EntityViewDisplay should know all this stuff already.
| Comment | File | Size | Author |
|---|---|---|---|
| #26 | 2938760-26.patch | 12.35 KB | bojanz |
| #21 | commerce-2938760-empty_variation_fields-21.patch | 9.38 KB | czigor |
| #21 | commerce-2938760-empty_variation_fields-21_ONLY_TESTS.patch | 8.03 KB | czigor |
| #20 | 2938760-20.patch | 5.23 KB | bojanz |
| #13 | interdiff-2938760-9-13.txt | 821 bytes | goz |
Comments
Comment #2
drugan commentedI also had a similar issue with Commerce Quick Purchase module. If only an attribute on the Add to cart is ajax refreshed then autocomplete field of the module is also somehow changed and don't work anymore. For now, as a quick and dirty solution I've made this:
http://cgit.drupalcode.org/commerce_quick_purchase/tree/src/QuickPurchas...
Not sure what the actual cause is but as it noticed already the DC attempts to render an empty field on ajax refresh. Which field? That is the question! :)
Comment #3
eclipsegc commentedOk, a little smarter solution. Something more like this.
Eclipse
Comment #4
fall_0ut commentedit's corrects the issue, when the first variation doesn't have the field filled
Comment #6
fall_0ut commentedFix PHP typo
Comment #7
Mirroar commentedThanks for the patch, it indeed fixes the vanishing fields issue. However, the duplicating divs still persist. That probably needs to be fixed wherever the variation fields are actually rendered?
Also, I'm not sure if we need to implement out own isEmpty-check, when there is helper functions like Element::isEmpty, though that one only works in special cases. Not sure if there is a better one we could use.
Comment #8
Mirroar commentedAlright, I've looked at this a little more closely. The patch was causing PHP notices because in
replaceRenderedFieldsit checks$content, but the variable is called$rendered_fieldhere. Furthermore, this whole second part isn't needed (which is why the patch worked despite the mistake), because all content inreplaceRenderedFieldshas already run thoughrenderField, where the fix was added initially.So I removed the offending portion and reworded some of the documentation.
I also looked into the wrapper div issue, and it seems we can't really do much about that in contrib. The offending code lies in
core/misc/ajax.jswhere it reads:Basically, it's checking whether the inserted content is only a single html element, else it creates a plain wrapper div. However, the output of the field template (at least when using the classy theme) contains whitespace before the actual element (and potentially twig debug comments if enabled), which means
$newContentcontains a whitespace text node before the actual element. So it just wraps it in a div.Comment #9
goz commentedThanks for the patch, this help to have something which work, even if i think we still have work to make a commit.
I make some improvements, but i still have some issues and see other cases this couldn't be enough.
1/ We still have the
<div>nested issue2/ I add a test in isEmpty() in case field has a #markup but no item. I think we should think about other cases like templates (no markup, no items), which also should display something even if the template return nothing.
3/ I add a hide class. I ok the markup is empty, but in case some css apply to this class, we should not display this (i guess)
Comment #12
ndf commented#9.1 there is an active core-issue for the nesting issue #736066: ajax.js insert command sometimes wraps content in a div, potentially producing invalid HTML and other bugs
Comment #13
goz commentedSome tests are missing, we should be sure #prefix and #suffix exists.
Comment #14
bojanz commentedMarked #3006862: List price doesn't display after switching to a variation without a list price as a duplicate.
Comment #15
lukasss commented#13 working for me
Comment #16
zincdesign commentedWill this patch work with Commerce 8.x-2.10 ?
Comment #17
lukasss commented@zincdesign yes
Comment #18
zincdesign commentedThat's great thanks. #13 Working for me.
Comment #19
bojanz commentedThe div nesting issue was fixed by Drupal 8.6.0, which we require.
Looking into this issue I found problems with performance and handling fields that are hidden on the entity display, fixed in #3010225: Optimize ProductVariationFieldRenderer performance. This patch now needs a reroll.
Implementation notes:
- We should be able to use Element::children() to check if the render array is empty.
- Setting #theme/#type to container should be an easy way to ensure a div.
Comment #20
bojanz commentedUpdated patch.
1) Still missing the main test coverage (ensuring that switching the variation renders the field). No good place to add it to right now, it seems.
2) There's some code duplication between renderFields() and renderField()
3) An empty div has a height, so it changes the way the page looks. We could explode either using a span, or adding an additional class that can be targetted with CSS.
Comment #21
czigor commentedAdding tests.
Comment #24
czigor commentedI think this redirect test tends to fail randomly.
Comment #25
mglamanComment #26
bojanz commentedThis should be the final patch.
Renamed and cleaned up the functional test, since it's now doing more than testing the view mode.
Eliminated the code duplication in ProductVariationFieldRenderer.
Decided to keep the container as-is (which means keeping its height) for now. It does prevent a sliding effect when a new value appears. People can target the div with :empty in CSS.
Comment #28
bojanz commentedThanks, everyone!