2

After upgrading Magento version the custom checkout component created to handle customer informations does not work anymore, with the previous version everything was fine but now I am getting those errors in browser console:

  • [ERROR] Failed to load the "ui/form/field" template requested by "checkout.billing.address.lastname".

  • [ERROR] Failed to load the "ui/group/group" template requested by "checkout.billing.address.street".

The code is very simple there is a layout processor that adds the information about the fields reading the attribute data and then join them using Magento merger (Magento\Checkout\Block\Checkout\AttributeMerger):

<?php 
declare(strict_types=1);

namespace CustomModules\Checkout\Block\Checkout;

use Magento\Eav\Api\Data\AttributeInterface;
use Magento\Checkout\Block\Checkout\AttributeMerger;
use Magento\Ui\Component\Form\AttributeMapper;
use Magento\Checkout\Block\Checkout\LayoutProcessorInterface;
use Magento\Customer\Model\Session as CustomerSession;
use Magento\Checkout\Model\Session as CheckoutSession;
use Magento\Customer\Model\AttributeMetadataDataProvider;
use Magento\Customer\Model\Customer;
use Magento\Quote\Model\Quote;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;

class LayoutProcessor implements LayoutProcessorInterface
{
   /**
    * @var AttributeMetadataDataProvider
    */
   private $attributeMetadataDataProvider;

   /**
    * @var AttributeMapper
    */
   private $attributeMapper;

   /**
    * @var AttributeMerger
    */
   private $merger;

   /**
    * @var CustomerSession
    */
   private $customerSession;

   /**
    * @var CheckoutSession
    */
   private $checkoutSession;

   /**
    * @var array
    */
   private $previousLayout;

   /**
    * @var array
    */
   private $jsLayout;

   /**
    * @param CustomerSession $customerSession
    * @param CheckoutSession $checkoutSession
    * @param AttributeMerger $merger
    * @param AttributeMapper $attributeMapper
    * @param AttributeMetadataDataProvider $attributeMetadataDataProvider
    * @param array $jsLayout
    */
   public function __construct(
       CustomerSession $customerSession,
       CheckoutSession $checkoutSession,
       AttributeMerger $merger,
       AttributeMapper $attributeMapper,
       AttributeMetadataDataProvider $attributeMetadataDataProvider,
       array $jsLayout = []
   ) {
       $this->attributeMetadataDataProvider = $attributeMetadataDataProvider;
       $this->attributeMapper = $attributeMapper;
       $this->merger = $merger;
       $this->customerSession = $customerSession;
       $this->checkoutSession = $checkoutSession;
       $this->jsLayout = $jsLayout;
   }

   /**
    * @param array $previosLayout
    * @return array
    * @throws LocalizedException
    */
   public function process($previousLayout): array
   {
       $this->previousLayout= $previousLayout;
       $this->processAddress();
       $this->movePayment();
       $this->prefillBillingAddressForm();
       return $this->jsLayout;
   }

   /**
    * @return void
    * @throws LocalizedException
    */
   private function processAddress(): void
   {
       $fields = $this->jsLayout['components']['checkout']['children']['billing']['children']['address']['children'];
       $elements = $this->getAddressAttributes();
       $fields = $this->merger->merge(
           $elements,
           'checkoutProvider',
           'billingAddress',
           $fields
       );

       $this->jsLayout['components']['checkout']['children']['billing']['children']['address']['children'] = $fields;
   }


   /**
    * @return array
    * @throws LocalizedException
    */
   private function getAddressAttributes(): array
   {
       $attributes = $this->attributeMetadataDataProvider->loadAttributesCollection(
           'customer_address',
           'customer_register_address'
       );

       $elements = [];
       foreach ($attributes as $attribute) {
           if (!$attribute->getIsUserDefined()) {
               $code = $attribute->getAttributeCode();
               $elements[$code] = $this->attributeMapper->map($attribute);
               if (isset($elements[$code]['label'])) {
                   $elements[$code]['label'] = \__($label);
               }
           }
       }
       return $elements;
   }


   /**
    * @return void
    */
   private function movePayment(): void
   {
       $paymentComponents = $this->previousLayout['components']['checkout']['children']['steps']['children']
       ['billing-step']['children']['payment'];

       $this->jsLayout['components']['checkout']['children']['payment'] = \array_replace_recursive(
           $paymentComponents,
           $this->jsLayout['components']['checkout']['children']['payment']
       );
       $this->removeUnusedPaymentForms();
   }

   /**
    * @return void
    */
   private function removeUnusedPaymentForms(): void
   {
       $unusedPaymentForms = array_keys($this->jsLayout['components']['checkout']
       ['children']['payment']['children']['payments-list']['children']);

       foreach ($unusedPaymentForms as $paymentFormName) {
           unset($this->jsLayout['components']['checkout']['children']['payment']
               ['children']['payments-list']['children'][$paymentFormName]);
       }
   }

   /**
    * @return void
    * @throws LocalizedException
    * @throws NoSuchEntityException
    */
   private function prefillBillingAddressForm(): void
   {
       $customer = $this->customerSession->getCustomer();
       $quote = $this->checkoutSession->getQuote();

       if (!$customer || !$quote) {
           return;
       }

       $billingAddress = $this->jsLayout['components']['checkout']['children']
       ['billing']['children']['address']['children'];
       if (!$this->customerSession->isLoggedIn()) {
           $this->prefillBillingAddressForGuest($billingAddress, $quote);
       } else {
           $this->prefillBillingAddressForCustomer($billingAddress, $customer);
       }

       $this->jsLayout['components']['checkout']['children']
       ['billing']['children']['address']['children'] = $billingAddress;
   }

   /**
    * @param array $billingAddress
    * @param Customer $customer
    * @return void
    */
   private function prefillBillingAddressForCustomer(array &$billingAddress, Customer $customer): void
   {
       $billingAddress['email']['value'] = $customer->getEmail();
       $billingAddress['telephone']['value'] = $customer->getPhonePrefix() . $customer->getPhone();
       $customerAddress = $customer->getDefaultBillingAddress();
       $billingAddress['street']['children'][0]['value'] = '';
       $billingAddress['street']['children'][1]['value'] = '';
       $billingAddress['city']['value'] = '';
       $billingAddress['country_id']['value'] = '';
       $billingAddress['region_id']['value'] = '';
       $billingAddress['postcode']['value'] = '';
       if ($customerAddress) {
           $billingAddress['street']['children'][0]['value'] = $customerAddress->getStreet()[0] ?? '';
           $billingAddress['street']['children'][1]['value'] = $customerAddress->getStreet()[1] ?? '';
           $billingAddress['postcode']['value'] = $customerAddress->getPostcode();
           $billingAddress['city']['value'] = $customerAddress->getCity();
           $billingAddress['country_id']['value'] = $customerAddress->getCountryId();
           $billingAddress['region_id']['value'] = $customerAddress->getRegionId();
       }
   }

   /**
    * @param array $billingAddress
    * @param Quote $quote
    * @return void
    */
   private function prefillBillingAddressForGuest(array &$billingAddress, Quote $quote): void
   {
       $billingAddress['firstname']['value'] = $quote->getCustomerFirstname();
       $billingAddress['lastname']['value'] = $quote->getCustomerLastname();
       $billingAddress['email']['value'] = $quote->getCustomerEmail();
       $billingAddressFromQuote = $quote->getBillingAddress();
       $billingAddress['telephone']['value'] = $billingAddressFromQuote->getTelephone();
   }
}

All the informations about the components are injected in the constructor parameter jsLayout from a di.xml file:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
   <preference for="Magento\Checkout\Block\Onepage"
               type="CustomModules\Checkout\Block\Checkout\Onepage"/>


   <type name="CustomModules\Checkout\Block\Checkout\Onepage">
       <arguments>
           <argument name="layoutProcessors" xsi:type="array">
               <item name="CustomModules_Checkout::CheckoutLayoutProcessor" xsi:type="object">
                 CustomModules\Checkout\Block\Checkout\LayoutProcessor
               </item>
               <item name="directoryData" xsi:type="object">
                   Magento\Checkout\Block\Checkout\DirectoryDataProcessor
               </item>
           </argument>
       </arguments>
   </type>

   <type name="CustomModules\Checkout\Block\Checkout\LayoutProcessor">
       <arguments>
           <argument name="jsLayout" xsi:type="array">
               <item name="components" xsi:type="array">
                   <item name="checkout" xsi:type="array">
                       <item name="component" xsi:type="string">uiComponent</item>
                       <item name="config" xsi:type="array">
                           <item name="template" xsi:type="string">CustomModules_Checkout/onepage</item>
                       </item>
                       <item name="children" xsi:type="array">

                           <item name="billing" xsi:type="array">
                               <item name="component" xsi:type="string">
                                   CustomModules_Checkout/js/view/billing
                               </item>
                               <item name="config" xsi:type="array">
                                   <item name="template" xsi:type="string">CustomModules_Checkout/onepage/billing</item>
                               </item>
                               <item name="provider" xsi:type="string">checkoutProvider</item>
                               <item name="deps" xsi:type="array">
                                   <item name="0" xsi:type="string">checkoutProvider</item>
                               </item>
                               <item name="dataScopePrefix" xsi:type="string">billingAddress</item>
                               <item name="children" xsi:type="array">
                                   <item name="address" xsi:type="array">
                                       <item name="component" xsi:type="string">uiComponent</item>
                                       <item name="displayArea" xsi:type="string">billingAddress</item>
                                       <item name="dataScopePrefix" xsi:type="string">billingAddress</item>
                                       <item name="children" xsi:type="array">
                                           <item name="firstname" xsi:type="array">
                                               <item name="config" xsi:type="array">
                                                   <item name="disabled" xsi:type="boolean">true</item>
                                               </item>
                                           </item>
                                           <item name="lastname" xsi:type="array">
                                               <item name="config" xsi:type="array">
                                                   <item name="disabled" xsi:type="boolean">true</item>
                                               </item>
                                           </item>
                                           <item name="email" xsi:type="array">
                                               <item name="config" xsi:type="array">
                                                   <item name="disabled" xsi:type="boolean">true</item>
                                                   <item name="template" xsi:type="string">
                                                       ui/form/field
                                                   </item>
                                                   <item name="elementTmpl" xsi:type="string">
                                                       ui/form/element/input
                                                   </item>
                                               </item>
                                               <item name="label" xsi:type="string" translate="true">
                                                   Email
                                               </item>
                                           </item>
                                           <item name="telephone" xsi:type="array">
                                               <item name="config" xsi:type="array">
                                                   <item name="disabled" xsi:type="boolean">true</item>
                                               </item>
                                               <item name="validation" xsi:type="array">
                                                   <item name="required-entry" xsi:type="boolean">
                                                       false
                                                   </item>
                                               </item>
                                           </item>

                                           <item name="region" xsi:type="array">
                                               <item name="visible" xsi:type="boolean">false</item>
                                           </item>
                                           <item name="region_id" xsi:type="array">
                                               <item name="component" xsi:type="string">
                                                   Magento_Ui/js/form/element/region
                                               </item>
                                               <item name="visible" xsi:type="boolean">false</item>
                                               <item name="config" xsi:type="array">
                                                   <item name="elementTmpl" xsi:type="string">
                                                       ui/form/element/select
                                                   </item>
                                                   <item name="customEntry" xsi:type="string">
                                                       billingAddress.region
                                                   </item>
                                                   <item name="additionalClasses" xsi:type="string">
                                                       select-option
                                                   </item>
                                               </item>
                                               <item name="validation" xsi:type="array">
                                                   <item name="required-entry" xsi:type="boolean">
                                                       true
                                                   </item>
                                               </item>
                                               <item name="filterBy" xsi:type="array">
                                                   <item name="target" xsi:type="string">
                                                       ${ $.provider }:${ $.parentScope }.country_id
                                                   </item>
                                                   <item name="field" xsi:type="string">
                                                       country_id
                                                   </item>
                                               </item>
                                           </item>
                                           <item name="country_id" xsi:type="array">
                                               <item name="config" xsi:type="array">
                                                   <item name="disabled" xsi:type="boolean">true</item>
                                               </item>
                                           </item>
                                           <item name="postcode" xsi:type="array">
                                               <item name="validation" xsi:type="array">
                                                   <item name="required-entry" xsi:type="boolean">
                                                       true
                                                   </item>
                                               </item>
                                           </item>
                                       </item>
                                   </item>
                               </item>
                           </item>
                       </item>
                   </item>
                   <item name="checkoutProvider" xsi:type="array">
                       <item name="component" xsi:type="string">uiComponent</item>
                   </item>
               </item>
           </argument>
       </arguments>
   </type>
</config>
6
  • Could you add the full file content of the PHP class and the xml file? That will make it easier for us to help you.
    – Tu Van
    Oct 23, 2022 at 16:14
  • Updated with the latest version. I add that another strange thing I saw right now is that if after I removed sortOrder from lastname and firstname in DI the error message changed to: [ERROR] Failed to load the "ui/form/field" template requested by "checkout.billing.address.firstname".
    – Simse
    Oct 23, 2022 at 17:22
  • Please add the file content of CustomModules\Checkout\Block\Checkout\Onepage class too
    – Tu Van
    Oct 23, 2022 at 17:35
  • And add the file content of 3 files: app/code/CustomModules/Checkout/view/frontend/web/template/onepage.html, app/code/CustomModules/Checkout/view/frontend/web/template/onepage/billing.html. app/code/CustomModules/Checkout/view/frontend/web/js/view/billing.js
    – Tu Van
    Oct 23, 2022 at 18:24
  • Hi sorry fo late reply. I figured this out, there was a custom template with a closing ko tag that had no opening tag. It seems that previous version of Magento KO engine is more flexible about this kind of things. Thank you for trying to help.
    – Simse
    Oct 27, 2022 at 19:47

1 Answer 1

0

app/design/frontend/Emizen/ipromo/Magento_Ui/web/templates/form/field.html

check this file and remove the extra tag from end of the file

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.