Understanding Magento Theme: Layout Customizations

Hello friends !!

In this blog. We will discuss the points related to magento theme layout customizations.

So first we try to understand how we can create custom theme in Magento 2. You can refer our blog Custom theme in Magento 2 to create custom theme.

While creating a custom theme, we should always use best practices for theme development to avoid conflicts and issues with our theme after updating or upgrading our Magento instance.

You can refer to our best approach to creating a Magento 2 blog theme to avoid conflicts.

Now back to our theme, for Magento theme layout customizations. We will cover the topics below

1. Override Core Magento Layout

In the example below, we override the Magento_Catalog module’s layout/catalog_category_view.xml layout in our custom theme to remove clutter from the category view page.

To achieve this we create the same layout file (catalog_category_view.xml) inside the theme’s sub directory:-

app/design/frontend/Webkul/customtheme/Magento_Catalog/layout/

<?xml version="1.0"?>
<!--
/**
 * Webkul Software.
 *
 * @category   Webkul
 * @package    Webkul_CustomTheme
 * @author     Webkul Software Private Limited
 * @copyright  Webkul Software Private Limited (https://webkul.com)
 * @license    https://store.webkul.com/license.html
 */
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="breadcrumbs" remove="true"/>
    </body>
</page>

Here is the output of the mentioned code. Breadcumbs has been removed from the category display page.

breadcrumbs-remove

2. Bypass the basic Magento style

In the example below, we override the web/css/_module.less style file of the Magento_Catalog module in our custom theme to change the color of the Add to Cart button.

To achieve this we create a file of the same style (_module.less) inside the theme directory below:-

application/to shape/frontend/Webkul/customtheme/Magento_Catalog/web/css/

@import 'source/_extend.less';
.actions-primary
    background-color: red;
    border:1px solid red;
    color:#fff;

Here is the output of the mentioned code.

Picture 1:- No css overriding.

Theme-layouts-customizations-before-css-override

Picture 2:- With css override. The color of the Add to cart button has been changed.

Theme-layouts-customizations-after-css-override

3. Bypass the basic Magento template

In the example below, we override the Magento_Catalog module templates/product/list.phtml module file in our custom theme to replace the add to cart text with buy now.

To achieve this we create the same template file (list.phtml) inside the directory below:-

application/to shape/frontend/Webkul/customtheme/Magento_Catalog/templates/product/

<?php
/**
 * Webkul Software.
 *
 * @category   Webkul
 * @package    Webkul_CustomTheme
 * @author     Webkul Software Private Limited
 * @copyright  Webkul Software Private Limited (https://webkul.com)
 * @license    https://store.webkul.com/license.html
 */
use Magento\Framework\App\Action\Action;

/**
 * Product list template
 *
 * @var $block \Magento\Catalog\Block\Product\ListProduct
 * @var \Magento\Framework\Escaper $escaper
 * @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer
 */
?>
<?php
$_productCollection = $block->getLoadedProductCollection();
/** @var \Magento\Catalog\Helper\Output $_helper */
$_helper = $block->getData('outputHelper');
?>
<?php if (!$_productCollection->count()): ?>
    <div class="message info empty">
        <div><?= $escaper->escapeHtml(__('We can\'t find products matching the selection.')) ?></div>
    </div>
<?php else: ?>
    <?= $block->getToolbarHtml() ?>
    <?= $block->getAdditionalHtml() ?>
    <?php
    if ($block->getMode() === 'grid') 
        $viewMode = 'grid';
        $imageDisplayArea = 'category_page_grid';
        $showDescription = false;
        $templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::SHORT_VIEW;
     else 
        $viewMode = 'list';
        $imageDisplayArea = 'category_page_list';
        $showDescription = true;
        $templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::FULL_VIEW;
    
    /**
     * Position for actions regarding image size changing in vde if needed
     */
    $pos = $block->getPositioned();
    ?>
    <div class="products wrapper <?= /* @noEscape */ $viewMode ?> products-<?= /* @noEscape */ $viewMode ?>">
        <ol class="products list items product-items">
            <?php /** @var $_product \Magento\Catalog\Model\Product */ ?>
            <?php foreach ($_productCollection as $_product): ?>
            <li class="item product product-item">
                <div class="product-item-info"
                     id="product-item-info_<?= /* @noEscape */ $_product->getId() ?>"
                     data-container="product-<?= /* @noEscape */ $viewMode ?>">
                    <?php
                    $productImage = $block->getImage($_product, $imageDisplayArea);
                    if ($pos != null) 
                        $position = 'left:' . $productImage->getWidth() . 'px;'
                            . 'top:' . $productImage->getHeight() . 'px;';
                    
                    ?>
                    <?php // Product Image ?>
                    <a href="<?= $escaper->escapeUrl($_product->getProductUrl()) ?>"
                       class="product photo product-item-photo"
                       tabindex="-1">
                        <?= $productImage->toHtml() ?>
                    </a>
                    <div class="product details product-item-details">
                        <?php $_productNameStripped = $block->stripTags($_product->getName(), null, true); ?>
                        <strong class="product name product-item-name">
                            <a class="product-item-link"
                               href="<?= $escaper->escapeUrl($_product->getProductUrl()) ?>">
                                <?=/* @noEscape */ $_helper->productAttribute($_product, $_product->getName(), 'name')?>
                            </a>
                        </strong>
                        <?= $block->getReviewsSummaryHtml($_product, $templateType) ?>
                        <?= /* @noEscape */ $block->getProductPrice($_product) ?>

                        <?= $block->getProductDetailsHtml($_product) ?>

                        <div class="product-item-inner">
                            <div class="product actions product-item-actions">
                                <div class="actions-primary">
                                    <?php if ($_product->isSaleable()):?>
                                        <?php $postParams = $block->getAddToCartPostParams($_product); ?>
                                        <form data-role="tocart-form"
                                              data-product-sku="<?= $escaper->escapeHtml($_product->getSku()) ?>"
                                              action="<?= $escaper->escapeUrl($postParams['action']) ?>"
                                              data-mage-init='"catalogAddToCart": '
                                              method="post">
                                            <?php $options = $block->getData('viewModel')->getOptionsData($_product); ?>
                                            <?php foreach ($options as $optionItem): ?>
                                                <input type="hidden"
                                                       name="<?= $escaper->escapeHtml($optionItem['name']) ?>"
                                                       value="<?= $escaper->escapeHtml($optionItem['value']) ?>">
                                            <?php endforeach; ?>
                                            <input type="hidden"
                                                   name="product"
                                                   value="<?= /* @noEscape */ $postParams['data']['product'] ?>">
                                            <input type="hidden"
                                                   name="<?= /* @noEscape */ Action::PARAM_NAME_URL_ENCODED ?>"
                                                   value="<?=
                                                   /* @noEscape */ $postParams['data'][Action::PARAM_NAME_URL_ENCODED]
                                                    ?>">
                                            <?= $block->getBlockHtml('formkey') ?>
                                            <button type="submit"
                                                    title="<?= $escaper->escapeHtmlAttr(__('Purchase Now')) ?>"
                                                    class="action tocart primary"
                                                    disabled>
                                                <span><?= $escaper->escapeHtml(__('Purchase Now')) ?></span>
                                            </button>
                                        </form>
                                    <?php else:?>
                                        <?php if ($_product->isAvailable()):?>
                                            <div class="stock available">
                                                <span><?= $escaper->escapeHtml(__('In stock')) ?></span></div>
                                        <?php else:?>
                                            <div class="stock unavailable">
                                                <span><?= $escaper->escapeHtml(__('Out of stock')) ?></span></div>
                                        <?php endif; ?>
                                    <?php endif; ?>
                                </div>
                                <?= ($pos && strpos($pos, $viewMode . '-primary')) ?
                                    /* @noEscape */ $secureRenderer->renderStyleAsTag(
                                        $position,
                                        'product-item-info_' . $_product->getId() . ' div.actions-primary'
                                    ) : '' ?>
                                <div data-role="add-to-links" class="actions-secondary">
                                    <?php if ($addToBlock = $block->getChildBlock('addto')): ?>
                                        <?= $addToBlock->setProduct($_product)->getChildHtml() ?>
                                    <?php endif; ?>
                                </div>
                                <?= ($pos && strpos($pos, $viewMode . '-secondary')) ?
                                    /* @noEscape */ $secureRenderer->renderStyleAsTag(
                                        $position,
                                        'product-item-info_' . $_product->getId() . ' div.actions-secondary'
                                    ) : '' ?>
                            </div>
                            <?php if ($showDescription): ?>
                                <div class="product description product-item-description">
                                    <?= /* @noEscape */ $_helper->productAttribute(
                                        $_product,
                                        $_product->getShortDescription(),
                                        'short_description'
                                    ) ?>
                                    <a href="<?= $escaper->escapeUrl($_product->getProductUrl()) ?>"
                                       title="<?= /* @noEscape */ $_productNameStripped ?>"
                                       class="action more"><?= $escaper->escapeHtml(__('Learn More')) ?></a>
                                </div>
                            <?php endif; ?>
                        </div>
                    </div>
                </div>
                <?= ($pos && strpos($pos, $viewMode . '-actions')) ?
                /* @noEscape */ $secureRenderer->renderStyleAsTag(
                    $position,
                    'product-item-info_' . $_product->getId() . ' div.product-item-actions'
                ) : '' ?>
            </li>
            <?php endforeach; ?>
        </ol>
    </div>
    <?= $block->getChildBlock('toolbar')->setIsBottom(true)->toHtml() ?>
    <?php // phpcs:ignore Magento2.Legacy.PhtmlTemplate ?>
<?php endif; ?>

Here is the output of the mentioned code. The Add to Cart text has been replaced with Buy Now.

Theme-layouts-customizations-template-override-1

4. Override Core Magento js

In the example below, we override the web/js/view/payment/default.js file of the Magento_Checkout module in our custom theme to add a confirmation to set the checkout button on click.

To achieve this we create the same js file (default.js) inside the below directory:-

application/to shape/frontend/Webkul/customtheme/Magento_Checkout/web/js/view/payment/

/**
 * Webkul Software.
 *
 * @category   Webkul
 * @package    Webkul_CustomTheme
 * @author     Webkul Software Private Limited
 * @copyright  Webkul Software Private Limited (https://webkul.com)
 * @license    https://store.webkul.com/license.html
 */

 define([
    'ko',
    'jquery',
    'uiComponent',
    'Magento_Checkout/js/action/place-order',
    'Magento_Checkout/js/action/select-payment-method',
    'Magento_Checkout/js/model/quote',
    'Magento_Customer/js/model/customer',
    'Magento_Checkout/js/model/payment-service',
    'Magento_Checkout/js/checkout-data',
    'Magento_Checkout/js/model/checkout-data-resolver',
    'uiRegistry',
    'Magento_Checkout/js/model/payment/additional-validators',
    'Magento_Ui/js/model/messages',
    'uiLayout',
    'Magento_Checkout/js/action/redirect-on-success',
    'Magento_Ui/js/modal/confirm'
], function (
    ko,
    $,
    Component,
    placeOrderAction,
    selectPaymentMethodAction,
    quote,
    customer,
    paymentService,
    checkoutData,
    checkoutDataResolver,
    registry,
    additionalValidators,
    Messages,
    layout,
    redirectOnSuccessAction,
    confirmation
) {
    'use strict';

    return Component.extend({
        redirectAfterPlaceOrder: true,
        isPlaceOrderActionAllowed: ko.observable(quote.billingAddress() != null),

        /**
         * After place order callback
         */
        afterPlaceOrder: function () 
            // Override this function and put after place order logic here
        ,

        /**
         * Initialize view.
         *
         * @return exports
         */
        initialize: function () 
            var billingAddressCode,
                billingAddressData,
                defaultAddressData;

            this._super().initChildren();
            quote.billingAddress.subscribe(function (address) 
                this.isPlaceOrderActionAllowed(address !== null);
            , this);
            checkoutDataResolver.resolveBillingAddress();

            billingAddressCode = 'billingAddress' + this.getCode();
            registry.async('checkoutProvider')(function (checkoutProvider) 
                defaultAddressData = checkoutProvider.get(billingAddressCode);

                if (defaultAddressData === undefined) 
                    // Skip if payment does not have a billing address form
                    return;
                
                billingAddressData = checkoutData.getBillingAddressFromData();

                if (billingAddressData) 
                    checkoutProvider.set(
                        billingAddressCode,
                        $.extend(true, , defaultAddressData, billingAddressData)
                    );
                
                checkoutProvider.on(billingAddressCode, function (providerBillingAddressData) 
                    checkoutData.setBillingAddressFromData(providerBillingAddressData);
                , billingAddressCode);
            );

            return this;
        ,

        /**
         * Initialize child elements
         *
         * @returns Component Chainable.
         */
        initChildren: function () 
            this.messageContainer = new Messages();
            this.createMessagesComponent();

            return this;
        ,

        /**
         * Create child message renderer component
         *
         * @returns Component Chainable.
         */
        createMessagesComponent: function () 

            var messagesComponent = 
                parent: this.name,
                name: this.name + '.messages',
                displayArea: 'messages',
                component: 'Magento_Ui/js/view/messages',
                config: 
                    messageContainer: this.messageContainer
                
            ;

            layout([messagesComponent]);

            return this;
        ,

        /**
         * Place order.
         */
        placeOrder: function (data, event) {
            var self = this;
            confirmation({
                title: 'Order Confirmation',
                content: 'Do you want to place this order?',
                actions: {
                    confirm: function () 
                        if (event) 
                            event.preventDefault();
                        
            
                        if (self.validate() &&
                            additionalValidators.validate() &&
                            self.isPlaceOrderActionAllowed() === true
                        ) 
                            self.isPlaceOrderActionAllowed(false);
            
                            self.getPlaceOrderDeferredObject()
                                .done(
                                    function () 
                                        self.afterPlaceOrder();
            
                                        if (self.redirectAfterPlaceOrder) 
                                            redirectOnSuccessAction.execute();
                                        
                                    
                                ).always(
                                    function () 
                                        self.isPlaceOrderActionAllowed(true);
                                    
                                );
            
                            return true;
                        
                    ,
                    cancel: function () 
                        return false;
                    
                }
            });
            return false;
        },

        /**
         * @return *
         */
        getPlaceOrderDeferredObject: function () 
            return $.when(
                placeOrderAction(this.getData(), this.messageContainer)
            );
        ,

        /**
         * @return Boolean
         */
        selectPaymentMethod: function () 
            selectPaymentMethodAction(this.getData());
            checkoutData.setSelectedPaymentMethod(this.item.method);

            return true;
        ,

        isChecked: ko.computed(function () 
            return quote.paymentMethod() ? quote.paymentMethod().method : null;
        ),

        isRadioButtonVisible: ko.computed(function () 
            return paymentService.getAvailablePaymentMethods().length !== 1;
        ),

        /**
         * Get payment method data
         */
        getData: function () 
            return 
                'method': this.item.method,
                'po_number': null,
                'additional_data': null
            ;
        ,

        /**
         * Get payment method type.
         */
        getTitle: function () 
            return this.item.title;
        ,

        /**
         * Get payment method code.
         */
        getCode: function () 
            return this.item.method;
        ,

        /**
         * @return Boolean
         */
        validate: function () 
            return true;
        ,

        /**
         * @return String
         */
        getBillingAddressFormName: function () 
            return 'billing-address-form-' + this.item.method;
        ,

        /**
         * Dispose billing address subscriptions
         */
        disposeSubscriptions: function () 
            // dispose all active subscriptions
            var billingAddressCode = 'billingAddress' + this.getCode();

            registry.async('checkoutProvider')(function (checkoutProvider) 
                checkoutProvider.off(billingAddressCode);
            );
        
    });
});

Here is the output of the mentioned code. Confirmation is added by clicking the order button.

Theme-layouts-customizations-override-js

That’s all about Magento theme: layout customizations. I hope this is helpful.

If you have any questions, please comment below and we will try to answer them.

Thank you! 🙂

Source link

Leave a Reply

Your email address will not be published. Required fields are marked *