AngularJS中实现下拉搜索框

先上效果,带有搜索功能:

image_1bvhghr9g1pe55u0eh583fmdg29.png-10.8kB

前端JS框架使用的是AngularJS.

一、从后台获取树形数据

获取到的树形结构是这个样子的,数组对象:

image_1bvhgmd6u1f4l1nd66t31bo416e09.png-103.3kB

至于后台如何达到此效果,详情见:
Java递归获取树父节点下的所有树子节点

二、前端JS与html

  • js中获取到树形数据
/**
 * 文件名:organizationcontroller.js
 * 路径:项目名/WebContent/xxx/scripts/controllers/platform/organizationcontroller.js
 */

//获取区域树
$scope.getDivisionTree=function () {
    divisionInfoService.getDivisionInfoTree().then(function (data) {
        if (data && data.statusCode == 200) {
            $scope.divisionTree = [{
                code: -1,
                name: '全国',
                children: data.result
            }];
            $scope.division = $scope.division || -1;
        }
    }, function (error) {
        tipService.popup.error(error);
    });
};

/*页面初始化*/
$scope.getDivisionTree();
  • html页面中使用数据
<!--
    文件名:organizationform.html
    路径:项目名/WebContent/xxx/views/platform/projectsetting/organizationform.html
-->

<label>所在区域</label>
<div>
    <search-box search-data="divisionTree" ng-search-model="organization.divisionCode" search-change="divisionChange(item)" search-value-text="code" search-label-name="name" ng-required="true" can-choose-parent="true">
    </search-box>
</div>

那么这个<search-box>标签是什么玩意?
标签里面的参数又是什么意思?是通过什么定义的?请继续往后看,在四、指令详细介绍。

先来简单说一下<search-box>标签中的各个属性是什么意思:
search-data 表示数据源。在一、从后台获取树形数据中,不是从后台查到了整个树形结构嘛,然后在js中获取到树形数据中,使用$scope.divisionTree这个变量接收了数据源,因此,html中的search-data,对应的就是divisionTree

ng-search-model AngularJS的特色之一,数据双向绑定。将当前的数据,与scope中的数据进行绑定。简单来说,只要页面的内容发生变化,那么对应的js中的变量值也就发生变化。在这里,ng-search-model的值是:organization.divisionCode; 但为什么model是code? 获取到的属性,除了code,还有idshortCodename等等,为什么这里的model偏偏是code?

search-value-text 那是因为:search-value-text="code";

search-label-name 同理,为什么效果图上,显示的是name? 因为:search-label-name="name";

ng-required="true" 必选;

can-choose-parent="true" 可以选择父节点;

search-change="divisionChange(item)" 当值产生变动。

三、树形结构的html与css

<!--
    文件名:searchbox.html
    路径:项目名/WebContent/xxx/scripts/directives/searchbox/searchbox.html
-->

<div class="search-box-main">
    <script type="text/ng-template" id="items_search.html">
        <div ui-tree-handle>
            <div class="tree-node-content" ng-click="node(this,item,$event)">
                <a class="btn handletools expand" data-nodrag ng-click="toggle(this)">
                    <span class="fa fa-fw" ng-class="{'fa-plus-square-o': collapsed, 'fa-minus-square-o': !collapsed}" ng-show="item.children.length"></span>
                </a>
                <span class="itemTitle">{{item[ngLabelName]}} </span>
            </div>
            <ol ui-tree-nodes="options" ng-model="item.children" ng-class="{hidden: collapsed}">
                <li ng-repeat="item in item.children | filter:ngText" class="ui-tree-child" ui-tree-node collapsed="true" ng-include="'items_search.html'" ng-show="visible(item)" data-nodrag="true">
                </li>
            </ol>
        </div>
    </script>
    <div class="search-box-input-context clearfix">
        <div class="search-icon search-down"><i class=" glyphicon glyphicon-chevron-down"></i></div>
        <div class="search-box-input"><input type="text" readonly class="search-box-input-text" ng-model="ngTempText" name="code" ng-disabled="ngDisable"/></div>
    </div>
    <div class="search-box-pop">
        <div class="search-box-input-context clearfix" ng-show="list&&list.length>0">
            <div class="search-icon search-clear"><i class=" glyphicon glyphicon-remove"></i></div>
            <div class="search-box-input"><input class="search-box-input-text" ng-model="ngText"/></div>
        </div>
        <div class="search-tree">
            <div class="search-tree-content">
                <div ui-tree="options">
                    <ol ui-tree-nodes data-ng-model="list" id="tree">
                        <li ng-repeat="item in list  | filter:ngText" class="ui-parent" ui-tree-node ng-include="'items_search.html'" ng-show="visible(item)" data-nodrag="true" >
                        </li>
                    </ol>
                </div>
            </div>
        </div>
    </div>
    <div class="error" ng-show="ngRequired&&!ngTempText">
        <small class="error" ng-show="ngRequired&&!ngTempText">该项必填</small>
    </div>
</div>

/*
    文件名:searchbox.css
    路径:项目名/WebContent/xxx/scripts/directives/searchbox/searchbox.css
*/

.search-box-main {
    position: relative;
}

.search-box-main .search-box-input-context {
    overflow: hidden;
    height: 30px;
    border: 1px solid #ccc;
    -webkit-box-shadow: 0 0 0 rgba(0, 0, 0, 0) !important;
    -moz-box-shadow: 0 0 0 rgba(0, 0, 0, 0) !important;
    box-shadow: 0 0 0 rgba(0, 0, 0, 0) !important;
}

.search-box-main .search-box-input-context .search-icon {
    float: right;
    width: 20px;
    height: 100%;
    line-height: 34px;
}

.search-box-main .search-box-input-context .search-icon:hover {
    opacity: 0.5;
}

.search-box-main .search-box-input-context .search-box-input {
    overflow: hidden;
}

.search-box-main .search-box-input-context .search-box-input .search-box-input-text {
    width: 100%;
    display: inline-block;
    height: 20px;
    line-height: 20px;
    margin: 5px 0;
    padding: 0 0 0 5px;
    border: none;
    outline: none;
}

.search-box-main.active .search-box-input-context, .search-box-main.focus .search-box-input-context {
    outline: 0;
    border: 1px solid #66afe9;
    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
}

.search-box-pop {
    display: none;
    position: absolute;
    width: 100%;
    overflow: hidden;
    z-index: 999999;
    padding-top: 40px;
}

.search-box-main.active .search-box-pop {
    display: block;
    border: 1px solid #66afe9;
    background: #fff;
}

.search-box-main .angular-ui-tree-handle {
    border: none;
    cursor: pointer;
    padding: 0;
}

.search-box-main .angular-ui-tree-handle .handletools.expand {
    padding: 0;
    position: static;
    vertical-align: middle;
}

.search-box-main .angular-ui-tree-handle .itemTitle {
    vertical-align: middle;

    white-space: nowrap;
}

.search-box-main .search-box-input-context .search-clear .glyphicon {
    display: none;
}

.search-box-main .search-box-input-context:hover .search-clear .glyphicon {
    display: block;
    vertical-align: middle;
    line-height: 30px;
    text-align: center;
}

.search-box-main.search-disabled .search-box-input-context:hover .search-clear {
    display: none;
}

.search-box-main.search-disabled .search-box-input-context .search-icon:hover {
    opacity: 1;
}

.search-box-main.search-disabled .search-box-input-context .search-box-input .search-box-input-text {
    background-color: #eee;
}

.search-box-main.search-disabled .search-box-input-context {
    background: #EEEEEE;
}

.search-box-main .angular-ui-tree-empty {
    background: #66AFE9;
    border: none;
    min-height: 15px;
}

.search-box-pop > .search-box-input-context {
    margin: 5px;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2;
}

.search-box-pop > .search-tree {
    height: 100%;
    overflow: auto;
}

.search-box-pop > .search-tree .search-tree-content li {
    word-break: keep-all;
    word-wrap: normal;
}

.search-box-pop .tree-node-content {
    white-space: nowrap;
    text-align: left;
}

.search-box-pop .angular-ui-tree-handle {
    overflow: visible;
}

#dataCentersTree .search-box-height {
    height: 200px;
}

#dataCentersTree #options {
    overflow-y: auto;
    height: 195px;
}

#dataCentersTree .search-box-pop {
    padding-top: 5px;
}

#dataCentersTree .retract {
    padding-left: 20px;
}

#dataCentersTree .handletools {
    padding: 2px 4px;
}

四、指令

/**
 * 文件名:searchbox.js
 * 路径:项目名/WebContent/xxx/scripts/directives/searchbox/searchbox.js
 */

/*
 * <search-box ng-search-model="inspectionKnowledgeBase.eqTypeCode" search-change="fn()" search-value-text="typeCode" search-label-name="typeName" search-data="service.equipmentTypes" name="code" required></search-box>

 * search-data:当前数据源;
 * search-label-name:当前下拉列表显示名称(在数据源中值的名称),默认情况下为name;
 * search-value-text:当前下拉列表返回的值的名称(在数据源中值的名称),默认情况下为空;
 * ng-search-model:数据绑定对象,当search-value-text为空时,返回当前model在数据源中的对象;
 * can-choose-parent="true":是否可以点击父节点,默认为false。
 * ng-required='true';是否必填;
 * search-change="fn()";内容去选择后的回调
 * */
 
cooleadCloud.directive('searchBox', ['$templateCache', '$http', '$compile', '$window', '$rootScope', '$timeout', '$safeapply', '$cookieStore',
    function ($templateCache, $http, $compile, $window, $rootScope, $timeout, $safeapply, $cookieStore) {
        return {
            restrict: 'AE',
            templateUrl: 'scripts/directives/searchbox/searchbox.html',
            transclude: true,
            scope: {
                ngSearchData: "=searchData",
                ngSearchModel: "=ngSearchModel",
                ngLabelName: "@searchLabelName",
                ngValueText: "@searchValueText",
                ngCanChooseParent: "=canChooseParent",
                ngChange: "&searchChange",
                ngRequired: "=ngRequired",
                ngDisable: "=ngDisabled",
                ngEmpty: "=ngNotEmpty"
            },
            replace: false,
            link: function ($scope, element, attrs) {
                $scope.ngTempText = "";
                $scope.ngEmpty = false;
                $scope.isChoosed = true;
                $scope.ngLabelName = $scope.ngLabelName || 'name';
                var valText = angular.copy($scope.ngValueText), tempText = angular.copy($scope.ngLabelName);
                var validateName = function (item, name) {
                    var te = null;
                    if (item) {
                        if (item.hasOwnProperty(name)) {
                            te = item[name];
                        } else {
                            te = item;
                        }
                    }
                    return te;
                }
                var iteatorValue = function (items, nv) {
                    var item;

                    if (items) {
                        for (var i = 0; i < items.length; i++) {
                            item = items[i];
                            if (nv == validateName(item, valText)) {
                                var text = validateName(item, tempText);
                                $scope.ngTempText = text;
                                if (text) {
                                    $scope.ngEmpty = true;
                                }
                            } else {
                                if (item && item.hasOwnProperty('children')) {
                                    var childrens = item.children;
                                    if (childrens && childrens.length > 0) {
                                        iteatorValue(childrens, nv);
                                    }
                                }
                            }
                        }
                    }
                    return;
                }

                function filterData(nv) {
                    var tempList = angular.copy($scope.list);
                    if (tempList) {
                        var nItem;
                        for (var i = 0; i < tempList.length; i++) {
                            nItem = tempList[i];
                            if (nv == validateName(nItem, valText)) {
                                var text = validateName(nItem, tempText);
                                $scope.ngTempText = text;
                                if (text) {
                                    $scope.ngEmpty = true;
                                }
                            } else {
                                if (nItem && nItem.hasOwnProperty('children')) {
                                    iteatorValue(nItem.children, nv);
                                }
                            }
                        }
                    }

                }

                $scope.$watch("ngText", function (nv, ov) {
                    if (typeof nv === "undefined") {
                        return;
                    }
                    $("#tree").hide();
                    var li = $("#tree  li");
                    angular.forEach(li, function (item) {
                        var span = $(item).find(".itemTitle");
                        for (var i = 0; i < span.length; i++) {
                            if ($(span[i]).text().indexOf(nv) !== -1) {
                                $(item).show();
                                break;
                            } else {
                                $(item).hide();
                            }
                        }
                    });
                    $("#tree").show();
                });

                if ($scope.ngSearchData) {
                    $scope.list = angular.copy($scope.ngSearchData);
                }
                /**
                 * add by likw 2016-05-31
                 *  监听数据源改变。
                 */
                $scope.$watch("ngSearchData", function (nv, ov) {
                	$scope.ngTempText = "";
                	$scope.list = angular.copy(nv);
                });
   
                var ngIndex = 0;
                $scope.$watch('ngSearchData', function (nV, oV) {
                    if ((nV && nV.length > 0) && !ngIndex) {
                        ngIndex++;
                        $scope.list = angular.copy(nV);
                        filterData($scope.ngSearchModel);
                    }
                });
                var $element = $(element);
                var $main = $('.search-box-main', $element);
                var $searchIcon = $(".search-down", $element);
                var $searchClear = $(".search-clear", $element);
                var $searchInput = $(".search-box-input-text", $element);
                $scope.$watch('ngSearchModel', function (nV, oV) {
                    if (!nV) {
                        $scope.ngTempText = "";
                        return;
                    }
                    if (nV != oV) {
                        $scope.ngTempText = "";
                        filterData(nV);
                    }
                });


                if ($scope.ngDisable) {
                    $main.addClass('search-disabled');
                }

                /*点击清空*/
                $searchClear.off('click').on('click', function () {
                    var closest = $(this).closest('.search-box-main');
                    if (closest.hasClass('search-disabled')) {
                        return;
                    }
                    $safeapply($scope, function () {
                        $scope.ngText = '';
                    });

                });
                /*点击下拉事件*/
                $searchIcon.off('click').on('click', function () {
                    var closest = $(this).closest('.search-box-main');
                    if (closest.hasClass('search-disabled')) {
                        return;
                    }
                    $main.addClass('active');
                    var $searchPop = closest.children('.search-box-pop').eq(0);
                    var $treeCon = $searchPop.children('.search-tree').eq(0);
                    var h = $treeCon.outerHeight(true);
                    if (h > 237) {
                        $searchPop.height(200);
                    }
                });
                $element.isSetHeight = false;
                $searchInput.off('click').on('click', function () {

                    var closest = $(this).closest('.search-box-main');
                    if (closest.hasClass('search-disabled')) {
                        return;
                    }

                    $safeapply($scope, function () {
                        $scope.ngText = '';
                    });
                    $main.addClass('active');
                    var $searchPop = closest.children('.search-box-pop').eq(0);
                    var $treeCon = $searchPop.children('.search-tree').eq(0);
                    var h = $treeCon.outerHeight(true);
                    if (h > 237) {
                        $searchPop.height(200);
                        $element.isSetHeight = true;
                    }
                });

                /*非当前区域点击事件*/
                $('body').on('click', function (e) {
                    var $target = $(e.target);
                    var $pTarget = $target.closest('.search-box-main');
                    if (!$pTarget[0]) {
                        $main.removeClass('active');
                    }
                })
                /*树状结构 开始*/
                $scope.visible = function (item) {
                    return !($scope.query && $scope.query.length > 0
                    && item.title.indexOf($scope.query) == -1);
                };
                $scope.toggle = function (scope) {
                    scope.toggle();
                };
                $scope.node = function (scope, item, $event) {
                    var isChoose = false;
                    $scope.isChoosed = true;
                    if ($scope.ngCanChooseParent) {
                        isChoose = true;
                    } else {
                        isChoose = !scope.hasChild()
                    }
                    if (isChoose && !angular.element($event.target).hasClass("fa")) {
                        $scope.ngSearchModel = validateName(item, valText);
                        $scope.ngTempText = validateName(item, tempText);
                        $main.removeClass('active');
                        $scope.ngChange({
                            item: item
                        });
                        $scope.isChoosed = false;

                        if ($scope.ngTempText) {
                            $scope.ngEmpty = true;
                        }
                        //是项目,项目树修改时,设置到cookies
                        if (item.shortCode && item.fullCode && item.divisionCode) {
                            $cookieStore.put('defaultProject', item);
                        }
                    }

                    var $target = $($event.target).closest(".expand");
                    if (!$element.isSetHeight && !isChoose && !scope.collapsed && $target && $target[0]) {
                        var $searchPop = $target.closest('.search-box-pop').eq(0);
                        var $treeCon = $searchPop.children('.search-tree').eq(0);
                        setTimeout(function () {
                            var h = $treeCon.outerHeight(true);
                            if (h > 237) {
                                $searchPop.height(200);
                                $element.isSetHeight = true;
                            }
                        }, 500);
                    }
                };
                /*树状结构 结束*/
            }
        };
    }]);

最后,别忘了页面中引入相关的js和css.

posted on 2017-11-22 18:01  Yoooshiki  阅读(1108)  评论(0编辑  收藏  举报

导航