Javascript Use Cases in Magento 2: Simple custom module

Research
 
Magento
 
03 October 2018 263

In the first article we have shared with you some practical techniques how to clean JavaScript code in Magento 2 with JQuery widget. There was reviewed a very abstract example, but we want our articles to be used in real life and help other engineers, that’s why today we will solve a task from everyday developers’ work. Let's assume that there is ticket regarding user experience story - make a button on the product page, which should inform a user about current quantity of the product (it can be useful for wholesale businesses).

 

Let's have a look at this task from technical perspective. The simplest decision would be to just output quantity from PHP side by means of putting the quantity value in `.phtml` template, but if to look deeper, your client’s store can be high load project, where the same product might be viewed by several people at a time and thus - the given quantity can be false, because it might have been already sold. Product quantity can be fetched by Magento 2 API endpoints, but just for demonstration let's make our own controller to serve the AJAX call.

 

First of all, let's create the module in order to speed up the process, it is possible to use code generators, for example HotRod Cli. By running the


vendor/bin/hotrod module:create OpsWay_StockInfo

 

all needed files were generated for OpsWay_StockInfo module initialization in the app/code directory.

  • app/code/OpsWay/StockInfo/registration.php

<?php



\Magento\Framework\Component\ComponentRegistrar::register(

 \Magento\Framework\Component\ComponentRegistrar::MODULE,

 'OpsWay_StockInfo',

 __DIR__

);

 

  • app/code/OpsWay/StockInfo/etc/module.xml

<?xml version="1.0"?>



<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">

 <module name="OpsWay_StockInfo" setup_version="1.0.0" />

</config>

 

Secondary, we have to overwrite the vendor/magento/module-catalog/view/frontend/templates/product/view/addtocart.phtml template. To make sure OpsWay_StockInfo will overwrite it in app/code/OpsWay/StockInfo/etc/module.xml should be added the module sequence, so then module will be loaded after Magento_Catalog. In such case app/code/OpsWay/StockInfo/etc/module.xml would be updated.


<?xml version="1.0"?>



<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">

 <module name="OpsWay_StockInfo" setup_version="1.0.0">

     <sequence>

         <module name="Magento_Catalog"/>

     </sequence>

 </module>

</config>

 

Now, it is possible to overwrite the quantity template. There are several methods to do it, more detailed examples can be found here. We will chose the most suitable method for the module development, that is we will overwrite it with layout block argument. Let's create a file app/code/OpsWay/StockInfo/view/frontend/layout/catalog_product_view.xml with the following content:


<?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.addtocart">

         <action method="setTemplate">

             <argument name="template" xsi:type="string">OpsWay_StockInfo::product/view/addtocart.phtml</argument>

         </action>

     </referenceBlock>

 </body>

</page>

 

Here we take Add to Cart block from Magento Catalog module, which is defined vendor/magento/module-catalog/view/frontend/layout/catalog_product_view.xml and set the new template app/code/OpsWay/StockInfo/view/frontend/templates/product/view/addtocart.phtml, which we create during out next step with original content. You can find it here: vendor/magento/module-catalog/view/frontend/templates/product/view/addtocart.phtml.

 

Now let's add the button which will handle the action. In <div> with class 'control' just after the quantity input we will add a simple button and div where we will display the results from the server, initially it will be hidden.


<div title="<?= /* @escapeNotVerified */ __('Check the stock') ?>"

     class="action primary"

     id="get-the-qty">

 <span><?= /* @escapeNotVerified */ __('Check the stock') ?></span>

</div>

<div class="no-display" id="info-div">

 <p><span id="stock-qty"></span> <?= /* @escapeNotVerified */ __('in stock') ?></p>

</div>

 

After running in terminal from root project directory


php bin/magento setup:upgrade

 

we can check the overwrite on the frontend:

Since it works, now it is high time to move to backend development, lets generate the endpoint and the controller to handle it with Hotrod Cli (M2 code generator tool). By running


vendor/bin/hotrod create:controller OpsWay_StockInfo opsway/stock/info --no-block=true --no-layout=true --no-template=true

 

the route file with opsway/stock/info  and app/code/OpsWay/StockInfo/Controller/Stock/Info.php controller to serve the route will be generated. Our controller will expect the product ID and if it is an Ajax call, it will return the response with quantity. If request is not Ajax the 404 page will be shown.


<?php



namespace OpsWay\StockInfo\Controller\Stock;



use \Magento\Framework\App\Action\Action;

use \Magento\Framework\App\Action\Context;

use \Magento\CatalogInventory\Model\Stock\StockItemRepository;

use \Magento\Framework\Controller\Result\JsonFactory;



class Info extends Action

{

protected $resultJsonFactory;



protected $stockItemRepository;



 public function __construct(

   Context $context,

     StockItemRepository $stockItemRepository,

   JsonFactory $jsonFactory)

{

    $this->stockItemRepository = $stockItemRepository;

   $this->resultJsonFactory = $jsonFactory;

   return parent::__construct($context);

}



 public function execute()

{

     if ($this->getRequest()->isAjax() && $this->getRequest()->getParam('productId')) {

         $stock = $this->stockItemRepository->get($this->getRequest()->getParam('productId'));

        

         $result = $this->resultJsonFactory->create();

         $result = $result->setData([

             'success' => 'true',

             'qty' => $stock->getQty()

         ]);



         return $result;

     }



     $this->_forward('noroute');

     return;

}

}

 

Let’s work with JavaScript now, assuming that most of the time we will get help from Hotrod Cli, lets generate the basic widget, add the mage-init code to app/code/OpsWay/StockInfo/view/frontend/templates/product/view/addtocart.phtml file and create a require-config.js file, all that can be done simply running a command:

 

vendor/bin/hotrod create:js-script OpsWay_StockInfo stock  --template=OpsWay//StockInfo//view//frontend//templates//product//view//addtocart.phtml --type=widget

 

In app/code/OpsWay/StockInfo/view/frontend/templates/product/view/addtocart.phtml we have to customize the generated mage-init code as


<script type="text/x-magento-init">

{

 "#get-the-qty": {

     "stock": {

         "productId": "<?php echo $_product->getId(); ?>"

     }

 }

}

</script>

 

In such case we set the widget to our button element and pass the product ID as productId widget option. Finally we should write the JavaScript logic in app/code/OpsWay/StockInfo/view/frontend/web/js/stock.js, let's begin with the widget options, we have to add the productId.


options: {

productId: ''

},

 

Then we should write the Ajax call function:


checkStock: function () {

var widget = this;



this.loading = true;

$.ajax({

 url: '/opsway/stock/info?productId=' + this.options.productId,

 type: 'POST',

 dataType: 'json',

 success: function (response) {

   widget.loading = false;

   $('#info-div').removeClass('no-display');

   $('#stock-qty').text(response.qty);

 }

});

}

 

In this example we will make a call to the route which was made earlier and in success function we will show our result div, and put there a value from the response. The whole widget would be:


define([

"jquery",

"jquery/ui"

], function ($) {

'use strict';



$.widget('mage.stock', {



 loading: false,



 options: {

   productId: ''

 },



 _create: function() {

   this._bind();

 },



 _bind: function () {

   var widget = this;

   this.element.on('click', function () {

     if (!widget.loading) {

       widget.checkStock();

     }

   });

 },



 checkStock: function () {

   var widget = this;



   this.loading = true;

   $.ajax({

     url: '/opsway/stock/info?productId=' + this.options.productId,

     type: 'POST',

     dataType: 'json',

     success: function (response) {

       widget.loading = false;

       $('#info-div').removeClass('no-display');

       $('#stock-qty').text(response.qty);

     }

   });

 }



});

});

 

You can find a source code of this tutorial on github.

 

This article was written by Dmitriy Ivanenko, senior full stack Magento 2 developer & clean code evangelist, who likes to read an elegant code as much as to write it by himself.