使用 JavaScript 实现表单的验证是一个很常见的问题,jQuery 有不少表单验证的插件,只需要经过简单的配置就能完成验证的功能。
因为是初学 JavaScript,所以先自己用 jQuery 写了一个表单验证的例子,然后讨论一下怎样将 jQuery 的表单验证用到 Backbone.js 构建的程序中,最后使用 Backbone.Model 的 validate() 方法来实现验证的过程。
表单只有四个 input,界面如图所示:
各字段的验证规则如下:
6.1.html 表单相关的 HTML 代码如下,因为想熟悉一下 jQuery 选择器的使用,所以 HTML 中没有使用任何的 id/class:
1 <form action="#" method="post"> 2 <p> 3 <label for="username">Username:</label> 4 <input type="text" name="username" /> 5 </p> 6 <p> 7 <label for="password">Password:</label> 8 <input type="text" name="password" /> 9 </p> 10 <p> 11 <label for="re-password">Re-password:</label> 12 <input type="text" name="re-password" /> 13 </p> 14 <p> 15 <label for="email">Email:</label> 16 <input type="text" name="email" /> 17 </p> 18 <p><input type="submit" value="Register" /></p> 19 </form>
jquery-validate-1.js 的源码如下,关键的地方已经给出注释:
1 $(document).ready(function(){ 2 3 //光标移动到username的input时触发,如果已经有错误/正确提示则不改变提示,如果没有提示输入 4 $("input[name='username']").focus(function(){ 5 if ($("input[name='username']+span.error").size() == 0 6 && $("input[name='username']+span.success").size() == 0) { 7 $(this).after("<span class='hint'>Please enter your username.</span>"); 8 } 9 }); 10 //光标移出username的input时触发 11 $("input[name='username']").blur(function(){ 12 $("input[name='username']+span").remove(); //去掉之前的提示 13 if($(this).val().length < 6 || $(this).val().length>12){ //判断长度,给出提示 14 $(this).after("<span class='error'>Username contains 6-12 characters.</span>"); 15 }else{ //向服务器发出AJAX请求,判断username是否已经存在,给出提示 16 $.ajax({ 17 url:"./rest/user/validate/"+$(this).val(), 18 success:function(data, textStatus){ 19 if(data == "true"){ 20 $("input[name='username']").after("<span class='success'>Passed.</span>"); 21 }else{ 22 $("input[name='username']").after("<span class='error'>Username has been used.</span>"); 23 } 24 } 25 }); 26 } 27 }); 28 29 $("input[name='password']").focus(function(){ 30 if ($("input[name='password']+span.error").size() == 0 31 && $("input[name='password']+span.success").size() == 0) { 32 $(this).after("<span class='hint'>Please enter password again.</span>"); 33 } 34 }); 35 $("input[name='password']").blur(function(){ 36 $("input[name='password']+span").remove(); 37 if($(this).val().length < 6 || $(this).val().length>12){ 38 $(this).after("<span class='error'>Password contains 6-12 characters.</span>"); 39 }else{ 40 $(this).after("<span class='success'>Passed.</span>"); 41 } 42 }); 43 44 45 $("input[name='re-password']").focus(function(){ 46 if ($("input[name='re-password']+span.error").size() == 0 47 && $("input[name='re-password']+span.success").size() == 0 ) { 48 $(this).after("<span class='hint'>Please enter your password again.</span>"); 49 } 50 }); 51 $("input[name='re-password']").blur(function(){ 52 $("input[name='re-password']+span").remove(); 53 if($(this).val() ==""){ 54 $(this).after("<span class='error'>Please enter your password again.</span>"); 55 }else if($(this).val() != $("input[name='password']").val()){ //判断两次输入的password是否相同 56 $(this).after("<span class='error'>Two passwords are not same.</span>"); 57 }else{ 58 $(this).after("<span class='success'>Passed.</span>"); 59 } 60 }); 61 62 $("input[name='email']").focus(function(){ 63 if ($("input[name='email']+span.error").size() == 0 64 && $("input[name='email']+span.success").size() == 0 ) { 65 $(this).after("<span class='hint'>Please enter your email.</span>"); 66 } 67 }); 68 $("input[name='email']").blur(function(){ 69 $("input[name='email']+span").remove(); 70 if(!$(this).val().match(/^\w{3,}@\w+(\.\w+)+$/)){ //正则表达式判断email格式是否正确 71 $(this).after("<span class='error'>Invalid email format.</span>"); 72 }else{ 73 $(this).after("<span class='success'>Passed.</span>"); 74 } 75 }); 76 77 //表单提交的时候触发所有input的blur事件,如果没有error才能提交表单 78 $("form").submit(function(){ 79 $("input").blur(); 80 if ($("form span.error").size() > 0) { 81 return false; 82 } 83 }); 84 85 });
注意看78-83行,表单提交的事件(submit)发生时手动触发了所有 input 的 blur 事件,如果没有 error 才能提交表单。这样可以避免没有填写某个 input,而导致它的 blur 事件没有触发,并没有进行正确的验证。
写这个例子时也遇到了一些问题,在解决问题的过程中学到了不少,对 jQuery 的选择器、AJAX请求、页面操作等等都更熟悉了。
上面 jQuery 的表单验证是在我学习 Backbone.js 之前写的,因此和我们的这个示例程序不太一样,我们之前写的 Backbone.js 程序中没有表单,少了 re-password,多了 phone,也懒得再改了,直接拿过来用吧,反正关键问题是如何整合起来使用。
我们简单地将 jquery-validate-1.js 引入到 5.html 文件中,测试发现根本不起作用。原因是 jquery-validate-1.js 中使用的是最常见的绑定事件的方法,只能将事件绑定到 $(document).ready() 执行的时候已经存在的页面元素上,而我们的所有 input 都是后来由 Backbone.js 显示出来的,所以绑定不上。
jQuery 中对事件的绑定有 bind() live() delegate() 三种方法,三者的区别看这里:http://kb.cnblogs.com/page/94469/。简单地来说 bind() 将事件绑定到特定的 DOM 元素上,加载的时候不存在就绑不上;live() 将事件绑定到 $(document) 元素上,事件发生的时候动态地去搜索符合要求的 DOM 元素;delegate() 将事件绑定到某个 DOM 元素上,事件发生的时候动态地去它的所有子元素里面搜索符合要求的 DOM 元素。因此我们使用 delegate() 改编一下 jquery-validate-1.js。
例如,在jquery-validate-1.js 中
$("input[name='username']").focus(function(){...});
改为 jquery-validate-2.js 中的:
$("#right").delegate("input[name='username']","focus",function(){...});
其他 input 相关的事件也这么修改一下就行了,测试之后发现对 input 的验证都可以正常使用了。
但是我们的程序中没有表单,因此也不会触发 submit 事件,只需要将 submit 事件上绑定的的内容移到 UserInfoView.submitEdit() UserListView.submitUserForm() 中,在 model 向服务器端 sync 之前判断有没有 error,如果有 error 就阻止 sync 的执行。mvc6.2.js 代码如下所示:
1 var UserInfoView = Backbone.View.extend({ 2 submitEdit : function() { //修改 3 $("input").blur(); 4 if ($("span.error").size() > 0) { 5 return; 6 } 7 this.model.save({ 8 "username":$("input[name='username']").val(), 9 "password":$("input[name='password']").val(), 10 "email":$("input[name='email']").val(), 11 "phone":$("input[name='phone']").val(), 12 }); 13 this.$("#user-info").removeClass("editing"); 14 }, 15 ...//其余不变 16 }); 17 18 var UserListView = Backbone.View.extend({ 19 submitUserForm : function() { //修改 20 $("input").blur(); 21 if ($("span.error").size() > 0) { 22 return; 23 } 24 var user = new User({ 25 "username":$("input[name='username']").val(), 26 "password":$("input[name='password']").val(), 27 "email":$("input[name='email']").val(), 28 "phone":$("input[name='phone']").val(), 29 }); 30 31 this.userList.create(user,{wait:true}); 32 $("input[name='username']").val(""), 33 $("input[name='password']").val(""), 34 $("input[name='email']").val(""), 35 $("input[name='phone']").val(""), 36 alert("Add a user!"); 37 }, 38 ...//其余不变 39 });
还有一个方法就是将这部分内容移到 Backbone.Model 的 validate() 方法中去,也就是说还可以这么修改:
1 var User = Backbone.Model.extend({ 2 validate : function(attrs) { //新增 3 $("input").blur(); 4 if ($("span.error").size() > 0) { 5 return "hahaha"; //随便返回什么,触发error事件就能阻止sync的发生了 6 } 7 } 8 });
上面两个解决方法选一个就行了。关于 validate() 方法的详解请看下一小节。
首先来看一下官网对 validate() 的说明:
model.validate(attributes)
该方法是未定义的,如果有在Javascript执行的需要,建议用自定义的验证逻辑重载它。 validate 会在 set 和 save 之前调用,并传入待更新的属性。 如果模型和属性通过验证,不返回任何值; 如果属性不合法,返回一个可选择的错误。该错误可以是简单的用于显示的字符串错误信息, 或者是一个可以描述错误详细的 error 对象。 如果 validate 返回错误,set 和 save 将不会执行。 失败的验证会触发一个 "error" 事件。
所以说 validate() 只能对 model 的所有属性整体做一次验证,然后返回错误信息,不能像 jQuery 那样在填完每一个 input 之后都进行验证。
Model 里面虽然没有定义 validate() 方法,但是有个 _validate() 方法,会在 Model.set()、Model.save()、Collection.create() 中被调用,_validate() 的源码如下:
1 // Run validation against the next complete set of model attributes, returning `true` if all is well. If a specific `error` callback has been passed, call that instead of firing the general `"error"` event. 2 _validate: function(attrs, options) { 3 if (options.silent || !this.validate) return true; 4 attrs = _.extend({}, this.attributes, attrs); 5 var error = this.validate(attrs, options); 6 if (!error) return true; 7 if (options && options.error) { 8 options.error(this, error, options); 9 } else { 10 this.trigger('error', this, error, options); 11 } 12 return false; 13 }
可以看出来,如果 validate() 有返回值,那么就会触发 error 事件(10行),并且有 this、error、options 三个参数,我们可以根据第二个参数来输出错误信息。
首先在 6.3.html 的表单中加入显示错误信息的部分:
1 <!-- 增加显示错误信息的位置 --> 2 <script type="text/template" id="user-info-template"> 3 <h3>User Information</h3> 4 <button id="edit">Edit</button> 5 <p class="error"></p> 6 <ul id="user-info"> 7 <li>ID:<span><%= id %></span></li> 8 <li>Username:<span><%= username %></span><input type="text" name="username" value="<%= username %>" /></li> 9 <li>Password:<span><%= password %></span><input type="password" name="password" value="<%= password %>" /></li> 10 <li>Email:<span><%= email %></span><input type="text" name="email" value="<%= email %>" /></li> 11 <li>Phone:<span><%= phone %></span><input type="text" name="phone" value="<%= phone %>" /></li> 12 <button id="edit-submit">Submit</button> 13 </ul> 14 </script> 15 <!-- 增加显示错误信息的位置 --> 16 <script type="text/template" id="user-form-template"> 17 <h3>Add User</h3> 18 <p class="error"></p> 19 <ul id="user-form" class="editing"> 20 <li>Username:<input type="text" name="username" /></li> 21 <li>Password:<input type="password" name="password" /></li> 22 <li>Email:<input type="text" name="email" /></li> 23 <li>Phone:<input type="text" name="phone" /></li> 24 <button id="add-submit">Submit</button> 25 </ul> 26 </script>
然后对 mvc6.3.js 进行修改,在 User 中加入 validate() 方法,在 UserInfoView.submitEdit()、UserListView.submitUserForm() 中加入对错误信息的显示:
1 $(document).ready(function() { 2 3 var User = Backbone.Model.extend({ 4 validate : function(attrs) { //新增,验证属性是否合法 5 if (attrs.username.length<6 || attrs.username.length>12) { 6 return "Username contains 6-12 characters."; 7 } 8 var obj = $.ajax({ 9 url : "./rest/user/validate/" + attrs.username, 10 async : false, 11 }); 12 if (obj.responseText == "false") { 13 return "Username has been used."; 14 } 15 if (attrs.password.length<6 || attrs.password.length>12) { 16 return "Password contains 6-12 characters."; 17 } 18 if (attrs.email.length == 0) { 19 return "Please enter your email."; 20 } 21 if(!attrs.email.match(/^\w{3,}@\w+(\.\w+)+$/)){ 22 return "Invalid email format."; 23 } 24 }, 25 }); 26 27 28 var UserInfoView = Backbone.View.extend({ 29 submitEdit : function() { //修改 30 var m = this.model; 31 this.model.save({ 32 "username":$("input[name='username']").val(), 33 "password":$("input[name='password']").val(), 34 "email":$("input[name='email']").val(), 35 "phone":$("input[name='phone']").val(), 36 }, 37 { 38 error : function(m,error) { //显示错误信息 39 $(".error").html(error); 40 }, 41 success : function() { 42 $("#user-info").removeClass("editing"); 43 } 44 }); 45 }, 46 ... //其余不变 47 }); 48 49 var UserListView = Backbone.View.extend({ 50 submitUserForm : function() { 51 var user = new User({ 52 "username":$("input[name='username']").val(), 53 "password":$("input[name='password']").val(), 54 "email":$("input[name='email']").val(), 55 "phone":$("input[name='phone']").val(), 56 }); 57 var m = this.model; //修改 58 this.userList.create(user,{ 59 wait : true, 60 error : function(m,error) { //显示错误信息 61 $(".error").html(error); 62 }, 63 success : function() { 64 $("input[name='username']").val(""), 65 $("input[name='password']").val(""), 66 $("input[name='email']").val(""), 67 $("input[name='phone']").val(""), 68 alert("Add a user!"); 69 } 70 }); 71 }, 72 ... //其余不变 73 }); 74 ... //其余不变 75 });
完成之后的界面如下图所示: