Table of content

Magento 2 Certified Professional JavaScript Developer Guide


Section 4: UI Components

4.1 Demonstrate understanding of Knockout customizations

Describe Magento modifications to the Knockout template engine

Describe the remote template engine, template syntax, custom tags and attributes, and the rendering mechanism

Remote Template Engine

Knockout Remote Template Engine is a custom Knockout Template Engine modification in Magento. It is used for loading remote templates via knockout template binding. Template loading is performed using RequireJS.

Example:

<div template="templateUrl"></div>

Template syntax and custom tags and attributes

In HTML templates, Magento allows a developer to use a binding syntax that is easier to read and write than the standard Knockout binding syntax. Magento allows to use bindings as stand-alone tags and attributes.

Example of Knockout binding:

<!-- ko if: isVisible-->
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse vel malesuada augue.
<!-- /ko -->

and

<div data-bind="if: isVisible">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse vel malesuada augue.
</div>

Example of Magento binding:

<if args="isVisible">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse vel malesuada augue.
</if>

and

<div if="isVisible">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse vel malesuada augue.
</div>

Rendering mechanism

The following handlers are used to render custom tags and attributes:

  1. renderer.handlers.node – Basic node handler. Replaces custom nodes with a corresponding knockout’s comment tag.
  2. renderer.handlers.attribute – Base attribute handler. Replaces custom attributes with a corresponding knockouts’ data binding.
  3. renderer.handlers.wrapAttribute – Wraps the provided node with a knockout’ comment tag.

Demonstrate the ability to use the custom Knockout bindings provided by Magento

Where can a full list of custom bindings be found?

Follow this link to access a full list of custom bindings

https://devdocs.magento.com/guides/v2.3/ui_comp_guide/concepts/knockout-bindings.html

What are they used for?

This is the list of custom Magento bindings with the description of their functions:

  • afterRender – notifies the subscriber as an associated element is inserted into the DOM
  • autoselect – automatically highlights the text in an input element, when it gets focus
  • bindHtml – renders the string provided, as a collection of HTML elements, inside of the associated node
  • collapsible – provides methods and properties required for implementing collapsible panels. It can automatically collapse panel when clicking outside of the associated node, toggle optional CSS class when node changes its visibility. It has additional helper bindings: toggleCollapsible, openCollapsible and closeCollapsible
  • datepicker – an adapter for the mage/calendar.js widget
  • fadeVisible – performs the gradual change of the element’s visibility (with an animation effect)
  • i18n – used to translate a string according to the currently enabled locale. Additionally, it creates the necessary elements for the TranslateInline jQuery widget, if it’s enabled on the page
  • keyboard – allows setting up listeners for the keypress event of a specific key
  • mageInit – an adapter for the [data-mage-init] attribute that is used to initialize jQuery widgets on the associated element
  • optgroup – a decorator for the standard Knockout’s options binding which adds the support of nested options, and renders them as the <optgroup> element
  • outerClick – allows to subscribe for the “click” event that happens outside of the boundaries of the associated element
  • range – an adapter for the jQuery UI Slider widget. It also implements necessary handlers to work with mobile devices
  • resizable – an adapter for the jQuery UI Resizable widget
  • scope – allows evaluating descendant nodes in the scope of an object found in the UiRegistry by provided string
  • staticChecked – implements the behavior similar to the standard checked binding. The difference is that staticChecked doesn’t change the array of the already selected elements if the value of the associated DOM element changes
  • template – a customization of the existing Knockout template binding. It is used to render a template inside of the associated element. The original Knockout’s implementation was overridden to support asynchronous loading of templates by the provided path, instead of searching for them on the page
  • tooltip – displaying a tooltip
What alternatives are there?

Some bindings can be placed inside the data-bind attribute, in a separate attribute or tag. Let us consider a part of a binding i18n source code as an example:

ko.bindingHandlers.i18n = {
    init: function (element, valueAccessor) {
        execute(element, valueAccessor);
    },
    update: function (element, valueAccessor) {
        execute(element, valueAccessor, true);
    }
};

ko.virtualElements.allowedBindings.i18n = true;

renderer
    .addNode('translate', {
        binding: 'i18n'
    })
    .addAttribute('translate', {
        binding: 'i18n'
    });

Here ko.bindingHandlers.i18n and ko.virtualElements.allowedBindings.i18n create binding i18n, that can be used in data-bind:

<div data-bind=”i18n: ‘Translate as a standard knockout binding'”></div>

renderer.addAttribute(‘translate’) enables to use i18n binding in a separate translate attribute:

<div translate=”‘Translate using the attribute'”></div>

renderer.addNode(‘translate’) allows to usei18n binding in the separate translate tag:

<translate args=”‘Translate using the tag'”></translate>

Describe the Knockout scope binding

uiRegistry stores connections between the component name and its information.

Components get into uiRegistry the following way:

<script type="text/x-magento-init">
    {
        "*": {
            "Magento_Ui/js/core/app": {
                "components": {
                        "COMPONENT_NAME": {
                            "component": "Magento_Ui/js/lib/step-wizard",
                            "initData": ...,
                            "stepsNames": ...,
                            "modalClass": ...
                        }
                    }
                }
            }
    }
</script>

Afterward, this component can be searched for in uiRegistry by COMPONENT_NAME.

Scope changes the current context for the component context. Here is the example:

<div data-bind="scope: 'COMPONENT_NAME'">
</div>
What is the purpose of the scope binding?

Scope binding provides a connection between the part of a template and a JavaScript component.

What Knockout problem does it solve?

Scope binding provides the possibility to initiate components as required instead of loading them beforehand, even if they are not utilized.

How exactly does this binding work?

  1. Scope binding handler loads an object from uiRegistry asynchronously
  2. The component is loaded with the help of RequireJS
  3. A new object of this component is created
  4. The component object is applied as ViewModel of the context

Demonstrate the ability to use the scope binding in customizations

How is the scope binding used?

Let us consider the example of using scope:

<scope args="requestChild('listing_paging')" render="totalTmpl"/>

Where requestChild – сreates ‘async’ wrapper for the specified child using uiRegistry ‘async’ method and caches it; totalTmpl – the name of the knockout field where the name of the template is stored.

How do nested scopes work?

Each scope has a context, and nested scopes are connected with the parent ones.

How can data of a parent scope be accessed from a child?
  1. $parent – the view model object in the parent context
  2. $parents – an array representing all of the parent view models
  3. $root – the topmost parent context
  4. $parentContext – refers to the binding context object at the parent level
How can the scope binding be applied to HTML in Ajax responses?

You can execute mage/apply/main.apply() or jQuery(‘body’).trigger(‘contentUpdated’).

For example:

<div id="testBlock">

</div>

<script type="text/javascript">
    require(['jquery', 'mage/apply/main'], function($, mage){
        $.ajax({
            url: 'page.html',
            success: function(content){
                $('#testBlock').html(content);

                // Use one of this:
                $('body').trigger('contentUpdated');
                // or
                mage.apply();
            }
        });
    });
</script>

4.2 Demonstrate understanding of Magento UI components

Describe how uiComponents are executed in Magento

What is the difference in uiCompnent execution compared to other JavaScript module types? What does it mean to “execute a uiComponent”? Why do we need the app component to execute uiComponents?

Unlike the common JavaScript modules, ui Component consists of XML configuration, JavaScript components and templates.

UI component call is performed by the uiComponent tag in a layout XML file:

<referenceContainer name="content">
    <uiComponent name="ui_component_name"/>
</referenceContainer>

This tag creates an output HTML code the following construction:

<div class="admin__data-grid-outer-wrap" data-bind="scope: 'cms_block_listing.cms_block_listing'">
    <div data-role="spinner" data-component="cms_block_listing.cms_block_listing.cms_block_columns" class="admin__data-grid-loading-mask">
        <div class="spinner">
          <span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>
        </div>
    </div>
    <!-- ko template: getTemplate() --><!-- /ko -->        
    <script type="text/x-magento-init">
        {"*": {"Magento_Ui/js/core/app": {...uiComponent configuration object...}}}
    </script>
</div>

This piece of code calls Magento_Ui/js/core/app RequireJS module and passes it the object that contains the necessary UI component configuration, as well as its child components. The object, in its turn, creates the necessary components and registers them in uiRegistry.

What is the role of the layout component?

uiLayout component is used to create and configure other components. It can also be applied to create dynamic components when the application is already running. Apart from this, it allows to create child UI components.

Describe the structure of a UiComponent

What is uiClass? How does it instantiate uiComponents?

uiClass is a low-level class, from which all the rest UI components are inherited. uiClass allows to inherit UI components (extend method) and initialize the configuration (method initConfig). In addition to this, this class makes it possible to specify the list of parameters and components interaction.

How can existing component instances be accessed?

uiRegistry is used to access existing component instances. This component provides access to all the available component instances.

var registry = require('uiRegistry');
var component = registry.get('%componentName%');
How can a uiComponent be modified? How do you extend an existing uiComponent?

An existing uiComponent can be extended with the extend method; this method also allows to override the existing methods.

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

    return function (Form) {
        return Form.extend({
            initialize: function () {
                this._super();
                //custom code
            }
        });
    }

This._super call allows to call the parent method a similar way, as ::parent call in PHP.

What is the role of the uiElement and uiCollection modules?

uiCollection module is aimed at base-class components that should contain UI components collection. In other cases, uiElement is more suitable as a base-class.

Demonstrate the ability to create a uiComponent with its own data, or operate with data of existing uiComponents

How does a uiComponent access the data it needs? What are the requirements for a subcomponent to provide data? How can data be loaded by Ajax? How can a component receive the data when it is loaded?

uiComponent accesses the data it needs with DataSource component. It is configured with <dataSource /> tag in the configurational XML UI of the component and has the following parameters:

<argument name="dataProvider" xsi:type="configurableObject">
    <argument name="class" xsi:type="string">Vendor\Module\Ui\DataProvider\ComponentNameDataProvider</argument>
    <argument name="name" xsi:type="string">ComponentName_data_source</argument>
    <argument name="primaryFieldName" xsi:type="string">entity_id</argument>
    <argument name="requestFieldName" xsi:type="string">id</argument>
</argument>

Where class argument contains PHP class that is aimed at realizing \Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface interface and inheriting \Magento\Ui\DataProvider\AbstractDataProvider class.

Describe the process of sharing data between components

How can one uiComponent instance access data of another instance?

For this one needs to use uiRegistry component, which provides access to all the component instances available.

var registry = require('uiRegistry');
var component = registry.get('%componentName%');
How can components communicate while taking into account their asynchronous nature?

For communication, the following UI component parameters can be applied:

  • Exports – copies the local value into an external parameter. If the external parameter is a function, it will be called using local value as an argument.
  • Imports – applied to track changes in an external parameter.
  • Links – synchronizes local and external values, so when you modify one, another is modified as well.
  • Listens – tracks the component parameters changes

4.3 Demonstrate the ability to use UI components

Describe the uiComponents lifecycle

What are the stages of uiComponent execution?

At the server:

  1. Layout loads UI component (for instance, your_ui_component_name).
  2. Search of a UI component’s xml files in every enabled module (for example, MODULE_DIR/view/adminhtml/ui_component/your_ui_component_name.xml).
  3. Merge of all the found UI components into a single configuration object.
  4. Configuration merge with definition.xml. The objects from definition.xml have lesser priority than a single configuration object, created in the previous step.
  5. Conversion of the received configuration into JSON and embedded into the page with the help of Magento\Ui\TemplateEngine\Xhtml\Result class.

As a result, we receive the following piece of code:

<script type="text/x-magento-init">
   {  
      "*":{  
         "Magento_Ui/js/core/app":{  
            "types":{ ... },
            "components":{ ... }
         }
      }
   }
</script>

At the client side:

  1. RequireJS loads Magento_Ui/js/core/app and passes the configuration as a parameter.
  2. Magento_Ui/js/core/app calls Magento_Ui/js/core/renderer/layout and passes it the configuration.
  3. layout.js creates instances of UI components and applies configurations to them.
  4. The HTML template is rendered with knockout.js and the component binding is applied to the template
What is the role of the layout module, and how does it load components, children, and data?

Layout module (Magento_Ui/js/core/renderer/layout) performs the initialization of UI components and applies configurations to them.

  1. Layout receives the configuration from Magento_Ui/js/core/app
  2. All the UI components are processed with the process function
    1. If the parent is not initialized, then waitParent is called that asynchronously gets parents from uiRegistry and then executes the process.
    2. If a component uses the template and it is not loaded, then waitTemplate is executed that asynchronously loads the template and then calls process.
    3. Otherwise
      1. The build function is executed for the component initialization
      2. The component is saved in the registry
      3. The process function is called recursively for all the child elements.

After the Layout module created the instance of this class, it overwrites the properties from the UI component’s defaults property using properties from the JSON. Then resulting properties become the first-level properties of the newly created UI component’s instance, and the original defaults property is deleted.

What are the types of components it supports?

The layout module supports every javascript module that returns functions.

Commonly, the following modules are implemented:

  1. uiClass (is rarely used directly)
  2. uiElement (inherited from uiClass)
  3. uiCollection (inherited from uiElement)
  4. uiComponent (equal to uiCollection)

If there are child elements, it is recommended to use uiCollection. If it is impossible, then use uiElement.

Demonstrate the ability to use uiComponents configuration to modify existing instances and create new instances

A particular instance of a UI component is defined primarily by the following:

  1. definition.xml
  2. UI component’s XML declaration (https://devdocs.magento.com/guides/v2.3/ui_comp_guide/concepts/ui_comp_xmldeclaration_concept.html)
  3. Backend/PHP modifiers (https://devdocs.magento.com/guides/v2.3/ui_comp_guide/concepts/ui_comp_modifier_concept.html)
  4. Configuration inside the JavaScript classes
Describe the definitons.xml file and uiComponent instance XML files

Definitons.xml (vendor/magento/module-ui/view/base/ui_component/etc/definition.xml) by default contains UI components configuration.

UiComponent instance XML files (for example, MODULE_DIR/view/adminhtml/ui_component/your_ui_component_name.xml) contains configurations for a certain UI component.

How can you modify an existing instance of a uiComponent using a configuration file?

Create an XML file of a UI component with the same name in your Magento module. The configuration will be united with the UI component file of the original Magento module.

What is the role of the Magento layout in the uiComponent workflow?

UiComponent is embedded into Magento layout using the uiComponent tag:

Example of layout xml file:

<?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>
        <referenceContainer name="content">
            <uiComponent name="your_ui_component_name"/>
        </referenceContainer>
    </body>
</page>

4.4 Demonstrate understanding of grids and forms

Custom Grid

KnockoutJS library is completely responsible for rendering and controlling UI Grid components in Magento 2, but, in addition to it, you will also need a common controller to display a base page.

Controller: app/code/Vendor/Module/Controller/Adminhtml/Item/Index.php

<?php
namespace Vendor\Module\Controller\Adminhtml\Item;

class Index extends \Magento\Backend\App\Action
{
	protected $resultPageFactory = false;

	public function __construct(
		\Magento\Backend\App\Action\Context $context,
		\Magento\Framework\View\Result\PageFactory $resultPageFactory
	)
	{
		parent::__construct($context);
		$this->resultPageFactory = $resultPageFactory;
	}

	public function execute()
	{
		$resultPage = $this->resultPageFactory->create();
		$resultPage->getConfig()->getTitle()->prepend((__('Items')));

		return $resultPage;
	}
}

Layout, in this case, has a single directive – UI component call.

Layout: app/code/Vendor/Module/view/adminhtml/layout/vendor_module_item_index.xml

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <uiComponent name="vendor_module_item_listing"/>
        </referenceContainer>
    </body>
</page>

XML of the UI component has all the necessary data for UI grid rendering: data source specification, the necessary columns list and their types, as well as additional elements, like buttons and filters.

Component layout file:

app/code/Vendor/Module/view/adminhtml/ui_component/vendor_module_item_listing.xml

<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">vendor_module_item_listing.vendor_module_item_listing_data_source</item>
            <item name="deps" xsi:type="string">vendor_module_item_listing.vendor_module_item_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">spinner_columns</item>
        <item name="buttons" xsi:type="array">
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" xsi:type="string" translate="true">Add New Item</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">*/*/new</item>
            </item>
        </item>
    </argument>
    <dataSource name="data_source_name">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider</argument>
            <argument name="name" xsi:type="string">vendor_module_item_listing_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">item_id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
                    <item name="update_url" xsi:type="url" path="mui/index/render"/>
                    <item name="storageConfig" xsi:type="array">
                        <item name="indexField" xsi:type="string">item_id</item>
                    </item>
                </item>
            </argument>
        </argument>
    </dataSource>
    <columns name="spinner_columns">
        <selectionsColumn name="ids">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="resizeEnabled" xsi:type="boolean">false</item>
                    <item name="resizeDefaultWidth" xsi:type="string">55</item>
                    <item name="indexField" xsi:type="string">item_id</item>
                </item>
            </argument>
        </selectionsColumn>
        <column name="item_id">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">textRange</item>
                    <item name="sorting" xsi:type="string">asc</item>
                    <item name="label" xsi:type="string" translate="true">ID</item>
                </item>
            </argument>
        </column>
        <column name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
	       <!-- VALIDATION EXAMPLE -->
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                            <!-- CUSTOM VALIDATION EXAMPLE -->
                            <item name="custom-validation" xsi:type="boolean">true</item>
                        </item>
                    </item>
                    <item name="label" xsi:type="string" translate="true">Name</item>
                </item>
            </argument>
        </column>
        <column name="created_at" class="Magento\Ui\Component\Listing\Columns\Date">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">dateRange</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                    <item name="dataType" xsi:type="string">date</item>
                    <item name="label" xsi:type="string" translate="true">Created</item>
                </item>
            </argument>
        </column>
        <column name="updated_at" class="Magento\Ui\Component\Listing\Columns\Date">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">dateRange</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                    <item name="dataType" xsi:type="string">date</item>
                    <item name="label" xsi:type="string" translate="true">Modified</item>
                </item>
            </argument>
        </column>
        <!-- IMAGE EXAMPLE -->
        <column name="image" class="Vendor\Module\Ui\Component\Listing\Column\Image">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/thumbnail</item>
                    <item name="sortable" xsi:type="boolean">false</item>
                    <item name="altField" xsi:type="string">title</item>
                    <item name="has_preview" xsi:type="string">1</item>
                    <item name="label" xsi:type="string" translate="true">Image</item>
                </item>
            </argument>
        </column>
    </columns>
</listing>
Image column

For image column, we need to create Vendor\Module\Ui\Component\Listing\Column\Image class.

<?php
namespace Vendor\Module\Ui\Component\Listing\Column;

use Magento\Catalog\Helper\Image;
use Magento\Framework\UrlInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Ui\Component\Listing\Columns\Column;

class Image extends Column
{
    const ALT_FIELD = 'title';

    protected $storeManager;

    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        Image $imageHelper,
        UrlInterface $urlBuilder,
        StoreManagerInterface $storeManager,
        array $components = [],
        array $data = []
    ) {
        $this->storeManager = $storeManager;
        $this->imageHelper = $imageHelper;
        $this->urlBuilder = $urlBuilder;
        parent::__construct($context, $uiComponentFactory, $components, $data);
    }

    public function prepareDataSource(array $dataSource)
    {
        if(isset($dataSource['data']['items'])) {
            $fieldName = $this->getData('name');
            foreach($dataSource['data']['items'] as & $item) {
                $url = '';
                if($item[$fieldName] != '') {
                    $url = $this->storeManager->getStore()->getBaseUrl(
                        \Magento\Framework\UrlInterface::URL_TYPE_MEDIA
                    ).'{IMAGE_PATH}/'.$item[$fieldName];
                }
                $item[$fieldName . '_src'] = $url;
                $item[$fieldName . '_alt'] = $this->getAlt($item) ?: '';
                $item[$fieldName . '_link'] = $this->urlBuilder->getUrl(
                    '{IMAGE_CLICK_LINK}',
                    ['yourentity_id' => $item['yourentity_id']]
                );
                $item[$fieldName . '_orig_src'] = $url;
            }
        }

        return $dataSource;
    }

    protected function getAlt($row)
    {
        $altField = $this->getData('config/altField') ?: self::ALT_FIELD;
        return isset($row[$altField]) ? $row[$altField] : null;
    }
}
Custom validation

For custom-validation rule to work, we need to create a RequireJs module

app/code/Vendor/Module/view/adminhtml/web/js/custom-validation.js

 require(
        [
            'Magento_Ui/js/lib/validation/validator',
            'jquery',
            'mage/translate'
    ], function(validator, $){
            validator.addRule(
                'custom-validation',
                function (value) {
                   //custom validation logic

                }
                ,$.mage.__('Custom Validation Message.')
            );
});

Then connect it to UI Grid on the page.

Custom Ui Form

Same as with UI grid, it requires a controller to render a base page.

Controller:

app/code/Vendor/Module/Controller/Adminhtml/Index/NewAction.php

<?php
namespace Vendor\Module\Controller\Adminhtml\Index;

class NewAction extends \Magento\Backend\App\Action
{

    const ADMIN_RESOURCE = 'Index';

    protected $resultPageFactory;

    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory)
    {
        $this->resultPageFactory = $resultPageFactory;
        parent::__construct($context);
    }

    public function execute()
    {
        return $this->resultPageFactory->create();
    }
}

Layout: app/code/Vendor/Module/view/adminhtml/layout/vendor_module_index_edit.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>
        <referenceContainer name="content">
            <uiComponent name="custom_ui_form" />
        </referenceContainer>
    </body>
</page>

Ui form component layout specifies the data source for the form, the necessary buttons, tabs, fields and their types.

Ui component definition: app/code/Vendor/Module/view/adminhtml/ui_component/custom_ui_form.xml

<?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">vendor_module_form.vendor_module_form_data_source</item>
            <item name="deps" xsi:type="string">vendor_module_form.vendor_module_form_data_source</item>
        </item>
        <item name="label" xsi:type="string" translate="true">Contact Form</item>
        <item name="config" xsi:type="array">
            <item name="dataScope" xsi:type="string">data</item>
            <item name="namespace" xsi:type="string">contact_form</item>
        </item>
        <item name="template" xsi:type="string">templates/form/collapsible</item>
        <item name="buttons" xsi:type="array">
            <item name="back" xsi:type="string">Vendor\Module\Block\Adminhtml\Contact\Edit\BackButton</item>
            <item name="delete" xsi:type="string">Vendor\Module\Block\Adminhtml\Contact\Edit\DeleteButton</item>
            <item name="reset" xsi:type="string">Vendor\Module\Block\Adminhtml\Contact\Edit\ResetButton</item>
            <item name="save" xsi:type="string">Vendor\Module\Block\Adminhtml\Contact\Edit\SaveButton</item>
        </item>
    </argument>
    <dataSource name="vendor_module_form_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Vendor\Module\Model\Contact\DataProvider</argument>
            <argument name="name" xsi:type="string">vendor_module_form_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">vendor_module_contact_id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="submit_url" xsi:type="url" path="vendor_module/index/save" />
                </item>
            </argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            </item>
        </argument>
    </dataSource>

    <fieldset name="contact">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="collapsible" xsi:type="boolean">false</item>
                <item name="label" xsi:type="string" translate="true">Contact Fieldset</item>
            </item>
        </argument>
        <field name="vendor_module_contact_id">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="visible" xsi:type="boolean">false</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">contact</item>
                </item>
            </argument>
        </field>
        <field name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string">Name</item>
                    <item name="visible" xsi:type="boolean">true</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">contact</item>
                </item>
            </argument>
        </field>
        <field name="email">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string">Email</item>
                    <item name="visible" xsi:type="boolean">true</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">contact</item>
                </item>
            </argument>
        </field>
        <field name="comment">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string">Comment</item>
                    <item name="visible" xsi:type="boolean">true</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">contact</item>
                </item>
            </argument>
        </field>
    </fieldset>
</form>

Modifying existing UI components

Since UI components utilize the Magento 2-standard XML layout handles system for configuration, adding and overriding is a simple task.

Let us take the example of a UI component, which configuration is located at the Magento_Catalog/view/adminhtml/ui_component/product_listing.xml file. To add a new column, create app/code/Vendor/Module/adminhtml/ui_component/product_listing.xml file in your module.

app/code/Vendor/Module/adminhtml/ui_component/product_listing.xml

<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
        <columns name="product_columns">
               <column name="custom_column">
                     <argument name="data" xsi:type="array">
                	         <item name="config" xsi:type="array">
                              <item name="filter" xsi:type="string">text</item>
                              <item name="sortable" xsi:type="boolean">false</item>
                              <item name="label" xsi:type="string" translate="true">Custom Column</item>
                              <item name="sortOrder" xsi:type="number">75</item>
                         </item>
            	    </argument>
               </column>
         </columns>
</listing>
pm-dragun
Igor-Dragun

Partner with Us

Send us a message to grow your business. Get a free quote now. Speak to us 1 650 353 2301

Send my message