jQuery插件实例七:一棵Tree的生成史

在需要表示级联、层级的关系中,Tree作为最直观的表达方式常出现在组织架构、权限选择等层级关系中。典型的表现形试类似于:

一颗树的生成常常包括三个部分:1)数据库设计;2)后台程序;3)前端代码。那么,具体是怎么样的呢?

一、数据库设计

数据库设计对于树的表达常会包含这么几个类似意思的字段:

parent_id、id、name。

id:用于描述自己;

parent_id:用于描述自己的上一级;

name:用于描述自己的名称;

例如:总办(id=3,parent_id=0,name=总办),客户服务中心(id=10,parent_id=3,name=客户服务中心) ,客户部(id=12,parent_id=10,name=客户部)。由此建立了三级层级关系。

二、后台程序

对于一个层级,可能会用于描述部门关系,还可能用于描述菜单关系等等,不同的用途有不同的数据库设计字段。但为了程序的通用性,不可能为一了一个表或功能做单独的前端插件,因此就要在后台为前端插件需要使用到的字段做一个规范(或者在数据库设计中做规范)。在此为“树结构”在后台作这样的规范:

 1     /// <summary>
 2 
 3     /// 层级
 4 
 5     /// </summary>
 6 
 7     public class vmHierarchy
 8 
 9     {
10 
11         public int id { get; set; }
12 
13         public int pid { get; set; }
14 
15         public string name { get; set; }
16 
17         public object sub { get; set; }
18 
19         public int status { get; set; }
20 
21 }

Pid:用于描述上级关系;

Sub:用于描述子级关系;

Status:用于描述自身状态或特殊标识;

以做部门的层级关系为例:分为两个部分:

1)  取数据:

 

        /// <summary>

        /// 取部门层级

        /// </summary>

        /// <returns></returns>

        public List<vmHierarchy> GetDepartmentRelation()

        {

            List<vmHierarchy> vmdrlist = new List<vmHierarchy>();

            using (var ctx = DB.ContextForName(DBConnection.DefaultConnection).UseTransaction(true))

            {

                List<au_Department> adlist = new List<au_Department>();

                adlist = base.GetModelAll();

                vmdrlist = GetDepartmentRelationSub(1, adlist);

            }

            return vmdrlist;

        }

2) 定层级:

       /// <summary>

        /// 取部门层级-子级

        /// </summary>

        /// <param name="parentid"></param>

        /// <param name="adlist"></param>

        /// <returns></returns>

        public List<vmHierarchy> GetDepartmentRelationSub(int parentid, List<au_Department> adlist)

        {

            List<vmHierarchy> vmdrlist = new List<vmHierarchy>();

            List<au_Department> modellist = new List<au_Department>();

            modellist = adlist.Where(s => s.parent_id == parentid).OrderBy(s => s.sequence).ToList<au_Department>();

            foreach (au_Department item in modellist)

            {

                vmHierarchy vmmodel = new vmHierarchy();

                vmmodel.id = item.id;

                vmmodel.pid = item.parent_id;

                vmmodel.name = item.name;

                vmmodel.sub = GetDepartmentRelationSub(item.id, adlist);

                vmdrlist.Add(vmmodel);

            }

            return vmdrlist;

        }

由此在前端可以得到类似这样的关系数据:

 

三、前端代码

在与前端代码时,关于树的逻辑关系理清是最为主要的。

1)  如何生成当前层级关系和期子级关系,每个节点的子节点都不同。

2)  需要复选框吗?

3)  需要折叠吗?

4)  当点击一个节点:

  A:其下还有一串节点,要全部选中/全部不选中?

  B:当前点击中其它子节点都被选中了,再选中这个节点,如何影响上级的选中与不选中?

总结为:在有复选框的情况下,如何影响它的下级和上级节点关系?

5)  三个事件:

  A:单击选中复选框事件;

  B:单击取消选中复选框事件;

  C:单击行事件;

事件顺序?冒泡?必要事件与用户自定义事件?

有需求的童鞋可以看看下面的jQuery代码:

jQuery:

//Tree层级关系 Begin
; (function ($, window, document, undefined) {
    var defaults = {
        ajaxurl: '',//ajax取数据的url[data==null时有效]
        data: null,//数据
        erow: null,//点击行时要执行的事件function(){}
        checbox: true,//是否有复选框
        initunfold: true,//初始展开true 初始折叠false
        event: {
            selectedrows: null,//单击行时要执行的事件
            checked: null,//选中了复选框时要执行的事件
            unchecked: null//取消选中复选框时要执行的事件
        },//事件
        exchangebar: false,//是否有全部展开 全部折叠 按钮
        onlyleafcheck: false//是否只有最终子节点才显示checkbox
    };
    $.fn.etree = function (options) {
        var $that = $(this);
        var _ops = $.extend(true, {}, defaults, options);
        var $con = null, _activehtml;
        var _lv = 0;
        //初始化数据
        function initdata() {
            if (_ops.data !== null) {
                generateTree(_ops.data);
            } else {
                $.ajax({
                    url: _ops.ajaxurl,
                    dataType: "JSON",
                    success: function (result) {
                        _ops.data = result;
                        generateTree(_ops.data);
                    }
                });
            }
        };
        function generateTree(_data) {
            console.log(_data);
            $con = $('<div></div>').appendTo($that);
            var $ul = $('<ul class="e-tree-ul"></ul>').appendTo($con);
            generateSub($con, _data, _lv);
            initEvent();
            if (_ops.initunfold == false) {
                $con.find('.tge-inv').each(function () {
                    $(this).click();
                })
            }
        };
        function generateSub($e, _data, _lv) {
            for (var i = 0; i < _data.length; i++) {
                var _tdata = _data[i];
                var $li = $('<li class="e-tree-li"></li>').appendTo($e);
                var $p = $('<p class="e-tree-p" lv=' + _lv + '></p>').appendTo($li);
                var $ti = $('<i class="tge-inv"></i>').appendTo($p);
                var $tif = $('<i class="tge-invf"></i>').appendTo($p);
                var $tc = null;
                if (_ops.checbox == true) {
                    if (_ops.onlyleafcheck == true && _tdata.sub.length == 0) {
                        $tc = $('<i class="sck" tid="' + _tdata.id + '"></i>').appendTo($p);
                    } else if (_ops.onlyleafcheck == false) {
                        $tc = $('<i class="sck" tid="' + _tdata.id + '"></i>').appendTo($p);
                    }
                }
                var $ts = $('<span class="e-tree-s"></span>').html(_tdata.name).appendTo($p);
                if (_tdata.sub.length > 0) {
                    $tif.addClass('tge-invfr');
                    var $ul = $('<ul class="e-tree-ul"></ul>').appendTo($li);
                    generateSub($ul, _tdata.sub, (_lv + 1));
                    $ti.addClass('tge-invd');
                } else {
                    $tif.addClass('tge-invfd');
                }
                if ($tc != null) {
                    if (_tdata.status == 1) {
                        $tc.addClass('ck');//选中
                    } else {
                        $tc.addClass('nock');//未选中
                    }
                }
            }
        };
        function checksubordinate($e) {
            var $slv = $e.parent('p').next('ul');
            if ($e.hasClass('ck')) {
                $slv.find('.sck').removeClass('nock').addClass('ck');
            } else if ($e.hasClass('nock')) {
                $slv.find('.sck').removeClass('ck').addClass('nock');
            }
        };
        function checksuperior($e) {
            var $plv = $e.parent('p').parent('li').parent('ul');
            if ($plv.length > 0) {
                var $sib = $plv.children('li');
                var $sumckdcount = $sib.children('p').children('.ck').length;
                var $scount = $sib.length - $sumckdcount;
                var $ppsck = $plv.prev('p').children('.sck');
                if ($scount == 0) {
                    $plv.prev('p').children('.sck').removeClass('nock').addClass('ck');
                } else {
                    $plv.prev('p').children('.sck').removeClass('ck').addClass('nock');
                }
                if ($ppsck.length > 0) {
                    checksuperior($ppsck);
                }
            }
        };
        function checkselect($e) {
            checksuperior($e);
            checksubordinate($e);
        };
        function setAction($e) {
            var $ts = $e;
            var $te = $ts.parent('p');
            var $thisid = parseInt($e.attr('tid'));
            $con.find('.e-tree-active').removeClass('e-tree-active');
            if ($ts.hasClass('ck')) {
                $ts.removeClass('ck').addClass('nock');
                checkselect($ts);
                if (typeof _ops.event.unchecked == "function") {
                    _ops.event.unchecked($te, iselectedhtml());//活动项,唯一选中项|null
                }
            } else if ($ts.hasClass('nock')) {
                $ts.removeClass('nock').addClass('ck');
                checkselect($ts);
                if (_ops.event.checked != null) {
                    _ops.event.checked($te, iselectedhtml());//活动项,唯一选中项|null
                }
            }
            var $shtml = iselectedhtml();
            if ($shtml != null) {
                var $ck = $shtml.children('.ck');
                var $sid = parseInt($shtml.attr('tid'));
                $ck.parent('p').addClass('e-tree-active');
                if ($thisid == $sid) {
                    setAction($ck);
                }
            }
        };
        function initEvent() {
            $con.find('.tge-inv').bind('click', function (e) {
                var $ts = $(this);
                var $next = $ts.parent('p').next();
                var $tsnext = $ts.next();
                if ($ts.hasClass('tge-invd')) {
                    $ts.removeClass('tge-invd').addClass('tge-invr');
                    $next.slideUp();
                    if ($tsnext.hasClass('tge-invfr')) {
                        $tsnext.removeClass('tge-invfr').addClass('tge-invfd');
                    }
                } else if ($ts.hasClass('tge-invr')) {
                    $ts.removeClass('tge-invr').addClass('tge-invd');
                    $next.slideDown();
                    if ($tsnext.hasClass('tge-invfd')) {
                        $tsnext.removeClass('tge-invfd').addClass('tge-invfr');
                    }
                }
                e.stopPropagation();
            });
            $con.find('.sck').bind('click', function (e) {
                var $ts = $(this);
                setAction($ts);
                e.stopPropagation();
            });
            $con.find('.e-tree-p').bind('click', function () {
                $(this).children('.sck').click();
            });
            if (typeof _ops.event.selectedrows == "function") {
                $con.find('.e-tree-p').bind('click', function () {
                    var $te = $(this).context;
                    _ops.event.selectedrows($($te), iselectedhtml());//活动项,唯一选中项|null
                });
            }
            $con.find('.e-tree-s').bind('click', function () {
                return false;
            });
        };
        function iactivehtml() {
            _activehtml = $con.find('.e-tree-active').html();
            return _activehtml;
        };
        function iactiveid() {
            var $thtml = $con.find('.e-tree-active');
            _activeid = parseInt($thtml.find('.sck').attr('tid'));
            return _activeid;
        };
        function iselectedids() {
            var _ids = new Array();
            $con.find('.ck').each(function () {
                _ids.push(parseInt($(this).attr('tid')));
            });
            return _ids;
        };
        function iselectedhtml() {
            if (iselectedids().length == 1) {
                return $con.find('.ck').parent('p');
            } else {
                return null;
            }
        };
        function iselectedid() {
            if (iselectedids().length == 1) {
                return parseInt($con.find('.ck').attr('tid'));
            } else {
                return null;
            }
        };
        initdata();
        //活动项html [活动项:当前点击的项]
        this.activehtml = function () {
            return iactivehtml();
        };
        //活动项id [活动项:当前点击的项]
        this.activeid = function () {
            return iactiveid();
        };
        //获取所有选中的项id
        this.selectedids = function () {
            return iselectedids();
        };
        //当前唯一选中项的html 不满足唯一选中时,返回null
        this.selectedhtml = function () {
            return iselectedhtml();
        };
        //当前唯一选中项的id 不满足唯一选中时,返回null
        this.selectedid = function () {
            return iselectedid();
        };
        return this;
    };
})(jQuery, window, document);
//Tree层级关系 End

使用:

@{
    Layout = null;
}
@using UCMS_Commons;
@using UCMS_Model;
@using UCMS_Model.ViewModel;

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>DepatmentManage</title>
    <link href="~/Content/themes/black/Css/eui.css" rel="stylesheet" />
    <script src="~/Scripts/jquery-1.8.2.min.js"></script>
    <script src="~/Scripts/extendjs/jquery.cookie.js"></script>
    <script src="~/Content/themes/black/Script/jquery.eui.js"></script>
    <style type="text/css">
        #dp-content {
            width: 920px;
            margin: 0 auto;
        }

        #de-cont {
            width: 400px;
            border: 1px solid #E4E4E4;
            padding: 20px;
            display: inline-block;
            vertical-align: top;
        }

        #oper-cont {
            width: 400px;
            border: 1px solid #E4E4E4;
            padding: 20px;
            display: inline-block;
            vertical-align: top;
            margin-left: 28px;
        }

        fieldset {
            border: 1px solid #ddd;
        }

        legend {
            color: #9b9b9b;
        }
    </style>
    <script type="text/javascript">
        $(function () {
            var actionUrl = {
                'GetDpInfo': '/SystemCenter/GetDpInfo',
                'adddp': '/SystemCenter/AddDp',
                'deletedp': '/SystemCenter/DeleteDp',
                'updatedp': '/SystemCenter/UpdateDp'
            };

            var t = JSON.parse('@Html.Raw(JSONNet.Serialize(Model))');
            var $opername = $('#oper-name');
            var $opersub = $('#oper-cont-sub');
            var $operinfoo = $('#oper-info-o');
            var $operinfoname = $('#oper-info-name');
            var $errormsg = $('#error-msg');
            var $deletedp = $('#deletedp');
            var $dpname = $('#dpname');
            var _status = -1, _ttname = '';

            $('input[name="opertype"]').bind('click', function () {
                var _index = parseInt($(this).attr('tp'));
                $operinfoo.show();
                switch (_index) {
                    case 0: { $opername.attr('readonly', 'readonly'); $opersub.show(); $opername.val(_ttname); $('#oper-info-o').show(); }; break;
                    case 1: { $opername.removeAttr('readonly'); $opersub.hide(); $opername.focus().val(''); _status = 1; }; break;
                    case 2: { $opername.removeAttr('readonly'); $opersub.hide(); $opername.focus().val(''); _status = 2; }; break;
                    case 3: { $opername.removeAttr('readonly'); $opersub.hide(); $opername.focus(); _status = 3; }; break;
                }
            });
            $('#savedp').bind('click', function () {
                switch (_status) {
                    case 1: { adddp(1); }; break;
                    case 2: { adddp(2); }; break;
                    case 3: { updatedp(); }; break;
                }
            });
            $('#deletedp').bind('click', function () {
                deletedp();
            });
            function adddp(_type) {
                var _id = _dptree.activeid();
                var _dpname = $opername.val();
                if (_dpname.length == 0) {
                    $errormsg.html('请填写 名称');
                }
                else {
                    $.ajax({
                        url: actionUrl.adddp,
                        data: { "thisid": _id, "addtype": _type == 1 ? 0 : 1, "addname": _dpname },
                        success: function (result) {
                            var _result = $.eui.checkresult(result);
                            if (_result) {
                                window.location.reload();
                            }
                        }
                    });
                }
            };
            function deletedp() {
                var _ids = _dptree.selectedids();
                var _idarray = JSON.stringify(_ids);
                $.ajax({
                    url: actionUrl.deletedp,
                    data: { "ids": _idarray },
                    success: function (result) {
                        var _result = $.eui.checkresult(result);
                        if (_result) {
                            window.location.reload();
                        }
                    }
                });
            };
            function updatedp() {
                var _id = _dptree.activeid();
                var _name = $opername.val();
                if (_name.length == 0) {
                    $errormsg.html('请填写 名称');
                }
                else {
                    $.ajax({
                        url: actionUrl.updatedp,
                        data: { "thisid": _id, "newname": _name },
                        success: function (result) {
                            var _result = $.eui.checkresult(result);
                            if (_result) {
                                window.location.reload();
                            }
                        }
                    });
                }
            };
            function getdpinfo(_id) {
                $.ajax({
                    url: actionUrl.GetDpInfo,
                    data: { "id": _id },
                    success: function (result) {
                        var $os = $('#oper-sub').html('');
                        for (var i = 0; i < result.length; i++) {
                            $('<span style="padding: 0 10px;"></span>').html(result[i].name + '(' + result[i].percount + '人)').appendTo($os);
                        }
                    }
                });
            };
            function schecked(el, sl) {
                $('input[name="opertype"]').eq(0).click();
                if (sl != null) {

                    var _name = sl.find('.e-tree-s').html();
                    $dpname.html(_name);
                    $opername.val(_name);
                    _ttname = _name;
                    getdpinfo(_dptree.selectedid());
                    $operinfoo.show();
                } else {
                    $dpname.html('已选中个数:' + _dptree.selectedids().length);
                    $operinfoo.hide();
                }
            };
            var _dptree = $('#de-cont-d').etree({
                data: t,
                checbox: true,
                onlyleafcheck: true,
                event: {
                    selectedrows: function (el, sl) {

                    },
                    checked: function (el, sl) {
                        var _tname = el.find('.e-tree-s').html();
                        $deletedp.show();
                        $errormsg.html('');
                        schecked(el, sl);
                    },
                    unchecked: function (el, sl) {
                        $operinfoo.hide();
                        schecked(el, sl);
                    }
                }
            });
        });
    </script>
</head>
<body>
    <div id="dp-content">
        <fieldset id="de-cont">
            <legend>部门</legend>
            <div id="de-cont-d">

            </div>
        </fieldset>
        <fieldset id="oper-cont">
            <legend>信息</legend>
            <div id="oper-info-name" style="line-height: 32px;">
                名称:<span id="dpname"></span>
                <a href="javascript:void(0);" class="eui-btns" style="float:right;display:none;" id="deletedp">删除?</a>
            </div>
            <div id="oper-info-o" style="display:none;">
                <hr class="hrgrey" />
                <p id="oper-cont-d" class="eui-p50">
                    操作类别:
                    <label><input name="opertype" type="radio" value="" tp="0" checked="checked" />查看信息</label>
                    <label><input name="opertype" type="radio" value="" tp="1" />添加同级</label>
                    <label><input name="opertype" type="radio" value="" tp="2" />添加子级</label>
                    <label><input name="opertype" type="radio" value="" tp="3" />修改自身</label>
                </p>
                <p class="eui-p50">
                    名称:
                    <input type="text" name="name" value="" id="oper-name" class="eui-input" readonly="readonly" />
                </p>
                <p class="eui-p50" id="oper-cont-sub">
                    下辖职位:<span id="oper-sub"></span>
                </p>
                <label id="error-msg" class="error-msg"></label>
                <a href="javascript:void(0);" class="eui-btns" style="float:right;bottom:0;" id="savedp">保存</a>
            </div>
        </fieldset>
    </div>
</body>
</html>
View Code

 

posted @ 2015-05-03 09:16  年华时代  阅读(2110)  评论(0编辑  收藏  举报