Extending JavaScript Form Validation Function with Policy and Decoration Patterns

Keywords: Mobile less

Last night in order to practice these two design patterns
Just write a small example of form validation.
There are still some problems in the process of knocking.
Now let's share with you.

Simple form validation

html structure

<!-- validata.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Validata</title>
</head>
<body>
  <form id="form">
    <label for="username">Account number:</label><input id="username" type="text"><br>
    <label for="password">Password:</label><input id="password" type="password"><br>
    <label for="phonenum">Mobile phone:</label><input id="phonenum" type="text"><br>
    <input id="submit" type="button" value="Submission">
  </form>
  <p id="warn"></p>
  <script src="validata.js"></script>
</body>
</html>

First, simply implement the following functions
Then use design patterns to enrich

// validata.js
var form = document.getElementById('form'),
    warn = document.getElementById('warn');
var validata = function(){
  if(form.username.value === ''){
    return warn.textContent = 'Account cannot be empty';
  }
  if(form.password.value === ''){
    return warn.textContent = 'Password cannot be empty';
  }
  if(form.phonenum.value === ''){
    return warn.textContent = 'Mobile phone number cannot be empty';
  }
  var msg = {
    username: form.username.value,
    password: form.password.value,
    phonenum: form.phonenum.value
  }
  //Ajax ('...', msg); Ajax submits data slightly
  return warn.textContent = 'User information has been successfully submitted to the server';
}
form.submit.onclick = function(){
  validata();
}

Then analyze the following code
validata has no reusability, but there are two other problems.

  • Functions assume both verification and submission responsibilities, contrary to the single responsibility principle.
  • Validation has poor scalability. If you want to add validation rules, you must go deep into the function and violate the open-closed principle.

So we need to improve it.

Reconstruction of Decoration Model

Firstly, we use decoration mode to solve the problem of multi-function and multi-responsibility.
The method is also simple.
Improve AOP Pre-Decoration Function.prototype.before
When the extended function (beforeFn) returns false, the current function is not executed
Then make the form validation function a pre-decoration of the form submission function
This validates before submission, and if the validation fails, no data will be submitted.

var form = document.getElementById('form'),
    warn = document.getElementById('warn');
Function.prototype.before = function(beforeFn){
  var self = this;
  return function(){
    if(beforeFn.apply(this, arguments) === false)
      return;
    return self.apply(this, arguments);
  }
}//Improved AOP Pre-Decorative Function
var validata = function(){
  if(form.username.value === ''){
    warn.textContent = 'Account cannot be empty';
    return false;
  }
  if(form.password.value === ''){
    warn.textContent = 'Password cannot be empty';
    return false;
  }
  if(form.phonenum.value === ''){
    warn.textContent = 'Mobile phone number cannot be empty';
    return false;
  }
}
var submitMsg = function(){ //Extract the submitted function from the validata function
  var msg = {
    username: form.username.value,
    password: form.password.value,
    phonenum: form.phonenum.value
  }
  //ajax('...', msg);
  return warn.textContent = 'User information has been successfully submitted to the server';
}
submitMsg = submitMsg.before(validata);
//Make form validation functions decorators of form submission functions
form.submit.onclick = function(){
  submitMsg();
};

Reconstruction of Policy Patterns

Here's how to solve the problem of inelasticity of functions
Using policy patterns requires policy objects/classes and environment objects/classes
There's no doubt that there should be validation rules in policy objects
Considering that the page may have more than one validation form
Best written in factory-class form
The complete code is as follows

var form = document.getElementById('form'),
    warn = document.getElementById('warn');
Function.prototype.before = function(beforeFn){
  var self = this;
  return function(){
    if(beforeFn.apply(this, arguments) === false)
      return;
    return self.apply(this, arguments);
  }
}
var vldStrategy = { //Policy Object-Validation Rules
  isNonEmpty: function(value, warnMsg){ //Input is not empty
    if(value === '')
      return warnMsg;
  },
  isLongEnough: function(value, length, warnMsg){ //Enter long enough
    if(value.length < length)
      return warnMsg;
  },
  isShortEnough: function(value, length, warnMsg){ //Enter short enough
    if(value.length > length)
      return warnMsg;
  },
  isMobile: function(value, warnMsg){ //Mobile phone number verification
    var reg = /^1[3|5|8][0-9]{9}$/;
    if(!reg.test(value))
      return warnMsg;
  }
}
var Validator = function(){ //Environmental class
  this.rules = []; //Arrays are used to store functions responsible for validation
};
Validator.prototype.add = function(domNode, ruleArr){ //Add validation rules
  var self = this;
  for(var i = 0, rule; rule = ruleArr[i++];){
    (function(rule){
      var strategyArr = rule.strategy.split(':'),
          warnMsg = rule.warnMsg;
      self.rules.push(function(){
        var tempArr = strategyArr.concat();
        var ruleName = tempArr.shift();
        tempArr.unshift(domNode.value);
        tempArr.push(warnMsg);
        return vldStrategy[ruleName].apply(domNode, tempArr);
      });
    })(rule);
  }
  return this;
};
Validator.prototype.start = function(){ //Start validating the form
  for(var i = 0, vldFn; vldFn = this.rules[i++];){
    var warnMsg = vldFn();
    if(warnMsg){
      warn.textContent = warnMsg;
      return false;
    }
  }
}
var vld = new Validator();
vld.add(form.username, [
  {
    strategy: 'isNonEmpty',
    warnMsg: 'Account cannot be empty'
  },
  {
    strategy: 'isLongEnough:4',
    warnMsg: 'Account No less than 4 digits'
  },
  {
    strategy: 'isShortEnough:20',
    warnMsg: 'Account number should not be greater than 20 digits'
  }
]).add(form.password, [
  {
    strategy: 'isNonEmpty',
    warnMsg: 'Password cannot be empty'
  }
]).add(form.phonenum, [
  {
    strategy: 'isNonEmpty',
    warnMsg: 'Mobile phone number cannot be empty'
  },
  {
    strategy: 'isMobile',
    warnMsg: 'Incorrect format of mobile phone number'
  }
]);
var submitMsg = function(){
  var msg = {
    username: form.username.value,
    password: form.password.value,
    phonenum: form.phonenum.value
  }
  //ajax('...', msg);
  return warn.textContent = 'User information has been successfully submitted to the server';
}
submitMsg = submitMsg.before(vld.start.bind(vld));
form.submit.onclick = function(){
  submitMsg();
};
//This is just a simulated submission. Actually, form.onsubmit should be used.

problem analysis

Summarize the mistakes and the problems I encountered when I knocked.

Validator.prototype.add = function(domNode, ruleArr){
  var self = this;
  for(var i = 0, rule; rule = ruleArr[i++];){
    (function(rule){
      var strategyArr = rule.strategy.split(':'),
          warnMsg = rule.warnMsg;
      self.rules.push(function(){
        var tempArr = strategyArr.concat();
        var ruleName = tempArr.shift();
        tempArr.unshift(domNode.value);
        tempArr.push(warnMsg);
        return vldStrategy[ruleName].apply(domNode, tempArr);
      });
    })(rule);
  }
  return this;
};

Attention should be paid to several problems of add function on Validator prototype chain
First add IIFE immediate execution function to solve the closure problem.
Functions are nested inside the function, causing this to be hijacked, so this must be cached.
var self = this;

At first, I did not copy the array, but used strategyArr directly.

Validator.prototype.add = function(domNode, ruleArr){ //Add validation rules
  var self = this;
  for(var i = 0, rule; rule = ruleArr[i++];){
    (function(rule){
      var strategyArr = rule.strategy.split(':'),
          warnMsg = rule.warnMsg;
      self.rules.push(function(){
        // var tempArr = strategyArr.concat();
        var ruleName = strategyArr.shift();
        strategyArr.unshift(domNode.value);
        strategyArr.push(warnMsg);
        return vldStrategy[ruleName].apply(domNode, strategyArr);
      });
    })(rule);
  }
  return this;
};

The first submission is fine, but the second submission will result in an error.

This is because after the first commit, the strategy Arr in the closure has changed
Later submission, the operation on this array is not the expected result

I made a small mistake in this place, which led to a long debugging of breakpoint _____________

Validator.prototype.start = function(){ //Start validating the form
  for(var i = 0, vldFn; vldFn = this.rules[i++];){
    var warnMsg = vldFn();
    if(warnMsg){
      warn.textContent = warnMsg;
      return false;
    }
  }
}

The error code before correction is like this

Validator.prototype.start = function(){ //Start validating the form
  for(var i = 0, vldFn; vldFn = this.rules[i++];){
    var warnMsg = vldFn();
    if(warnMsg)
      warn.textContent = warnMsg;
      return false;
  }
}

That's right. It's just because there's less braces.
Maybe there was only one line before, but I forgot to add it when I added return false.

I don't write braces here just for brevity's sake.
We should never write code like that. We just dig holes and jump for ourselves.

submitMsg = submitMsg.before(vld.start.bind(vld));

Attention should also be paid to the place where decorators are added.
If you don't write bind, this hijacking will happen, and you will report an error as well.

== Home Portal==

Posted by cali_dotcom on Wed, 27 Mar 2019 17:27:30 -0700