Add qty increment buttons to product page

As you probably know, Magento is using a plain text field to handle quantity on product page. This is the bulletproof solution – it is simple and covers all needs. But, sometimes, merchants want to use something more appealing, and many of them settle for quantity increment buttons (those + and buttons next to the quantity input field). Adding this kind of behaviour will be the topic of this post.

Approaching this problem, or any other Magento related problem for that matter, few options present themselves. We could use good old inline javascript and handle everything from there. Since we’re using Magento 2, we could choose a route of jQuery widgets, and create one that would handle quantity increments. And, lastly, the thing we will use in this article are UI components with jQuery and Knockout.js.

If you need more information on Magento UI components, and using Knockout.js, take a look at this Inchoo post.

First thing we have to do is create an empty module. We’ll call it Inchoo_QtyIncrementors.
It will be located at /app/code/Inchoo/QtyIncrementors.
In order for Magento to see our module, we’ll need to create etc/module.xml and registration.php. Just put usual stuff in there (check core modules if you get stuck).
Since we’re changing product page functionality, we have to find the place where default quantity field is being rendered.
Let’s take a few (not so) wild guesses:

  1. it should be somewhere in core files
  2. it should be in the Catalog module
  3. it should be in frontend templates (view/frontend/templates directory)
  4. it is a product template, so it must be in product directory
  5. since it is not in product listing, it must be in a view directory (product/view)
  6. addtocart.phtml looks interesting, let’s check it out

(we now have /app/code/Magento/Catalog/view/frontend/template/catalog/product/view/addtocart.phtml)

And, after closer examination, there it is:

<div class="control">
    <input type="number"
           name="qty"
           id="qty"
           maxlength="12"
           value="<?php /* @escapeNotVerified */ echo $block->getProductDefaultQty() * 1 ?>"
           title="<?php /* @escapeNotVerified */ echo __('Qty') ?>" class="input-text qty"
           data-validate="<?php echo $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>"
           />
</div>

It is just a regular input field, as expected.
Let’s change it. Copy the file to your own module: /app/code/Inchoo/QtyIncrementors/view/frontend/templates/catalog/product/view/addtocart.phtml.

In order to create UI component, we need to initialize it. Add this somewhere below the input field.

<script type="text/x-magento-init">
    {
        "*": {
            "Magento_Ui/js/core/app": {
                "components": {
                    "qty_change": {
                        "component": "Inchoo_QtyIncrementors/js/view/product/view/qty_change",
                        "defaultQty": <?php echo $block->getProductDefaultQty() * 1 ?>
                    }
                 }
            }
        }
    }
</script>

We are telling Magento to create a component that can be found in the path mentioned (Inchoo_QtyIncrementors/js/view/product/view/qty_change – which translates to Inchoo/QtyIncrementors/view/frontend/web/js/view/product/view/qty_change.js), and also, we’re sending a parameter called 'defaultQty' which is rendered from server side.

In order to connect this component to HTML on the frontend, one more thing is needed. We have to bind it to some HTML element. Check this:

<div class="control" data-bind="scope: 'qty_change'">
 
    <button data-bind="click: decreaseQty">-</button>
 
    <input  data-bind="value: qty()"
            type="number"
            name="qty"
            id="qty"
            maxlength="12"
            title="<?php /* @escapeNotVerified */ echo __('Qty') ?>" class="input-text qty"
            data-validate="<?php echo $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>"
        />
 
    <button data-bind="click: increaseQty">+</button>
</div>

We’ve added a data-bind attribute to an outer div, as well as removed default value attribute on input field, and added a data-bind attribute of our own.
Think of the data-bind as a way of connecting HTML to a javascript function from our component. So, we’re binding the div and everything in it to our component (qty_change). What this means is that every variable or function call invoked there (knockout way, of course) will be searched for in our components code. This makes data-bind="value: qty()" clearer now: value of the input element is linked to a result of invoking qty() in our component.
There are two new buttons here as well, linked to our component via click event (data-bind="click: increaseQty"). Hopefully this makes sense, and if not, I promise you, it will in a minute.

Now, off to the last piece of the puzzle: component.

Located at /Inchoo/QtyIncrementors/view/frontend/web/js/view/product/view/qty_change.js, it has the following content:

define([
    'ko',
    'uiComponent'
], function (ko, Component) {
    'use strict';
 
    return Component.extend({
        initialize: function () {
            //initialize parent Component
            this._super();
            this.qty = ko.observable(this.defaultQty);
        },
 
        decreaseQty: function() {
            var newQty = this.qty() - 1;
            if (newQty < 1) {
                newQty = 1;
            }
            this.qty(newQty);
        },
 
        increaseQty: function() {
            var newQty = this.qty() + 1;
            this.qty(newQty);
        }
 
    });
});

Everything is much clearer now. In the initialize function, we have a qty variable (actually an observable, which is a knockout.js thing), that returns its value when invoked like a function (thus data-bind="value: qty()"). Also, there are two methods (decreaseQty and increaseQty) modifying this variable when button is clicked (remember the data-bind="click: increaseQty" ?).

And that is it.

If you try and refresh the page, nothing happens, because we forgot to change the default addtocart.phtml template, i.e. Magento does not know we want to use our own, so we have to change the template path in the layout. Let’s do it now. Create a new xml file /Inchoo/QtyIncrementors/view/frontend/layout/catalog_product_view.xml

Here is the file content:

<?xml version="1.0"?>
<page layout="1column" 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.addtocart">
            <action method="setTemplate">
                <argument name="template" xsi_type="string">Inchoo_QtyIncrementors::catalog/product/view/addtocart.phtml</argument>
            </action>
        </referenceBlock>
        <referenceBlock name="product.info.addtocart.additional">
            <action method="setTemplate">
                <argument name="template" xsi_type="string">Inchoo_QtyIncrementors::catalog/product/view/addtocart.phtml</argument>
            </action>
        </referenceBlock>
    </body>
</page>

We’re referencing two blocks here – product.info.addtocart which is used for simple products, and product.info.addtocart.additional which is used for configurable ones. All we need to do is change the template – instead of using the default one, we use our own (from our module). This way, you can easily change templates for grouped and bundle products, as well. To implement qty increments for grouped or bundle products, you can use the same component, just make sure to have it instantiated for each input field.

Note: if you are overriding the files in your theme, it would be best to perform changes there, and not in the module.

And that’s it.

As always, we’ve prepared a module you can install via composer:

Add module as a dependency:

composer config repositories.inchoo_qtyincrementors vcs git@bitbucket.org:lurajcevi/inchoo_qtyincrementors.git

Ask Composer to download it:

composer require inchoo/qty_incrementors:dev-master

If Composer asks you for a set of credentials, please follow these instructions.

When composer does his thing, we can enable our module:

bin/magento module:enable Inchoo_QtyIncrementors

and later

bin/magento setup:upgrade

Our module has been enabled, and we can use it.

However, if this or anything else regarding Magento development confuses you, we will gladly check out your site and offer technical insights on what to improve based on our detailed custom report. Feel free to get in touch!