Magento 2 – Disable/Remove Cart Button for Out of Stock Configurable Product

configurable-productmagento-2.1out-of-stock

I am using Magento 2.1 and I found that for configurable products, if any variation of associated product is out of stock, then add to cart button is still visible.
I want to hide/disable it when selected simple product is not in stock so that user knows that the product is not in stock.
Currently, Add to cart button is visible for all product options and when I click on Add to cart, it shows me error message that the product is not in stock.
Please guide how can I achieve this.

Best Answer

This answer assumes that you know how to create a module in Magento 2 as it is out of the scope of the question.

To begin with, create a layout file in your module of which targets the configurable product handle. So, the file name would be catalog_product_view_type_configurable.xml and it would be store in [Vendor]/[Module]/view/frontend/layout.

Inside of this create a block of which will allow you to add a template into the page. This block will need custom functionality so it will have to be created. So for example, your xml might look like this:

<?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.options.wrapper">
            <block class="[Vendor]\[Module]\Block\[BlockClass]" name="your.name.here" template="[Vendor]_[Module]::path/to/your/template.phtml" after="-"/>
        </referenceBlock>
    </body>
</page>

To get this functionality into the page will require JS. We are able to take advantage of Magento's native text/x-magento-init script type here to allow us to populate JS models and views. So for example, our template might look like so:

<?php /** @var \Magento\Catalog\Model\Product $product */ ?>
<?php $product = $block->getProduct(); ?>

<?php if ($product): ?>
    <script type="text/x-magento-init">
        {
            "*": {  
                "[Vendor]_[Module]/js/path/to/js/file":<?php echo $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getProductChildData($product->getId())); ?>
            }
        }
    </script>
<?php endif; ?>

Now we need to populate our JS with some data about the children of the current product. To do this, we can create a function in our block (here I have named it getProductChildData() but it doesn't matter what you call it obviously.

So, in your block class, the function would look something like this:

     /**
     * Array of attributes to be used for stock data
     * 
     * @var array
     */
    protected $_stockDataAttributes = [
        'qty',
        'is_in_stock'
    ];

    public function getProductChildData($productId)
    {
        $data = [];
        /** @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable $_configurableProductModel */
        $children = $this->_configurableProductModel->getChildrenIds($productId);

        foreach ($children as $child) {
            foreach ($child as $item) {
                /** @var \Magento\CatalogInventory\Model\StockRegistry $_stockItemRegistry */
                $stockItem = $this->_stockItemRegistry->getStockItem($item);
                if($stockItem) {
                    foreach ($this->_stockDataAttributes as $attribute) {
                        $data[$item][$attribute] = $stockItem->getData($attribute);
                    }
                }
            }
        }

        return $data;
    }

Now all we need is our JS to manipulate the data in order to only show the buy now button / do whatever you like with the buy now button when the product is in stock.

So for example, your JS could look like this:

define([
    'jquery',
    'domReady!'
], function ($) {
    'use strict';

    $.widget('name.of.widget', {
    options: {
    },
    _create: function () {
        this._bind();
    },
    _bind: function() {
        var options = this.options;
        var select = $('[class^="super-attribute"]');
        var id = $('[name="selected_configurable_option"]');
        var self = this;

        setTimeout(function() {
            select.on("change", function() {
            if(typeof options[id.val()] !== 'undefined') {
                if (options[id.val()].is_in_stock == 0) {
                    self._toggleShow(0);
                } else {
                    self._toggleShow(1);
                }
            }
        })}, 300);

    },
    _toggleShow: function (inStock) {
        if (inStock) {
            $('.box-tocart').removeClass('no-display');
        } else if(!inStock) {
            $('.box-tocart').addClass('no-display');
        }
    }

});
return $.name.of.widget;

});

This should then use the data parsed as JSON data from the block to judge if the product variation is in stock or not. If it is not, it will hide the add to cart, if it is in stock it will show it.