The original text is included in my GitHub blog (github.com/jawil/blog), like you can pay attention to the latest trends, we communicate and learn together, common progress, to the identity of learners to write blog, record bit by bit.
In a Web project, registration, login, modifying user information, placing orders and other functions are implemented without submitting forms. This article explains how to write relatively comfortable form validation code.
Suppose we are writing a page to register, and before clicking the register button, we have the following verification logic.
- All options cannot be empty
- The username must contain at least six characters
- The password must contain at least six characters
- The mobile phone number must conform to the format
- The email address must conform to the format
Note: For simplicity, the following example uses traditional browser form authentication, Ajax asynchronous request is not explored, browser-side authentication schematic diagram:
Brief description:
Here we only do the browser side of the front-end validation. Many tools can intercept form data after the form has been validated but before the browser sends the request. Attackers can modify the data in the request to bypass JavaScript and inject malicious data into the server, which increases the chances of XSS (Cross Site Scripting) attacks. Browser-side form validation is deprecated for general web sites. The browser-server dual authentication method adds server-side authentication on the basis of browser-side authentication method. Its principle is shown in the figure. This method adds server-side authentication and makes up for the shortcomings of traditional browser-side authentication. If the form input does not meet the requirements, Javascript validation on the browser side can respond quickly, while server-side validation can prevent malicious users from bypassing Javascript validation and ensure the accuracy of the final data.
HTML code:
<! DOCTYPE html> <html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<titleExplore several form validation best practices </title>
</head>
<body>
<form action="http://xxx.com/register" id="registerForm" method="post">
<div class="form-group">
<label for="user"> Please enter the user name :</label>
<input type="text" class="form-control" id="user" name="userName">
</div>
<div class="form-group">
<label for="pwd"> Please enter the password :</label>
<input type="password" class="form-control" id="pwd" name="passWord">
</div>
<div class="form-group">
<label for="phone"> Please enter your mobile phone number :</label>
<input type="tel" class="form-control" id="phone" name="phoneNumber">
</div>
<div class="form-group">
<label for="email"> Please enter email address :</label>
<input type="text" class="form-control" id="email" name="emailAddress">
</div>
<button type="button" class="btn btn-default">Submit</button>
</form>
</body>
</html>Copy the code
JavaScript code:
let registerForm = document.querySelector('#registerForm')
registerForm.addEventListener('submit'.function() {
if (registerForm.userName.value = = = '') {
alert('User name cannot be empty!')
return false
}
if (registerForm.userName.length < 6) {
alert('The username must be at least 6 characters long!')
return false
}
if (registerForm.passWord.value = = = '') {
alert('Password cannot be empty!')
return false
}
if (registerForm.passWord.value.length < 6) {
alert('The password must be at least 6 characters long!')
return false
}
if (registerForm.phoneNumber.value = = = '') {
alert('Mobile phone number cannot be empty!')
return false
}
if (!/^1 (3|5|7|8|9)[0-9]{9}$/.test(registerForm.phoneNumber.value)) {
alert('Incorrect phone number format!')
return false
}
if (registerForm.emailAddress.value = = = '') {
alert('Email address cannot be empty!')
return false
}
if (!/^\w+([+-.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
$/.test(registerForm.emailAddress.value)) {
alert('Email address format is not correct!')
return false}},false)Copy the code
1, problem,
Writing the code in this way can indeed meet the requirements of the business, can complete the form verification, but there are many problems, such as:
registerForm.addEventListener
The bound functions are quite large and contain manyif-else
Statements, disgusting to look at, these statements need to override all the validation rules.registerForm.addEventListener
Binding functions are inflexible, so if we add a new validation rule, or want to change the password length from 6 to 8, we have to drill downregisterForm.addEventListener
The internal implementation of a bound function, which violates the open-closed principle.- The algorithm is not reusable, and if another form is added to the program that requires some similar validation, we are likely to copy the validation logic all over the place.
So-called method always more than problems, is to some, such as to explain the use of the strategy pattern Make form validation more elegant and more perfect, I believe many people are resistant design patterns, the sound of the design pattern will feel very far away, feel rarely use design pattern in the work, then you are wrong, especially flexible languages such as JavaScript, Sometimes you already use design patterns in your code and you just don’t know it. There will be more blog posts about design patterns in the future, but I hope you can get rid of the mystique of design patterns, which are simply a generic way to get things done.
2, train of thought
Anyway, if we don’t want to use too many if-else statements, what’s the ideal way to write code? Can we do form validation the same way we write configuration? Wouldn’t it be nice to have a one-click verification feature? The answer is yes, so the ideal way to write code is as follows:
//Gets the form element
let registerForm = document.querySelector('#registerForm')
//Create a form validation instance
let validator = new Validator(a);//Writing verification configuration
validator.add(registerForm.userName.'isNonEmpty'.'The user name cannot be empty')
validator.add(registerForm.userName.'minLength:6'.'The username cannot contain less than 6 characters')
//Start verification and receive error messages
let errorMsg = validator.start(a)//If an error message is displayed, the verification fails
if(errorMsg){
alert(errorMsg)
return false//Preventing form submission
}Copy the code
How’s that? Feel it. Doesn’t it look elegant? Well, with these ideas in mind, we’re on our way to where we want to go, and the next step is to understand what strategic patterns are.
3. Strategy mode
Strategy mode, simply by its name “strategy”, refers to the way of doing things. For example, we want to travel somewhere. You can choose from several strategies: 1. 2, train, you can choose high-speed rail travel, designed for airplane phobia. 3, walking, can be regarded as a choice of exercise. 4, other method…
In the program design, we also often encounter a similar situation, to achieve a scheme there are a variety of options, for example, a compressed file program, you can choose zip algorithm, you can also choose GZIP algorithm.
So, you have a lot of ways to do something, what’s called strategy, and that’s what the strategy model that we’re going to talk about today means, and the core idea is to separate what to do from who to do it. Therefore, a complete policy pattern should have two classes, one is the policy class, one is the environment class (the main class), the environment class receives the request, but does not process the request, it will delegate the request to the policy class, let the policy class to handle, and the policy class is easy to extend, so that our code is easy to extend. In the form validation example, the various validation methods form the policy class, such as empty (isNonEmpty), minimum length (minLength), mobile phone number (isMoblie), etc. They form the policy class, which provides the environment class to delegate the request. Now, let’s do it.
4. Refactor form validation with policy mode
The composition of the policy pattern
Abstract Policy roles: Policy classes, usually implemented by an interface or abstract class. Concrete policy roles: Wrap the associated algorithms and behaviors. Context role: Holds a reference to a policy class that is ultimately used by the client.
4.1 Specific Policy Roles – Write policy classes
The policy class is very simple. It is an object made up of a set of validation methods, namely the policy object, which reconstructs the code for form validation. The obvious first step is to encapsulate the validation logic as a policy object:
/* Policy object */
const strategies = {
isNonEmpty(value.errorMsg) {
return value = = = '' ?
errorMsg : void 0
},
minLength(value.length.errorMsg) {
return value.length < length ?
errorMsg : void 0
},
isMoblie(value.errorMsg) {
return !/^1 (3|5|7|8|9)[0-9]{9}$/.test(value) ?
errorMsg : void 0
},
isEmail(value.errorMsg) {
return !/^\w+([+ -.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(value) ?
errorMsg : void 0}}Copy the code
4.2 Abstract Policy Roles — Write the Validator class
Based on our thinking, we use the add method to add the validation configuration as follows:
validator.add(registerForm.userName.'isNonEmpty'.'The user name cannot be empty')
validator.add(registerForm.userName.'minLength:6'.'The username cannot contain less than 6 characters')Copy the code
The add method takes three parameters: the first is a form field, the second is the name of the policy method in the policy object, and the third is the error message for the failed validation.
Then use the start method to start the validation. If the validation fails, an error message is returned as follows:
let errorMsg = validator.start()
Copy the code
Also, explain the following code:
The first argument to the add method is the form element to validate, the second argument is a string separated by a colon (:), followed by the policy method name, followed by the argument passed to the method, and the third argument is still an error message.
But this parameter configuration or have a question, our demands are various validation rules, such as user name can not empty, again to meet user name length not less than 6, not a single, why the above wrote twice, this looks uncomfortable, this time I will need to make some small changes to configuration parameters, we use an array to pass multiple validation rules:
validator.add(registerForm.userName, [{
strategy: 'isNonEmpty',
errorMsg: 'User name cannot be empty!'
}, {
strategy: 'minLength:6',
errorMsg: 'The username must be at least 6 characters long!'
}])Copy the code
Finally, an implementation of the Validator class:
/ * the Validator class * /
class Validator {
constructor() {
this.cache = [] //Save validation rules
}
add(dom.rules) {
for (let rule of rules) {
let strategyAry = rule.strategy.split(':') //For example, [' minLength, 6]
let errorMsg = rule.errorMsg //'User name cannot be empty'
this.cache.push(() = > {
let strategy = strategyAry.shift(a)//User-selected strategy
strategyAry.unshift(dom.value) //Add input value to the parameter list
strategyAry.push(errorMsg) //Add errorMsg to the parameter list, [dom.value,6,errorMsg]
return strategies[strategy].apply(dom, strategyAry)
})
}
}
start() {
for (let validatorFunc of this.cache) {
let errorMsg = validatorFunc(a)//Start verification and get the return information after verification
if (errorMsg) {//R If an exact value is returned, the verification fails
return errorMsg
}
}
}
}Copy the code
4.3 Environment Role – Client calls code
By refactoring code using policy patterns, you can validate a form simply by ‘configuring’ it, and these validation rules can be reused anywhere in the program and easily ported to other projects as plug-ins.
/* Client call code */
let registerForm = document.querySelector('#registerForm')
const validatorFunc =(a)= > {
let validator = new Validator(a)validator.add(registerForm.userName, [{
strategy: 'isNonEmpty',
errorMsg: 'User name cannot be empty!'
}, {
strategy: 'minLength:6',
errorMsg: 'The username must be at least 6 characters long!'
}])
validator.add(registerForm.passWord, [{
strategy: 'isNonEmpty',
errorMsg: 'Password cannot be empty!'
}, {
strategy: 'minLength:',
errorMsg: 'The password must be at least 6 characters long!'
}])
validator.add(registerForm.phoneNumber, [{
strategy: 'isNonEmpty',
errorMsg: 'Mobile phone number cannot be empty!'
}, {
strategy: 'isMoblie',
errorMsg: 'Incorrect phone number format!'
}])
validator.add(registerForm.emailAddress, [{
strategy: 'isNonEmpty',
errorMsg: 'Email address cannot be empty!'
}, {
strategy: 'isEmail',
errorMsg: 'Email address format is not correct!'
}])
let errorMsg = validator.start(a)return errorMsg
}
registerForm.addEventListener('submit'.function() {
let errorMsg = validatorFunc(a)if (errorMsg) {
alert(errorMsg)
return false}},false)Copy the code
Only a small amount of code needs to be written or rewritten to modify a validation rule. For example, we want to change the validation rule of the user name input field to a user name that must be at least 4 characters long. As you can see, the modifications are effortless. The code is as follows:
validator.add(registerForm.userName, [{
strategy: 'isNonEmpty',
errorMsg: 'User name cannot be empty!'
}, {
strategy: 'minLength:4',
errorMsg: 'The username must be at least 4 characters long!'
}])Copy the code
4.4 Advantages and disadvantages of the policy mode
- Strategy mode can avoid multiple conditional selection statements by using combination, delegation and polymorphism.
- The strategy mode provides perfect support for the open-closed principle and encapsulates the algorithm in an independent strategy, making it easy to switch, understand and expand.
- The algorithm in the policy pattern can also be reused in other parts of the system, so as to avoid a lot of repeated copy and paste work;
- Using composition and delegation in policy mode to give Context the ability to execute the algorithm is also a lighter alternative to inheritance.
Of course, there are some disadvantages to the strategic pattern, but if the strategic pattern is mastered, these disadvantages are not serious.
- Writing more difficult, more code, this is one of the most intuitive weaknesses, but it is not a weakness, after all, can not be measured solely by how much code.
- First, using policy patterns adds many policy classes or policy objects to your program, but it’s actually better than stacking the logic they’re responsible for in the Context.
- Secondly, in order to use the strategy pattern, it is necessary to understand all strategies and the differences between each strategy, so as to choose a suitable strategy. For example, to choose a suitable travel route, we must first understand the details of choosing planes, trains, bicycles and other options. At this point strategy exposes all of its implementations to the customer, which violates the least knowledge principle.
4.5 Significance of the Policy Mode
The policy pattern enables developers to develop software that consists of many replaceable parts that are weakly connected to each other. The feature of weak connection makes the software more expansible and easy to maintain; More importantly, it greatly improves the reusability of software.
Proxy objects about ES6d
Strategy mode is feasible, but the packaging is a bit too much, and it is not easy to write, the amount of code to write increased a lot, that is, there is a certain threshold, is there a better way to achieve it? Can we use a layer of proxies to intercept properties when they are set? This is the ES6 Proxy object today.
1, an overview of the
Proxy is used to modify the default behavior of certain operations, which is equivalent to making changes at the language level. Therefore, it is a kind of “meta programming”, that is, programming a programming language.
Proxy can be understood as a layer of “interception” before the target object. All external access to the object must pass this layer of interception. Therefore, Proxy provides a mechanism for filtering and rewriting external access. The word Proxy is used to mean that it acts as a Proxy for certain operations.
let obj = new Proxy({}, {
get (target, key, receiver) {
console.log(`getting The ${key}!`)
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
console.log(`setting The ${key}!`)
return Reflect.set(target, key, value, receiver)
}
})Copy the code
The code above sets up a layer of interception on an empty object, redefining the read (get) and set (set) behavior of the property. I won’t explain the syntax here, but just look at the results. To read and write properties of obj, the object with interception behavior set, the following result is obtained.
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2Copy the code
The code above shows that the Proxy actually overrides the point operator by overwriting the original definition of the language with its own definition.
ES6 natively provides a Proxy constructor to generate a Proxy instance.
let proxy = new Proxy(target, handler);Copy the code
All uses of Proxy objects are in this form, except for the handler arguments. The new Proxy() parameter represents the generation of a Proxy instance, the target parameter represents the target object to intercept, and the handler parameter is also an object used to customize the interception behavior.
Here is another example of intercepting the behavior of reading properties.
var proxy = new Proxy({}, {
get: function(target.property) {
return 35; }});proxy.time // 35
proxy.name // 35
proxy.title // 35Copy the code
In the code above, Proxy takes two arguments as a constructor. The first parameter is the target object (an empty object in this example) that the operation would have accessed without Proxy intervention. The second argument is a configuration object that needs to provide a corresponding handler function for each propped operation that intercepts the corresponding operation. For example, in the code above, the configuration object has a GET method that intercepts access requests to properties of the target object. The two arguments to the GET method are the target object and the property to be accessed. As you can see, since the interceptor always returns 35, accessing any property yields 35.
Note that for the Proxy to work, you must operate on the Proxy instance (the Proxy object in this example), not the target object (the empty object in this example).
2. Refactor form validation using Proxy
Use proxy to intercept data that does not meet requirements
function validator(target.validator.errorMsg) {
return new Proxy(target, {
_validator: validator,
set(target.key.value.proxy) {
let errMsg = errorMsg
if (value = = '') {
alert(`The ${errMsg[key]}Can't be empty!`)
return target[key] = false
}
let va = this._validator[key]
if (!!!!!va(value)) {
return Reflect.set(target, key, value, proxy)
} else {
alert(`The ${errMsg[key]}Malformed`)
return target[key] = false}}})}Copy the code
The logical code responsible for verification
const validators = {
name(value) {
return value.length > 6
},
passwd(value) {
return value.length > 6
},
moblie(value) {
return /^1 (3|5|7|8|9)[0-9]{9}$/.test(value)
},
email(value) {
return /^\w+([+ -.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(value)
}
}Copy the code
The client calls the code
const errorMsg = { name: 'The user name', passwd: 'password', moblie: 'Mobile phone number', email: 'Email address' }
const vali = validator({}, validators, errorMsg)
let registerForm = document.querySelector('#registerForm')
registerForm.addEventListener('submit'.function() {
let validatorNext = function*() {
yield vali.name = registerForm.userName.value
yield vali.passwd = registerForm.passWord.value
yield vali.moblie = registerForm.phoneNumber.value
yield vali.email = registerForm.emailAddress.value
}
let validator = validatorNext(a)validator.next(a);!vali.name || validator.next(a);//Perform the next step only when the verification succeeds
!vali.passwd || validator.next(a);!vali.moblie || validator.next(a); },false)Copy the code
Advantages: Conditions are completely isolated from the object itself, subsequent code maintenance, code cleanliness, and code robustness and reuse become very strong. Disadvantages: Poor compatibility, Babel, rough version, many details can be optimized, here is only one idea.
reference
Application of the ECMAScript 6 Entry Policy pattern to form validation