当加载AJAX的块时,Magento 2中的产品价格上涨

I am working on a Magento 2 module that uses AJAX to load the upsell products. The upsell products can be different per customer so AJAX is used to load the block to allow for cache busting.

For this I have a custom module where my block extends the \Magento\Catalog\Block\Product\ProductList\Upsell. In the modules layout for catalog_product_view.xml I have the following -

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="product.info.upsell" remove="true" />
        <referenceContainer name="content.aside">
            <block class="MyCompany\MyModule\Block\Product\ProductList\Upsell"
                   name="personalised.product.upsell"
                   template="MyCompany_MyModule::upsell.phtml" />
        </referenceContainer>
    </body>
</page>

In my upsell.phtml -

<div id="personalised-upsells-container" data-role="personalised-upsells"></div>
<script type="text/x-magento-init">
    {
        "*": {
            "MyCompany_MyModule/js/upsell": {
                "upsellAjaxUrl": "<?php echo $block->getUpsellAjaxUrl(); ?>"
            }
        }
    }
</script>

The getUpsellAjaxUrl() generates http://magento2.dev/personalised/products/upsellAjax/id/6

My upsell.js -

define([
    'jquery',
    'upsellProducts'
], function($) {

    function getUpsellContent(url) {
        $.ajax({
            url: url,
            dataType: 'html'
        }).done(function (data) {
            $('#personalised-upsells-container').html(data).promise().done(function(){
                $('.upsell').upsellProducts();
            });
        });
    }

    return function (config, element) {
        getUpsellContent(config.upsellAjaxUrl);
    };
});

My controller (upsellAjax) -

class UpsellAjax extends ProductController
{
    public function execute()
    {
        $productId = (int) $this->getRequest()->getParam('id');
        $product = $this->loadProduct($productId);
        if (!$product) {
            return false;
        }
        /** @var \Magento\Framework\View\Result\Layout $resultLayout */
        $resultLayout = $this->resultFactory->create(ResultFactory::TYPE_LAYOUT);
        return $resultLayout;
    }
}

My personalised_products_upsellajax.xml -

<?xml version="1.0"?>
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd">
    <container name="root">
        <block class="MyCompany\MyModule\Block\Product\ProductList\Upsell" name="product.info.personalised.upsell" template="Magento_Catalog::product/list/items.phtml">
            <arguments>
                <argument name="type" xsi:type="string">upsell</argument>
            </arguments>
        </block>
    </container>
</layout>

As you would expect this correctly loads the product upsell block via ajax, pushes the HTML into my container then initialises the upsellProducts widget on the page. My upsell products show as expected but without prices.

I have tried a couple of things to debug the situation but as far as I can see it fails to load the priceRender on line 428 of \Magento\Catalog\Block\Product\AbstractProduct inside the getProductPriceHtml() method. The line $priceRender = $this->getLayout()->getBlock('product.price.render.default'); always returns false when the block is loaded via AJAX.

This is also the situation when I replace using my block for the default block within the layout (personalised_products_upsellajax.xml) e.g.

<?xml version="1.0"?>
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd">
    <container name="root">
        <block class="Magento\Catalog\Block\Product\ProductList\Upsell" name="product.info.upsell" template="Magento_Catalog::product/list/items.phtml">
            <arguments>
                <argument name="type" xsi:type="string">upsell</argument>
            </arguments>
        </block>
    </container>
</layout>

I was thinking it might have something to do with the removal of the upsell block first in my layout i.e <referenceBlock name="product.info.upsell" remove="true" /> I decided to comment this line out which results in two upsell blocks appearing, one is the default loaded block and the other is my AJAX block. Same results where the default block show correct information however my AJAX block is still missing prices.

Any help would be greatly appreciated.

You can try:

<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd">
    <container name="root">
        <block class="Magento\Catalog\Block\Product\ProductList\Upsell" name="product.info.upsell" template="Magento_Catalog::product/list/items.phtml">
            <arguments>
                <argument name="type" xsi:type="string">upsell</argument>
            </arguments>
        </block>
        <block class="Magento\Framework\Pricing\Render" name="product.price.render.default">
            <arguments>
                <argument name="price_render_handle" xsi:type="string">catalog_product_prices</argument>
                <argument name="use_link_for_as_low_as" xsi:type="boolean">true</argument>
            </arguments>
        </block>
    </container>
</layout>

I recently ran into the same issue and after much digging and searching we appear to have found a solution. Adding <update handle="empty"/> to the layout file seems to resolve the issue and make the pricing appear. So in your case the layout would end up looking like

<?xml version="1.0"?>
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd">
<update handle="empty"/>
        <container name="root">
            <block class="MyCompany\MyModule\Block\Product\ProductList\Upsell" name="product.info.personalised.upsell" template="Magento_Catalog::product/list/items.phtml">
                <arguments>
                    <argument name="type" xsi:type="string">upsell</argument>
                </arguments>
            </block>
        </container>
    </layout>

I can't begin to explain why it works. I got here by looking at the 1column layout and using that as my basis. We've used this to fix the issue in two different places so far.

I had the same problem: the block product.price.render.default needs to be available in the layout, as well as some of its child blocks, to render prices. The idea to the answer is simple: since this block is loaded for the default handle, make sure this handle is available in the layout, by adding it early in the request:

public function __construct(Context $context, LayoutInterface $layout)
{
    parent::__construct($context);

    $this->layout = $layout;
    $this->layout->getUpdate()->addHandle('default');
}