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.
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');
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With