Angularjs自定义指令小试

##简介
指令(Directives)是Angularjs非常强大的属性之一,使用指令可以创建很多共用的组建,用于减轻代码的重复性和实现复杂逻辑。

指令标记在一个DOM元素上(例如一个属性、一个元素名称、注释或者CSS类),告诉Angularjs的HTML编译器($compile)将指定的行为附加到DOM元素上或者甚至转换DOM元素和它的子元素。

Angular附带了一系列内建的指令,例如ngBindngModelngClass,就像你创建控制器和服务,你可以为Angular创建自己的指令来使用。当Angular引导你的应用时,HTML编译器会遍历DOM元素来匹配DOM对应的指令。

下面简单实现一个年月日下拉框联动选择的Angular自定义指令。

##声明指令
Angular使用directive()这个方法来定义指令:

angular.module('myApp',[])
    .directive('myDirective',function(){
        //定义指令的内容
    });

directive()方法接受两个参数:

  1. name(字符串)。指令的名称。
  2. factory_function。该函数返回一个定义了指令的全部行为的对象。

当Angular启动应用的时候,会把第一个参数作为名称来注册第二个参数返回的对象。Angular会解析DOM元素中使用这个名称的地方,并在这个地方引用对应的指令。

<div my-directive></div>

注意:定义的指令名称为驼峰式的字符串,但是使用的时候要用横杆替换。

##指令的配置项

本例子使用到的指令配置项如下

####restrict

restrict是一个可选的参数,它告诉Angular该指令在DOM中可以以何种形式被声明。默认值为A,即以属性的形式来进行声明。该配置项的可选值如下:

E(元素)
<my-directive></my-directive>
A(属性,默认值)
<div my-directive></div>
C(类名)
<div class="my-directive"></div>
M(注释)
<!--directive:my-directive-->

以上可选值可以独立使用,也可以混合使用。但是请注意,如果目标网页应用有兼容低版本IE浏览器的需求时,请勿只使用E选项,明智点的方法是同时使用EA选项。

####replace

replace是一个可选的参数,如果设置了其值必须是true,因为默认值就是false。默认值表示指令中定义的模板会被当作自元素插入到调用此指令的元素内部。

<div my-directive></div>
.directive('myDirective',function(){
    return {
        template: '<div>Some stuff here</div>'
    }
});

调用结果如下
<div my-directive>
    <div>Some stuff here</div>
</div>

当replace的值设为true的时候

.directive('myDirective',function(){
    return {
        replace: true,
        template: '<div>Some stuff here</div>'
    }
});

结果如下
<div>Some stuff here</div>

####scope
该选项用于设置指令的作用域信息。选项可以被设为true或者一个对象。默认值为false。

当选项被设成true的时候,表示会从父作用域继承并创建一个新的作用域对象。

scope的选项也可以是一个空对象,这表示隔离作用域。隔离作用域是scope三个选项中最难理解的一个,但也是最强大的。楼主能力有限,并且本例子不使用该选项,所以在此略过。

scope的最后一个选项是绑定策略。Angular提供了几种方法能够将指令内部的隔离作用域同指令外部的作用域进行绑定。

本地作用域属性:@ or @attr。将本地作用域同DOM属性的值绑定,指令内部作用域可以使用外部作用域的变量。
双向绑定:= or =attr。将本地作用域上的属性同父级作用域上的属性进行双向数据绑定。
父级作用域绑定:& or &attr。对父级作用域进行绑定,以便在指令作用域中运行函数。对这个值进行设置时会声称一个指向父级作用域的包装函数。

####controller
该选项表示指令的控制器,用来控制指令的逻辑行为。控制器中有一些特殊的服务可以被注入到指令当中

  1. $scope。与指令相关联的当前作用域。
  2. $element。当前指令对应的元素。
  3. $attrs。 当前元素的属性组成的对象。
  4. $transclude。嵌入连接函数会与对应的嵌入作用域进行预绑定。

####template
该选项可选值如下:

  1. 一段HTML文本。
  2. 可以接受两个参数,参数为tElement和tAttrs,并返回一个代表模板的字符串。

##逻辑
年月日下拉框联动选择的Angular自定义指令目标是创建三个下拉框,第一个表示年份,第二个表示月份,第三个表示日期,年月日下拉框必须能够联动,并且当选择闰年的二月份的时候,日下拉框必须有29的选项。同时该指令能够将选择的结果返回给父作用域。

年月日下拉框联动选择(以下简称qbDate)指令的模板如下:

template: '\
      <div>\
        <select ng-model="year">\
            <option value=""></option>\
            <option ng-repeat="item in yearList" value="{{item}}">{{item}}</option>\
        </select>\
        <select ng-model="month">\
            <option value=""></option>\
            <option ng-repeat="item in monthList track by $index" value="{{item}}">{{item}}</option>\
        </select>\
        <select ng-model="day">\
            <option></option>\
            <option ng-repeat="item in dayList track by $index" value="{{item}}">{{item}}</option>\
        </select>\
      </div>\
    '

qbDate的控制器逻辑代码如下

controller: function($scope,$element){
    console.log($scope.year,$scope.month,$scope.day);
    $scope.yearList = [];
    for(var i = 1900;i<=2030;i++){
        $scope.yearList.push(i);
    }
    $scope.monthList = [];
    $scope.dayList = [];
    for(var i = 1;i<=12;i++){
        $scope.monthList.push(i);
    }
    $scope.$watch('year',function(newValue,oldValue){
        if(newValue){
            if(newValue.length > 4){
                $scope.year = oldValue;
            }
            $scope.dayList = [];
            $scope.month = '';
            $scope.day = '';

        }

    });
    $scope.$watch('month',function(newValue,oldValue){
        var months31 = [1,3,5,7,8,10,12];
        var months30 = [4,6,9,11];
        var month = parseInt(newValue);
        var maxDay = 31;
        if(months31.indexOf(month) > 0){
            maxDay = 31;
        } else if (months30.indexOf(month) > 0){
            maxDay = 30;
        } else{
            var year = parseInt($scope.year);
            if((year%4==0 && year%100!=0)||(year%100==0 && year%400==0)){
                maxDay=29;
            }else{
                maxDay=28;
            }
        }
        for(var i = 1;i<=maxDay;i++){
            $scope.dayList.push(i);
        }
    });
},

qbDate的作用域设置如下

scope: {
        year: '=yearModel',
        month: '=monthModel',
        day: '=dayModel'
    },

使用的时候这么用:

<div qb-date year-model="year" month-model="month" day-model="day"><div>

结合qbDate的作用域设置详情,qbDate 的作用域里面的year属性将绑定到qbDate指令的属性yearModel,而yearModel的值为qbDate指令父作用域year的值。由于qbDate指令是双向绑定的,所以指令内部的值变化将会反应到父作用域的值变化。

##源码
本例子源码已经放在JSFiddle上。

仓促赶工,如有谬误,还望指正。