I must admit this is pretty good code for someone that has just started, I like that you use the more "functional" style Javascript allow you to use . So good job :) But there are some design issues I see with the code.
1. Factory vs constructor
Your Validator has a make
method which takes the rules in, It is basically used as a factory method (A factory is a design pattern if you are not familuar with it). But it has none of the benefits a factory gives you, since it is a instance method.
Having to instantiate a Validator
object and then being forced to call make
is cumbersome. Instead I suggest you make the make
function static and add a constructor
class Validator
{
static make(rules)
{
return new Validator(rules);
}
constructor(rules)
{
this.rules = rules;
}
}
2. Checking class properties
You don't need to do this check in your valid
function.
if (!this.hasOwnProperty('rules')) {
return true;
}
Since it's practically not possible to not have rules with the proposed factory. I understand why this is here though. You wanted to make sure you can't forget the make
call after instantiating a Validator
object. But that's a wrong schematic. If the caller uses your code wrong it should fail, and not say "I don't care, but this is valid"
3. Naming
Methods should indicate that they are actions, and properties that they are values.
Your valid
method sounds a lot like a property, like a value. I would call it validate
since that is a verb and makes it clear that it does something and not "is" something. And I would rename the property valid
that your return in your valid
method to isValid
4. ES6
EDIT: I noticed to late that you already used an arrow function in your code. Ignore the explanation if you want and just replace the Object.keys().forEach(callback)
callback with an arrow function. BTW you do not need the parentheses if you only have one parameter.
You can use a arrow function in your forEach(function)
function. This is a es6 feature and a bit more advanced tho. Using functions is fine but it can solve the issue you have with scoping.
The function
keyword does create a new scope and therefore changes the meaning of this
. The arrow function on the other hand behaves exactly like a function but does not change the meaning of this
validate() {
let errors = [];
Object.keys(rules).forEach(key => {
let formElement = document.getElementsByName(key)[0];
if (formElement == undefined) {
return;
}
let ruleSet = rules[key].split('|');
ruleSet.forEach((rule) => {
let result = this.checkRuleForElement(rule, formElement)
if (!result.valid && result.message != undefined) {
errors.push(result.message);
}
});
});
return {
isValid: errors.length == 0,
errors
}
}
Another thing you can leave out is the
errors: errors
part. You can shorten it to just errors
since the name of the variable is the same as key of the object
5. Decouple things
Currently your "rule running" and "rule definitions" are all in the same class/method (in the switch in checkRuleForElement
) So if you want to add a new rule you have to modify your Validator class which should only do one thing: Invoke the rules for a element. The rule definitions are a whole separate concern.
You can try to decouple it by using arrays.
A sample implementation may look like this:
// This is not good code, take it as an "idea" or psuedocode
class Validator
{
static registeredRules = [];
static registerRule(name, run)
{
Validator.registeredRules.push({ name, run });
}
checkRuleForElement(ruleName, formElement)
{
var rule = Validator.registeredRule.find(element => element.name === ruleName);
return rule.run(formElement);
}
}
6. Abstract things
A thing you may want to consider is that there may be multiple forms or multiple elements, with your current implementation you are relying on the fact that there are is a unique element foreach "rule". Also you are interacting with the Dom directly which makes things hard to change when that assumption changes.
You could solve this by, for example using Dom elements as an input:
Validator.make({
document.querySelector("#user_input"): "required"
})
Bonus exercises
You can try to add a more advanced "rule parser" and add parameters for rules.
This could look like this:
Validator.make({
"username": "required|min:5"
})
Another thing you could try is, skipping this whole "naming" thing and using just functions
For example
const required = element => element != undefined;
// If you do not understand this Syntax, it's basically a function that returns a function.
const min = count => element => element.length > count
Validator.make({
"username": [required, min(5)]
});
This would give you several advantages. For example that your rules can be checked by a static analyzer, If you use strings there is always a chance you mistype something, like use "regired" instead of "required" and not even notice it, with functions on the other hand you will get an error if you use something that is undefined. Also you could reuse rules better.
Have fun :)
I wrote this from my mobile so if there are any mistakes let my know. I will most likely add more when I get my hands on a pc!