I am working on a project written with native JS these days, which needs the function of form verification. Because the form verification in the previous company project is written in the business, it is particularly troublesome to change, so I want to write a small tool for form verification. Originally wanted to find a tutorial study on the Internet, but did not find too good, finally decided to study it. The code of the examples in this article is my own demo, without reference to the source code of some frameworks or libraries, so the code may be ugly.
The point is to analyze the ideaLater, I will post the perfect code to Github.

Principle:

Here are some ideas or principles for the form validation tool I wrote:

  • Validation code is separated from business code
  • Extensibility is strong
  • Validation rules are separated from validation logic

There’s nothing to say about the first point, because most tools do this, and they suffer a lot from high coupling, so I’ll put it out there anyway. What I hope to achieve with this tool is that developers only need to pay attention to which data needs to be verified, and pass the data to the tool to get the verification results directly, so that developers can pay more attention to other businesses.

The second point is that developers should be able to customize the validation rules, because no amount of rules built in won’t create a weird requirement.

The third point is the separation of validation rules from validation logic, which is actually more in service of the second point.

The validation logic



This flow chart is the validation logic of my tool. At the beginning, I judge whether the incoming value can be null, if it can be null then I verify whether there are other validation rules, if there are no other validation rules (generally speaking, if the form value can be null, there will be no other rules in most cases). If there are other validation rules, go through the validation loop, and if one rule fails, the validation fails.

If the input can’t be empty, just loop through the rules and, again, fail the validation if there’s an error.

class Vaildation{}Copy the code

This is the class of the validation tool, so let’s think about what configuration parameters to pass to the class.

let vaild = [
  {
    value:this.username,
    type: "Username".rules: ["isDefine", 
      {
        name:"limit".check: true.min:5.max:12}]}, {value:this.password,
    type: "Password".rules: ["isDefine", 
      {
        name:"mix".check:true}, {name:"limit".check: true.min:5.max:12}}]];Copy the code

This is the configuration I want to pass to the class (you can use other styles if you want to design one yourself, either data or object). It is an array containing each object that needs to be validated. A brief description of the properties in the configuration is as follows:

Value: indicates the value of the data to be authenticated. The type is String or number. Type: indicates the name or label of the data to be passed in, such as the user name, password, email address, or phone number. There are two ways to write rules. There are two ways to write rules.'isDefine'.'limit'], which means it cannot be empty and has a word limit. The default rule is used. You can also customize specific rules such as: ['isDefine', {name: limit, check:true.min:5.max:12}], the check parameter means to make the rule take effect if you writefalseThis rule doesn't work even if you write it down. Min and Max are easy to understand6- 16A character.Copy the code

Let’s go back to class design:

class Vaildation{
  constructor() {this.isCheck = false;    
    this.vaild_item = [];    
    this.vaild; }}Copy the code

Vaild_item: stores the parameters of the class we passed in. Vaild: returns the message if the verification succeeds or failsCopy the code

Next, let’s design validation rules:

class Vaildation{
  constructor() {... } rules(){return {      
      mix: (vaild) = > {        
        for(let i = 0; i < vaild.rules.length; i++){          
          if(vaild.rules[i] == "mix" || vaild.rules[i].name == "mix") {if(vaild.rules[i].check){              
              if(! vaild.rules[i].newRules){return new RegExp(/^[a-zA-Z0-9]*([a-zA-Z][0-9]|[0-9][a-zA-Z])[a-zA-Z0-9]*$/).test(vaild.value);               
              }else{                
                return new RegExp(vaild.rules[i].newRules).test(vaild.value); }}else{              
              return true; }}}},limit: (vaild) = > {        
        let min;        
        let max;        
        vaild.rules.forEach((item) = > {          
          if(item == "limit" || item.name == "limit"){            
            min = item.min || 6;            
            max = item.max || 16; }})if(vaild.value.length < min || vaild.value.length > max){          
          return false;        
        }else{          
          return true; }},isDefine: (vaild) = > {        
        return!!!!! vaild.value.trim(); }}}},Copy the code

The rules method returns a rule object, and I have two rules built in:

One is “mix”, which specifies that the input is a mixture of numbers and letters.

Another “limit” sets the limit on the number of characters to be entered.

They take the value of the rules property from the previous configuration array, i.e. [“isDefine”, “mix”, “limit”]. We can see that these rules support passing strings or objects, and we can do some extra configuration when passing objects. For example, when we have some weird requirements such as “input must have a letter every third word”, we can add a new regular expression to the “mix” :

{  
  name:"mix".check:true.newRules: new regular expression,},Copy the code

Of course, if you have new rules such as phone and email, you can add them manually after creating a new instance of Vaildation.

let vaildation = new Vaildation();
vaild.rules()[newRules] = function(vaild){
    //new Rules
}Copy the code

With rules, we also need error messages. Here I use the same name for each rule as for the validation logic:

error(){
    return {
      mix: (vaild) = >{
        return vaild.type + "Must be a combination of letters and numbers.";
      },
      limit: (vaild) = > {
        let min;
        let max;
        vaild.rules.forEach((item) = > {
          if(item == "limit" || item.name == "limit"){
            min = item.min || 6;
            max = item.max || 16; }})return vaild.type + "Digit is" + min + "-" + max + "位";
      },
      define: (vaild) = > {
        return vaild.type + "Can't be empty."; }}}Copy the code

We can use the type attribute in the configuration to handle errors, or we can write the error file in error.

Finally, we’ll look at the logical handling of validation:

check_result(check, err = null) {return {      
    check: check, // Verify that it is null
    err: err, // Copy when validation fails
  } 
}

check(){
  for(let i = 0; i < this.vaild_item.length; i++){
    // Loop through each configuration
    let vaild = this.vaild_item[i].rules.findIndex(function(val){
      // Check whether there is an isDefine rule
      return val === "isDefine";
    });
    if(vaild > - 1) {let rules_arr = this.vaild_item[i].rules;
      // If there is an isDefine rule, remove it and process the rest of the rules
      rules_arr.splice(vaild,1);
      if(rules_arr.length > 0) {for(let j = 0; j < rules_arr.length; j++){
          for(let o in this.rules()){
            if(rules_arr[j]['name'] == o && !this.rules()[rules_arr[j]['name']].call(this.this.vaild_item[i])){
              // Matches the incoming configuration rule with the rule in the class. If the match fails, the verification information is returned
              this.vaild = [this.check_result(false.this.error()[rules_arr[j]['name']].call(this.this.vaild_item[i]))];
              this.isCheck = false;
              return this.vaild;
            }else{
              this.isCheck = true;
              this.vaild = [this.check_result(true)];
            }
          }
        }
      }
    }else{
      if(this.vaild_item[i].rules.length > 0&&!!!!!this.vaild_item[i].value){
        let rules_arr = this.vaild_item[i].rules;
        for(let j = 0; j < rules_arr.length; j++){
          // The processing logic is the same as above
          for(let o in this.rules()){
            if(rules_arr[j]['name'] == o && !this.rules()[rules_arr[j]['name']].call(this.this.vaild_item[i])){
              this.vaild = [this.check_result(false.this.error()[rules_arr[j]['name']].call(this.this.vaild_item[i]))];
              this.isCheck = false;
              return this.vaild;
            }else{
              this.isCheck = true;
              this.vaild = [this.check_result(true)];
            }
          }
        }
      }
    }
  }
  return this.vaild;
}
Copy the code

The check method is the core logic, which is actually the content of the above flowchart. I have also noted some important points (sorry, this part of the code is a little ugly, demo… demo…) .

Finally, I’ll show you what the tool looks like in action:

// Config file
let vaild = [
  {
    value:this.username,
    type: "Username".rules: ["isDefine", 
      {
        name:"limit".check: true.min:5.max:12}]}, {value:this.password,
    type: "Password".rules: ["isDefine", 
      {
        name:"mix".check:true}, {name:"limit".check: true.min:5.max:12}}]];let vaildation = new Vaildation(); // Create a new instance of Vaildation
vaildation.check_items = vaild;  // Pass the configuration to the check_items in the instance
let result = vaildation.check(); // Ok, you have the result

// Don't forget to check for success in result.check or vaildation. IsCheckCopy the code

This article is just an idea when I design this tool. There may still be a lot of problems, so I will introduce them to you and hope you can correct them more. Finally, thank you for watching.