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.
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:-
({ // 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.
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.
Happy Trailblazing..!!
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..!!
Thank you for the nice tutorials. Would it be possible to have a Lightning tutorial on Lightning Events?
ReplyDeleteHi Alo, Next tutorial will be on Lightning Events only. I'll upload it asap probably tomorrow. Thanks for reaching out. :-)
DeleteGreat 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.
ReplyDeleteHi 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.
DeleteRahul Sir,
ReplyDeleteI 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.
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
DeleteRahul Sir,
ReplyDeleteCan You upload a video or blog on components communicating with events This Week.It is very important topic actualy.
Will do it by this weekend for sure :-)
DeleteThank You So much In Advance :-)
DeleteHi Sir,
ReplyDeleteHow to do the dynamic searching in lightning
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
DeleteRahul Sir, Today or Tommarow You are going to upload the events tutorial
DeletePlease have some patience. I'll upload it asap.
DeleteThis comment has been removed by the author.
ReplyDeleteThank you Rahul. You have made Lightning easy with clear explanation. Well documented.
ReplyDeleteThank you so much for your feedback :-)
Deletegreat article.
ReplyDeleteThanks. Do share it in your network too :-)
DeleteHi Rahul,
ReplyDeleteHow 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.
Hi Rahul any help?
DeleteHi, 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.
DeleteNice tutorial
ReplyDeleteThank you. Do share it in your network too :-)
Deletei got below error:
ReplyDeleteField_Integrity_Exceptions:
Failed to save ContactList.cmp: Invalid definition for null:ContactListController: null: Source
Replied to you below :-)
DeleteHi Rahul,
ReplyDeletePlease reply me back for below error solution please...
Failed to save ContactList.cmp: Invalid definition for null:ContactListController: null: Source
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.
DeleteCould you please provide the Helper() code and style code as well
ReplyDeleteHi 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
Deletehi.. can u explain this line inputCmp.get("v.validity").valid
ReplyDeletewhat 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]
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:-
DeleteinputCmp.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
Hi Rahul,
ReplyDeleteThanks 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.
Hi,
DeleteIt depends on how you're creating a lookup, if you're using standard lightning inputfield, the standard validation rules will do the trick.