此章节重点说明Angular.js内置的已经注入到默认的作用域控制器、指令、服务。后续章节讨论用户定如何自定义指令、服务等。
2.3.1.概念
作用域(scope)是构成AngularJS应用的核心基础,在整个框架中都被广泛使用,应用的作用域是和应用的数据模型相关联的,同时作用域也是表达式执行的上下文。 $scope对象是定义应用业务逻辑、控制器方法和视图属性的地方。Angular.js作用域与JavaScript作用域不同,名称以$开头的方法表示与作用域有关。
作用域是视图和控制器之间的胶水。在应用将视图渲染并呈献给用户之前,视图中的模板会和作用域进行连接,然后应用会对DOM进行设置以便将属性变化通知给AngularJS。作用域是应用状态的基础。基于动态绑定,我们可以依赖视图在修改数据时立刻更新 $scope,也可以依赖$scope在其发生变化时立刻重新渲染视图。
AngularJS将$scope设计成和DOM类似的结构,因此$scope可以进行嵌套,也就是说我们可以引用父级$scope中的属性。作用域提供了监视数据模型变化的能力。它允许开发者使用其中的 apply 机制,将数据模型的变化在整个应用范围内进行通知。我们在作用域的上下文中定义和执行表达式,同时它也是将事件通知给另一个控制器和应用其他部分的中介。
2.3.2.视图(View)与$scope
$rootScope 是AngularJS中最接近全局作用域的对象。不要在$rootScope上附加业务逻,否则与JavaScript的全局变量是一样的问题。$scope对象就是一个普通的JavaScript对象,我们可以在其上随意修改或添加属性。
$scope对象在AngularJS中充当数据模型,但与传统的数据模型不一样,$scope并不负责处理和操作数据,它只是视图和HTML之间的桥梁,它是视图和控制器之间的胶水。$scope的所有属性,都可以自动被视图访问到。
我们可以在AngularJS应用的模板中使用多种标记,包括下面这些。指令:将DOM元素增强为可复用的DOM组件的属性或元素。值绑定:模板语法{{ }}可以将表达式绑定到视图上。过滤器:可以在视图中使用的函数,用来进行格式化。表单控件:用来检验用户输入的控件。
2.3.3.作用域的好处
提供观察者以监视数据模型的变化;可以将数据模型的变化通知给整个应用,甚至是系统外的组件;可以进行嵌套,隔离业务功能和数据;给表达式提供运算时所需的执行环境。作用域包含了渲染视图时所需的功能和数据,它是所有视图的唯一源头。可以将作用域理解成视图模型(view model)。
2.3.4.$scope的生命周期
用户在通过 ng-model 属性监控的输入字段中输入,或者带有ng-click属性的按钮被点击时,ngular的事件循环都会启动。这个事件将在Angular执行上下文中处理。每当事件被处理时, $scope 就会对定义的表达式求值。此时事件循环会启动,并且Angular应用会监控应用程序内的所有对象,脏值检测循环也会运行。
创建阶段:在创建控制器或指令时,AngularJS会用 $injector 创建一个新的作用域,并在这个新建的控制器或指令运行时将作用域传递进去。
链接阶段:当Angular开始运行时,所有的 $scope对象都会附加或者链接到视图中。所有创建$scope对象的函数也会将自身附加到视图中。这些作用域将会注册当Angular应用上下文中发生变化时需要运行的函数。这些函数被称为$watch函数,Angular通过这些函数获知何时启动事件循环。
更新阶段:当事件循环运行时,它通常执行在顶层 $scope对象上(被称作$rootScope),每个子作用域都执行自己的脏值检测。每个监控函数都会检查变化。如果检测到任意变化,$scope对象就会触发指定的回调函数。
销毁阶段:当一个$scope在视图中不再需要时,这个作用域将会清理和销毁自己。$scope上有个叫做 $destory() 的方法来清理这个作用域。
2.3.5.指令和作用域
指令在AngularJS中被广泛使用,指令通常不会创建自己的 $scope,ng-controller和ng-repeat 指令会创建自己的子作用域并将它们附加到DOM元素上。
2.3.6.表达式(Expression)
用{{ }}符号将一个变量绑定到 $scope上的写法本质上就是一个表达式:{{ expression }}。当用 $watch 进行监听时,AngularJS会对表达式或函数进行运算。
表达式和eval(javascript)非常相似,但有明显的不同。所有的表达式都在其所属的作用域内部执行,并有访问本地$scope的权限;如果表达式发生了TypeError和ReferenceError并不会抛出异常;不允许使用任何流程控制功能(条件控制,例如 if/else);可以接受过滤器和过滤器链。
尽管AngularJS会在运行$digest循环的过程中自动解析表达式,但有时手动解析表达式也是非常有用的。示例工程:T61。
HTML代码:
- <!DOCTYPE html>
- <html lang="en" ng-app="myApp">
- <head>
- <Meta charset="UTF-8">
- <title>T61</title>
- <script type="text/javascript" src="vendor/angular.js"></script>
- <script type="text/javascript" src="index.js"></script>
- </head>
- <body ng-controller="IndexController">
- <p>
- <span>最简单的表达式:</span>
- <span>{{3*4*5*6}}</span>
- </p>
- <p>
- <ul>
- <li>
- <span>监视Model的变化:</span>
- <input ng-model="user.displayName" type="text" placeholder="请输入数字">
- </li>
- <li style="{{user.tipStyleString}}">
- <span>用户当前输入了:</span>
- <span>{{user.originalInput}}</span>
- </li>
- <li>
- <span>处理后的用户输入:</span>
- <span>{{user.disposalInput}}</span>
- </li>
- </ul>
- </p>
- </body>
- </html>
controller代码:
- var module = angular.module("myApp",[]);
- module.controller("IndexController",function ($scope,$parse) {
- $scope.user = {
- displayName: "",tipStyleString: "background-color: #FFF",originalInput: "",disposalInput: ""
- };
- module.parseValue=$parse;
- $scope.$watch("user.displayName",watchDisplayName);
- });
- function watchDisplayName(newVal,oldVal,scope) {
- if (newVal === oldVal) {
- return;
- }
- scope.user.originalInput = newVal;
- if (isExistsAlphabet(newVal)) {
- newVal=clearAlphabet(newVal);
- scope.user.tipStyleString = "background-color: #F00";
- } else {
- scope.user.tipStyleString = "background-color: #FFF";
- }
- var parseFunc = module.parseValue(newVal);
- scope.user.disposalInput = parseFunc(scope);
- }
- function isExistsAlphabet(str) {
- if (typeof str != "string") {
- return false;
- }
- for (let i = 0; i < str.length; i++) {
- if (str[i] <= '9' && str[i] >= '0') {
- continue;
- }
- return true;
- }
- return false;
- }
- function clearAlphabet(str) {
- if (typeof str != "string") {
- return "";
- }
- var newVal="";
- for (let i = 0; i < str.length; i++) {
- if (str[i] <= '9' && str[i] >= '0') {
- newVal+=str[i];
- }
- }
- return newVal;
- }
2.3.7.控制器嵌套(作用域包含作用域)
控制器在AngularJS中的作用是增强视图。AngularJS中的控制器是一个函数,用来向视图的作用域中添加额外的功能。我们用它来给作用域对象设置初始状态,并添加自定义行为。当我们在页面上创建一个新的控制器时,AngularJS会生成并传递一个新的$scope给这个控制器。
ng-click指令将浏览器中的 mouseup事件,同设置在DOM元素上的事件处理程序绑定在一起。AngularJS同其他JavaScript框架最主要的一个区别就是,控制器并不适合用来执行DOM操作、格式化或数据操作,以及除存储数据模型之外的状态维护操作。它只是视图和$scope之间的桥梁。
AngularJS应用的任何一个部分,无论它渲染在哪个上下文中,都有父级作用域存在。对于ng-app 所处的层级来讲,它的父级作用域就是 $rootScope。除了孤立作用域外,所有的作用域都通过原型继承而来。
AngularJS在当前作用域中无法找到某个属性时,便会在父级作用域中进行查找。如果AngularJS找不到对应的属性,会顺着父级作用域一直向上寻找,直到抵达 $rootScope为止。如果在 $rootScope 中也找不到,程序会继续运行,但视图无法更新。示例工程:T5。
HTML代码:
- <!DOCTYPE html>
- <html ng-app="myApp">
- <head lang="en">
- <Meta charset="UTF-8">
- <title>T5</title>
- <script type="text/javascript" src="vendor/angular.js"></script>
- <script type="text/javascript" src="index.js"></script>
- </head>
- <body>
- <div ng-controller="ParentController">
- <div ng-controller="ChildController">
- <p>{{selfText}}</p>
- <p>{{printSelf()}}</p>
- </div>
- <p style="color:red">{{status}}</p>
- </div>
- </body>
- </html>
JS代码:
- var module=angular.module("myApp",[]);
- module.controller("ParentController",function($scope,$timeout){
- $scope.person={
- name:"李婷",age:22
- };
- });
- module.controller("ChildController",function($scope){
- $scope.printSelf=function(){
- $scope.selfText="我叫"+$scope.person.name+",我今年"+$scope.person.age+"岁了。";
- setTimeout(()=>{
- $scope.$parent.$apply(()=>{$scope.$parent.status="OK";});
- },5000);
- return "调用父作用域的person对象操作成功结束。";
- }
- });
2.3.8.过滤器
过滤器用来格式化需要展示给用户的数据。AngularJS有很多实用的内置过滤器,同时也提供了方便的途径可以自己创建过滤器。模板绑定符号{{ }}内通过 | 符号来调用过滤器。可以用 | 符号作为分割符来同时使用多个过滤器。如果需要传递参数给过滤器,只要在过滤器名字后面加冒号即可。如果有多个参数,可以在每个参数后面都加入冒号。
Angular.js内置一个名称为filter的过滤器,它可以从给定数组中选择一个子集,并将其生成一个新数组返回。这个过滤器通常用来过滤需要进行展示的元素。例如,在做客户端搜索时,可以从一个数组中立刻过滤出所需的结果。filter选择器第二个参数是可选参数,bool类型或者返回bool类型的方法。
当参数是字符串时返回所有包含这个字符串的元素。如果我们想返回不包含该字符串的元素,在参数前加!符号。当参数是对象时AngularJS会将待过滤对象的属性同这个对象中的同名属性进行比较,如果属性值是字符串就会判断是否包含该字符串。如果我们希望对全部属性都进行对比,可以将$当作键名。当参数是函数时对每个元素都执行这个函数,返回非假值的元素会出现在新的数组中并返回。
Angular.js内置一个名称为orderBy的过滤器。它可以用表达式对指定的数组进行排序,接受两个参数,第一个是必需的,第二个是可选的。第一个参数是用来确定数组排序方向的谓词,可以是函数、字符串、数组。当第一个参数是函数时,该函数会被当作待排序对象的getter方法。当第一个参数是字符串时,字符串对这个字符串进行解析的结果将决定数组元素的排序方向。我们可以传入 + 或 - 来强制进行升序或降序排列。当第一个参数是数组时,在排序表达式中使用数组元素作为谓词。对于与表达式结果并不严格相等的每个元素,则使用第一个谓词。第二个参数用来控制排序的方向(是否逆向)。
示例工程:T7。
HTML代码:
- <!DOCTYPE html>
- <html ng-app="myApp">
- <head lang="en">
- <Meta charset="UTF-8">
- <title>T7</title>
- <script type="text/javascript" src="../vendor/angular.js"></script>
- <script type="text/javascript" src="../controllers/hello.js"></script>
- <style>
- td{
- align-content: center;
- }
- </style>
- </head>
- <body ng-controller="HelloController">
- <h1 width="100%" align="center">Angular.js内置过滤器完整示例</h1>
- <table border="1px" cellspacing="0" cellpadding="5" rules="all" style="border: solid 1px #0000FF" width="100%">
- <tr>
- <th width="40%">过滤器</th>
- <th width="60%">值</th>
- </tr>
- <tr>
- <td align="right">字符串小写:</td>
- <td>{{"Hello World 1!"|lowercase}}</td>
- </tr>
- <tr>
- <td align="right">JS代码如何用过滤器:</td>
- <td>{{hello2}}</td>
- </tr>
- <tr>
- <td align="right">小数位数以及四舍五入:</td>
- <td>{{hello3|number:3}}</td>
- </tr>
- <tr>
- <td align="right">字符串大写:</td>
- <td>{{"Hello World 4!"|uppercase}}</td>
- </tr>
- <tr>
- <td align="right">货币:</td>
- <td>{{hello5|currency}}</td>
- </tr>
- <tr>
- <td align="right">各种日期格式:</td>
- <td>
- <ul>
- <li>
- <p>
- 总体格式(西方格式)
- </p>
- <ol>
- <li>标准格式:{{ hello6 | date:'medium' }}</li>
- <li>短格式:{{ hello6 | date:'short' }}</li>
- <li>长格式:{{ hello6 | date:'fullDate' }}</li>
- <li>长日期格式:{{ hello6 | date:'longDate' }}</li>
- <li>标准日期格式:{{ hello6 | date:'mediumDate' }}</li>
- <li>短日期格式:{{ hello6 | date:'shortDate' }}</li>
- <li>标准时间格式:{{ hello6 | date:'mediumTime' }}</li>
- <li>短时间格式:{{ hello6 | date:'shortTime' }}</li>
- </ol>
- </li>
- <li>
- <p>
- 年份格式化
- </p>
- <ol>
- <li>
- 四位年份:{{ hello6 | date:'yyyy' }} <!-- 2013 -->
- </li>
- <li>
- 两位年份:{{ hello6 | date:'yy' }} <!-- 13 -->
- </li>
- <li>
- 一位年份:{{ hello6 | date:'y' }} <!-- 2013 -->
- </li>
- </ol>
- </li>
- <li>
- <p>
- 月份格式化
- </p>
- <ol>
- <li>
- 英文月份:{{ hello6 | date:'MMMM' }} <!-- August -->
- </li>
- <li>
- 英文月份简写:{{ hello6 | date:'MMM' }} <!-- Aug -->
- </li>
- <li>
- 数字月份:{{ hello6 |date:'MM' }} <!-- 08 -->
- </li>
- <li>
- 一年中的第几个月份:{{ hello6 |date:'M' }} <!-- 8 -->
- </li>
- </ol>
- </li>
- <li>
- <p>
- 日期格式化
- </p>
- <ol>
- <li>
- 一个月中的第几天:{{ hello6 | date:'d' }} <!-- 9 -->
- </li>
- <li>
- 英文星期:{{ hello6 | date:'EEEE' }} <!-- Thursday -->
- </li>
- <li>
- 英文星期简写:{{ hello6 | date:'EEE' }} <!-- Thu -->
- </li>
- </ol>
- </li>
- <li>
- <p>
- 小时格式化
- </p>
- <ol>
- <li>
- 24小时制数字小时:{{hello6|date:'HH'}} <!--00-->
- </li>
- <li>
- 一天中的第几个小时:{{hello6|date:'H'}} <!--0-->
- </li>
- <li>
- 12小时制数字小时:{{hello6|date:'hh'}} <!--12-->
- </li>
- <li>
- 上午或下午的第几个小时:{{hello6|date:'h'}} <!--12-->
- </li>
- </ol>
- </li>
- <li>
- <p>
- 分钟格式化
- </p>
- <ol>
- <li>
- 数字分钟数:{{ hello6 | date:'mm' }} <!-- 09 -->
- </li>
- <li>
- 一个小时中的第几分钟:{{ hello6 | date:'m' }} <!-- 9 -->
- </li>
- </ol>
- </li>
- <li>
- <p>
- 秒数格式化
- </p>
- <ol>
- <li>
- 数字秒数:{{ hello6 | date:'ss' }} <!-- 02 -->
- </li>
- <li>
- 一分钟内的第几秒:{{ hello6 | date:'s' }} <!-- 2 -->
- </li>
- <li>
- 毫秒数:{{ hello6 | date:'.sss' }} <!-- .995 -->
- </li>
- </ol>
- </li>
- <li>
- <p>
- 字符格式化
- </p>
- <ol>
- <li>
- 上下午标识:{{ hello6 | date:'a' }} <!-- AM -->
- </li>
- <li>
- 四位时区标识:{{ hello6 | date:'Z' }} <!--- 0700 -->
- </li>
- </ol>
- </li>
- <li>
- <p>
- 自定义日期格式的示例
- </p>
- <ol>
- <li>
- {{ hello6 | date:'MMMd,y' }} <!-- Aug9,2013 -->
- </li>
- <li>
- {{ hello6 | date:'EEEE,d,M' }} <!-- Thursday,9,8-->
- </li>
- <li>
- {{ hello6 | date:'hh:mm:ss.sss' }} <!-- 12:09:02.995 -->
- </li>
- </ol>
- </li>
- </ul>
- </td>
- </tr>
- <tr>
- <td align="right">字符串搜索选择器:</td>
- <td>{{hello7|filter:"e"}}</td>
- </tr>
- <tr>
- <td align="right">对象搜索选择器:</td>
- <td>{{hello8|filter:{name:"To"} }}</td>
- </tr>
- <tr>
- <td align="right">方法(函数)选择器:</td>
- <td>{{hello7|filter:hello9}}</td>
- </tr>
- <tr>
- <td align="right">JSON过滤器:</td>
- <td>{{hello10|json}}</td>
- </tr>
- <tr>
- <td align="right">字符串截断:</td>
- <td>
- <p>从左到右截断:{{hello11|limitTo:5}}</p>
- <p>从右到左截断:{{hello11|limitTo:-5}}</p>
- </td>
- </tr>
- <tr>
- <td align="right">按姓名字母顺序排序:</td>
- <td>{{hello8|orderBy:"name":true}}</td>
- </tr>
- </table>
- </body>
- </html>
JS代码:
- var hello = angular.module("myApp",[]);
- hello.controller("HelloController",['$scope','$filter',$filter) {
- $scope.hello2 = $filter('lowercase')("Hello World 2!");
- $scope.hello3 = 1.23456789;
- $scope.hello5 = 123.456;
- $scope.hello6 = new Date();
- $scope.hello7 = ['Ari','Lerner','Likes','To','Eat','Pizza'];
- $scope.hello8 =
- [{
- name: "Ari",age: 21
- },{
- name: "Lerner",age: 22
- },{
- name: "Likes",age: 23
- },{
- name: "To",age: 24
- },{
- name: "Eat",age: 25
- },age: 26
- },{
- name: "Pizza",age: 27
- }];
- $scope.hello9=function(value){
- if(value.length>3){
- return true;
- }
- return false;
- };
- $scope.hello10={
- name:"李婷",age:22,hobby:"羽毛球"
- };
- $scope.hello11="你好,我叫李婷,我今年22岁了,我喜欢打羽毛球。";
- $scope.hello12=function(){
- return arguments[0][0]>arguments[1][0];
- };
- }]);
2.3.9.自定义过滤器
过滤器本质上是一个会把我们输入的内容当作参数传入进去的函数。示例工程:T71。
HTML代码:
- <!DOCTYPE html>
- <html ng-app="myApp">
- <head lang="en">
- <Meta charset="UTF-8">
- <title>T71</title>
- <script type="text/javascript" src="../vendor/angular.js"></script>
- <script type="text/javascript" src="../controllers/hello.js"></script>
- </head>
- <body ng-controller="HelloController">
- <table border="1px" cellpadding="5" cellspacing="0" rules="all" style="border:solid 1px #0000FF;" width="100%">
- <tr>
- <td width="10%">请输入:</td>
- <td width="90%"><input ng-model="description" type="text" placeholder="请输入你想说的话。" size="40"></td>
- </tr>
- <tr>
- <td>您输入了:</td>
- <td>{{description|wordUpper}}</td>
- </tr>
- </table>
- </body>
- </html>
JS代码:
- var module=angular.module("myApp",[]);
- module.filter("wordUpper",function(){
- return function(input){
- if(!input){
- return "";
- }
- if(typeof input !== "string"){
- return "";
- }
- if(input.length<=0){
- return "";
- }
- var ret="";
- if(input[0]>='a'&&input[0]<='z'){
- ret+=input[0].toUpperCase();
- }else{
- ret+=input[0];
- }
- if(input.length>2){
- for(let i=1;i<input.length;i++){
- ret+=input[i];
- if(input[i]==' '){
- if(i+1<input.length){
- if(input[i+1]>='a'&&input[i+1]<='z'){
- ret+=input[i+1].toUpperCase();
- i++;
- }
- }
- }
- }
- }
- return ret;
- };
- });
- module.controller("HelloController",["$scope","$timeout",$timeout){
- $scope.description="";
- }]);
2.3.10.表单验证
表单验证能够根据用户在表单中输入的内容给出实时视觉反馈是非常重要的,不仅能给用户提供有用的反馈,同时也能保护我们的Web应用不会被恶意或者错误的输入所破坏。我们要在Web前端尽力保护后端。虽然Web应用安全不能完全依赖客户端验证,但客户端验证可以提供表单状态的实时反馈。
表单的每一项的name属性不能为空。Angular的表单验证使用了部分HTML特性,低版本的浏览器上运行可能存在问题。如果想要屏蔽浏览器对表单的默认验证行为,可以在表单元素上添加 novalidate 标记。
必填项:required属性。最小长度:ng-minlength。最大长度:ng-maxlength。模式匹配:ng-pattern,属性值是正则表达式。电子邮件:type=email。数字:type=number。URL:type=url。自定义验证:通过自定义指令实现。
样式:.ng-pristine、.ng-dirty、.ng-valid、.ng-invalid。有条件改变DOM外观。$error、$valid、$invalid、$pristine作用表单项的属性验证表单是否符合规则,置于ng-show可用显隐输入提示,引用格式是:表单名.表单项名.规则.[错误子规则]。
示例工程:T72。
- <!DOCTYPE html>
- <html ng-app="myApp">
- <head lang="en">
- <Meta charset="UTF-8">
- <title>T72</title>
- <script type="text/javascript" src="../vendor/angular.js"></script>
- <script type="text/javascript" src="../controllers/hello.js"></script>
- <link rel="stylesheet" href="../index.css">
- </head>
- <body ng-controller="StudentController">
- <h1 align="center">新增学生基本信息</h1>
- <ng-form name="form1" novalidate ng-submit="onSubmit()">
- <table border="1" cellspacing="0" cellpadding="5" width="500px" rules="all" style="border:solid 1px #0000FF" align="center">
- <tr>
- <td width="35%" align="right">姓名:</td>
- <td width="65%">
- <input name="name" type="text" ng-minlength="2" ng-maxlength="7" ng-model="student.name" chinese-name="name" required>
- <span ng-show="form1.name.$error.minlength">太短</span>
- <span ng-show="form1.name.$error.maxlength">太长</span>
- <span ng-show="form1.name.$error.chineseName&&!form1.name.$error.required">必须中文</span>
- <span ng-show="form1.name.$error.required">必填</span>
- </td>
- </tr>
- <tr>
- <td align="right">性别:</td>
- <td>
- <select name="sex" ng-model="student.sex" style="width: 50px;" ng-options="sex for sex in sexs" required></select>
- <span ng-show="form1.sex.$error.required">必填</span>
- </td>
- </tr>
- <tr>
- <td align="right">身高:</td>
- <td>
- <input name="height" type="number" style="width: 50px;" ng-pattern="/^\d.\d{2}$/" ng-model="student.height" required>米
- <span ng-show="form1.height.$error.required">必填</span>
- <span ng-show="form1.height.$error.pattern">格式错误</span>
- </td>
- </tr>
- <tr>
- <td align="right">电子邮箱:</td>
- <td>
- <input type="email" name="email" ng-model="student.email" required>
- <span ng-show="form1.email.$error.required">必填</span>
- <span ng-show="form1.email.$invalid&&!form1.email.$pristine">格式错误</span>
- </td>
- </tr>
- <tr>
- <td align="right">户口所属学区:</td>
- <td>
- <table border="0" cellpadding="0" cellspacing="0" width="100%">
- <tr ng-repeat="street in streets">
- <td ng-repeat="column in street.columns">
- <label>
- <input type="radio"
- name="street"
- ng-value="column.name"
- ng-click="onStreetClicked(this)"
- ng-model="student.street"
- >
- {{column.name}}
- </label>
- </td>
- </tr>
- <tr ng-show="student.street.length==0"
- style="color:red">
- <td colspan="{{streets[0].columns.length}}">
- 必须选择一个。
- <input type="hidden"
- name="streetHidden"
- ng-model="student.street"
- valid-street="student.street">
- </td>
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- <td align="right">学业成绩:</td>
- <td><table border="0" cellpadding="0" cellspacing="0" width="100%">
- <tr ng-repeat="examination in examinations">
- <td ng-repeat="column in examination.columns">
- <label>
- <input type="radio"
- name="examination"
- ng-value="column.name"
- ng-click="onExaminationClicked(this)"
- ng-model="student.examination">
- {{column.name}}
- </label>
- </td>
- </tr>
- <tr ng-show="student.examination.length==0"
- style="color:red">
- <td colspan="{{examinations[0].columns.length}}">
- 必须选择一个。
- <input type="hidden"
- name="examinationHidden"
- valid-examination="student.examination"
- ng-model="student.examination">
- </td>
- </tr>
- </table></td>
- </tr>
- <tr>
- <td align="right">爱好:</td>
- <td><table border="0" cellpadding="0" cellspacing="0" width="100%">
- <tr ng-repeat="hobby in hobbies">
- <input type="hidden" value="{{hobbyIndex=$index}}">
- <td ng-repeat="column in hobby.columns">
- <label>
- <input type="checkBox"
- ng-value="column.checked"
- ng-change="onHobbyChanged(this)"
- ng-model="bindHobbies[hobbyIndex][$index]">
- {{column.name}}
- </label>
- </td>
- </tr>
- <tr ng-show="isHobbyInvalid()"
- style="color:red">
- <td colspan="{{hobbies[0].columns.length}}">
- 至少选择一个。
- <input type="hidden"
- ng-model="hobbyUpdated"
- valid-hobby="hobbyUpdated"
- name="hobbyHidden">
- </td>
- </tr>
- </table></td>
- </tr>
- <tr>
- <td></td>
- <td>
- <input type="submit" value="提交" ng-click="onSubmitClicked" ng-disabled="!form1.name.$valid">
- <input type="reset" value="重置" ng-click="onResetClicked">
- </td>
- </tr>
- </table>
- </ng-form>
- </body>
- </html>
JS代码:
- var module = angular.module("myApp",[]);
- module.directive("chineseName",function($http){
- return {
- require:"ngModel",link:function(scope,ele,attrs,c){
- console.log(c);
- scope.$watch(attrs.ngModel,function() {
- var str = ele.val();
- for (let i = 0; i < str.length; i++) {
- if (str.charCodeAt(i) < 128) {
- c.$setValidity("chineseName",false);
- return;
- }
- }
- c.$setValidity("chineseName",true);
- });
- }
- };
- });
- module.directive("validStreet",c){
- scope.$watch(attrs.ngModel,function(){
- c.$setValidity("validStreet",scope.student.street.length==0);
- });
- }
- };
- });
- module.directive("validExamination",function(){
- c.$setValidity("validExamination",scope.student.examination.length==0);
- });
- }
- };
- });
- module.directive("validHobby",function($http){
- return {
- require:'ngModel',c){
- scope.$watch("hobbyUpdated",function(){
- c.$setValidity("validHobby",scope.student.hobbies.length==0);
- });
- }
- };
- });
- module.controller("StudentController",function ($scope) {
- initDatas($scope);
- initListeners($scope);
- $scope.streetIndex=0;
- $scope.examinationIndex=0;
- $scope.hobbyIndex=0;
- $scope.student = {
- name: "",sex: "",height: "",email: "",street:"",examination:"",hobbies:[]
- };
- }]);
- function initDatas($scope){
- $scope.sexs = ["男","女"];
- $scope.hobbyIndex=-1;
- $scope.bindHobbies=[[]];
- $scope.hobbyUpdated=new Date();
- $scope.streets = [{
- columns: [{
- name: "粤海街道",checked: true
- },{
- name: "蛇口街道",checked: false
- }]
- },{
- columns: [{
- name: "赤湾街道",checked: false
- },{
- name: "华侨城街道",{
- columns:[{
- name: "大学城街道",{
- name: "西丽街道",checked: false
- }]
- }];
- $scope.examinations = [{
- columns: [{
- name: "A",{
- name: "B",{
- columns: [{
- name: "C",{
- name: "D",{
- columns:[{
- name: "E",{
- name: "F",checked: false
- }]
- }];
- $scope.hobbies = [{
- columns: [{
- name: "围棋",{
- name: "象棋",{
- columns: [{
- name: "书法",{
- name: "绘画",{
- columns:[{
- name: "钢琴",{
- name: "古筝",checked: false
- }]
- }];
- }
- function initListeners($scope){
- $scope.isHobbyInvalid=function(){
- return $scope.student.hobbies.length==0;
- };
- //onchanged比isHobbyInvalid执行要早
- $scope.onHobbyChanged=function(dom){
- var col=dom.column;
- col.checked=!col.checked;
- var index=$scope.student.hobbies.indexOf(col);
- if(col.checked){
- if(index==-1){
- $scope.student.hobbies.push(col);
- }
- }else{
- if(index!=-1){
- $scope.student.hobbies.splice(index,1);
- }
- }
- $scope.hobbyUpdated=new Date();
- };
- $scope.onStreetClicked=function(context){
- console.log($scope.student.street);
- };
- $scope.onExaminationClicked=function(context){
- console.log($scope.student.examination);
- };
- $scope.isExistsInvalid=function(){
- console.log(document.getElementsByName("streetHidden")[0].$error);
- };
- $scope.onSubmitClicked=function(){
- };
- $scope.onResetClicked=function(){
- }
- $scope.onSubmit=function(){
- }
- }
转载时请遵重他人的劳动成果,不要删除作者原文链接。
转载请注明来源:http://blog.csdn.net/caoshiying。谢谢合作。