Category: Magento2

KnockoutJs custom bindings in Magento 2

Today I will discuss KnockoutJs two important custom bindings in Magento 2. This is the series of Magento 2 Certified Professional Javascript Developer exam.

Two important knockoutjs bindings are:

1. Knockout Fast Foreach
2. Repeat

How these bindings are the map in magento 2?
vendor/magento/module-ui/view/base/web/js/lib/knockout/bindings/bootstrap.js

1. Knockout Fast Foreach
KnockoutJs default foreach is slow for large data. For overcome this slowness magento use Knockout Fast Foreach out of box. Location lib/web/knockoutjs/knockout-fast-foreach.js. Ex.

<div data-bind="scope: 'outerfasteach-example'">
    <table>
        <thead>
            <tr><th>First name</th><th>Last name</th></tr>
        </thead>
        <tbody data-bind='fastForEach: {data: getCustomerData()}'>
            <tr>
                <td data-bind="text: firstName"></td>
                <td data-bind="text: lastName"></td>
            </tr>
        </tbody>
    </table>
</div>
<script type="text/x-magento-init">
{
    "*": {
        "Magento_Ui/js/core/app": {
            "components": {
                "outerfasteach-example": {
                    "component": "VendorName_ModuleName/js/fast-foreach"
                }
            }
        }
    }
}
</script>

app/code/VendorName/ModuleName/view/frontend/web/js/fast-foreach.js

define([
    'ko',
    'uiComponent',
    'underscore'
], function (ko, Component, _) {
    'use strict';

    return Component.extend({
        customerNameList: ko.observableArray([]),
        /** @inheritdoc */
        initialize: function () {
            this._super();
            var self = this;
            for (var i = 0; i < 5000; i++) {
                self.customerNameList.push({'firstName': 'firstName_'+i, 'lastName': 'lastName_'+i});
            }
        },
        getCustomerData: function () {
            return this.customerNameList;
        }
    });
});

You can use as a node, so attribute name should be outerfasteach. Check here how magento uses in the grid listing:
vendor/magento/module-ui/view/base/web/templates/grid/listing.html

<div class="admin__data-grid-wrap" data-role="grid-wrapper">
    <table class="data-grid" data-role="grid">
       <thead>
            <tr each="data: getVisible(), as: '$col'" render="getHeader()"/>
        </thead>
        <tbody>
            <tr class="data-row" repeat="foreach: rows, item: '$row'" css="'_odd-row': $index % 2">
                <td outerfasteach="data: getVisible(), as: '$col'"
                    css="getFieldClass($row())" click="getFieldHandler($row())" template="getBody()"/>
            </tr>
            <tr ifnot="hasData()" class="data-grid-tr-no-data">
                <td attr="colspan: countVisible()" translate="'We couldn\'t find any records.'"/>
            </tr>
        </tbody>
    </table>
</div>

2. Repeat

You can use repeat binding alternative of foreach. This is also faster than foreach binding.

Here’s a comparison between foreach and repeat for a data table:

<table>
    <tbody data-bind="foreach: data">
        <tr data-bind="foreach: $parent.columns">
            <td data-bind="text: $parent[$data.propertyName]"></td>
        </tr>
    </tbody>
</table>

<table>
    <tbody>
        <tr data-bind="repeat: { foreach: data, item: '$row' }">
            <td data-bind="repeat: { foreach: columns, item: '$col' }"
                data-repeat-bind="text: $row()[$col().propertyName]"></td>
        </tr>
    </tbody>
</table>

For more details, you can check here

Magento uses this in timeline:
vendor/magento/module-ui/view/base/web/templates/timeline/timeline.html

Pass Magento 2 Certified Professional JavaScript Developer

I have passed the Magento 2 Certified Professional JavaScript Developer exam yesterday. Today I share my experience, preparation, etc with great Magento community.

When I took my Magento 2 Certified Professional JavaScript Developer exam?
I took my Magento 2 Certified Professional JavaScript Developer exam on 28th July 2019. It is a great feeling that I am now Magento 2 Certified Full-Stack Developer. I have passed 6 Magento 2 exams 😀

How I start My preparation?
Well, I set my mind to take this exam when Magento declared new Magento 2 Certified Full-Stack Developer exam. So I start learning all topic from study guide and practice. Also, checked my last project where I worked lots of modification using JS. When I worked with a project I didn’t touch many areas. One of is how Magento works with layout(uiLayout), that means core things. So I start learning from here. I already covered many areas when I take Professional Plus, Professional and Front-end exam. So it was my plus point that I just revised those areas. If you have working experience how ajax data handle using knockoutjs, how checkout work then you can easily answer all questions of those areas. No need to learn php stuff, I didn’t see any question related to that.

Who can take this exam?
Developer who have 2 years of hands-on working experience with Magento 2. It is important that you have must hands-on experience. If you think you memorized all area according to study guide then you should think twice before taking this exam. My opinion will be practice as much as you can.

How hard this exam?
Honestly, it was not a hard exam if you have working experience. All questions were logically and you can answer those question easily if you have working experience. I already said I learned most of the area when I took other Magento 2 exams, I think that helps me lots.

What is the passing score?

  • 60 multiple choice questions
  • 90 minutes to complete the exam
  • 63% or higher needed to pass

Where can I find a good resource?
Still, devdocs is a good resource that I have found. Also, check every topic from the study guide. Practice more on every topic.

Feel free to reach me if you have any question regarding certification. I am active in Magento Slack, Magento StackExchange or through my blog.

Pass Magento 2 Certified Professional Developer Plus Exam

It’s been a long time, No new post publish because I was so busy with Magento 2 exams. I have passed 5 Magento 2 certifications. My latest certification was Magento 2 Certified Professional Developer Plus. Today I share my experience and how I prepared Magento 2 Certified Professional Developer Plus exam.

When I took the exam?
I took my Magento 2 Certified Professional Developer Plus exam on 31st December 2018.

How I start preparation?
Magento 2 Certified Professional Developer Plus exam related resource is not available so much. Also, this exam is so young. When Magento announce this exam, I start preparation. My Magento 2 Certified Professional Developer exam preparation help me a lot. I start digging the Magento 2 commerce feature. At this point, Magento 2 Certified Solution Specialist exam preparation helps me to Magento 2 commerce default functionality. I start practicing Staging, Segmentation, RMA, MessageQueue, Database Sharding, etc. feature. I digging how a feature works by coding.

Firstly I find out that area, which I am weak. Then I start that area. It was so much dedication because after full-time work, taking preparation is not easy. But I did that.

Who can take the exam?
Anyone who have 2 years working experience with Magento 2 commerce. Actually, you need hands-on experience with it. This exam is built on Magento 2 commerce only. So it is necessary to work with Magento 2 commerce and Magento 2 commerce knowledge.

How hard this exam compared to other exams, especially professional?
Well, I would say this is hard more than all exam. I see many great Magento developer fail their first attempt. You need so deep knowledge about Magento 2 commerce. So before taking exam you must revise all area (according to study guide) deeply.

How deep we need to study?
Well, it is sooo deep 😀 . For example: for a console command, you need to learn in detail. How you declare in di.xml which area you need to declare (global or frontend, or adminhtml) that, how you declare required/optional option, what is the common error and how to fix that, how to interact with commerce feature, etc. Same for all. It is must check how to interact with Magento 2 commerce which features that is common for community edition.

What is the passing score?

  • 60 multiple choice questions
  • 90 minutes to complete the exam
  • 62% or higher needed to pass

For more details check official site

Where can I find good resource?
Still devdocs is good resource that I have found. Also check every topic from study guide. Also follow my blog 😀 , I have a plan to discuss important topic one by one. Read use guide for default feature.

One important feature I have found for this exam is Security. This feature should include for Professional exam too. Security is so important when you build something for the merchant. So you must know how Magento 2 handle this stuff.

Feel free to reach me if you have any question regarding certification. I am active in Magento Slack, Magento StackExchange or through my blog.

How to overwrite Swatch Renderer JS in Magento 2

In Magento 2, you can easily overwrite any js using mixin. So today I will show, how you can overwrite core Swatch Renderer Js.

Step 1: Create app/code/VendorName/ModuleName/view/frontend/requirejs-config.js

var config = {
    config: {
        mixins: {
            'Magento_Swatches/js/swatch-renderer': {
                'VendorName_ModuleName/js/swatch-renderer-mixin': true
            }
        }
    }
};

Step 2: Create app/code/VendorName/ModuleName/view/frontend/web/js/swatch-renderer-mixin.js

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

    return function (SwatchRenderer) {
        $.widget('mage.SwatchRenderer', $['mage']['SwatchRenderer'], {
            _init: function () {
                console.log('getProductSwatchRenderer');
                this._super();
            }
        });
        return $['mage']['SwatchRenderer'];
    };
});

That’s it.

Now you need to remove static content and deploy static content.

How to get customer id from block when full page cache enable in magento 2

Today I discuss about getting customer_id from block when full page cache(FPC) is enable. If FPC is enable then you can’t get customer id from block just because magento2 reset all customer session. You can get customer session data if your layout set cacheable=”false” in that case magento2 just ignore cache whole page. How? lets dive.

If you open vendor/magento/module-customer/Model/Layout/DepersonalizePlugin.php


/**
 * After generate Xml
 *
 * @param \Magento\Framework\View\LayoutInterface $subject
 * @param \Magento\Framework\View\LayoutInterface $result
 * @return \Magento\Framework\View\LayoutInterface
 */
public function afterGenerateXml(\Magento\Framework\View\LayoutInterface $subject, $result)
{
    if ($this->depersonalizeChecker->checkIfDepersonalize($subject)) {
        $this->visitor->setSkipRequestLogging(true);
        $this->visitor->unsetData();
        $this->session->clearStorage();
        $this->customerSession->clearStorage();
        $this->session->setData(\Magento\Framework\Data\Form\FormKey::FORM_KEY, $this->formKey);
        $this->customerSession->setCustomerGroupId($this->customerGroupId);
        $this->customerSession->setCustomer($this->customerFactory->create()->setGroupId($this->customerGroupId));
    }
    return $result;
}

Now check the if condition, basically this return true/false from following function
vendor/magento/module-page-cache/Model/DepersonalizeChecker.php


/**
 * Check if depersonalize or not
 *
 * @param \Magento\Framework\View\LayoutInterface $subject
 * @return bool
 * @api
 */
public function checkIfDepersonalize(\Magento\Framework\View\LayoutInterface $subject)
{
    return ($this->moduleManager->isEnabled('Magento_PageCache')
        && $this->cacheConfig->isEnabled()
        && !$this->request->isAjax()
        && ($this->request->isGet() || $this->request->isHead())
        && $subject->isCacheable());
}

Here $subject->isCacheable() return false if your layout has ‘cacheable=”false”‘ attribute, lets check
vendor/magento/framework/View/Layout.php


/**
 * Check is exists non-cacheable layout elements
 *
 * @return bool
 */
public function isCacheable()
{
    $this->build();
    $cacheableXml = !(bool)count($this->getXml()->xpath('//' . Element::TYPE_BLOCK . '[@cacheable="false"]'));
    return $this->cacheable && $cacheableXml;
}

So if full page cahce enable and cacheable=”false” is not exist then magento 2 reset customer session by


$this->session->clearStorage();
$this->customerSession->clearStorage();

That’s why if you try to get customer session data from block, it’s return always empty basically only group_id is present in customer session data.

So how to get customer information then.

You can follow Magento2 official tutorial.

Now Today I discuss how you can get customer_id if cache enable. Lets dive.

Suppose vendor name ‘SR‘ and module name ‘CustomerSession

Create a contex class [SR/CustomerSession/Model/Customer/Context.php]


namespace SR\CustomerSession\Model\Customer;

class Context
{
    /**
     * Customer authorization cache context
     */
    const CONTEXT_CUSTOMER_ID = 'logged_in_customer_id';
}

Now pluginize Magento\Framework\App\Action\AbstractAction dispatch method.
So create a di.xml [SR/CustomerSession/etc/frontend/di.xml]


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\App\Action\AbstractAction">
        <plugin name="sr-customer-app-action-dispatchController-context-plugin" type="SR\CustomerSession\Plugin\App\Action\Context" sortOrder="15"/>
    </type>
</config>

Now create SR/CustomerSession/Plugin/App/Action/Context.php


namespace SR\CustomerSession\Plugin\App\Action;

use SR\CustomerSession\Model\Customer\Context as CustomerSessionContext;

class Context
{
    /**
     * @var \Magento\Customer\Model\Session
     */
    protected $customerSession;

    /**
     * @var \Magento\Framework\App\Http\Context
     */
    protected $httpContext;

    /**
     * @param \Magento\Customer\Model\Session $customerSession
     * @param \Magento\Framework\App\Http\Context $httpContext
     */
    public function __construct(
        \Magento\Customer\Model\Session $customerSession,
        \Magento\Framework\App\Http\Context $httpContext
    ) {
        $this->customerSession = $customerSession;
        $this->httpContext = $httpContext;
    }

    /**
     * @param \Magento\Framework\App\ActionInterface $subject
     * @param callable $proceed
     * @param \Magento\Framework\App\RequestInterface $request
     * @return mixed
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function aroundDispatch(
        \Magento\Framework\App\ActionInterface $subject,
        \Closure $proceed,
        \Magento\Framework\App\RequestInterface $request
    ) {
        $customerId = $this->customerSession->getCustomerId();
        if(!$customerId) {
            $customerId = 0;
        }

        $this->httpContext->setValue(
            CustomerSessionContext::CONTEXT_CUSTOMER_ID,
            $customerId,
            false
        );

        return $proceed($request);
    }
}

That’s it. Now you can able to get customer id following way even cache enable:


/**
* @var \Magento\Framework\App\Http\Context
*/
$this->httpContext->getValue(SR\CustomerSession\Model\Customer\Context::CONTEXT_CUSTOMER_ID);

Download full module from here

Generate invoice and shipment automatically when a new order is placed in Magento 2

Many merchants wants to create invoice / shipment and complete the order all at once when place order. So today I will discuss about how achieve their need. Although it’s custom needs/requirement so we need to create a custom module for that.

In that case, you need to do is to observe the checkout_submit_all_after event. So lets start.

Here Vendor is ‘SR’ and Module name ‘AutoInvoiceShipment’.

So create a module you can follow this post

Create events.xml [SR/AutoInvoiceShipment/etc/events.xml]

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="checkout_submit_all_after">
        <observer name="sr_auto_invoice_shipment_checkout_submit_all_after" instance="SR\AutoInvoiceShipment\Observer\CheckoutAllSubmitAfterObserver"/>
    </event>
</config>

Create observer class for that [SR/AutoInvoiceShipment/Observer/CheckoutAllSubmitAfterObserver.php]

namespace SR\AutoInvoiceShipment\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;

class CheckoutAllSubmitAfterObserver implements ObserverInterface
{
    /**
     *
     * @var \SR\AutoInvoiceShipment\Helper\Data
     */
    protected $helper;

    /**
     * @param \SR\AutoInvoiceShipment\Helper\Data $helper
     */
    public function __construct(
        \SR\AutoInvoiceShipment\Helper\Data $helper
    ) {
        $this->helper = $helper;
    }

    /**
     *
     * @param Observer $observer
     * @return $this
     */
    public function execute(Observer $observer)
    {
        if(!$this->helper->isEnabled()) {
            return $this;
        }

        $order = $observer->getEvent()->getOrder();
        if(!$order->getId()) {
            return $this;
        }

        $invoice = $this->helper->createInvoice($order);
        if($invoice) {
            $this->helper->createShipment($order, $invoice);
        }

        return $this;
    }
}

Create a helper[SR/AutoInvoiceShipment/Helper/Data.php]

namespace SR\AutoInvoiceShipment\Helper;

use Magento\Framework\App\Helper\AbstractHelper;

class Data extends AbstractHelper
{
    const MODULE_ENABLED = 'sr_auto_invoice_shipment/settings/enabled';

    /**
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    protected $scopeConfig;

    /**
     *
     * @var \Magento\Sales\Model\ResourceModel\Order\Invoice\CollectionFactory
     */
    protected $invoiceCollectionFactory;

    /**
     *
     * @var \Magento\Sales\Model\Service\InvoiceService
     */
    protected $invoiceService;

    /**
     *
     * @var \Magento\Sales\Model\Order\ShipmentFactory
     */
    protected $shipmentFactory;

    /**
     *
     * @var \Magento\Framework\DB\TransactionFactory
     */
    protected $transactionFactory;

    /**
     * @param \Magento\Framework\App\Helper\Context $context
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     * @param \Magento\Sales\Model\ResourceModel\Order\Invoice\CollectionFactory $invoiceCollectionFactory
     * @param \Magento\Sales\Model\Service\InvoiceService $invoiceService
     * @param \Magento\Sales\Model\Order\ShipmentFactory $shipmentFactory
     * @param \Magento\Framework\DB\TransactionFactory $transactionFactory
     */
    public function __construct(
        \Magento\Framework\App\Helper\Context $context,
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Magento\Sales\Model\ResourceModel\Order\Invoice\CollectionFactory $invoiceCollectionFactory,
        \Magento\Sales\Model\Service\InvoiceService $invoiceService,
        \Magento\Sales\Model\Order\ShipmentFactory $shipmentFactory,
        \Magento\Framework\DB\TransactionFactory $transactionFactory
    ) {
        parent::__construct($context);
        $this->scopeConfig = $scopeConfig;
        $this->invoiceCollectionFactory = $invoiceCollectionFactory;
        $this->invoiceService = $invoiceService;
        $this->shipmentFactory = $shipmentFactory;
        $this->transactionFactory = $transactionFactory;
    }

    public function createInvoice($order)
    {
        try {
            $invoices = $this->invoiceCollectionFactory->create()
                ->addAttributeToFilter('order_id', array('eq' => $order->getId()));

            $invoices->getSelect()->limit(1);

            if ((int)$invoices->count() !== 0) {
                return null;
            }

            if(!$order->canInvoice()) {
                return null;
            }

            $invoice = $this->invoiceService->prepareInvoice($order);
            $invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE);
            $invoice->register();
            $invoice->getOrder()->setCustomerNoteNotify(false);
            $invoice->getOrder()->setIsInProcess(true);
            $order->addStatusHistoryComment('Automatically INVOICED', false);
            $transactionSave = $this->transactionFactory->create()->addObject($invoice)->addObject($invoice->getOrder());
            $transactionSave->save();
        } catch (\Exception $e) {
            $order->addStatusHistoryComment('Exception message: '.$e->getMessage(), false);
            $order->save();
            return null;
        }

        return $invoice;
    }

    public function createShipment($order, $invoice)
    {
        try {
            $shipment = $this->prepareShipment($invoice);
            if ($shipment) {
                $order->setIsInProcess(true);
                $order->addStatusHistoryComment('Automatically SHIPPED', false);
                $this->transactionFactory->create()->addObject($shipment)->addObject($shipment->getOrder())->save();
            }
        } catch (\Exception $e) {
            $order->addStatusHistoryComment('Exception message: '.$e->getMessage(), false);
            $order->save();
        }
    }

    public function prepareShipment($invoice)
    {
        $shipment = $this->shipmentFactory->create(
            $invoice->getOrder(),
            []
        );

        return $shipment->getTotalQty() ? $shipment->register() : false;
    }

    /**
     * Is the module enabled in configuration.
     *
     * @return bool
     */
    public function isEnabled()
    {
        return $this->scopeConfig->getValue(self::MODULE_ENABLED);
    }
}

That’s it.

If you want an option that you can handle this module on/off. So create a configuration
[SR/AutoInvoiceShipment/etc/adminhtml/system.xml]

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="sr_auto_invoice_shipment" translate="label" type="text" sortOrder="1300" showInDefault="1" showInWebsite="1" showInStore="1">
            <label>Auto Invoice Shipment</label>
            <tab>sales</tab>
            <resource>SR_AutoInvoiceShipment::sr_auto_invoice_shipment</resource>
            <group id="settings" translate="label" sortOrder="1000" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Extension Settings</label>
                <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Enabled</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
            </group>
        </section>
    </system>
</config>

Download Full Module From Here