SFDC Stop - Always the latest about Salesforce


Full Tutorial Series with videos, free apps, live sessions, salesforce consulting and much more.


Telegram logo   Join our Telegram Channel

Friday 2 March 2018

Salesforce Lightning Tutorial - Part 5 | Adding Validations

Welcome to the 5th tutorial in the Salesforce Lightning Tutorial Series. In this post, you'll learn about how you can validations in custom lightning component. I'll be extending the code used in my previous posts so, if you are just starting or need to learn only about applying validations only do have a look at my previous posts or at least the code by having a look at my blog posts starting from here or my github repository code in create branch here so that you can understand the progress till now and the further additions that I'll do in this post.

So, let's begin by adding validations in our Lightning Component. Here our main focus is on client side validations only so all this validation part will be handled by making changes only in the Lightning Component and the Lightning Controller.

1. Lightning Component

Let's have a look at the code below and then I'll highlight the changes.
<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId" controller="ContactListController" access="global">
    <!-- Handler to call function when page is loaded initially -->
    <aura:handler name="init" action="{!c.getContactsList}" value="{!this}" />
    <!-- List of contacts stored in attribute -->
    <aura:attribute name="contactList" type="List" />
    <!-- New Contact Object -->
    <aura:attribute name="contact" type="Contact"
    default="{
        'SObjectType': 'Contact',
        'FirstName': '',
        'LastName': '',
        'Email': '',
        'Phone': ''
    }">            
    </aura:attribute>
    <!-- Method to validate new contact -->
    <aura:method name="validateContact" action="{!c.validateContact}" />
    <!-- Lightning card to show contacts -->
    <lightning:card title="Contacts">
        <!-- Body of lightning card starts here -->
        <p class="slds-p-horizontal_small">
            <!-- Aura iteration to iterate list, similar to apex:repeat -->
            <div aura:id="recordViewForm">
                <aura:iteration items="{!v.contactList}" var="contact">
                    <!-- recordViewForm to view the record -->
                    <lightning:recordViewForm recordId="{!contact.Id}" objectApiName="Contact">
                        <div class="slds-box slds-theme_default">
                            <!-- inputfield checkbox used to check wether to delete the contact or not -->
                            <lightning:input type="checkbox" value="{!contact.Id}" label="Mark for Deletion" aura:id="deleteContact" />
                            <br />
                            <!-- outputfield used to output the record field data inside recordViewForm -->
                            <lightning:outputField fieldName="FirstName" />
                            <lightning:outputField fieldName="LastName" />
                            <lightning:outputField fieldName="Email" />
                            <lightning:outputField fieldName="Phone" />
                        </div>
                    </lightning:recordViewForm>
                    <!-- Line break between two records -->
                    <br />
                </aura:iteration>
            </div>
            <div aura:id="recordEditForm" class="formHide">
                <aura:iteration items="{!v.contactList}" var="contact">
                    <div class="slds-box slds-theme_default">
                        <!-- inputfield used to update the record field data -->
                        <lightning:input value="{!contact.FirstName}" />
                        <!-- Validation added -->
                        <lightning:input aura:id="fieldToValidate" value="{!contact.LastName}" messageWhenValueMissing="Contact's Last Name is Mandatory" required="true"/>
                        <lightning:input type="email" value="{!contact.Email}" />
                        <!-- Validation added -->
                        <lightning:input aura:id="fieldToValidate" messageWhenPatternMismatch="Please enter the number in this pattern - (XXX) XXX-XXXX" type="tel" value="{!contact.Phone}" pattern="\([0-9]{3}\) [0-9]{3}-[0-9]{4}" />
                    </div>
                    <br />
                    <!-- Line break between two records -->
                </aura:iteration>
            </div>
        </p>
        <!-- Lightning card actions -->
        <aura:set attribute="actions">
            <!-- New contact modal button added -->
            <lightning:button name="contactModal" label="New Contact" onclick="{!c.openModal}" />
            <!-- Delete button added -->
            <lightning:button variant="destructive" label="Delete" onclick="{!c.deleteContacts}" />
            <!-- New button added -->
            <lightning:button label="New" onclick="{!c.newContact}" />
            <!-- Edit/Save button added -->
            <lightning:button variant="brand" label="Edit" name="edit" onclick="{!c.editContacts}" />
        </aura:set>
    </lightning:card>
    <!-- Contacts Modal Section -->
    <div>
        <section aura:id="contactModal" role="dialog" tabindex="-1" aria-labelledby="contactModalHeading" aria-modal="true" aria-describedby="contactModalBody" class="slds-modal">
            <!-- Modal Container -->
            <div class="slds-modal__container">
                <!-- Modal Header ( consists of close button and heading of modal ) -->
                <header class="slds-modal__header">
                    <lightning:buttonIcon class="slds-modal__close" alternativeText="Close" iconName="utility:close" onclick="{!c.closeModal}" variant="bare-inverse" size="large"></lightning:buttonIcon>
                    <h2 id="contactModalHeading" class="slds-text-heading_medium slds-hyphenate">New Contact</h2>
                </header>
                <!-- Modal Body ( consists of form ) -->
                <div class="slds-modal__content slds-p-around_medium" id="contactModalBody">
                    <!-- Validation added -->
                    <lightning:input aura:id="formFieldToValidate" label="First Name" messageWhenValueMissing="Contact's First Name is Mandatory" required="true" value="{!v.contact.FirstName}" />
                    <!-- Validation added -->
                    <lightning:input aura:id="formFieldToValidate" label="Last Name" messageWhenValueMissing="Contacts's Last Name is Mandatory" required="true" value="{!v.contact.LastName}" />
                    <!-- Custom Validation added -->
                    <lightning:input aura:id="formFieldToValidate" label="Email" name="emailField" value="{!v.contact.Email}" />
                    <lightning:input label="Phone" value="{!v.contact.Phone}" />
                </div>
                <!-- Modal Footer ( consists of cancel and save buttons ) -->
                <footer class="slds-modal__footer">
                    <lightning:button onclick="{!c.closeModal}" variant="neutral">Cancel</lightning:button>
                    <lightning:button onclick="{!c.createContact}" variant="brand" >Save</lightning:button>
                </footer>
            </div>
        </section>
        <!-- Modal Backdrop -->
        <div aura:id="contactModalBackdrop" class="slds-backdrop"></div>
    </div>
</aura:component>
I have made changes in mainly the edit form and the new record form i.e. the modal which is used to create a new contact. The edit form is mainly the form with aura:id as recordEditForm. In the edit form, focus on the fields with <!-- Validation added --> comments, the first field is the Last name field, as the last name of a contact is required so I have set the required attribute to true and there is another attribute named messageWhenValueMissing i.e. this message will be displayed as a validation error when value of required attribute is missing. So, I have provided a message for the same as - Contact's Last Name is Mandatory. Another validation is for the phone field in which we added a pattern, when we created this form so we have to add that type of validation error message in the respective attribute. So, the attribute used here is messageWhenPatternMismatch and a friendly message is given that will be displayed when this attribute doesn't follow the pattern specified. One more thing to notice is that all the fields that are marked for validation has the same aura:id as fieldToValidate. <lightning:input> allows us to add validations in all fields at once, so I have given the same aura id to access all as an array. You'll see its usage when we move on to controller. The same procedure is applied to add validations in fields included in modal. In those fields, one field has <!-- Custom Validation added --> comment in which there is no particular message as we are going to use it to add custom validation. I have also added an aura:method attribute with name as validateContact and action as {!c.validateContact} which is the function that we have defined in controller. This function will be called from controller itself, that's why we need to specify it using aura:method tag to have it's definition in component so that it can be called using the component reference as component.validateContact(); | general syntax - component.<aura method name attribute>();

2. Lightning Controller

Moving on to the last part of our validation i.e. Lightning Controller, let's have a look at the code below before discussion.
({
    // Function called on initial page loading to get contact list from server
    getContactsList : function(component, event, helper) {
        // Helper function - fetchContacts called for interaction with server
        helper.fetchContacts(component, event, helper);
    },

    // Function used to create a new Contact
    newContact: function(component, event, helper) {
        // Global event force:createRecord is used
        var createContact = $A.get("e.force:createRecord");
        // Parameters like apiName and defaultValues are set
        createContact.setParams({
            "entityApiName": "Contact",
            "defaultFieldValues": {
                "AccountId": component.get("v.recordId")
            }
        });
        // Event fired and new contact dialog open
        createContact.fire();
    },

    // Function used to update the contacts
    editContacts: function(component, event, helper) {
        // Getting the button element
        var btn = event.getSource();
        // Getting the value in the name attribute
        var name = btn.get('v.name');
        // Getting the record view form and the record edit form elements
        var recordViewForm = component.find('recordViewForm');
        var recordEditForm = component.find('recordEditForm'); 
        // If button is edit
        if(name=='edit') {
            // Hiding the recordView Form and making the recordEdit form visible
            $A.util.addClass(recordViewForm,'formHide');
            $A.util.removeClass(recordEditForm,'formHide');
            // Changing the button name and label
            btn.set('v.name','save');
            btn.set('v.label','Save');
        }
        else if(name=='save') {
            // Getting the edit form fields to validate
            var contactFields = component.find("fieldToValidate");
            // Initialize the counter to zero - used to check validity of fields
            var blank=0;
            // If there are more than 1 fields
            if(contactFields.length!=undefined) {
                // Iterating all the fields
                var allValid = contactFields.reduce(function (validSoFar, inputCmp) {
                    // Show help message if single field is invalid
                    inputCmp.showHelpMessageIfInvalid();
                    // return whether all fields are valid or not
                    return validSoFar && inputCmp.get('v.validity').valid;
                }, true);
                // If all fields are not valid increment the counter
                if (!allValid) {
                    blank++;
                }
            } else {
                // If there is only one field, get that field and check for validity (true/false)
                var allValid = contactFields;
                // If field is not valid, increment the counter
                if (!allValid.get('v.validity').valid) {
                    blank++;
                }
            }
            // Call the helper method only when counter is 0
            if(blank==0) {
                // Calling saveContacts if the button is save
                helper.saveContacts(component, event, helper);                
            }
        }
    },
    
    // Function used to delete the contacts
    deleteContacts: function(component, event, helper) {
        // Calling removeContacts Helper Function
        helper.removeContacts(component, event, helper);
    },

    // Function used to open the contact modal
    openModal: function(component, event, helper) {
        var modal = component.find("contactModal");
        var modalBackdrop = component.find("contactModalBackdrop");
        $A.util.addClass(modal,"slds-fade-in-open");
        $A.util.addClass(modalBackdrop,"slds-backdrop_open");
    },

    // Function used to close the contact modal
    closeModal: function(component, event, helper) {
        var modal = component.find("contactModal");
        var modalBackdrop = component.find("contactModalBackdrop");
        $A.util.removeClass(modal,"slds-fade-in-open");
        $A.util.removeClass(modalBackdrop,"slds-backdrop_open");
    },

    // Function used to create new contact
    createContact: function(component, event, helper) {
        var isContactValid = component.validateContact(component, event, helper);
        if(isContactValid) {
           helper.insertContact(component, event, helper);
        }
    },

    // Function to validate new contact - Aura method used for the same
    validateContact: function(component, event, helper) {
        // Getting all fields and iterate them to check for validity
        var allValid = component.find('formFieldToValidate').reduce(function (validSoFar, inputCmp) {
            // Show help message if single field is invalid
            inputCmp.showHelpMessageIfInvalid();
            // Get the name of each field
            var name = inputCmp.get('v.name');
            // Check if name is emailField
            if(name=='emailField') {
                // Getting the value of that field
                var value = inputCmp.get('v.value');
                // If value is not equal to rahul@gmail.com, add custom validation
                if(value != 'rahul@gmail.com') {
                    // Focus on that field to make custom validation work
                    inputCmp.focus();
                    // Setting the custom validation
                    inputCmp.set('v.validity', {valid:false, badInput :true});
                }                
            }
            // Returning the final result of validations
            return validSoFar && inputCmp.get('v.validity').valid;
        }, true);
        // Returning Validate contact result in boolen
        return allValid;
    }

})
In the above code, I have made some changes in the editContacts and createContact function, and also added a new function named validateContact for which we used the aura:method tag in the component. Starting with the editContacts  function, earlier in this, in the save section we called helper.saveContacts() and performed all the server related tasks there. But now we have to validate the fields first. In save section, First of all, we get all the fields with aura id fieldToValidate and store it in variable contactFields. Now, it may be possible that component.find return a single element too if there is only one field on which the validation is applied. So, if the contactFields is an array (checked by length!=undefined as an array should have a length). We are applying the reduce function to contactFields which is a javascript method in which we iterate each element of array and here we refer it by inputCmp.

The reduce function takes 2 parameters:- first the function whose result we have to return and 2nd the initial value or initial result. So, initially we have assumed that all fields are valid so we have given true as the 2nd parameter and in the first parameter, the function will take two parameters, first the result till now and 2nd the current element and the return value of this function will further be passed to the same function in next iteration in the first parameter (result till now). We have 2 variables validSoFar and inputCmp. If you want more detailed explaination for the reduce function(), I am sharing a link here. Inside the function which is passed as first argument to reduce, first we called inputCmp.showHelpMessageIfInvalid(); this is a predefined method that will show our error message when we click on save button if the particular inputCmp is invalid and then we simply returned our validSoFar && inputCmp.get('v.validity').valid this means there are 2 possibilities, either our result from the previous iteration was false so validSoFar is false and the result will be false again. If all the fields till now are valid then we and the current result with the validity of current inputCmp therefore, if the inputCmp is not valid, the result of AND operation will be false and this false result is passed to the next iteration as the validSoFar parameter. We have a counter named blank whose value is incremented if all fields are not valid. Similarly if the component.find() doesn't return an array, we checked it's valid attribute i.e. if it returns false, then we further increment the blank counter ( you can check this by removing the fieldToValidate aura id from one of the two input fields as this will lead to only one field left with that aura id and the condition will be executed ). Finally, if the blank counter has a value 0, this means that all our fields are valid. Therefore, saveContacts() method of helper is called and the contacts are saved. Till now we have worked on applying validations to the edit form which looks like this:-

Custom Validation

Moving on to our createContact() function, we called the validContact() function that is also defined in the controller itself using component.validContact(component, event, helper). This is possible only because we have added aura:method tag in our lightning component. So, let's explore our validateContact function now and see what's there. It's much similar to out previous solution, in this also, we are calling component.find() on input fields with id formFieldToValidate. If you remember in the lightning component, I have given formFieldToValidate aura id to inputs that were in the modal which is mainly used to create a new Contact. Here I am not checking for array or single element as I know I have more than one fields with same aura:id so definitely, component.find() will return an array. I again called the showHelpMessageIfInvalid(). Now, I have to add custom validation on field with name emailField so I get the name of field using inputCmp.get("v.name") and if name equals emailField I am going to show an error if it's value is not equal to rahul@gmail.com this is just an example of custom validation you can add any other condition. So, I get the value of field using inputCmp.get("v.value") and if this value is not equal to rahul@gmail.com I focused on the inputCmp using inputCmp.focus() and set the validity attribute of inputCmp to {valid:false, badInput :true}. The validity attribute looks like this:-


This means that I am making the validity attribute of that field invalid and the reason is badInput as I am making that true and finally I am returning the AND of  validSoFar and current input fields validity and the default value is true just like before. But if you notice the contactList component, in the modal email field, we haven't given any attribute and it's value like :- messageWhenBadInput="" so the default message will be displayed i.e. Enter a valid value.So, in this way, we can add custom validations in our lightning component and if you are wondering about that inputCmp.focus(); line so it's necessary to focus the particular field to show the custom validation error message to appear. You can remove this line and give it a try, in that case, you have to manually focus it to show the error message. Actually, the lightning:input tag is still in beta version so there maybe any further advancements possible to make custom validations more easier. If you want to learn more about the various validations we can apply and the respective attributes for lightning:input, you can find them in the official doc here. Just scroll down to the error messages part while viewing the same. Also, if you came across a better approach, feel free to share it in comments section below. We have applied custom validations in the modal section and it looks like this:-


Tired of reading or just scrolled down..!! Don't worry, you can watch the video too.

If you liked this post then do follow, subscribe, comment your views or any feedback and share it with everyone in your circle so that they can also get benefit by this. Hope to see you next time too when we'll learn how to use lightning events hopefully as most of the people were demanding for a tutorial on that. For the whole code at one place, please refer to my github repository here. You can fork this repo and do your own changes. However please switch to validate branch to get the code specified here and not the future changes. Otherwise, directly go to the validate branch by clicking here.

Congratulations..!! You have completed all the 5 parts in Salesforce Lightning Basics Tutorial Series. Now before moving to the next step i.e. Salesforce Lightning Events Tutorial Series. Let's have a look at a small thank you gift - sObject Convertor that I have made for you for supporting SFDC Stop upto this level.

Happy Trailblazing..!!

33 comments:

  1. Thank you for the nice tutorials. Would it be possible to have a Lightning tutorial on Lightning Events?

    ReplyDelete
    Replies
    1. Hi Alo, Next tutorial will be on Lightning Events only. I'll upload it asap probably tomorrow. Thanks for reaching out. :-)

      Delete
  2. Great tutorial. I am having issues when calling showHelpMessageIfInvalid(). It does not display the error message on badInput unless I click on the input field and get out of it. I even when ahead in javascript set focus to the input field, then set focus on another field and then back to the input field with the error and nothing. I just can't get it to display the error. How can I show the error message after calling showHelpMessageIfInvalid(). Thank you in advance.

    ReplyDelete
    Replies
    1. Hi Isabel, there is problem of cache in lightning too so just make sure you have refreshed the page 2-3 times if the changes is not appearing as I don't think you are doing anything wrong. If it still doesn't effect you can share the code or link to your code snippet so that I can check. Thanks for reaching out.

      Delete
  3. Rahul Sir,
    I want to do validation of Start Date and End Date.By this procedure, I m giving true as initial value.But,I got an error like expected String Found Boolean.Please Give me an idea what to do.

    ReplyDelete
    Replies
    1. Where are you giving true ? That error means that the code is expecting the string but you have given Boolean value i.e. true. If you are assigning true to a field make sure it's of type Boolean

      Delete
  4. Rahul Sir,
    Can You upload a video or blog on components communicating with events This Week.It is very important topic actualy.

    ReplyDelete
  5. Hi Sir,
    How to do the dynamic searching in lightning

    ReplyDelete
    Replies
    1. Hi Aman, if you want to implement search in a custom lightning component, you have to use SOSL queries. Rest it depends on the scenario what I used in sObject Convertor is SOSL. However, you can try this and have a look at the code by testing sObject Convertor app once - http://sfdcstop.blogspot.com/2018/03/sobject-convertor-utility-application.html here is the step by step tutorial for the same. There are some issues on github repo too if you are interested to contribute in this application

      Delete
    2. Rahul Sir, Today or Tommarow You are going to upload the events tutorial

      Delete
    3. Please have some patience. I'll upload it asap.

      Delete
  6. This comment has been removed by the author.

    ReplyDelete
  7. Thank you Rahul. You have made Lightning easy with clear explanation. Well documented.

    ReplyDelete
  8. Hi Rahul,

    How to write custom account page in lightning to create and update existing records.I am new to lightning please help me how to do this.

    ReplyDelete
    Replies
    1. Hi, If you want to deal with a single record then I'll suggest you to use LDS or lightning:recordForm, you can have a look at the documentation here:- https://developer.salesforce.com/docs/component-library/bundle/lightning:recordForm/documentation, I have another blog about LDS too. However, if you need to deal with multiple records, then you need to do it yourself by creating custom controller. Follow my lightning tutorial series starting from here:- https://sfdcstop.blogspot.com/2018/01/salesforce-lightning-basics.html and you'll be able to make it yourself.

      Delete
  9. Replies
    1. Thank you. Do share it in your network too :-)

      Delete
  10. i got below error:

    Field_Integrity_Exceptions:
    Failed to save ContactList.cmp: Invalid definition for null:ContactListController: null: Source

    ReplyDelete
  11. Hi Rahul,
    Please reply me back for below error solution please...

    Failed to save ContactList.cmp: Invalid definition for null:ContactListController: null: Source

    ReplyDelete
    Replies
    1. Hi Bhavin, this maybe because you have not made an apex controller for the lightning component with name "ContactListController" as this tutorial is a continuation in the series, I have already made an apex controller for this component before and using that in component. Make sure you've made the same.

      Delete
  12. Could you please provide the Helper() code and style code as well

    ReplyDelete
    Replies
    1. Hi Raj, this tutorial is in continuation of Salesforce Lightning Tutorial Series, so if you 've followed the series you should have the helper code (also in part 4) and the styling css (also in part 2). Moreover I recommend you to have a look at the GitHub repository where you can find the whole code:- https://github.com/rahulmalhotra/SFDC-Lightning/tree/validate/src/aura/ContactList

      Delete
  13. hi.. can u explain this line inputCmp.get("v.validity").valid
    what exactly are these valid and validty.. are these predefined or u have defined if yes where

    I am actually getting this error Uncaught Action failed: c:EMT_BaseNewEventComponent$controller$saveRecord [Cannot read property 'valid' of undefined]

    ReplyDelete
    Replies
    1. Hi Smruti, yes, the inputCmp will have a default property validity which is an object and has a value for valid in it. If you can share your code or DM me then maybe I can help, here we're basically checking if all the fields are valid or not. However, salesforce has updated the syntax now and you can now do it like:-

      inputCmp.reportValidity();
      return validSoFar && inputCmp.checkValidity();

      Also for a custom error message:- you can do it like:- inputCmp.setCustomValidity("Enter a valid value");
      inputCmp.reportValidity();

      I'll need to add a new blog as an update to this. Thanks for pointing out and your error will resolve if you're getting the inputCmp properly, check if you've given the aura id and do:- console.log(inputCmp) to check if it's undefined

      Delete
  14. Hi Rahul,
    Thanks for the articles which are very helpful. When trying to make "lookup" field it is not showing error messages. Is there a way to do it? TIA.

    ReplyDelete
    Replies
    1. Hi,

      It depends on how you're creating a lookup, if you're using standard lightning inputfield, the standard validation rules will do the trick.

      Delete