Next lesson playing in 5 seconds

Cancel
  • Overview
  • Transcript

2.6 Custom Validation

URLs are unique, and we need to provide users with near-immediate feedback when their desired URL is already in use. We’ll use Axios in conjunction with VeeValidate to pull off this feature.

Related Links

2.6 Custom Validation

We are almost done validating our form. The only thing that we have left is validating the URL in almost real time. The reason why I want to do this is because as a user, I don't want to have to type type in a URL, and then submit it, in order to find out that the URL is not available. Instead, I want immediate feedback, so we're going to have to write a custom validator for this, because there's nothing built in that performs any AJAX requests, because that's what we're going to have to do. We're going to have to issue an HTTP request with JavaScript, in order to check the database, if there are any records with that URL. So we're going to start with our controller, because we need to add a method for checking if a URL exists. So we're going to go to app > HTTP > Controllers > Admin > PagesController, and we're going to add a public method called urlExists. And this is going to have a request passed to it. And let's go ahead and decide that, as far as the URL parameter is concerned, we're just going to call it URL. Now, we need to think about how we are going to validate whether or not a URL exists. Because we have to remember that this form is being used not just for creating a new page, but it's also being used for editing a new page. And whenever you edit a new page, the URL that is listed in that field is, by default, going to exist within the database. So we can't just check to see if there's a record with this URL and return true or false. That's just not going to work. So instead, we can do something else. We can check to see if the URL exists, and if it does, then we could also include, or maybe just return, the page idea associated with it. So that whenever we validate it in JavaScript, we can check to see if the URL's page ID corresponds with the page that we are editing. So let's do this. Our query is going to look like this. We'll say page, and then firstOrNew, and then we're going to check the URL. If it is equal to what we have in the request, then we're going to get the ID of that record. Now if one doesn't exist, then this is going to create a new instance of page, and in that case, the ID is going to be null. So that is pretty much going to give us exactly what we need. Now there may be a better approach, but off the top of my head this should be just fine. So let's return this as JSON, because that makes sense to do so. And let's call this property pageId, and we will set its value to the result of performing our query there. So that's it, as far as our controller is concerned, but we do need to set up a route here, because the routes that were defined for the pages controller were all resource routes. So if we go to Web.php, let's copy what we did for the pages controller. And we, of course, don't need any exceptions, so let's take that out, and we don't need a resource. We're just going to do a get, and let's just call this url-exists. That's going to be the resource that we are going to make a request to. And then, of course, we need to specify the method here so, url\Exists. So that should be that as far, as setting up the backend. Now, we need to write our code in JavaScript. So let's go to our resources folder, and let's drill down all the way to our PageFormFieldsComponent. And we could do this in a couple of different ways. We can write all of this inside of an external JavaScript file, so that we can import it to wherever we need to. However, this type of validation is pretty specific for this particular form. I mean, it is for checking if the URL exists. So really, it doesn't make sense to do that, so we can include all of the code that we need here. So the first thing that we need to do is import axios from 'axios', that way we can use it to issue our HTTP requests. And we also need to pull in the validator from vee-validate. Now, the reason why we need this is because we are going to write a validator, and then we need to register it, so that we can use that validator. And then, we need to decide where we are going to create this Validator. Well, we have, for the lack of a better term, events that we have seen the mounted method. Whenever we looked at the example component, the mounted method is executed whenever the component is mounted. Similarly, there is one called created, which is called whenever the instance of this component is created. And in that case, it makes sense to write our validator here. So what we will end up doing is saying validator, and then there's a method called extend, and then we pass two arguments to this. The first is the name of the validator that we want. So we could call this unique-url, and then the second is an object that's going to have two methods. The first is called getMessage. This is going to accept the field that is being validated. So it would be title, or URL, or content, or things like that. But of course, this is only going to be used for our URLs, so we don't really need that field to begin with. So, we're just going to return a string that says the provided URL is not unique, and that would be fine. We could probably word that a little bit better but, that's going to work. So, that's the first method. The second method is simply called a validate, and this is what is going to execute whenever the URL field is validated. Now, this is going to pass the value of that field. So really, the first thing we need to do is check to see if we have a value, and if not then we can just return false, because there's nothing else that we need to do. However, if we do have a value, then we, of course, want to submit that to our server application, so that we can then validate the URL. And so, let's create a few variables here. The first is going to be serviceUrl, and this is going to be set to /admin/pages/ and then, url-exists. And then, the second variable is going to be simply called params. This is going to be an object that we are going to include with our HTTP request. So it's going to add whatever properties to the query string. So we want this query parameter called url, and the value was simply going to be value that is specified, and then we want to make our request. So we are going to say axios.get. We're going to pass in the serviceUrl, and then we need to pass in an options object. And this options object is going to have a property called params, and we'll just set that to our params object. Or we can use the new syntax of just specifying params, and that would be fine, as well. Now, of course, this is asynchronous, so we are going to say then, and then we're going to work with a response. Now, the response is going to automatically parse our JSON data. So let's say pageId = response.data.pageId, and then we just want to check to see if the pageId is null. If it is, then of course, that URL is not being used. But then, we also want to check against the pageId that is currently loaded in the form. And we can do that like this. Inside of the created method, we can say let that = this, and then inside of this validate method, we can use, that. Now, we can't just simply return true or false, instead we need to return an object that has a property called valid. And if valid is true, then it's valid, if it's false, it's not valid. So, here we can say that if pageId = null, or if pageId is equal to that.model.id, then the form is valid, otherwise it's not valid. So that should be fine there, and then we just need to use this validator in our form. So we're going to take this unique-url, and we're going to up to our URL field, and we're going to add that to v-validate attribute. So we will say required| unique-url. Now, the default behavior for validating an input element of typed text is to validate on every keystroke. That's not something that we really want to do in this particular case. I mean, that would be great if that was acceptable, but it's not. So let's find some middle ground. We want some results as close to immediate as possible, but we don't want to wait too long. So I think, whenever the user blurs out of the URL field, that would be a fine time to perform the validation. So we essentially want to specify that we only want to perform this validation for certain events. And we can do that with an attribute called data-vv-validate-on. Now, this is already a string, so we don't have to say double quotes, and then single quotes inside of it. It's already going to be considered a string. So then, we just specify the events that we want to listen for. So in this case it's blur. But if we wanted to perform this validation on other events, then we would just separate them with a pipe, and then at those events, as well. But blur is going to be great for us. So let's go to the browser. Let's refresh our page, and let's just work on the URL. Now, of course, we still see the URL field as required, so we haven't changed that validation, thankfully. But let's type in a URL that we know is currently in use, and that is this is a new page. So whenever I click on something else, then we should see a message that says, the provided URL is not unique, and that's exactly what we want. Now, of course, this is still going to be executed whenever we submit the form, because that is part of the validators. It's going to validate everything. So whenever we click on submit, we see that the form submits. Now, we have written the code to prevent that. However, if we stop and think about this, we are making an AJAX request, which is, of course, asynchronous, but it is going to take a little bit of time in order for that to execute. So before validateAll can finish, the browser has already determined that, okay, everything is fine. I'm just going to submit the form, and then it goes on. So basically, what we want to do is provide enough time for the validateAll method to do its work, and then submit the form, if that's indeed what we want to do. So we could do something like this. We could prevent the default action right from the get-go, just so that nothing is going to happen. And then, whenever all the validators are finished, if we have a result, then we just want to submit the form. And we can do it with target.form.submit();. That's not going to reissue this click event. That is going to submit the form. So let's give that a try. Let's refresh the page, and let's click on the submit button. Now, we see that nothing's submitted, and that's good. Let's click on it again, just to make sure. Everything is fine there, so let's type in a URL that we know is not good. Okay, we are still getting our error message. Let's submit the form. That's great, so let's create a new page then. We'll say that this is a new Page 2. The URL will change with -2, and the error goes away. And then, for the content, doesn't matter what that is, then we will submit, so that's great. Now, let's edit that, and make sure that everything is going to be okay. So as far as the URL is concerned, let's change this, so that we have no dash two. Once again, we see that the provided URL is not unique. That is the behavior that we were expecting, but let's add back in the URL for this particular page, and it goes away. So all of the validation is working as we would expect it to, and we don't have as quick of results that I would like, but it is still better than the default behavior. After all, we do have to weigh what is best for the user and what is best for the application. And I think using the blur event was a nice middle ground.

Back to the top