博弈AngularJS讲义(12) - 表单

   本节我们将通过几个具体的例子来讲解Angular表单。

简单的表单

   ngModel指令实现了双向的数据绑定,即模型和视图双向同步,同时它也为其他指令提供了API来扩展ngModel的行为。让我们看如下示例:

   

<div ng-controller="ExampleController">
  <form novalidate class="simple-form">
    Name: <input type="text" ng-model="user.name" /><br />
    E-mail: <input type="email" ng-model="user.email" /><br />
    Gender: <input type="radio" ng-model="user.gender" value="male" />male
    <input type="radio" ng-model="user.gender" value="female" />female<br />
    <input type="button" ng-click="reset()" value="Reset" />
    <input type="submit" ng-click="update(user)" value="Save" />
  </form>
  <pre>form = {{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>

   在上述代码中,我们定义了个简单的表单,novalidate表明没有添加任何校验逻辑,在表单组定义了各种输入控件绑定到模型user的不同属性,当用户输入是会看到form信息不停的更新,点击“Save”按钮会把当前表单的值保存到master变量中,master信息会相应的更新, 点击"Reset"信息会重置表单的信息,即把之前保存的快照master赋值给user. 这里通过angular.copy函数做了深拷贝。

   运行结果如下:

博弈AngularJS讲义(12) - 表单

 

表单的CSS样式 

   angular为表单控件添加了额外的样式来提升用户体验:

    - ng-valid: 数据合法时的CSS样式

    - ng-invalid: 数据不合法时的CSS样式

    - ng-valid-[key]: 通过$setValidity指定的key合法时生效的样式

    - ng-invalid-[key]: 通过$setValidity指定的key不合法时生效的样式

    - ng-pristine: 未曾与表单控件交互时的样式

    - ng-dirty: 与控件发生了交互后生效的样式

    - ng-touched: 控件失去焦点的样式

    - ng-untouched: 控件没有失去焦点时的样式

    - ng-pending: 等待异步校验过程中的(in-progress)生效的样式

  基于上面的例子我们加上了校验,并通过上述样式体现出来,user的name和email属性对应的控件加上了required属性, 当用户未输入name和email信息时会标红提示。

   

<div ng-controller="ExampleController">
  <form novalidate class="css-form">
    Name: <input type="text" ng-model="user.name" required /><br />
    E-mail: <input type="email" ng-model="user.email" required /><br />
    Gender: <input type="radio" ng-model="user.gender" value="male" />male
    <input type="radio" ng-model="user.gender" value="female" />female<br />
    <input type="button" ng-click="reset()" value="Reset" />
    <input type="submit" ng-click="update(user)" value="Save" />
  </form>
</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>

   运行结果:

博弈AngularJS讲义(12) - 表单
 

绑定及控制表单状态

   每一个表单都有一个表单控制器与之对应。可以通过name属性将表单实例公开到作用域。同理一个输入控件可以通过ngModel指令持有一个NgModelController,并且可以通过name属性变成表单实例的属性,name属性值即表单实例的属性。

   这就意味着表单控件的内部状态可以通过angular的标准方式来绑定。

   我们就上面的例子进行一下扩展:

    - 在用户和控件交互后显示定制的错误消息(通过$touched服务组件)

    - 在提交表单按钮时显示定制的错误消息(通过$submitted服务组件)

  index.html

  

<div ng-controller="ExampleController">
  <form name="form" class="css-form" novalidate>
    Name:
    <input type="text" ng-model="user.name" name="uName" required="" />
    <br />
    <div ng-show="form.$submitted || form.uName.$touched">
      <div ng-show="form.uName.$error.required">Tell us your name.</div>
    </div>

    E-mail:
    <input type="email" ng-model="user.email" name="uEmail" required="" />
    <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:
    <input type="radio" ng-model="user.gender" value="male" />male
    <input type="radio" ng-model="user.gender" value="female" />female
    <br />
    <input type="checkbox" ng-model="user.agree" name="userAgree" required="" />

    I agree:
    <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>
</div>

 

 script.js

 

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();
}]);

 

  在上面的html中,通过name="form" 暴露了form实例,既可以在 $scope.reset = function(form) 通过参数注入,同样<inputtype="email"ng-model="user.email"name="uEmail"required=""/>,通过name="uEmail"将该控件绑定到form.uEmail属性.

 

  运行结果:

博弈AngularJS讲义(12) - 表单

 

定制触发器更新模型 

  默认情况下,表单的每次输入都会触发与模型的同步和表单的校验。我们可以通过ngModelOptions指令改变模型同步和表单验证的触发条件。例如ng-model-options="{ updateOn: 'blur' }",会使Angular在输入控件失去焦点时更新模型或者进行校验。多个事件通过空白隔开,例如:

ng-model-options="{ updateOn: 'mousedown blur' }". 如果保持默认触.发条件并添加新的触发事件,加上“default”选项,

例如:ng-model-options="{ updateOn: 'default blur' }".

  下面例子中,对模型的同步和表单校验将会在表单控件失去焦点是被触发:

  index.html

<div ng-controller="ExampleController">
  <form>
    Name:
    <input type="text" ng-model="user.name" ng-model-options="{ updateOn: 'blur' }" /><br />
    Other data:
    <input type="text" ng-model="user.data" /><br />
  </form>
  <pre>username = "{{user.name}}"</pre>
  <pre>userdata = "{{user.data}}"</pre>
</div>

  script.js

angular.module('customTriggerExample', [])
.controller('ExampleController', ['$scope', function($scope) {
  $scope.user = {};
}]);

 

  运行结果:

   
博弈AngularJS讲义(12) - 表单

 

  在name属性对应的控件中通过ng-model-options="{ updateOn: 'blur' }" 设置了在blur事件发生时更新username属性而属性userdata这在每次输入后即更新模型,结果如图所示, username并没有实时更新。

 

延迟模型更新(去抖)

我们可以通过debounce属性让模型延迟更新。这个延迟对解析器,校验器和模型的标记(例如$dirty和$pristine)都生效。例如:ng-model-options="{ debounce: 500 }" 将会在内容更新后半秒钟更新模型和校验表单。如果使用自定义的触发器,可以每个触发器的每个事件设置延迟更新,例如:ng-model-options="{ updateOn: 'default blur', debounce: { default: 500, blur: 0 }。这些属性设置对所在的元素及子元素或控件有效。如下例子中我们设置了延迟250毫秒更新模型:

index.html

<div ng-controller="ExampleController">
  <form>
    Name:
    <input type="text" ng-model="user.name" ng-model-options="{ debounce: 250 }" /><br />
  </form>
  <pre>username = "{{user.name}}"</pre>
</div>

 script.js

angular.module('debounceExample', [])
.controller('ExampleController', ['$scope', function($scope) {
  $scope.user = {};
}]);

 

自定义校验

   Angular对HTML5最常用的输入控件及校验属性提供了支持,例如 text, number, url, email, radio, checkbox控件,required,pattern, minLength, maxLength, min, max属性。我们可以为ngMedelController自定义校验器($validators)。$validators里面的每个方法包含两个参数,modelValue和viewValue, 返回值布尔类型。Angular会调用$setValidity设置校验结果。校验方法会在输入变化($setViewValue)或者模型更新后被调用。校验会在$parsers和$formatter运行成功后进行。校验的错误信息会保存在ngModelController.$error中。

   另外Angular还提供了$asyncValidators对象处理异步校验,例如基于ajax的校验。 该对象中的方法需返回promise对象,校验成功被设成resolved状态,失败为rejected状态,正在进行中的校验则存在ngModelController.$pending属性中。

  在下面的例子中我们创建了两个指令:

  - 一个integer指令来校验输入是否为合法的整数。例如1.23为非法的整数。这里我们校验的是控件中的输入,不是所绑定的模型,因为$parsers会把input[number]控件中的输入转换成数字。

  - 一个username指令,对用户输入异步校验。我们通过$q来mock服务器请求。

  index.html

<form name="form" class="css-form" novalidate>
  <div>
    Size (integer 0 - 10):
    <input type="number" ng-model="size" name="size"
           min="0" max="10" integer />{{size}}<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>
  </div>

  <div>
    Username:
    <input type="text" ng-model="name" name="name" username />{{name}}<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>

  script.js

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.when();
        }

        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;
      };
    }
  };
});

  运行结果:


博弈AngularJS讲义(12) - 表单
Username的校验是异步的可以看到通过$q.defer() mock了延迟。

 

重写Angular内建的校验器

   Angular默认使用$validators校验器, 我们可以根据需求重写或者去掉内建的校验器。下面的例子中我们重写了input[email]校验器,email地址必须包含顶级域名example.com. 

   index.html

<form name="form" class="css-form" novalidate>
  <div>
    Overwritten Email:
    <input type="email" ng-model="myEmail" overwrite-email name="overwrittenEmail" />
    <span ng-show="form.overwrittenEmail.$error.email">This email format is invalid!</span><br>
    Model: {{myEmail}}
    </div>
</form>

   script.js

var app = angular.module('form-example-modify-validators', []);

app.directive('overwriteEmail', function() {
  var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@example\.com$/i;

  return {
    require: 'ngModel',
    restrict: '',
    link: function(scope, elm, attrs, ctrl) {
      // only apply the validator if ngModel is present and Angular has added the email validator
      if (ctrl && ctrl.$validators.email) {

        // this will overwrite the default Angular email validator
        ctrl.$validators.email = function(modelValue) {
          return ctrl.$isEmpty(modelValue) || EMAIL_REGEXP.test(modelValue);
        };
      }
    }
  };
});

   在link方法中我们加入了对email顶级域名的检查,可以从运行结果中看到,只有但email通过校验是,才会给模型赋值。

   运行结果:

   

 

  

自定义表单控件

   Angular实现了所有的HTML表单基本控件,可以满足大部分需求。如果我们需要更灵活的表单控件,可以将其实现为指令。

   可以通过如下方法实现双向数据绑定,是控件能够和ngModel一起工作:

   - 实现$render方法,在NgModelController.$formatters 执行完后渲染数据。

   - 调用$setViewValue方法来同步视图控件和模型。通常在DOM事件监听器里面调用。

   可以查看$compileProvider.directive API获得更多的知识.

   让我们通过具体的例子详解contentEditable元素的双向数据绑定。

   index.html

<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>

   script.js  

angular.module('form-example2', []).directive('contenteditable', function() {
  return {
    require: 'ngModel',
    link: function(scope, elm, attrs, ctrl) {
      // view -> model
      elm.on('blur', function() {
        scope.$apply(function() {
          ctrl.$setViewValue(elm.html());
        });
      });

      // model -> view
      ctrl.$render = function() {
        elm.html(ctrl.$viewValue);
      };

      // load init value from DOM
      ctrl.$setViewValue(elm.html());
    }
  };
});

   运行结果: 


 

你可能感兴趣的:(form,api,表单,Angular,文档翻译)