skip to Main Content

I am a newcomer in Magento2 and face with a next task. I have to do custom validation for city and street inputs at Checkout page for both shipping and payment steps.
There are 2 issues I am misunderstanding totally.

  1. As I could investigate templates for city and street are inserted by Knockout. To get cities and streets list I have to insert php method in script tag. This php method provides to me URL for next Ajax request. Because of general Knockout template with ‘.html’ type I can’t insert php code there.
    So how can I call my js file from Knockout html template?
  2. City and street inputs must offer coincidences for first entered letters (as a result of Ajax request) in their lists below. How this lists can be realized?

I have read Magento devdocs and a lot of communities but couldn’t find intelligible explanation. Sorry for possible repeat.

app/design/frontend/Vendor/Theme/Magento_Checkout/web/template/shipping-address/form.html (inputs are inserted inside id="shipping-new-address-form")

<div class="amtheme-shipping-wrap">
    <form class="form form-shipping-address amtheme-form-address"
          id="co-shipping-form"
          data-bind="attr: {'data-hasrequired': $t('* Required Fields')}">
        <div class="step-title" data-bind="text: setAddressTitle" data-role="title"></div>
        <!-- ko foreach: getRegion('before-fields') -->
            <!-- ko template: getTemplate() --><!-- /ko -->
        <!--/ko-->
        <div id="shipping-new-address-form" class="fieldset address">
            <!-- ko foreach: getRegion('additional-fieldsets') -->
                <!-- ko template: getTemplate() --><!-- /ko -->
            <!--/ko-->
            <!-- ko if: (isCustomerLoggedIn) -->
            <div class="field choice" data-bind="visible: !isFormInline">
                <input type="checkbox" class="checkbox" id="shipping-save-in-address-book" data-bind="checked: saveInAddressBook" />
                <label class="label" for="shipping-save-in-address-book">
                    <span data-bind="i18n: 'Save in address book'"></span>
                </label>
            </div>
            <!-- /ko -->
            <div class="amtheme-address-toolbar" data-bind="visible: !isFormInline && !isFormPopUpVisible()">
                <button type="button"
                        class="action action-cancel"
                        click="hideNewAddress"
                        text="$t('Cancel')"
                        data-bind="attr: {title: $t('Cancel')}">
                </button>
                <button type="button"
                        class="action action-save"
                        click="saveNewAddress"
                        text="$t('Ship here')"
                        data-bind="attr: {title: $t('Ship here')}">
                </button>
            </div>
        </div>
    </form>
</div>

I was about to write inside form.html something like this:

<script>
    require([
        'jquery',
        'Magento_Theme/js/govaddress-validation'
    ], function($) {
        $(function () {
            $('input[name="city"]').keyup(function () {
                console.log('keyup event worked');
                govAddressValidation.getCityList('<?php echo $this->getUrl("opgovaddress"); ?>');
            });
        })
    })
</script>

My JS file is not matter as it is unreachable for now

app/design/frontend/Vendor/Theme/Magento_Theme/web/js/govaddress-validation.js

define([
    'jquery'
], function ($) {
    'use strict';
    return {
        url: '',
        getCityList: function (url) {
            var inputValue = $('input[name="city"]').val();
            this.inputValue = inputValue;
            this.url = url;
            this.ajaxCall();
            console.log('getCityList');
        },

        ...
    }
})

app/design/frontend/Vendor/Theme/Magento_Theme/requirejs-config.js

var config = {
    map: {
        '*': {
            backTop: 'Magento_Theme/js/components/back-to-top',
            amMenu: 'Magento_Theme/js/components/am-menu',
            amQty: 'Magento_Theme/js/components/am-qty',
            amSelect: 'Magento_Theme/js/components/am-select',
            amFileUpload: 'Magento_Theme/js/components/am-file-upload',
            amStickyHeader: 'Magento_Theme/js/components/am-sticky-header',
            govAddressValidation: 'Magento_Theme/js/govaddress-validation'
        }
    },

    config: {
        mixins: {
            'mage/validation': {
                'Magento_Theme/js/lib/mage/validation-mixin': false
            },
            'mage/menu': {
                'Magento_Theme/js/lib/mage/menu-mixin': true
            }
        }
    }
};

2

Answers


  1. Firstly, Magento’s validation for checkout_index_index.xml is not working properly so, you have to insert the rules while you replace the street fields.
    You can do this by creating a plugin and inserting the fields in Layout AfterProcessor.

    namespace/module/etc/frontend/di.xml

    <type name="MagentoCheckoutBlockCheckoutLayoutProcessor">
        <plugin name="rewrite-street" type="NamespaceModuleModelCheckoutLayoutProcessorPlugin" sortOrder="10"/>
    </type>
    

    And the LayoutProcessorPlugin should look like this: (Please see in my attachment)
    https://pastebin.com/AE6AiLxk

    You can insert in the validation array any validation rule you want.

    Login or Signup to reply.
  2. So what you do is open your checkout and look for this field you want to validate. I will choose your first field, city. Opening dev tools gives me this:

    <input class="input-text" type="text" data-bind=" ..." name="city" ...>
    

    So we could do something with the name since we don’t have a really useful id. I see in your question that you already have this part!

    Since you say you want both shipping and payment step, I will go for the last. So to do this we need a custom validator on the payment step. I assume you already have a custom extension, something like Vendor_MyCheckoutValidator. First we will create a copy of checkout_index_index.xml (from Magento Checkout module) in Vendor/MyCheckoutValidator/view/frontend/layout and we will leave out all the stuff we don’t need. Simply copy the steps down to payment:

    <body>
      <referenceBlock name="checkout.root">
        <arguments>
          [... all the way down to payment ...]
           <item name="payment" xsi:type="array">
             <item name="children" xsi:type="array">
               <item name="additional-payment-validators" xsi:type="array">
                 <item name="children" xsi:type="array">
                   <item name="my-city-validator" xsi:type="array">
                     <item name="component" xsi:type="string">Vendor_MyCheckoutValidator/js/view/finalcheck-validation</item>
                     </item>
                     [... and all the way back down again ...]
    

    So there you have it, I named it my-city-validator and it points to Vendor_MyCheckoutValidator/js/view/finalcheck-validation.

    Let’s create ths finalcheck-validation.js in your module under Vendor/MyCheckoutValidator/view/frontend/web/js/view:

    define(
     [
      'uiComponent',
      'Magento_Checkout/js/model/payment/additional-validators',
      'Vendor_MyCheckoutValidator/js/model/final-address-check-validator'
     ],
      function (Component, additionalValidators, finalAddressCheckValidator) {
       'use strict';
       additionalValidators.registerValidator(finalAddressCheckValidator);
       return Component.extend({});
      }
    );
    

    It looks more complicated then it is but simply said our define says it is a payment additinal validator, it can be found in js/model/final-address-check-validator and these are then used in the function. So we now we need our final bit, the final-address-check-validator.js that should be in Vendor/MyCheckoutValidator/view/frontend/web/js/model :

    Note: the path mentioned in the checkout_index_index.xml is Vendor_MyCheckoutValidator/js/view/finalcheck-validation and in the above js it is Vendor_MyCheckoutValidator/js/model/final-address-check-validator but in reality you need the "view/frontend/web" bit as well (Magento knows where to find it but this can be confusing for new developers) so our path is Vendor/MyCheckoutValidator/view/frontend/web/js/view and Vendor/MyCheckoutValidator/view/frontend/web/js/model

    Back to our last bit, the final-address-check-validator.js. Here we have this problem of not knowing the id but we do have the name of the input as seen at the beginning of my post, the name is "city". So let’s check if it starts with a Z because we don’t want to send things to cities that start with an Z.

    We need some standard stuff such as mage/translate and the (error) messageList and of course jquery:

    define(
    ['jquery', 'mage/translate', 'Magento_Ui/js/model/messageList'],
    function ($, $t, messageList) {
     'use strict';
     return {
      validate: function () {
        var cityIsFine = true,
          city = $('input[name="city"]').val(); // getting our city input by name!
        if(city.match("^Z") { 
          messageList.addErrorMessage({ message: $t('We are sorry, your city starts with a Z.') });
          cityIsFine = false;
        }
        // you could do some checks here and maybe a console.log
        console.log(city);
        if(!cityIsFine) {
           return false;
        }
        return true;
       } 
      }
     }
    );
        
    

    Install your module, make sure to clear all cache! Especially frontend cache! Deploy your frontend, clear browser cache thoroughly and try with a city named Zen. You should be able to fill in everything fine and then you push the payment button and you get an error!

    As you can see I did not make use of any templates or mixins or other things (I could but it simply isn’t needed here). You only have to declare your custom validator on the payment step, add it to the additional validators as shown and do some jquery magic in it.

    I concentrated on city but you could do the same for street. Have a look on your frontend what its name is. Say it has name="street[0]" then your jQuery value would look like street0 = $(‘input[name="street[0]"]’).val();

    You also could start with throwing an error right away and get that working before you add regex and other rules. Then you know you have this working 🙂

    define(
    ['jquery', 'mage/translate', 'Magento_Ui/js/model/messageList'],
    function ($, $t, messageList) {
     'use strict';
     return {
      validate: function () {
        console.log('Sorry!');
        messageList.addErrorMessage({ message: $t('We are sorry you will never get past the payment button') });
        return false;
       }
      }
     }
    );
    

    And this part I haven’t tried but I think you can do an ajax call in the above validator. I’m not sure if you want a kind of auto-complete from your question so I concentrated on the validation of city in the above answer.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search