angular学习(十一)—— Form

来源:互联网 发布:几点睡觉 知乎 编辑:程序博客网 时间:2024/04/29 11:45

转载请写明来源地址:http://blog.csdn.net/lastsweetop/article/details/54972551

form

control主要用于接收用户输入,例如input, select, textarea。而form是一组相关控件的集合。form和control提供验证服务,可以在用户提交表单到后端前得到无效输入的通知。

一个简单的form

form的验证要比服务端的验证好很多,因为更加及时得到反馈以便纠正错误。虽然客户端的验证体验很好,但是可以很容易做到规避验证,服务端的验证还是非常必要的,特别是那些安全要求高的应用。

理解angularjs双向绑定的关键点在于指令ngModel,ngModel提供了视图到模型,模型到视图的双向绑定,它还提供了一些api给其他指令,用于增强它的作用。

<!DOCTYPE html><html><head>    <meta charset="uft-8"/>    <title></title></head><script src="script/angular.min.js"></script><body ng-app="formExample"><div ng-controller="ExampleController">    <form novalidate class="simple-form">        <label>Name: <input type="text" ng-model="user.name" /></label><br />        <label>E-mail: <input type="email" ng-model="user.email" /></label><br />        Best Editor: <label><input type="radio" ng-model="user.preference" value="vi" />vi</label>        <label><input type="radio" ng-model="user.preference" value="emacs" />emacs</label><br />        <input type="button" ng-click="reset()" value="Reset" />        <input type="submit" ng-click="update(user)" value="Save" />    </form>    <pre>user = {{user | json}}</pre>    <pre>master = {{master | json}}</pre></div><script>    angular.module('formExample', [])            .controller('ExampleController', ['$scope', function($scope) {                $scope.master = {};                $scope.update = function(user) {                    $scope.master = angular.copy(user);                };                $scope.reset = function() {                    $scope.user = angular.copy($scope.master);                };                $scope.reset();            }]);</script></body></html>

注意novalidate可以禁用浏览器对表单进行原生性的校验。
除非输入通过校验,否则ngModel对应的值不会被设置。比如email类型的输入框必须使用user@domain的格式。

使用css class

为了表单和控件一样都有样式,ngModel增加了一些css class:

  • ng-valid:model有效
  • ng-invalid:model无效
  • ng-valid-[key]:通过$setValidity添加的每一个有效的key
  • ng-invalid-[key]:通过$setValidity添加的每一个无效的key
  • ng-pristine:控件值为初始状态
  • ng-dirty:控件的值已经做过了修改
  • ng-touched:控件已经被点击过并失去焦点
  • ng-untouched:控件还未被点击过
  • ng-pending:还有$asyncValidators未完成。

下面的例子使用css来显示控件的输入有效性。在这个例子中,user.name和user.email是必须输入的,但是只有当输入是模糊的时候(控件被点击并失去焦点之后)才会显示红色的背景,这确保了用户只有在和控件交互之后,而输入无法满足验证条件,才会提示错误,而在交换之前说不会提示错误的。

<!DOCTYPE html><html><head>    <meta charset="uft-8"/>    <title></title></head><script src="script/angular.min.js"></script><body ng-app="formExample"><div ng-controller="ExampleController">    <form novalidate class="css-form">        <label>Name: <input type="text" ng-model="user.name" required /></label><br />        <label>E-mail: <input type="email" ng-model="user.email" required /></label><br />        Gender: <label><input type="radio" ng-model="user.gender" value="male" />male</label>        <label><input type="radio" ng-model="user.gender" value="female" />female</label><br />        <input type="button" ng-click="reset()" value="Reset" />        <input type="submit" ng-click="update(user)" value="Save" />    </form>    <pre>user = {{user | json}}</pre>    <pre>master = {{master | json}}</pre></div><style type="text/css">    .css-form input.ng-invalid.ng-touched {        background-color: #FA787E;    }    .css-form input.ng-valid.ng-touched {        background-color: #78FA89;    }</style><script>    angular.module('formExample', [])            .controller('ExampleController', ['$scope', function($scope) {                $scope.master = {};                $scope.update = function(user) {                    $scope.master = angular.copy(user);                };                $scope.reset = function() {                    $scope.user = angular.copy($scope.master);                };                $scope.reset();            }]);</script></body></html>

form和control的状态绑定

在angualr中,form是FormController的实例,form实例可以通过name属性发布到scope中。同样,带有ngModel指令的输入控件一样也是NgModelController实例,一样可以通过控件的name属性发布,作为form实例的一个属性。
这意味着form和控件的内部状态一样可以通过标准的绑定方式在视图中绑定。

我们来扩展一下上面的例子:

  • 在用户和控件交互之后,显示自定义的错误信息
  • 用户提交表单时显示自定义错误信息,即使用户和控件没有交互
<!DOCTYPE html><html><head>    <meta charset="uft-8"/>    <title></title></head><script src="script/angular.min.js"></script><body ng-app="formExample"><div ng-controller="ExampleController">    <form  novalidate class="css-form" name="form">        <label>Name: <input type="text" ng-model="user.name" name="uName" required="" /></label><br />        <div ng-show="form.$submitted || form.uName.$touched">            <div ng-show="form.uName.$error.required">Tell us your name.</div>        </div>        <label>E-mail: <input type="email" ng-model="user.email" name="uEmail" required /></label><br />        <div ng-show="form.$submitted || form.uEmail.$touched">            <span ng-show="form.uEmail.$error.required">Tell us your email.</span>            <span ng-show="form.uEmail.$error.email">This is not a valid email.</span>        </div>        Gender: <label><input type="radio" ng-model="user.gender"  value="male" />male</label>        <label><input type="radio" ng-model="user.gender" value="female" />female</label><br />        <label>        <input type="checkbox" ng-model="user.agree" name="userAgree" required="" />        I agree:        </label>        <input ng-show="user.agree" type="text" ng-model="user.agreeSign" required="" />        <br />        <div ng-show="form.$submitted || form.userAgree.$touched">            <div ng-show="!user.agree || !user.agreeSign">Please agree and sign.</div>        </div>        <input type="button" ng-click="reset(form)" value="Reset" />        <input type="submit" ng-click="update(user)" value="Save" />    </form>    <pre>user = {{user | json}}</pre>    <pre>master = {{master | json}}</pre></div><style type="text/css">    .css-form input.ng-invalid.ng-touched {        background-color: #FA787E;    }    .css-form input.ng-valid.ng-touched {        background-color: #78FA89;    }</style><script>    angular.module('formExample', [])            .controller('ExampleController', ['$scope', function($scope) {                $scope.master = {};                $scope.update = function(user) {                    $scope.master = angular.copy(user);                };                $scope.reset = function(form) {                    if (form) {                        form.$setPristine();                        form.$setUntouched();                    }                    $scope.user = angular.copy($scope.master);                };                $scope.reset();            }]);</script></body></html>

自定义model更新触发器

默认情况下,对内容对任何更改都会触发model的改变和表单的验证。你可以通过ngModelOptions指令绑定到特殊的事件列表中以重写这种行为。ng-model-options="{ updateOn: 'blur' }"表示仅仅在用户输入并且失去焦点时才会更新model并且进行表单验证。如果是多个事件,可以使用空格分开,例如:ng-model-options="{ updateOn: 'mousedown blur' }"

如果想保留默认的行为,而只是在默认事件上添加新的事件可以触发更新和验证,那么只需添加default作为一个普通的事件之一。
ng-model-options="{ updateOn: 'default blur' }"

下面是个重写立即更新对例子。只有在用户和输入框产生交互时(点击控件并且失去焦点时),输入框的更改才会更新到model中。

<!DOCTYPE html><html><head>    <meta charset="uft-8"/>    <title></title></head><script src="script/angular.min.js"></script><script>    angular.module('customTriggerExample', [])            .controller('ExampleController', ['$scope', function($scope) {                $scope.user = {name:"aa"};            }]);</script><body ng-app="customTriggerExample"><div ng-controller="ExampleController">    <form>        <label>Name:            <input type="text" ng-model="user.name" ng-model-options="{ updateOn: 'blur' }" /></label><br />        <label>            Other data:            <input type="text" ng-model="user.data" /></label><br />    </form>    <pre>username = "{{user.name}}"</pre>    <pre>userdata = "{{user.data}}"</pre></div></body></html>

延时model更新

你也可以通过ngModelOptions指令的debounce键延时更新和验证。这个延迟对于解析器,验证器和model的标签($dirty或$pristine.)同样起作用。

ng-model-options="{ debounce: 500 }"表示将最后一次内容的改变触发model更新和form验证之前要等上500毫秒。

如果使用了自定义的触发器,那么使用debounce对象对每一个事件加上自定义的消抖超时时间。这在某些特定情况下还是有用的,比如blur事件后需要立即更新和验证。语法如下:
ng-model-options="{ updateOn: 'default blur', debounce: { default: 500, blur: 0 } }"

注意,如果这些属性添加到某个元素中,那么这个元素的所有子元素和控件都会继承他的设定,除非子元素和控件重写这些属性。

下面的例子演示的是model更改的消抖,model将会在内容最后一次更改250毫米后进行更新。

<!DOCTYPE html><html><head>    <meta charset="uft-8"/>    <title></title></head><script src="script/angular.min.js"></script><script>    angular.module('debounceExample', [])            .controller('ExampleController', ['$scope', function($scope) {                $scope.user = {};            }]);</script><body ng-app="debounceExample"><div ng-controller="ExampleController">    <form>        <label>Name:            <input type="text" ng-model="user.name" ng-model-options="{ debounce: 250 }" /></label><br />    </form>    <pre>username = "{{user.name}}"</pre></div></body></html>

自定义验证

AngularJS为html5最常用的输入类型(text, number, url, email, date, radio, checkbox)提供了基本的实现,以及一些用于验证的指令(required, pattern, minlength, maxlength, min, max)。

如果想自定义验证的指令,你需要在ngModelController实例的$validators对象中增加你自定义的验证函数。可以参照下面的例子。

$validators对象的每一个验证函数都有两个参数modelValue和viewValue,angularjs会在内部调用$setValidity函数,参数就是这个验证函数的返回值。每次输入发生变化($setViewValue被调用)或约束模型发生变化时,验证函数都会执行。parsersformatters成功运行之后,验证会分别进行。如果验证失败,错误的key会保存在ngModelController.$error中。

此外,$asyncValidators对象处理异步的验证,比如需要通过http到后台请求验证数据的情况。添加到这个对象的验证函数必须返回一个promise,在有效时候resolve,无效时候reject。在异步验证的过程中,验证的key会被保存到ngModelController.$pending中。

下面的例子创建了两个验证指令:

  • integer指令验证输入是否是一个整数。例如,1.33就是个无效值。注意我们验证的事viewValue,控件中输入的值,而不是modleValue,这是因为类型为number的输入框会通过$parsers将viewValue转化为数字,如果我们输入的事1.00,那么modelValue将会是1,而viewValue是1.00,显然是无效的

  • username指令,异步检查用户输入的值是否已经存在,我们用$q.defer()来模拟服务器请求。

<!DOCTYPE html><html><head>    <meta charset="uft-8"/>    <title></title></head><script src="script/angular.min.js"></script><body ng-app="form-example1"><form name="form" class="css-form" novalidate>    <div>        <label>            Size (integer 0 - 10):            <input type="number" ng-model="size" name="size"                   min="0" max="10" integer />{{size}}</label><br />        <span ng-show="form.size.$error.integer">The value is not a valid integer!</span>        <span ng-show="form.size.$error.min || form.size.$error.max">      The value must be in range 0 to 10!</span>        <span ng-show="form.size.$error.require">111</span>    </div>    <div>        <label>            Username:            <input type="text" ng-model="name" name="name" username />{{name}}</label><br />        <span ng-show="form.name.$pending.username">Checking if this name is available...</span>        <span ng-show="form.name.$error.username">This username is already taken!</span>    </div></form></body><script>    var app = angular.module('form-example1', []);    var INTEGER_REGEXP = /^-?\d+$/;    app.directive('integer', function() {        return {            require: 'ngModel',            link: function(scope, elm, attrs, ctrl) {                ctrl.$validators.integer = function(modelValue, viewValue) {                    if (ctrl.$isEmpty(modelValue)) {                        // consider empty models to be valid                        return true;                    }                    if (INTEGER_REGEXP.test(viewValue)) {                        // it is valid                        return true;                    }                    // it is invalid                    return false;                };            }        };    });    app.directive('username', function($q, $timeout) {        return {            require: 'ngModel',            link: function(scope, elm, attrs, ctrl) {                var usernames = ['Jim', 'John', 'Jill', 'Jackie'];                ctrl.$asyncValidators.username = function(modelValue, viewValue) {                    if (ctrl.$isEmpty(modelValue)) {                        // consider empty model valid                        return $q.resolve();                    }                    var def = $q.defer();                    $timeout(function() {                        // Mock a delayed response                        if (usernames.indexOf(modelValue) === -1) {                            // The username is available                            def.resolve();                        } else {                            def.reject();                        }                    }, 2000);                    return def.promise;                };            }        };    });</script></html>

修改内置验证

angularjs有一些内置的验证器,你可以很容易的移除或替换这些验证器。下面的例子演示了如何重写类型为email的验证器。

<!DOCTYPE html><html><head>    <meta charset="uft-8"/>    <title></title></head><script src="script/angular.min.js"></script><body ng-app="form-example-modify-validators"><form name="form" class="css-form" novalidate>    <div>        <label>            Overwritten Email:            <input type="email" ng-model="myEmail" overwrite-email name="overwrittenEmail" />        </label>        <span ng-show="form.overwrittenEmail.$error.email">This email format is invalid!</span><br>        Model: {{myEmail}}    </div></form></body><script>    var app = angular.module('form-example-modify-validators', []);    app.directive('overwriteEmail', function() {        var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@example\.com$/i;        return {            require: 'ngModel',            link: function(scope, elm, attrs, ctrl) {                // 指定了ngModel,并且输入类型为email                if (ctrl && ctrl.$validators.email) {                    // 重写angularjs的内置email验证器                    ctrl.$validators.email = function(modelValue) {                        return ctrl.$isEmpty(modelValue) || EMAIL_REGEXP.test(modelValue);                    };                }            }        };    });</script></html>

自定义form控件

angularjs实现了所有的html基本控件(input, select, textarea),大多数情况下足够用了,但是如果你需要更多的灵活性,你可以自定义form控件。

为了让自定义的控件可以支持ngModel,并且带有双向绑定,需要这样做:

  • 实现$render方法,在NgModelController.$formatters之后它负责对数据进行渲染
  • 在用户和控件交互之后,模型需要更新时,调用$setViewValue方法。通常是由DOM的事件监听器做的。

可以看下下面这个例子:

<!DOCTYPE html><html><head>    <meta charset="uft-8"/>    <title></title></head><script src="script/angular.min.js"></script><body ng-app="form-example2"><div contentEditable="true" ng-model="content" title="Click to edit">Some</div><pre>model = {{content}}</pre><style type="text/css">    div[contentEditable] {        cursor: pointer;        background-color: #D0D0D0;    }</style></body><script>    angular.module('form-example2', []).directive('contenteditable', function() {        return {            require: 'ngModel',            link: function(scope, elm, attrs, ctrl) {                // view -> model                elm.on('blur', function() {                    ctrl.$setViewValue(elm.html());                });                // model -> view                ctrl.$render = function() {                    elm.html(ctrl.$viewValue);                };                // load init value from DOM                ctrl.$setViewValue(elm.html());            }        };    });</script></html>





如果我的文章对您有帮助,请用支付宝打赏:

1 0
原创粉丝点击