AngularJS 学习笔记(表单校验篇)

在新的项目中巧妙地接触到了Google提供的前端MVC框架 AngularJS,它的数据双向绑定真的能够让前端开发者从繁复的DOM操作中解放出来。

在我断断续续的学习AngularJS一个多月后,可以来讲讲我的 AngularJS 学习笔记了,当前首先要讲的是前端必不可少的元素——表单,以下用ng代替AngularJS,本文章适合有一定ng基础的同学。

Demo页面我使用Bootstrap3.2.0作为前端显示框架,同时引入了jQuery1.11.1,AngularJS 为刚刚新发布的1.2.25(使用1.3.0-rc2出现了一个奇怪的BUG,看来还是稳定版稳定),首先来一个大而全的表单页面:

<div class="row" ng-controller="myCtrl">
	<div class="col-sm-6">
		<form name="myForm" autocomplete="off" ng-submit="doSubmit()" novalidate>
			<fieldset>
				<legend>My AngularJS Form</legend>
				<div class="form-group" ng-class="{'has-error':myForm.submitted && myForm.username.$invalid}">
					<label for="username"><code>*</code>用户名称</label>
					<!---->
					<input type="text" class="form-control" name="username" ng-model="formArgs.username" ng-minlength="5" ng-maxlength="30" required>
					<!---->
					<span class="help-block" ng-show="myForm.submitted && myForm.username.$error.required">请输入5-30个字符的用户名称!</span>
					<!---->
					<span class="help-block" ng-show="myForm.submitted && myForm.username.$error.minlength">输入的用户名必须在5至30个字符之间!</span>
					<!---->
					<span class="help-block" ng-show="myForm.submitted && myForm.username.$error.remoted">输入的用户名称已经被使用!</span>
				</div>
				<div class="form-group" ng-class="{'has-error':myForm.submitted && myForm.userEmail.$invalid}">
					<label for="userEmail"><code>*</code>用户邮箱</label>
					<!---->
					<input type="email" class="form-control" name="userEmail" ng-model="formArgs.userEmail" maxlength="30" required>
					<!---->
					<span class="help-block" ng-show="myForm.submitted && myForm.userEmail.$error.required">请输入您常用的电子邮箱!</span>
					<!---->
					<span class="help-block" ng-show="myForm.submitted && myForm.userEmail.$error.email">输入的电子邮箱地址有误!</span>
				</div>
				<div class="form-group" ng-class="{'has-error':myForm.submitted && myForm.password.$invalid}">
					<label for="password"><code>*</code>用户密码</label>
					<!---->
					<input type="password" class="form-control" name="password" ng-model="formArgs.password" ng-minlength="8" ng-maxlength="30" required>
					<!---->
					<span class="help-block" ng-show="myForm.submitted && myForm.password.$error.required">请输入8-30个字符的用户密码!</span>
					<!---->
					<span class="help-block" ng-show="myForm.submitted && myForm.password.$error.minlength">输入的重复密码必须在8至30个字符之间!</span>
				</div>
				<div class="form-group" ng-class="{'has-error':myForm.submitted && myForm.rpassword.$invalid}">
					<label for="rpassword"><code>*</code>重复密码</label>
					<!---->
					<input type="password" class="form-control" name="rpassword" ng-model="tmpArgs.rpassword" ng-minlength="8" ng-maxlength="30" my-pwd-match="myForm.password"
						required>
					<!---->
					<span class="help-block" ng-show="myForm.submitted && myForm.rpassword.$error.required">请输入8-30个字符的重复密码!</span>
					<!---->
					<span class="help-block" ng-show="myForm.submitted && myForm.rpassword.$error.minlength">输入的重复密码必须在8至30个字符之间!</span>
					<!---->
					<span class="help-block" ng-show="myForm.submitted && myForm.rpassword.$error.pwdmatch">重复密码必须与用户密码一致!</span>
				</div>
				<div class="form-group" ng-class="{'has-error':myForm.submitted && myForm.userUrl.$invalid}">
					<label for="userUrl">用户主页</label>
					<!---->
					<input type="url" class="form-control" name="userUrl" ng-model="formArgs.userUrl" maxlength="200">
					<!---->
					<span class="help-block" ng-show="myForm.submitted && myForm.email.$error.url">输入的网址有误!</span>
				</div>
				<div class="form-group" ng-class="{'has-error':myForm.submitted && myForm.userAge.$invalid}">
					<label for="userAge">用户年龄</label>
					<!---->
					<input type="number" class="form-control" name="userAge" ng-model="formArgs.userAge" min="0" max="150">
					<!---->
					<span class="help-block" ng-show="myForm.submitted && myForm.userAge.$error.number">输入的年龄有误!</span>
				</div>
				<div class="form-group" ng-class="{'has-error':myForm.submitted && myForm.safeType.$invalid}">
					<label for="safeType"><code>*</code>安全选项</label>
					<!---->
					<select class="form-control" name="safeType" ng-model="formArgs.safeType" ng-options="it.value as it.text for it in safeTypes" required>
						<option value="">--请选择--</option>
					</select> <span class="help-block" ng-show="myForm.submitted && myForm.safeType.$error.required">请选择安全选项!</span>
				</div>
				<div class="form-group" ng-class="{'has-error':myForm.submitted && myForm.privateType.$invalid}">
					<label><code>*</code>谁可以找到我</label>
					<div class="radio" ng-init="formArgs.privateType='member'">
						<label><input type="radio" name="privateType" value="all" ng-model="formArgs.privateType"> 所与人</label>
						<!---->
						<label><input type="radio" name="privateType" value="member" ng-model="formArgs.privateType" checked> 注册会员</label>
						<!---->
						<label><input type="radio" name="privateType" value="self" ng-model="formArgs.privateType"> 只有我自己</label>
					</div>
				</div>
				<div class="form-group">
					<button class="btn btn-warning" type="reset">重置</button>
					<button class="btn btn-primary" type="submit">提交</button>
				</div>
			</fieldset>
		</form>
	</div>
	<div class="col-sm-6">
		<pre>{{formArgs|json}}</pre>
		<pre>{{myForm|json}}</pre>
	</div>
</div>

请忽略这个表单字段组合的合理性^_^。表单中,为所有字段绑定了ngModel,设置了校验指令,例如required,ngMinlength,ngMaxlength等,同时利用了ngShow、ngClass这两个指令来实现校验错误信息的提示。

在ngApp内定义一个form,实际上表示这个form已经被ng接管了,在相关的 ngController 的 $scope 域中会自动生成与这个form验证有关的字段,但是需要注意:

  1. 给 form 设置 name 属性才能使用ng的表单校验,可能设置了name,ng才会接管这个form
  2. 给 form 设置 novalidate 属性是关闭浏览器内置的校验器,各个浏览器内置的校验器功能不一,也不够ng丰富,所以还是关了比较好;
  3. 避免给 form 设置 action 属性,设置了可能会导致表单不经校验直接提交的结果,当然也可以用其他方案解决这个问题;
  4. 给 form 设置 autocomplete="off" 是关闭浏览器的自动填写功能,这个功能有时候连按钮的disabled状态也记住了,可能会导致避免表单重复提交策略上的问题。

页面或CSS中可以通过 formName.inputFieldName.property 来访问表单字段的验证状态,主要有这几种状态:

  1. formName.inputFieldName.$pristine,Boolean类型,表单字段是否未修改;
  2. formName.inputFieldName.$dirty,Boolean类型,表单字段是否已修改;
  3. formName.inputFieldName.$valid,Boolean类型,表单字段是否验证通过;
  4. formName.inputFieldName.$invalid,Boolean类型,表单字段是否未通过验证;
  5. formName.inputFieldName.$error,Object类型,存储表单字段验证项的通过与否,例如
    formName.inputFieldName.$error.required,这个Boolean类型表示表单字段是否未通过必填的验证;

以上是默认的表单字段的验证状态,存储在$scope域中,表单自身也有这几个状态:

  1. formName.$pristine
  2. formName.$dirty
  3. formName.$valid
  4. formName.$invalid

ng会给表单元素加上一些校验相关的css,表单字段会根据验证状态添加这几个class:

  • .ng-valid
  • .ng-invalid
  • .ng-pristine
  • .ng-dirty

可以设置这些css的属性改变表单域的样式,例如设置:input.ng-invalid {border-color:red;},把未验证通过的表单元素边框设置为红色。

ng支持大部分的的HTML5表单类型,具体请查看ng的文档。表单中需要注意的几点:
  1. 如果在input绑定了ng-model,点击表单上的reset按钮并不会重置ng-model中绑定的数据,所以建议给reset按钮添加ng-click事件实现表单的重置;
  2. select 元素如果绑定了ng-model且无空值的option,那么ng会自动给select添加一个空option,在用户选择select的值以后,这个空 option会自动消失,所以为了显示方便,建议给相关的select添加一个value属性为空的option,例如<optionvalue=""></option>,或者使用ng-init给绑定的ng-model初始化一个值;
  3. checkbox 或radio在用户点击后会优先执行浏览器给定的状态,所以可能会出现设置ng-checked失效的情况,例如给checkbox设置ng- checked="selectCount ==10",如果 $scope.selectCount 永远不等于10,但用户手动去点击这个checkbox,依然可以使这个checkbox处于选中状态,不过如果用户操作其他元素使 $scope.selectCount 的值改变还是会导致checkbox的状态变化的,我当前暂时未发现更优雅的解决方案,主要是给该checkbox添加ng-click事件,重复了 ng-checked 的检查代码,然后通过DOM操作重新设置了checkbox的状态;

表单字段的验证也可以自定义,在Demo的表单中,我自定义了一个重复密码校验的指令(my-pwd-match="myForm.password"):

myApp.directive('myPwdMatch', [function(){
	return {
		restrict: "A",
		require: 'ngModel',
		link: function(scope,element,attrs,ctrl){
			var tageCtrl = scope.$eval(attrs.myPwdMatch);
			tageCtrl.$parsers.push(function(viewValue){
				ctrl.$setValidity('pwdmatch', viewValue == ctrl.$viewValue);
				return viewValue;
			});
			ctrl.$parsers.push(function(viewValue){
				if(viewValue == tageCtrl.$viewValue){
					ctrl.$setValidity('pwdmatch', true);
					return viewValue;
				} else{
					ctrl.$setValidity('pwdmatch', false);
					return undefined;
				}
			});
		}
	};
}]);

该自定义指令具体作用是:同时监听“用户密码”和”重复密码“两个字段的变化,根据用户的输入值的异同设置”重复密码“验证状态:

myForm.rpassword.$error.pwdmatch;
注意:重复密码输入框必须绑定ngModel才能使用这个指令。在ng的1.3.0-rc2版本上我定义这个指令在使用时发现ng无故给“重复密码”设置了一个验证状态:

myForm.rpassword.$error.parse;
暂不清楚这个是 BUG 还是1.2和1.3两个版本之间指令实现的差异。

上面这个是实时的本地验证,如果需要要实现字段的远端验证,一般验证的频率不会那么频繁,建议添加一些触发条件:

  • 当字段值长度等于多少时执行远端验证(这个比较适合图片验证码);
  • 延迟验证,就是等一两秒数据如果没有变化再执行验证;
  • 输入框丢失焦点后再触发验证(适合大部分输入框);
  • 用户点击提交按钮后再触发验证。

开发者可以使用这些远端验证中的一种或几种的组合,在确定输入框是否处于丢失焦点状态可以使用如下的指令:

myApp.directive('myFocusValid', [function(){
	return {
		restrict: "A",
		require: 'ngModel',
		link: function(scope,element,attrs,ctrl){
			ctrl.$focused = false;
			ctrl.$blured = true;
			element.bind("focus", function(evt){
				scope.$apply(function(){
					ctrl.$focused = true;
					ctrl.$blured = false;
				});
			}).bind("blur", function(evt){
				scope.$apply(function(){
					ctrl.$focused = false;
					ctrl.$blured = true;
				});
			});
		}
	};
}]);
该指令是给输入框校验器添加两个状态:$focused 和 $blured,为input添加 my-focus-valid 属性即可以使用这个指令。

不过我还是喜欢在用户点击按钮以后再使用远端验证,因为这样做可以避免在js中写复杂的阻止表单提交的逻辑,这个表单我使用ngSubmit指令,详细的实现请看controller:

myApp.controller("myCtrl", ["$scope", "$http", function($scope,$http){
	console.log($scope)
	
	$scope.safeTypes = [{
		value: 0,
		text: "不保存账户状态"
	}, {
		value: 30,
		text: "保存半个小时"
	}, {
		value: 60,
		text: "保存一个小时"
	}, {
		value: 180,
		text: "保存三个小时"
	}, {
		value: 60 * 24,
		text: "保存一天"
	}, {
		value: 60 * 24 * 7,
		text: "保存一周"
	}, {
		value: 60 * 24 * 30,
		text: "保存一个月"
	}];

	$scope.$watch("formArgs.username", function(newVal,oldVal){
		var ctrl = $scope.myForm.username;
		var usedNames = ctrl.$usedNames;
		if(usedNames && usedNames[newVal]){
			ctrl.$setValidity('remoted', false);
		} else{
			ctrl.$setValidity('remoted', true);
		}
	});

	$scope.doSubmit = function(){
		var username = $scope.formArgs ? $scope.formArgs.username : undefined;
		var ctrl = $scope.myForm.username;
		if(username){
			$http({
				method: 'POST',
				url: 'json/check-username.json',
				data: {
					username: username
				}
			}).success(function(resp){
				if(resp.status != "success"){
					ctrl.$setValidity('remoted', false);
					if(ctrl.$usedNames){
						ctrl.$usedNames[username] = true;
					} else{
						var obj = {};
						obj[username] = true;
						ctrl.$usedNames = obj;
					}
				} else{
					ctrl.$setValidity('remoted', true);
				}
				if($scope.myForm.$valid){
					alert("提交表单数据");
				}
			}).error(function(){
				ctrl.$setValidity('remoted', false);
			});
		} else{
			ctrl.$setValidity('remoted', true);
		}
		$scope.myForm.submitted = true;
	}
}]);
doSubmit事件中,我先判断用户名是否可以已被使用,然后再真正的执行表单的提交动作,同时也无论验证状态如何,都会设置表单的 submitted 状态为 true,让校验的结果能够在页面上快速的显示出来。

ng的1.2版本支持的表单元素主要是input、select、button、textarea,其中input的type主要包括:text、hidden、password、checkbox、radio、file、image、reset、submit和扩展的email、url、number,1.3版在1.2版的基础上又扩展了一些字段,包括:

  1. dateTimeLocal:yyyy-MM-dd'T'HH:mm:ss;
  2. date:yyyy-MM-dd;
  3. month:yyyy-MM;
  4. time:HH:mm:ss;
  5. week:yyyy-W##;

这些日期类型,所有的日期字段默认都是ISO的标准样式,但跟国内的习惯还是有一些差异,例如dateTimeLocal中间多了一个字母T,week表示该年的第几周,例如2014-W49,这些日期类型的加入大大的增强了ng的功能,希望1.3版本能够尽快的实现stable,不过不知道ng是否支持<input type="range">这个值域类型呢?

表单验证的效果图:

项目工程文件:

http://download.csdn.net/download/cackling/7954867


版权信息:发表在 http://blog.csdn.net/vipshop_ebs 本文为本人所为,依然原创。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值