AngularJS Dropdown指令相关的总结

Dropdown组件

自己封装的一个简单的dropdown的组件

.ui-dropdown {
    position:relative;
    display:inline-block;
    *display:inline;
    *zoom:1;
    width:162px;
    border-radius:3px;
    text-align:left;
    font:12px/28px tahoma,arial,Hiragino Sans GB,WenQuanYi Micro Hei,'\5FAE\8F6F\96C5\9ED1','\5B8B\4F53',sans-serif;
    vertical-align:top
}
.ui-dropdown-bd {
    display:none;
    top:31px;
    left:0;
    min-width:calc(100% - 2px);
    max-height:346px;
    overflow-y:auto;
    overflow-x:hidden;
    _overflow:auto;
    position:absolute;
    padding-bottom:8px;
    background-color:#fff;
    border:1px solid #848484;
    border-radius:0 0 3px 3px;
    -webkit-box-shadow:0 0 0 2px #f7f7f7;
    box-shadow:0 0 0 2px #f7f7f7
}
.ui-dropdown-bd-right {
    left:auto;
    right:0
}
.ui-dropdown-bd .selected a,.ui-dropdown-bd .selected a:hover,.ui-dropdown-bd .selected a:active {
    background-color:#f10180;
    color:#fff
}
.ui-dropdown-bd a {
    display:block;
    padding:0 10px;
    text-overflow:ellipsis;
    white-space:nowrap;
    overflow:hidden;
    color:#333;
    text-decoration:none;
    *zoom:1
}
.ui-dropdown-bd a:hover {
    color:#666;
    text-decoration:none;
    background-color:#ededed
}
.ui-dropdown-bd a:active {
    background-color:#f10180;
    color:#fff
}
.ui-dropdown-hd {
    position:relative;
    z-index:2;
    padding:0 10px;
    border:1px solid #b3b3b3;
    border-radius:3px;
    background-color:#fff;
    -webkit-user-select:none;
    -moz-user-select:none;
    -ms-user-select:none;
    user-select:none;
    cursor:pointer;
    *zoom:1
}
.ui-dropdown-hd:hover {
    border-color:#949494
}
.ui-dropdown-hd:hover .vipFont {
    color:#949494
}
.ui-dropdown-hd .vipFont {
    position:absolute;
    right:7px;
    color:#b3b3b3;
    -webkit-transition:-webkit-transform .2s;
    transition:transform .2s;
    line-height:1
}
.ui-dropdown-hd .i-arrow-down {
    top:7px
}
.ui-dropdown-hd .i-arrow-up {
    visibility:hidden;
    -webkit-transform:rotate(-180deg);
    -ms-transform:rotate(-180deg);
    transform:rotate(-180deg);
    top:6px
}
.ui-dropdown-current {
    display:block;
    zoom:1;
    color:#999;
    line-height:30px;
    text-decoration:none;
    font-size:14px;
    margin-right:10px;
    text-overflow:ellipsis;
    white-space:nowrap;
    overflow:hidden
}
.ui-dropdown-current:hover {
    color:#999;
    text-decoration:none
}
.ui-dropdown-tips {
    padding:12px 0 4px 10px;
    line-height:20px;
    color:#ed1616
}
.ui-dropdown .ui-dropdown-menu .ui-dropdown-loading {
    float:none;
    width:auto;
    padding-top:10px;
    line-height:80px;
    text-align:center;
    font-size:14px;
    color:#333
}
.ui-dropdown .ui-dropdown-menu .ui-dropdown-loading .ii-loading-pink-16x16,.ui-dropdown .ui-dropdown-menu .ui-dropdown-loading .ii-loading-pink-24x24,.ui-dropdown .ui-dropdown-menu .ui-dropdown-loading .ii-loading-pink-32x32 {
    vertical-align:middle;
    margin-right:12px
}
.ui-dropdown .ui-dropdown-menu .ui-dropdown-loading .text {
    display:inline-block;
    vertical-align:middle
}
.ui-dropdown-col-2,.ui-dropdown-col-3,.ui-dropdown-col-4 {
    width:115px
}
.z-ui-dropdown-open .ui-dropdown-col-2 .ui-dropdown-hd,.z-ui-dropdown-open .ui-dropdown-col-3 .ui-dropdown-hd,.z-ui-dropdown-open .ui-dropdown-col-4 .ui-dropdown-hd {
    border-bottom:0;
    padding-bottom:1px
}
.ui-dropdown-col-2 .ui-dropdown-bd,.ui-dropdown-col-3 .ui-dropdown-bd,.ui-dropdown-col-4 .ui-dropdown-bd {
    padding-top:8px
}
.ui-dropdown-col-2 .ui-dropdown-tips,.ui-dropdown-col-3 .ui-dropdown-tips,.ui-dropdown-col-4 .ui-dropdown-tips {
    padding-top:0
}
.ui-dropdown-col-2 .ui-dropdown-menu,.ui-dropdown-col-3 .ui-dropdown-menu,.ui-dropdown-col-4 .ui-dropdown-menu {
    padding:0 5px
}
.ui-dropdown-col-2 .ui-dropdown-menu li,.ui-dropdown-col-3 .ui-dropdown-menu li,.ui-dropdown-col-4 .ui-dropdown-menu li {
    float:left;
    _overflow:hidden
}
.ui-dropdown-col-2 .ui-dropdown-menu li a,.ui-dropdown-col-3 .ui-dropdown-menu li a,.ui-dropdown-col-4 .ui-dropdown-menu li a {
    _width:83px;
    padding:0 0 0 5px;
    margin:0 5px 2px
}
.ui-dropdown-col-2 .ui-dropdown-bd {
    width:190px
}
.ui-dropdown-col-2 .ui-dropdown-menu li {
    width:50%
}
.ui-dropdown-col-3 .ui-dropdown-bd {
    width:280px
}
.ui-dropdown-col-3 .ui-dropdown-menu li {
    width:33%
}
.ui-dropdown-col-4 .ui-dropdown-bd {
    width:370px
}
.ui-dropdown-col-4 .ui-dropdown-menu li {
    width:25%
}
.ui-dropdown-float {
    line-height:26px;
    width:120px
}
.ui-dropdown-float:hover,.ui-dropdown-float-hover {
    z-index:100
}
.ui-dropdown-float:hover .ui-dropdown-hd .vipFont,.ui-dropdown-float-hover .ui-dropdown-hd .vipFont {
    color:#848484;
    line-height:1
}
.ui-dropdown-float:hover .ui-dropdown-hd .i-arrow-up,.ui-dropdown-float-hover .ui-dropdown-hd .i-arrow-up {
    visibility:visible;
    -webkit-transform:rotate(0deg);
    -ms-transform:rotate(0deg);
    transform:rotate(0deg)
}
.ui-dropdown-float:hover .ui-dropdown-hd .i-arrow-down,.ui-dropdown-float-hover .ui-dropdown-hd .i-arrow-down {
    visibility:hidden;
    -webkit-transform:rotate(180deg);
    -ms-transform:rotate(180deg);
    transform:rotate(180deg)
}
.ui-dropdown-float:hover .ui-dropdown-bd,.ui-dropdown-float-hover .ui-dropdown-bd {
    display:block
}
.ui-dropdown-float:hover .ui-dropdown-current,.ui-dropdown-float-hover .ui-dropdown-current {
    color:#333
}
.ui-dropdown-float .ui-dropdown-current {
    display:inline-block;
    padding-right:16px;
    margin-right:0;
    color:#666;
    font-size:12px
}
.ui-dropdown-float .ui-dropdown-current:hover {
    color:#333
}
.ui-dropdown-float .ui-dropdown-hd {
    border:0 none;
    background:0
}
.ui-dropdown-float .ui-dropdown-hd .vipFont {
    right:auto;
    margin-left:-16px
}
.ui-dropdown-float .ui-dropdown-bd {
    top:0;
    max-height:312px;
    padding-top:30px;
    border-radius:0;
    border-color:#cbcaca
}
.ui-dropdown-float .ui-dropdown-bd a {
    color:#666
}
.ui-dropdown-float .ui-dropdown-bd a:hover,.ui-dropdown-float .ui-dropdown-bd a:active {
    background:0;
    color:#f10180
}
.ui-dropdown-float .ui-dropdown-bd .selected a,.ui-dropdown-float .ui-dropdown-bd .selected a:hover,.ui-dropdown-float .ui-dropdown-bd .selected a:active {
    background:0;
    color:#f10180
}
.ui-dropdown-float .ui-dropdown-menu .ui-dropdown-loading {
    line-height:50px;
    font-size:12px
}
.z-ui-dropdown-selected .ui-dropdown-current,.z-ui-dropdown-selected .ui-dropdown-current:hover {
    color:#333
}
.z-ui-dropdown-disable .ui-dropdown-hd {
    background-color:#f0efef;
    color:#999;
    cursor:not-allowed
}
.z-ui-dropdown-disable .ui-dropdown-hd:hover .vipFont {
    color:#b3b3b3
}
.z-ui-dropdown-disable .ui-dropdown-current,.z-ui-dropdown-disable .ui-dropdown-current:hover {
    color:#999;
    cursor:not-allowed
}
.z-ui-dropdown-open {
    z-index:100;
    -webkit-box-shadow:0 0 0 2px rgba(153,153,153,.08);
    box-shadow:0 0 0 2px rgba(153,153,153,.08)
}
.z-ui-dropdown-open .ui-dropdown-hd {
    border-radius:3px 3px 0 0;
    border-color:#848484;
    border-bottom-color:#dcdada
}
.z-ui-dropdown-open .ui-dropdown-hd .vipFont {
    color:#848484
}
.z-ui-dropdown-open .ui-dropdown-hd .i-arrow-up {
    visibility:visible;
    -webkit-transform:rotate(0deg);
    -ms-transform:rotate(0deg);
    transform:rotate(0deg)
}
.z-ui-dropdown-open .ui-dropdown-hd .i-arrow-down {
    visibility:hidden;
    -webkit-transform:rotate(180deg);
    -ms-transform:rotate(180deg);
    transform:rotate(180deg)
}
.z-ui-dropdown-open .ui-dropdown-bd {
    display:block
}
function BindDropdownEvent(elements, callback) {

    $('.ui-dropdown-hd', elements).unbind().click(function() {

        var element = $(this),
            uiDropdown = element.parent('.ui-dropdown');
        if (element.hasClass('z-ui-dropdown-disable')) {
            return false;
        }

        if (uiDropdown.hasClass('z-ui-dropdown-open')) {
            uiDropdown.removeClass('z-ui-dropdown-open');
        } else {
            $('.ui-dropdown').not(uiDropdown).removeClass('z-ui-dropdown-open');
            $('.multi-dropdown').not(uiDropdown).removeClass('z-ui-dropdown-open');
            uiDropdown.addClass('z-ui-dropdown-open');
            $(document).one('click', function() {
                uiDropdown.removeClass('z-ui-dropdown-open');
                return false;
            });
        }
        return false;
    });

    $('.ui-dropdown-bd', elements).unbind().each(function() {
        var element = $(this),
            uiDropdown = element.parent('.ui-dropdown'),
            liList = element.find('li'),
            uiDropdownCurrent = uiDropdown.find('.ui-dropdown-current');
        liList.click(function() {
            var subElement = $(this),
                item = subElement.find('a');
            liList.removeClass('selected');
            subElement.addClass('selected');

            var selectValue = item.attr('value');
            uiDropdownCurrent.attr('value', selectValue).text(item.text());
            uiDropdown.removeClass('z-ui-dropdown-open');
            $.isFunction(callback) && callback(item.text(), selectValue);

        });
    });
}

/**
     * Dropdown单选下拉框指令的实现
     * name : 区分不同的下拉框组件,字符串形式
     * ngModel : 单选下拉框绑定的值,默认是选中项的key值
     * dataList : 下拉框下拉选项列表,数组形式,数组元素对象包含key 以及value。key作为向后台交互的值,value作为前端控件显示的值
     * empty : 布尔属性值,当赋值为true的时候,对下拉框组件进行充值,下拉框中的值显示的是placeholder的值"请选择"
     * TODO : 多层级联下拉框的实现方法      ------     当下拉框列表接受点击事件时,广播带有组件name值为参数的selectUpdate的事件,用户可以捕获事件,根据时间的参数做出相应的响应。
     *        下拉框未选为空的状态
     *        下拉框相应双向数据绑定的model,当model变化时,对应的下拉框组件可以显示对应的选项
     */

    app.directive('dropdown',function(){
        return {
            restrict : 'EA',
            replace: true,
            template : '<div class="ui-form-item-group">'+
                            '<div class="ui-dropdown" ng-click="dropdown()">'+
                                '<div class="ui-dropdown-hd">'+
                                    '<a href="javascript:;" role="button" title="请选择" class="ui-dropdown-current">请选择</a> <i class="vipFont i-arrow-up"></i> <i class="vipFont i-arrow-down"></i>'+
                                '</div>'+
                                '<div class="ui-dropdown-bd">'+
                                    '<ul class="ui-dropdown-menu" >'+
                                        '<li ng-repeat="item in datalist" >'+
                                            '<a href="javascript:;" role="button" value="item.key" ng-click="select()">{{item.value}}</a>'+
                                        '</li>'+
                                    '</ul>'+
                                '</div>'+
                            '</div>'+
                        '</div>',
            scope :{
                name : '@',
                ngModel : "=",
                datalist : "=",
                empty : "="
            },
            link : function(scope,iElement,iAttrs){
                debugger
                var $elem = $(iElement);

                var dependents = scope.dependents ? scope.dependents.split(',') : false;
                var parentScope = scope.$parent;
                scope.name = scope.name || 'multi-select-' + Math.floor(Math.random() * 900000 + 100000);
                
                scope.$watch('empty',function(newValue, oldValue, scope){
                    debugger
                    if(newValue != oldValue){
                        if( newValue == true){
                            $elem.find('.ui-dropdown').removeClass("z-ui-dropdown-selected");
                            $elem.find(".ui-dropdown-current").text('请选择');      
                        }

                         
                    }
                })
                
                scope.$watch('ngModel',function(newValue,oldValue,scope){
                    console.log('----------enter watch-------------');
                    debugger
                    if(newValue != oldValue){
                         scope.$root.$broadcast('selectUpdate', {
                                    // 将变动的菜单的name属性广播出去,便于依赖于它的菜单进行识别
                                    name: scope.name
                                });
                        for(var item in scope.datalist){
                            if(scope.datalist[item].key === newValue){
                                $elem.find(".ui-dropdown-current").text(scope.datalist[item].value);   
                                
                            }
                        }
                        
                    }
                })

                scope.dropdown = function(){
                    $elem.find('.ui-dropdown').toggleClass("z-ui-dropdown-open");

                }

                scope.select = function(){
                    // "z-ui-dropdown-selected"
                    debugger
                    $elem.find(".ui-dropdown").addClass("z-ui-dropdown-selected");
                    scope.empty = false;
                    scope.ngModel = scope.datalist[this.$index].key;
                    
                    // iAttrs["ngModel"] = "0"+this.$index;
//                    $elem.find(".ui-dropdown-current").text(scope.datalist[this.$index].value);
                    // scope.datafunc();
                   
                }
            }
        }
    })

在页面中使用

 <label class="ui-label label-margin">标记原因:</label>
                                                    <dropdown ng-model="formData.markReason" datalist="markReasons" name="markreasons-select"></dropdown>

级联事件的响应


 $scope.$on('selectUpdate',function(e,data){
            //TODO:
            debugger
            // console.log(data);
            if(data.name == "risktypes-select"){
                if($scope.formData.riskType == ''){
                    $scope.markReasons2 = [{key : '', value:"全部"}];
                    $scope.formData.markReason = '';
                    return ;
                }
                $scope.formData.markReason = '';
                util.ajax({
                    "url": "XXXX",
                    "type": "post",
                    $http: $http,
                    contentType: "application/x-www-form-urlencoded; charset=utf-8",
                    data: {
                        riskType: $scope.formData.riskType
                    },
                    success: function(res) {                
                        if (!res) {
                            return _showMessage("对不起您没有权限", "信息错误");
                        }else if(res.success == false) {
                            return _showMessage("用户信息不匹配(未查到相关数据!)", "信息错误")
                        }else {     
                            $scope.reasonListSeleted = res.results._defaultResult;
                            debugger
                                    $scope.markReasons2 = [{key : '', value:"全部"}];
                                for (var i = 0; i < $scope.reasonListSeleted.length; i++) {
                                    $scope.markReasons2.push({
                                        key : 
                                        $scope.reasonListSeleted[i].reasonId,
                                        value : 
                                        $scope.reasonListSeleted[i].reasonDesc
                                    });
                                }
                        };
                    }
                });
            }
        })

相关注意点

  • 指令绑定参数时,最好不要是基本数据类型
    指令在进行值绑定的时候,传入的值最好是非基本类型。譬如绑定一个字符串类型riskTypengModel上,ngModel需要双向数据绑定。
<dropdown ng-model="riskType" datalist="riskTypes">

这样的绑定会出现一定的问题
最好在controller中传入的参数的对象的属性,如下

<dropdown ng-model="obj.riskType" datalist="riskTypes">

在这个示例中datalist绑定的数据是数组类型,并非是简单数据类型,所以直接绑定即可。

扩展

后期又对下拉框进行优化,支持disable,搜索筛选选项等功能。

    app.directive('searchdropdown',['$document','$filter','$timeout',function($document,$filter,$timeout){
        return {
            restrict : 'EA',
            replace: true,
            template : '<div class="ui-form-item-group">'+
                            '<div class="ui-dropdown" >'+
                                '<div class="ui-dropdown-hd">'+
                                    '<input placeholder="请选择,输入可筛选" class="ui-dropdown-search" ng-model="searchData" style="border: none;font: inherit;" ng-show="searchable && searching">'+
                                    '<a href="javascript:;" role="button" title="请选择" class="ui-dropdown-current" ng-show="!(searchable && searching) "  ng-click="dropdown()">请选择</a> <i class="vipFont i-arrow-up"  ng-click="dropdown()"></i> <i class="vipFont i-arrow-down"  ng-click="dropdown()"></i>'+
                                '</div>'+
                                '<div class="ui-dropdown-bd | filter:searchData">'+
                                    '<ul class="ui-dropdown-menu" >'+
                                        '<li ng-repeat="item in datalist | filter:{text:searchData}" >'+
                                            '<a href="javascript:;" role="button" value="item.value" ng-click="select($event)">{{item.text}}</a>'+
                                        '</li>'+
                                    '</ul>'+
                                '</div>'+
                            '</div>'+
                        '</div>',
            scope :{
                ngModel : "=",
                datalist : "=",
                disabled : "=",
                searchable : "="
            },
            controller : function($scope){
                $scope.name = $scope.name || 'dropdown-' + Math.floor(Math.random() * 900000 + 100000);
                $scope.searching = false;
                $scope.myComparator = function (expected, actual) {
                    return angular.equals(expected.toLowerCase(), actual.toLowerCase());
                }
            },
            link : function(scope,iElement,iAttrs){
                
                var $elem = $(iElement);
               
                for(var item in scope.datalist){
                    if(scope.datalist[item].value === scope.ngModel){
                        $elem.find(".ui-dropdown-current").text(scope.datalist[item].text);   
                        $elem.find(".ui-dropdown").addClass("z-ui-dropdown-selected");
                    }
                }
                if(scope.disabled == true){
                    $elem.find(".ui-dropdown").addClass("z-ui-dropdown-disable");
                }

                $elem.find('.ui-dropdown-search').bind('focus',function(event){
                    
                    console.log('ui-dropdown-search is on focus');
                    $elem.find('.ui-dropdown').addClass("z-ui-dropdown-open");
                    event.stopPropagation();
                })

                // $elem.find(".ui-dropdown-hd").bind('blur',function(){
                //     alert('blur');
                // })

                // $elem.find('.ui-dropdown-search').bind('blur',function(event){
                    
                //     console.log('ui-dropdown-search is on blur');
                //     scope.$apply(function(){
                //         scope.searching = false;      
                //     })
                // })
                
                scope.dropdown = function(){
                    console.log("ui-dropdown trigger dropdown event");
                    if(scope.disabled)
                        return ;
                    $elem.find('.ui-dropdown').toggleClass("z-ui-dropdown-open");
                    if($elem.find('.ui-dropdown').hasClass("z-ui-dropdown-open")){
                        console.log("---------" + scope.name +"------bind $document event -----------");
                        // 对于这种问题,jQuery的解决方案是使用事件绑定的命名空间。即在事件名称后添加 .something 来区分自己这部分行为逻辑范围。  
                        $(document).bind("click."+scope.name, function(event) {
                            console.log("-------go into $document click"+scope.name+"-------");
                            var judge = $(event.target).closest($elem).length > 0;
                            // if(!$elem.find('.ui-dropdown').hasClass("z-ui-dropdown-open")){
                            //     console.log("---------" + scope.name +"------unbind $document event -----------");
                            //     $(document).unbind("click."+scope.name);
                            // }
                            if(!judge){
                                if(scope.searchable && scope.searching)  {
                                    scope.$apply(function(){
                                        scope.searching = false;   
                                    })
                                }
                                $elem.find(".ui-dropdown").removeClass("z-ui-dropdown-open");   
                                $(document).unbind("click."+scope.name);
                            }
                        });
                    }else{
                         console.log("---------" + scope.name +"------unbind $document event -----------");
                        $(document).unbind("click."+scope.name);
                    }
                    if(scope.searchable){
                        scope.searching = !scope.searching;
                        // if(scope.searching){
                            scope.searchData = scope.ngModel;
                            var timer = $timeout(
                                function() {
                                    $elem.find('.ui-dropdown-search').focus();
                                    console.log( "Timeout executed", Date.now() );
                            },100);

                            scope.$on("$destroy",function( event ) {

                                $timeout.cancel( timer );
                            });
                        // }
                    }
                }

                scope.select = function($event){
                    
                    $elem.find(".ui-dropdown").addClass("z-ui-dropdown-selected");
                    var temp = $filter('filter')(scope.datalist,scope.searchData);
                    console.log(temp);
                    scope.ngModel = temp[this.$index].value;
                    $elem.find(".ui-dropdown-current").text(temp[this.$index].text)
                    $elem.find(".ui-dropdown").removeClass("z-ui-dropdown-open");  
                    scope.searching = false;
                    console.log(scope.searching);
                }
                
                scope.$watch('ngModel',function(newValue,oldValue,scope){
                     var isHitted = false;
                    if(newValue != oldValue){
                        for(var item in scope.datalist){
                            if(scope.datalist[item].value === newValue){
                                $elem.find(".ui-dropdown-current").text(scope.datalist[item].text);   
                                $elem.find(".ui-dropdown").addClass("z-ui-dropdown-selected");
                                isHitted = true;
                            }
                        }
                        if(!isHitted){
                            $elem.find(".ui-dropdown-current").text('请选择');   
                            $elem.find(".ui-dropdown").removeClass("z-ui-dropdown-selected");
                        }
                    }
                })

                // $(document).bind("click."+scope.name, function(event) {
                //             console.log("-------go into $document click-------");
                //             var judge = $(event.target).closest($elem).length > 0;
                //             if(!judge){
                //                 if(scope.searchable && scope.searching)  {
                //                     scope.$apply(function(){
                //                         scope.searching = false;   
                //                     })
                //                 }
                //                 $elem.find(".ui-dropdown").removeClass("z-ui-dropdown-open");   
                //             }
                //         });


                // $document.bind("click", function(event) {
                //     var judge = $(event.target).closest($elem).length > 0;
                //     if(!judge){
                //         if(scope.searchable && scope.searching)  {
                //             // $elem.find('.ui-dropdown-search').blur();
                //             // console.log('manually trigger blur');
                //             scope.$apply(function(){
                //                 scope.searching = false;   
                //             })
                //         }
                //         $elem.find(".ui-dropdown").removeClass("z-ui-dropdown-open");   
                //     }
                // });

                // $(document).one("click", function(event) {
                //     var judge = $(event.target).closest($elem).length > 0;
                //     if(!judge){
                //         if(scope.searchable && scope.searching)  {
                //             // $elem.find('.ui-dropdown-search').blur();
                //             // console.log('manually trigger blur');
                //             scope.$apply(function(){
                //                 scope.searching = false;   
                //             })
                //         }
                //         $elem.find(".ui-dropdown").removeClass("z-ui-dropdown-open");   
                //     }
                // });

            }
        }
    }]) 
simple-select.gif
search-select.gif
disbaled.png

AngularJS中实现无限级联动菜单

AngularJS中实现无限级联动菜单

directive('multiLevelSelect', ['$parse', '$timeout', function ($parse, $timeout) {
 
    // 利用闭包,保存父级scope中的所有多级联动菜单,便于取值
    var selects = {};
 
    return {
 
        restrict: 'CA',
 
        scope: {
            // 用于依赖声明时指定父级标签
            name: '@name',
 
            // 依赖数组,逗号分割
            dependents: '@dependents',
 
            // 提供具体option值的函数,在父级change时被调用,允许同步/异步的返回结果
            // 无论同步还是异步,数据应该是[{text: 'text', value: 'value'},]的结构
            source: '=source',
 
            // 是否支持控制选项,如果是,空值的标签是什么
            empty: '@empty',
 
            // 用于parse解析获取model值(而非viewValue值)
            modelName: '@ngModel'
        },
 
        template: ''
            // 使用ng-show而非ng-if,原因上文已经提到
            + '<option ng-show="empty" value="">{{empty}}</option>'
            // 使用朴素的ng-repeat
            + '<option ng-repeat="item in items" value="{{item.value}}">{{item.text}}</option>',
 
        require: 'ngModel',
 
        link: function (scope, elem, attr, model) {
 
            var dependents = scope.dependents ? scope.dependents.split(',') : false;
            var parentScope = scope.$parent;
            scope.name = scope.name || 'multi-select-' + Math.floor(Math.random() * 900000 + 100000);
 
            // 将当前菜单的getValue函数封装起来,放在闭包中的selects对象中方便调用
            selects[scope.name] = {
                getValue: function () {
                    return $parse(scope.modelName)(parentScope);
                }
            };
 
            // 保存初始值,原因上文已经提到
            var initValue = selects[scope.name].getValue();
 
            var inited = !initValue;
            model.$setViewValue('');
 
            // 父级标签变化时被调用的回调函数
            function onParentChange() {
                var values = {};
                // 获取所有依赖的菜单的当前值
                if (dependents) {
                    $.each(dependents, function (index, dependent) {
                        values[dependent] = selects[dependent].getValue();
                    });
                }
 
                // 利用闭包判断io造成的异步过期
                (function (thenValues) {
 
                    // 调用source函数,取新的option数据
                    var returned = scope.source ? scope.source(values) : false;
 
                    // 利用多层闭包,将同步结果包装为有then方法的对象
                    !returned || (returned = returned.then ? returned : {
                        then: (function (data) {
                            return function (callback) {
                                callback.call(window, data);
                            };
                        })(returned)
                    }).then(function (items) {
 
                        // 防止由异步造成的过期
                        for (var name in thenValues) {
                            if (thenValues[name] !== selects[name].getValue()) {
                                return;
                            }
                        }
 
                        scope.items = items;
 
                        $timeout(function () {
 
                            // 防止由同步(严格的说也是异步,注意事件队列)造成的过期
                            if (scope.items !== items) return;
 
                            // 如果有空值,选择空值,否则选择第一个选项
                            if (scope.empty) {
                                model.$setViewValue('');
                            } else {
                                model.$setViewValue(scope.items[0].value);
                            }
 
                            // 判断恢复初始值的条件是否成熟
                            var initValueIncluded = !inited && (function () {
                                for (var i = 0; i < scope.items.length; i++) {
                                    if (scope.items[i].value === initValue) {
                                        return true;
                                    }
                                }
                                return false;
                            })();
 
                            // 恢复初始值
                            if (initValueIncluded) {
                                inited = true;
                                model.$setViewValue(initValue);
                            }
 
                            model.$render();
 
                        });
                    });
 
                })(values);
 
                 
            }
 
            // 是否有依赖,如果没有,直接触发onParentChange以还原初始值
            !dependents ? onParentChange() : scope.$on('selectUpdate', function (e, data) {
                if ($.inArray(data.name, dependents) >= 0) {
                    onParentChange();
                }
            });
 
            // 对当前值进行监听,发生变化时对其进行广播
            parentScope.$watch(scope.modelName, function (newValue, oldValue) {
                if (newValue || '' !== oldValue || '') {
                    scope.$root.$broadcast('selectUpdate', {
                        // 将变动的菜单的name属性广播出去,便于依赖于它的菜单进行识别
                        name: scope.name
                    });
                }
            });
 
        }
    };
}]);

angularJS中组件复用和封装

https://www.zybuluo.com/lxjwlt/note/331587

http://www.jb51.net/article/58229.htm

http://www.tuicool.com/articles/QfyMna

http://www.tuicool.com/articles/fqiI73M

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,295评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,928评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,682评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,209评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,237评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,965评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,586评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,487评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,016评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,136评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,271评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,948评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,619评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,139评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,252评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,598评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,267评论 2 358

推荐阅读更多精彩内容