Table of content

Magento 2 Certified Professional Front End Developer Guide


Section 4: Create and Customize Template Files

4.1. Assign a customized template file using layout XML

How can a customized template file be assigned to a block using layout XML?

Let’s say you want to assign a customized wishlist template.

You need to copy the standard file of the wishlist module.

<Magento_folder>\vendor\magento\module-wishlist\view\frontend\templates\view.phtml

Paste it to following folder:

<Vendor>_<Module>/view/frontend/templates\view.phtml

Then, you need to create a file:

<Vendor>_<Module>/view/frontend/layout/wishlist_index_index.xml

<?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="customer.wishlist">
   <arguments>
     <argument name="template" xsi:type="string">Vendor_Module::view.phtml</argument>
   </arguments>
 </referenceBlock>
</body>
</page>

Another way of changing the template:

<?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="customer.wishlist" template="Namespace_Module::new_template.phtml" />
</body>
</page>

And there is also an old method:

<?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="customer.wishlist">
   <action method="setTemplate">
     <argument name="template" xsi:type="string">Vendor_Module::view.phtml</argument>
   </action>
 </referenceBlock>
</body>
</page>

There is one more step to make your overriding come into force. You should add a sequence in your module.xml file for the module that contains the changing template file. In this example, your etc/module.xml file will look like this:

<?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="Vendor_Module" setup_version="100.0.0">
 <sequence>
   <module name="Magento_Wishlist"/>
 </sequence>
</module>
</config>

It ensures that the Magento_Wishlist module will be downloaded and added to the common template file before your module. If you need to assign a customized template file which displays necessary content, you need to create a new .phtml file in the folder with the theme in the magento_theme module:

<Magento_folder>\app\design\frontend\<vendor_name>\<theme_name>\Magento_Theme\templates\header\clickme.phtml

With the default content (link with the icon and text):

<div class="clickme-wrap" >
 <a href="">
   <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 129 129" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 129 129">
     <g>
       <path d="m121.6,40.1c-3.3-16.6-15.1-27.3-30.3-27.3-8.5,0-17.7,3.5-26.7,10.1-9.1-6.8-18.3-10.3-26.9-10.3-15.2,0-27.1,10.8-30.3,27.6-4.8,24.9 10.6,58 55.7,76 0.5,0.2 1,0.3 1.5,0.3 0.5,0 1-0.1 1.5-0.3 45-18.4 60.3-51.4 55.5-76.1zm-57,67.9c-39.6-16.4-53.3-45-49.2-66.3 2.4-12.7 11.2-21 22.3-21 7.5,0 15.9,3.6 24.3,10.5 1.5,1.2 3.6,1.2 5.1,0 8.4-6.7 16.7-10.2 24.2-10.2 11.1,0 19.8,8.1 22.3,20.7 4.1,21.1-9.5,49.6-49,66.3z"/>
     </g>
   </svg>
   <span>click me</span>
 </a>
</div>

Then you need to assign the created block in the XML file where you want (for example, you want to add the link with the icon to the header close to the other links):

Magento-2-Certified-Professional-Front-End-Developer-Guide-Screenshot-46

At the same time, we add the XML file.

<Magento_folder>\app\design\frontend\BelVG\<theme_name>\Magento_Theme\layout\default.xml

We add the following code to the file:

<referenceContainer name="header-wrapper">
   <block class="Magento\Framework\View\Element\Template" name="clickme-header" before="-" template="Magento_Theme::header/clickme.phtml"/>
</referenceContainer>

Clear the cache:

php bin/magento cache:flush

On the front, we get the following:

Magento-2-Certified-Professional-Front-End-Developer-Guide-Screenshot-47

If you need to customize the standard template of the module and display it in the necessary place on the website, you should do the following:

Copy the necessary template file to the folder with the theme by relative path.

For example, the addtocart.phtml template is on the path:

<Magento_folder>/vendor/magento/module-catalog/view/frontend/templates/product/view/addtocart.phtml

Copy on the path:

<Magento_folder>\app\design\frontend\BelVG\<theme_name>\Magento_Catalog\templates\product\view\addtocart.phtml

Then, in the XML file, we set the template display in the necessary place :

<referenceContainer name="product.info.main">
       <block class="Magento\Catalog\Block\Product\View" name="product.info.addtocart.second" as="addtocart-second" template="product/view/addtocart.phtml"/>
</referenceContainer>

You need to change name and as to the custom ones to make sure they are not the same as name and as of the original block.

Then, we clear the cache:

php bin/magento cache:flush

As a result, you see two identical blocks one of which is located in the place that we need. (For example, standard block output can be removed with the help of <referenceBlock name=" " remove="true"/>):

Magento-2-Certified-Professional-Front-End-Developer-Guide-Screenshot-48

This way, the block can be displayed anywhere.

How does overriding a template affect upgradability?

When you override the template file with the help of layout XML, you actually violate the fallback path for the template. It does not allow the themes to modify the templates. While upgrading a module, it can happen that new functionality which appears in templates after the upgrade, is not available on the website as the template has been overridden.

What precautions can be taken to ease future upgrades when customizing templates?

If you are a backend developer: use plugin for the block (in this case, it will not allow to use view models), which will turn on the set template only when needed. The best way is to avoid overriding when possible.

4.2. Override a native template file with a customized template file, using the design fallback

How can the design fallback be used to render customized templates?

If you need to use a customized template instead of the standard one, all you need is to copy the necessary phtml file to your theme with the right path. This way you override the standard theme. For example, in order to use customized template of the “Add to the Cart” button for the product:

Copy the file:

<Magento folder>\vendor\magento\module-catalog\view\frontend\templates\product\view\addtocart.phtml

Magento-2-Certified-Professional-Front-End-Developer-Guide-Screenshot-49

Then, move it to the path:

<Magento folder>\app\design\frontend\BelVG\<theme_name>\Magento_Catalog\templates\product\view\addtocart.phtml

Magento-2-Certified-Professional-Front-End-Developer-Guide-Screenshot-50

On the front:

Magento-2-Certified-Professional-Front-End-Developer-Guide-Screenshot-51

How does that influence upgradability?

When upgrading, the template functionality can be interrupted. The reason is that the template which has been overridden, could be upgraded together with the other system aspects which depend on the template. If the overridden template does not upgrade, everything can break down.

How can you determine which template a block renders?

In Magento 2, there are a few ways to find out which template renders.You can search in the Magento files. Usually, the template files are in the XML files of the layout. In the XML file (for example, if you look for the .box-tocart block of the addtocart.phtml template, it is likely to be set in the <Magento_folder>\app\design\frontend\BelVG\<theme_name>\Magento_Catalog\layout\catalog_product_view.xml file). You need to find the block which renders by the template:

<container name="product.info.form.content" as="product_info_form_content">
   <block class="Magento\Catalog\Block\Product\View" name="product.info.addtocart" as="addtocart" template="product/view/addtocart.phtml"/>
</container>

In the block, we see template="product/view/addtocart.phtml"

path to the template. In some places, the template file is defined in the PHP class. Then, you need to search for the word $_template in the block’s class.

Defining the template

<Magento_folder>\app\code\Belvg\PaymentTrade\view\frontend\templates\form\paymenttrade.phtml

<?php

namespace Belvg\PaymentTrade\Block\Form;

/**
* Class PaymentTrade
* @package Belvg\PaymentTrade\Block\Form
*/
class PaymentTrade extends \Magento\Payment\Block\Form
{
   /**
    * Purchase order template
    *
    * @var string
    */
   protected $_template = 'Belvg_PaymentTrade::form/paymenttrade.phtml';
}

The easiest way to look at the path to the template is to enable the template path hints. In order to do this, proceed to:

STORES -> Settings -> Configuration

Magento-2-Certified-Professional-Front-End-Developer-Guide-Screenshot-52

Here, on the tab ADVANCED -> Developer in the “Debug” section, you give the priority “Enabled Template Path Hints for Storefront” value “Yes”.

Magento-2-Certified-Professional-Front-End-Developer-Guide-Screenshot-53

As a result, on the website, you can see the paths to the templates in red:

Magento-2-Certified-Professional-Front-End-Developer-Guide-Screenshot-54

Another way to enable the path to the template is via the console command:

bin/magento dev:template-hints:enable

4.3. Describe conventions used in template files

What conventions are used in PHP templates?

  • $this is no longer applicable to the block rendering. No matter which object you use in the template ($ block or $ this), it will always call the method from $block. If you take a look at \Magento\Framework\View\TemplateEngine\Php::__call() you will see:
    public function __call($method, $args) {
     return call_user_func_array([$this->_currentBlock, $method], $args); 
    }
    
  • Here you can see that  \Magento\Framework\View\TemplateEngine\Php is just a proxy between the template and $block, so the use of $block is preferable because of the direct call.
  • echo command in PHP should be written using a short tag in the Magento templates. <?= $block->getModuleName() ?> instead <?php echo $block->getModuleName() ?>
  • To make sure that your theme is displayed correctly with any language applicable to store view, check that unique lines of your theme are added in i18n. To get your new line added to the vocabulary and translated, use the __(‘<your_string>’) method when displaying the line in the .phtml template.
    <?php echo __('Hello world') ?>

    If the line includes a variable, then you should use the following syntax:

    <?php echo __('Hello %1', $yourVariable) ?>
  • If you need to use loop, use the foreach > endforeach constructions
    <?php foreach ($answerData as $answerPosted):?>
    	<h4>Hello</h4>
    	<p>Answer given by: <?php echo $answerPosted['username']; ?></p>
    	<p>Answer: <?php echo $answerPosted['answer']; ?></p>
    <?php endforeach; ?>
    

Why aren’t the common PHP loop and block constructs used?

Magento templates do not use constructions with { } because curly brackets are more difficult to identify in HTML.

<?php if($block->value): ?>
Hello
<?php elseif($block->asd): ?>
Your name is: <?= $block->name ?>
<?php else: ?>
You don't have a name.
<?php endif; ?>

Which common methods are available on the $block variable?

All the common methods from the block context are available in template.

Block context is a block class which is assigned to the template in layout XML file. It is equal to the block type in Magento 1. By default it is \Magento\Framework\View\Element\Template which is equal to Mage_Core_Block_Template in Magento 1.

This block context is assigned to the template as the $block variable during rendering.

Name the common methods available on the $block variable:

getRootDirectory() – Instantiates filesystem directory

getMediaDirectory() – Get media directory

getUrl($route = '', $params = []) – Generate url by route and parameters

getBaseUrl() – Get base url of the application

getChildBlock($alias) – Retrieve child block by name

getChildHtml($alias = '', $useCache = true) – Retrieve child block HTML

getChildChildHtml($alias, $childChildAlias = '', $useCache = true) – Render output of child child element

getChildData($alias, $key = '') – Get a value from child block by specified key

getBlockHtml($name) – Retrieve block html

formatDate($date = null, $format = \IntlDateFormatter::SHORT, $showTime = false, $timezone = null) – Retrieve formatting date

formatTime($time = null, $format = \IntlDateFormatter::SHORT, $showDate = false) – Retrieve formatting time

getModuleName() – Retrieve module name of block

escapeHtml($data, $allowedTags = null) – Escape HTML entities

escapeJs($string) – Escape string for the JavaScript context

escapeHtmlAttr($string, $escapingSingleQuote = true) – Escape a string for the HTML attribute context

escapeCss($string) – Escape string for the CSS context

stripTags($data, $allowableTags = null, $allowHtmlEntries = false) – Wrapper for standard strip_tags() function with extra functionality for html entities

escapeUrl($string) – Escape URL

getVar($name, $module = null) – Get variable value from view configuration

How can a child block be rendered?

Use the  \Magento\Framework\View\Element\AbstractBlock::getChildHtml() method:

public function getChildHtml($alias = '', $useCache = true)
{
   $layout = $block->getLayout();
   if (!$layout) {
       return '';
   }
   $name = $this->getNameInLayout();
   $out = '';
   if ($alias) {
       $childName = $layout->getChildName($name, $alias);
       if ($childName) {
           $out = $layout->renderElement($childName, $useCache);
       }
   } else {
       foreach ($layout->getChildNames($name) as $child) {
           $out .= $layout->renderElement($child, $useCache);
       }
   }

 
   return $out;
}

Here is the example of use:

public function getSaveButtonHtml()
{
   return $block->getChildHtml('save_button');
}

Or in the template:

$block->getChildHtml('save_button');

How can all child blocks be rendered?

The same way as you would call a specific child block but without using the name variable:

$block->getChildHtml();

How can a group of child blocks be rendered?

The block group methods are located in vendor/magento/framework/View/LayoutInterface.php

The main group example in Magento core is located on the product detail page’s tabs (https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml)

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
// @codingStandardsIgnoreFile
?>
<?php if ($detailedInfoGroup = $block->getGroupChildNames('detailed_info', 'getChildHtml')):?>
<div class="product info detailed">
    <?php $layout = $block->getLayout(); ?>
<div class="product data items" data-mage-init='{"tabs":{"openedState":"active"}}'>
        <?php foreach ($detailedInfoGroup as $name):?>
        <?php
                    $html = $layout->renderElement($name);
                    if (!trim($html)) {
                        continue;
                    }
                    $alias = $layout->getElementAlias($name);
                    $label = $block->getChildData($alias, 'title');
                ?>
        <div class="data item title" data-role="collapsible" id="tab-label-<?= /* @escapeNotVerified */ $alias ?>">
            <a class="data switch" tabindex="-1" data-toggle="trigger" href="#<?= /* @escapeNotVerified */ $alias ?>" id="tab-label-<?= /* @escapeNotVerified */ $alias ?>-title">
                <?= /* @escapeNotVerified */ $label ?>
            </a>
        </div>
        <div class="data item content" aria-labelledby="tab-label-<?= /* @escapeNotVerified */ $alias ?>-title" id="<?= /* @escapeNotVerified */ $alias ?>" data-role="content">
            <?= /* @escapeNotVerified */ $html ?>
        </div>
        <?php endforeach;?>
    </div>
</div>
<?php endif; ?>

Their rendering includes getting all their names with the use of getGroupChildNames and then rendering of every block by name in the loop.

4.4. Render values of arguments set via layout XML

How can values set on a block in layout XML be accessed and rendered in a template?

Let’s take a look at the example. For instance, you want to add a class to the product form block using XML.

In order to do it, in the module file (for example,

/app/design/frontend/{vendor_name}/{theme_name}/Magento_Catalog/layout/catalog_product_view.xml), you need do add the following:

<referenceBlock name="product.info" template="product/view/form.phtml" after="alert.urls">
 <arguments>
   <argument name="custom_class" xsi:type="string">custom-class</argument>
 </arguments>
</referenceBlock>

In product.info block, we set an argument with the name “custom_class” (or any other), assign a type (type=”string”), then the value – “custom-class”

In order to use this value in the template file you should use one of the following methods:

app/design/frontend/BelVG/{theme_name}/Magento_Catalog/templates/product/view/form.phtml

$cssClass = $block->hasCustomClass() ? ' ' . $block->getCustomClass() : '';

or

$cssClass = $block- >getData('ustom_class');

Let’s add the variables with different methods of obtaining the value and render them in the class=”” attribute.

<?php $cssClass = $block->hasCustomClass() ? ' ' . $block->getCustomClass() : ''; ?>
<?php $cssClass2 =  $block->getData('custom_class'); ?>
<div class="product-add-form <?php echo $cssClass; ?> <?php echo $cssClass2; ?>">

As a result, it looks like that:

Magento-2-Certified-Professional-Front-End-Developer-Guide-Screenshot-55

4.5. Demonstrate ability to escape content rendered and template files

How can dynamic values be rendered securely in HTML, HTML attributes, JavaScript, and in URLs?

To render the values securely, you need to use the block’s built-in methods:

For JavaScript:     $block->escapeJs('value');

For HTML:            $block->escapeHtml('value', $allowedTags);

HTML attributes:    $block->escapeHtmlAttr('value', $escapeSingleQuote);

URLs:                     $block->escapeUrl($url);

All these methods are described in AbstractBlock (Magento\Framework\Escaper)

escapeJs()

/** 
* Escape string for the JavaScript context * 
* @param string $string 
* @return string
 */
 public function escapeJs($string)

The unicode symbols are encrypted, for example,⌛ turns into U+231B. This method is used for JS string literal. For inline JavaScript,

(for example, onсlick) you should use escapeHtmlAttr()

escapeHtml()

/** 
* Escape string for HTML context. allowedTags will not be escaped, except the following: script, img, embed, 
* iframe, video, source, object, audio 
* 
* @param string|array $data 
* @param array|null $allowedTags 
* @return string|array 
*/
 public function escapeHtml($data, $allowedTags = null)

It should be your method of escaping data by default for any output. The main point is that the output of all the methods that do not include “Html” should be escaped.

escapeHtmlAttr()

/** 
* Escape a string for the HTML attribute context 
* 
* @param string $string 
* @param boolean $escapeSingleQuote 
* @return string 
*/ 
public function escapeHtmlAttr($string, $escapeSingleQuote = true)

Use it for escaping the output in the HTML attribute, for example:

title="<?php echo $block->escapeHtmlAttr($title) ?>"

By default, it also escapes single quotes:

onclick="console.log('<?php echo $block->escapeHtmlAttr($message) ?>')"

Set the value false on the second variable if you do not need it.

escapeUrl()

/** 
* Escape URL 
* 
* @param string $string 
* @return string 
*/ 
public function escapeUrl($string)

This method is used for URL output. It applies HTML literal by default and additionally deletes javascript:, vbscript: and data:. If you want to deny URLs similar to these among the links provided by a user, you can use the method.

In the releases before Magento 2.1, this function was not enabled and you had to use escapeXssInUrl().

 

Vlad Yunusov banner
Vlad-Yunusov

Tell us about your project

Get in touch with our team. Send us an email at [email protected] or call us 1 650 353 2301

Send request