To make our AngularJS data model validation more robust, we want the ability to make complex validators that have child validators.
Now that we've implemented the first piece of our validator library, it's time to move on to some of the additional functionality we described in the ReadMe. The next set of functionality we described was validators that took arbitrary special options.
In this case, we've defined a validator that takes an ignore option. This is passed in as "this.ignore." We want to make sure that we successfully are ignoring dollar signs at the start of words or numbers, and that this still validates to a number. Five with a period and two zeros would evaluate to a float, so that would work with just ignoring the dollar sign.
We also want to make sure that the custom validation message also receives "this.ignore," which we're stringifying here.
If we save that out, we will see that the tests are already passing. This is perfect. This is exactly what we want to see happening. It ensures us that we've already created the first part of our library correctly. Let's move on to another test.
I've already pasted in the next test here. We described previously that we want to enable ourselves to have validators that contain child validators. In this case, we're making a min validator which validates a minimum length, a max validator which validates a maximum length, and then we define a length validator which has two child options, minimum and maximum.
We're going to configure these. When we configure these, it's going to return an array of validators. It will contain the min and max as the zero and first validators in that array.
We want to ensure that teach of those child validators was created correctly. The min validator, with a minimum of five and the message "high," will be false. The max validator, with five characters and a max of 10, will validate to true. We'll make sure that both of these messages get created correctly.
We also want to make sure that we can use configuration options to override the defaults, since we haven't previously tested that. We're going to configure these validators with a different message than what was defaulted. Things like...I like the number 10...is going to come out of this.
We're going to isolate our tests here, and start working. We see here that this fails, which is exactly what we expect.
Let's head back to our validator class and start to perform some additional setup here. In order to set up our child validators, what we're going to do is say that for every validator we create, we're going to create an empty hash of child validators where we'll store potential child validators. I also want to store the context here so we can access it from other methods that aren't always exposed on "this." This will make sure that we can always reference this context when we'd like to.
Then we're going to configure the child validators. We'll say "add child validators." We'll pass in this...Actually, it will be "validation function.options," since that will take the options. Remember, if we have, for instance, the length function that we had created before, it would say "length function.options =", and it will be a hash of options.
Here we had said "min =" the min validator, and "max =" the max validator. When we've configured these options, we've added validators in as two of these options.
In "add child validators," this function here, we're going to take some options. We're going to loop through each of those options. We'll say "if isValidator option." We're going to say "validator.child validators."
Again, we're grabbing the context "this" by using the validator variable that we've assigned. Here we'll say value and key. The value is the validator and the key is the name of the validator.
We'll say, if the value is a validator, then the key equals the value. The child validator's hash is going to store each of these child validators for us,
Then we'll define what it means to be a validator. Here we'll take an option. We'll return "option.constructor.name = validator." That will be this name. It's the name of the function that will have created child validators.
Here we're looping through, adding each of those child validators if any of the options are child validators. This gives us the ability to continue to define other options here if we want the other options hash to have another purpose besides exposing child validators.
Let's head back to the configure block. If a validator has child validators, we don't want to return a validation function for the parent validator. Instead, we want to return the array of child validators. We'll say "if hascChildren." We'll define that in a moment. We'll say "return." We'll say "configured children," and we'll pass it the options here.
This will configure each of the child validators with these options and return an array of configured children instead. That will look something like an array, in this case, of the min validator -- that will be configured so it will be the min validation function -- and the max validation function. That will be what is returned if we have child validators.
Child validators. We can define hasChildren. Remember, we have this childValidators . We'll say "return object.keys validator.childValidators" -- this will be all of the keys that exist in this hash -- ".length > 0". As long as there's at least one child validator, "this" is a parent.
Now we have to create this configured children function. This takes some options. This a bit of a doozy, so I'll try explain as we go.
We're going to loop through each of the validator.childValidators. What I'm actually going to use here is chain, which is going to wrap this so we can apply each of these functions in order.
Instead of "each," I want to map these values. This is a function. Remember, childValidators is an array that has a value which is the child validator. We'll say the child validator name. In fact, let me rename this name so it's a bit easier to read.
We'll say "if options name", meaning that we passed the name of the child validator. If minimum was passed or if maximum was passed, then we want to configure that particular validator. We'll "return child validator.configure name". Actually, it will be "options name". We want to pass it the options for that particular validator.
Sometimes this will return undefined because not all child validators will be used every time we configure the length function. Sometimes we'll use min, sometimes we'll use max, and otherwise it will return undefined. We want to only return values that we have defined in configured. Then we'll call ".value". That is going to unwrap this chained argument.
If we save that, we'll see that all of our tests pass, which is really exciting. It means that not only does this work for the parent validators but it works for all child validators. We've furthermore tested bit of functionality that we hadn't tested before, which was overriding the default options.
Now we've successfully implemented a majority of the pieces of the validator library. We'll continue fine-tune it as we create the rest of the validatable library. We'll be moving on to that in the next video in this series.