/** @Name:layui.tree 树 @Author:star1029 @License:MIT */ layui.define('form', function (exports) { "use strict"; var $ = layui.$ , form = layui.form , layer = layui.layer //模块名 , MOD_NAME = 'tree' //外部接口 , tree = { config: {} , index: layui[MOD_NAME] ? (layui[MOD_NAME].index + 10000) : 0 //设置全局项 , set: function (options) { var that = this; that.config = $.extend({}, that.config, options); return that; } //事件监听 , on: function (events, callback) { return layui.onevent.call(this, MOD_NAME, events, callback); } } //操作当前实例 , thisModule = function () { var that = this , options = that.config , id = options.id || that.index; thisModule.that[id] = that; //记录当前实例对象 thisModule.config[id] = options; //记录当前实例配置项 return { config: options //重置实例 , reload: function (options) { that.reload.call(that, options); } , getChecked: function () { return that.getChecked.call(that); } , setChecked: function (id) {//设置值 return that.setChecked.call(that, id); } } } //获取当前实例配置项 , getThisModuleConfig = function (id) { var config = thisModule.config[id]; if (!config) hint.error('The ID option was not found in the ' + MOD_NAME + ' instance'); return config || null; } //字符常量 , SHOW = 'layui-show', HIDE = 'layui-hide', NONE = 'layui-none', DISABLED = 'layui-disabled' , ELEM_VIEW = 'layui-tree', ELEM_SET = 'layui-tree-set', ICON_CLICK = 'layui-tree-iconClick' , ICON_ADD = 'layui-icon-addition', ICON_SUB = 'layui-icon-subtraction', ELEM_ENTRY = 'layui-tree-entry', ELEM_MAIN = 'layui-tree-main', ELEM_TEXT = 'layui-tree-txt', ELEM_PACK = 'layui-tree-pack', ELEM_SPREAD = 'layui-tree-spread' , ELEM_LINE_SHORT = 'layui-tree-setLineShort', ELEM_SHOW = 'layui-tree-showLine', ELEM_EXTEND = 'layui-tree-lineExtend' //构造器 , Class = function (options) { var that = this; that.index = ++tree.index; that.config = $.extend({}, that.config, tree.config, options); that.render(); }; //默认配置 Class.prototype.config = { data: [] //数据 , showCheckbox: false //是否显示复选框 , showLine: true //是否开启连接线 , accordion: false //是否开启手风琴模式 , onlyIconControl: false //是否仅允许节点左侧图标控制展开收缩 , isJump: false //是否允许点击节点时弹出新窗口跳转 , edit: false //是否开启节点的操作图标 , text: { defaultNodeName: '未命名' //节点默认名称 , none: '无数据' //数据为空时的文本提示 } }; //重载实例 Class.prototype.reload = function (options) { var that = this; layui.each(options, function (key, item) { if (item.constructor === Array) delete that.config[key]; }); that.config = $.extend(true, {}, that.config, options); that.render(); }; //主体渲染 Class.prototype.render = function () { var that = this , options = that.config; that.checkids = []; var temp = $('
'); that.tree(temp); var othis = options.elem = $(options.elem); if (!othis[0]) return; //索引 that.key = options.id || that.index; //插入组件结构 that.elem = temp; that.elemNone = $('
' + options.text.none + '
'); othis.html(that.elem); if (that.elem.find('.layui-tree-set').length == 0) { return that.elem.append(that.elemNone); }; //复选框渲染 if (options.showCheckbox) { that.renderForm('checkbox'); }; that.elem.find('.layui-tree-set').each(function () { var othis = $(this); //最外层 if (!othis.parent('.layui-tree-pack')[0]) { othis.addClass('layui-tree-setHide'); }; //没有下一个节点 上一层父级有延伸线 if (!othis.next()[0] && othis.parents('.layui-tree-pack').eq(1).hasClass('layui-tree-lineExtend')) { othis.addClass(ELEM_LINE_SHORT); }; //没有下一个节点 外层最后一个 if (!othis.next()[0] && !othis.parents('.layui-tree-set').eq(0).next()[0]) { othis.addClass(ELEM_LINE_SHORT); }; }); that.events(); }; //渲染表单 Class.prototype.renderForm = function (type) { form.render(type, 'LAY-tree-' + this.index); }; //节点解析 Class.prototype.tree = function (elem, children) { var that = this , options = that.config , data = children || options.data; //遍历数据 layui.each(data, function (index, item) { var hasChild = item.children && item.children.length > 0 , packDiv = $('
') , entryDiv = $(['
' , '
' , '
' //箭头 , function () { if (options.showLine) { if (hasChild) { return ''; } else { return ''; }; } else { return ''; }; }() //复选框 , function () { return options.showCheckbox ? '' : ''; }() //节点 , function () { if (options.isJump && item.href) { return '' + (item.title || item.label || options.text.defaultNodeName) + ''; } else { return '' + (item.title || item.label || options.text.defaultNodeName) + ''; } }() , '
' //节点操作图标 , function () { if (!options.edit) return ''; var editIcon = { add: '' , update: '' , del: '' }, arr = ['
']; if (options.edit === true) { options.edit = ['update', 'del'] } if (typeof options.edit === 'object') { layui.each(options.edit, function (i, val) { arr.push(editIcon[val] || '') }); return arr.join('') + '
'; } }() , '
'].join('')); //如果有子节点,则递归继续生成树 if (hasChild) { entryDiv.append(packDiv); that.tree(packDiv, item.children); }; elem.append(entryDiv); //若有前置节点,前置节点加连接线 if (entryDiv.prev('.' + ELEM_SET)[0]) { entryDiv.prev().children('.layui-tree-pack').addClass('layui-tree-showLine'); }; //若无子节点,则父节点加延伸线 if (!hasChild) { entryDiv.parent('.layui-tree-pack').addClass('layui-tree-lineExtend'); }; //展开节点操作 that.spread(entryDiv, item); //选择框 if (options.showCheckbox) { item.checked && that.checkids.push(item.id); that.checkClick(entryDiv, item); } //操作节点 options.edit && that.operate(entryDiv, item); }); }; //展开节点 Class.prototype.spread = function (elem, item) { var that = this , options = that.config , entry = elem.children('.' + ELEM_ENTRY) , elemMain = entry.children('.' + ELEM_MAIN) , elemIcon = entry.find('.' + ICON_CLICK) , elemText = entry.find('.' + ELEM_TEXT) , touchOpen = options.onlyIconControl ? elemIcon : elemMain //判断展开通过节点还是箭头图标 , state = ''; //展开收缩 touchOpen.on('click', function (e) { var packCont = elem.children('.' + ELEM_PACK) , iconClick = touchOpen.children('.layui-icon')[0] ? touchOpen.children('.layui-icon') : touchOpen.find('.layui-tree-icon').children('.layui-icon'); //若没有子节点 if (!packCont[0]) { state = 'normal'; } else { if (elem.hasClass(ELEM_SPREAD)) { elem.removeClass(ELEM_SPREAD); packCont.slideUp(200); iconClick.removeClass(ICON_SUB).addClass(ICON_ADD); } else { elem.addClass(ELEM_SPREAD); packCont.slideDown(200); iconClick.addClass(ICON_SUB).removeClass(ICON_ADD); //是否手风琴 if (options.accordion) { var sibls = elem.siblings('.' + ELEM_SET); sibls.removeClass(ELEM_SPREAD); sibls.children('.' + ELEM_PACK).slideUp(200); sibls.find('.layui-tree-icon').children('.layui-icon').removeClass(ICON_SUB).addClass(ICON_ADD); }; }; }; }); //点击回调 elemText.on('click', function () { var othis = $(this); //判断是否禁用状态 if (othis.hasClass(DISABLED)) return; //判断展开收缩状态 if (elem.hasClass(ELEM_SPREAD)) { state = options.onlyIconControl ? 'open' : 'close'; } else { state = options.onlyIconControl ? 'close' : 'open'; } $('.treeactive').removeClass("treeactive"); othis.addClass("treeactive"); //点击产生的回调 options.click && options.click({ elem: elem , state: state , data: item }); }); }; //计算复选框选中状态 Class.prototype.setCheckbox = function (elem, item, elemCheckbox) { var that = this , options = that.config , checked = elemCheckbox.prop('checked'); if (elemCheckbox.prop('disabled')) return; //同步子节点选中状态 if (typeof item.children === 'object' || elem.find('.' + ELEM_PACK)[0]) { var childs = elem.find('.' + ELEM_PACK).find('input[same="layuiTreeCheck"]'); childs.each(function () { if (this.disabled) return; //不可点击则跳过 this.checked = checked; }); }; //同步父节点选中状态 var setParentsChecked = function (thisNodeElem) { //若无父节点,则终止递归 if (!thisNodeElem.parents('.' + ELEM_SET)[0]) return; var state , parentPack = thisNodeElem.parent('.' + ELEM_PACK) , parentNodeElem = parentPack.parent() , parentCheckbox = parentPack.prev().find('input[same="layuiTreeCheck"]'); //如果子节点有任意一条选中,则父节点为选中状态 if (checked) { parentCheckbox.prop('checked', checked); } else { //如果当前节点取消选中,则根据计算“兄弟和子孙”节点选中状态,来同步父节点选中状态 parentPack.find('input[same="layuiTreeCheck"]').each(function () { if (this.checked) { state = true; } }); //如果兄弟子孙节点全部未选中,则父节点也应为非选中状态 state || parentCheckbox.prop('checked', false); } //向父节点递归 setParentsChecked(parentNodeElem); }; setParentsChecked(elem); that.renderForm('checkbox'); }; //复选框选择 Class.prototype.checkClick = function (elem, item) { var that = this , options = that.config , entry = elem.children('.' + ELEM_ENTRY) , elemMain = entry.children('.' + ELEM_MAIN); //点击复选框 elemMain.on('click', 'input[same="layuiTreeCheck"]+', function (e) { layui.stope(e); //阻止点击节点事件 var elemCheckbox = $(this).prev() , checked = elemCheckbox.prop('checked'); if (elemCheckbox.prop('disabled')) return; that.setCheckbox(elem, item, elemCheckbox); //复选框点击产生的回调 options.oncheck && options.oncheck({ elem: elem , checked: checked , data: item }); }); }; //节点操作 Class.prototype.operate = function (elem, item) { var that = this , options = that.config , entry = elem.children('.' + ELEM_ENTRY) , elemMain = entry.children('.' + ELEM_MAIN); entry.children('.layui-tree-btnGroup').on('click', '.layui-icon', function (e) { layui.stope(e); //阻止节点操作 var type = $(this).data("type") , packCont = elem.children('.' + ELEM_PACK) , returnObj = { data: item , type: type , elem: elem }; //增加 if (type == 'add') { //若节点本身无子节点 if (!packCont[0]) { //若开启连接线,更改图标样式 if (options.showLine) { elemMain.find('.' + ICON_CLICK).addClass('layui-tree-icon'); elemMain.find('.' + ICON_CLICK).children('.layui-icon').addClass(ICON_ADD).removeClass('layui-icon-file'); //若未开启连接线,显示箭头 } else { elemMain.find('.layui-tree-iconArrow').removeClass(HIDE); }; //节点添加子节点容器 elem.append('
'); }; //新增节点 var key = options.operate && options.operate(returnObj) , obj = {}; obj.title = options.text.defaultNodeName; obj.id = key; that.tree(elem.children('.' + ELEM_PACK), [obj]); //放在新增后面,因为要对元素进行操作 if (options.showLine) { //节点本身无子节点 if (!packCont[0]) { //遍历兄弟节点,判断兄弟节点是否有子节点 var siblings = elem.siblings('.' + ELEM_SET), num = 1 , parentPack = elem.parent('.' + ELEM_PACK); layui.each(siblings, function (index, i) { if (!$(i).children('.' + ELEM_PACK)[0]) { num = 0; }; }); //若兄弟节点都有子节点 if (num == 1) { //兄弟节点添加连接线 siblings.children('.' + ELEM_PACK).addClass(ELEM_SHOW); siblings.children('.' + ELEM_PACK).children('.' + ELEM_SET).removeClass(ELEM_LINE_SHORT); elem.children('.' + ELEM_PACK).addClass(ELEM_SHOW); //父级移除延伸线 parentPack.removeClass(ELEM_EXTEND); //同层节点最后一个更改线的状态 parentPack.children('.' + ELEM_SET).last().children('.' + ELEM_PACK).children('.' + ELEM_SET).last().addClass(ELEM_LINE_SHORT); } else { elem.children('.' + ELEM_PACK).children('.' + ELEM_SET).addClass(ELEM_LINE_SHORT); }; } else { //添加延伸线 if (!packCont.hasClass(ELEM_EXTEND)) { packCont.addClass(ELEM_EXTEND); }; //子节点添加延伸线 elem.find('.' + ELEM_PACK).each(function () { $(this).children('.' + ELEM_SET).last().addClass(ELEM_LINE_SHORT); }); //如果前一个节点有延伸线 if (packCont.children('.' + ELEM_SET).last().prev().hasClass(ELEM_LINE_SHORT)) { packCont.children('.' + ELEM_SET).last().prev().removeClass(ELEM_LINE_SHORT); } else { //若之前的没有,说明处于连接状态 packCont.children('.' + ELEM_SET).last().removeClass(ELEM_LINE_SHORT); }; //若是最外层,要始终保持相连的状态 if (!elem.parent('.' + ELEM_PACK)[0] && elem.next()[0]) { packCont.children('.' + ELEM_SET).last().removeClass(ELEM_LINE_SHORT); }; }; }; if (!options.showCheckbox) return; //若开启复选框,同步新增节点状态 if (elemMain.find('input[same="layuiTreeCheck"]')[0].checked) { var packLast = elem.children('.' + ELEM_PACK).children('.' + ELEM_SET).last(); packLast.find('input[same="layuiTreeCheck"]')[0].checked = true; }; that.renderForm('checkbox'); //修改 } else if (type == 'update') { var text = elemMain.children('.' + ELEM_TEXT).html(); elemMain.children('.' + ELEM_TEXT).html(''); //添加输入框,覆盖在文字上方 elemMain.append(''); //获取焦点 elemMain.children('.layui-tree-editInput').val(text).focus(); //嵌入文字移除输入框 var getVal = function (input) { var textNew = input.val().trim(); textNew = textNew ? textNew : options.text.defaultNodeName; input.remove(); elemMain.children('.' + ELEM_TEXT).html(textNew); //同步数据 returnObj.data.title = textNew; //节点修改的回调 options.operate && options.operate(returnObj); }; //失去焦点 elemMain.children('.layui-tree-editInput').blur(function () { getVal($(this)); }); //回车 elemMain.children('.layui-tree-editInput').on('keydown', function (e) { if (e.keyCode === 13) { e.preventDefault(); getVal($(this)); }; }); //删除 } else { layer.confirm('确认删除该节点 "' + (item.title || '') + '" 吗?', function (index) { options.operate && options.operate(returnObj); //节点删除的回调 returnObj.status = 'remove'; //标注节点删除 layer.close(index); //若删除最后一个,显示空数据提示 if (!elem.prev('.' + ELEM_SET)[0] && !elem.next('.' + ELEM_SET)[0] && !elem.parent('.' + ELEM_PACK)[0]) { elem.remove(); that.elem.append(that.elemNone); return; }; //若有兄弟节点 if (elem.siblings('.' + ELEM_SET).children('.' + ELEM_ENTRY)[0]) { //若开启复选框 if (options.showCheckbox) { //若开启复选框,进行下步操作 var elemDel = function (elem) { //若无父结点,则不执行 if (!elem.parents('.' + ELEM_SET)[0]) return; var siblingTree = elem.siblings('.' + ELEM_SET).children('.' + ELEM_ENTRY) , parentTree = elem.parent('.' + ELEM_PACK).prev() , checkState = parentTree.find('input[same="layuiTreeCheck"]')[0] , state = 1, num = 0; //若父节点未勾选 if (checkState.checked == false) { //遍历兄弟节点 siblingTree.each(function (i, item1) { var input = $(item1).find('input[same="layuiTreeCheck"]')[0] if (input.checked == false && !input.disabled) { state = 0; }; //判断是否全为不可勾选框 if (!input.disabled) { num = 1; }; }); //若有可勾选选择框并且已勾选 if (state == 1 && num == 1) { //勾选父节点 checkState.checked = true; that.renderForm('checkbox'); //向上遍历祖先节点 elemDel(parentTree.parent('.' + ELEM_SET)); }; }; }; elemDel(elem); }; //若开启连接线 if (options.showLine) { //遍历兄弟节点,判断兄弟节点是否有子节点 var siblings = elem.siblings('.' + ELEM_SET), num = 1 , parentPack = elem.parent('.' + ELEM_PACK); layui.each(siblings, function (index, i) { if (!$(i).children('.' + ELEM_PACK)[0]) { num = 0; }; }); //若兄弟节点都有子节点 if (num == 1) { //若节点本身无子节点 if (!packCont[0]) { //父级去除延伸线,因为此时子节点里没有空节点 parentPack.removeClass(ELEM_EXTEND); siblings.children('.' + ELEM_PACK).addClass(ELEM_SHOW); siblings.children('.' + ELEM_PACK).children('.' + ELEM_SET).removeClass(ELEM_LINE_SHORT); }; //若为最后一个节点 if (!elem.next()[0]) { elem.prev().children('.' + ELEM_PACK).children('.' + ELEM_SET).last().addClass(ELEM_LINE_SHORT); } else { parentPack.children('.' + ELEM_SET).last().children('.' + ELEM_PACK).children('.' + ELEM_SET).last().addClass(ELEM_LINE_SHORT); }; //若为最外层最后一个节点,去除前一个结点的连接线 if (!elem.next()[0] && !elem.parents('.' + ELEM_SET)[1] && !elem.parents('.' + ELEM_SET).eq(0).next()[0]) { elem.prev('.' + ELEM_SET).addClass(ELEM_LINE_SHORT); }; } else { //若为最后一个节点且有延伸线 if (!elem.next()[0] && elem.hasClass(ELEM_LINE_SHORT)) { elem.prev().addClass(ELEM_LINE_SHORT); }; }; }; } else { //若无兄弟节点 var prevDiv = elem.parent('.' + ELEM_PACK).prev(); //若开启了连接线 if (options.showLine) { prevDiv.find('.' + ICON_CLICK).removeClass('layui-tree-icon'); prevDiv.find('.' + ICON_CLICK).children('.layui-icon').removeClass(ICON_SUB).addClass('layui-icon-file'); //父节点所在层添加延伸线 var pare = prevDiv.parents('.' + ELEM_PACK).eq(0); pare.addClass(ELEM_EXTEND); //兄弟节点最后子节点添加延伸线 pare.children('.' + ELEM_SET).each(function () { $(this).children('.' + ELEM_PACK).children('.' + ELEM_SET).last().addClass(ELEM_LINE_SHORT); }); } else { //父节点隐藏箭头 prevDiv.find('.layui-tree-iconArrow').addClass(HIDE); }; //移除展开属性 elem.parents('.' + ELEM_SET).eq(0).removeClass(ELEM_SPREAD); //移除节点容器 elem.parent('.' + ELEM_PACK).remove(); }; elem.remove(); }); }; }); }; //部分事件 Class.prototype.events = function () { var that = this , options = that.config , checkWarp = that.elem.find('.layui-tree-checkedFirst'); //初始选中 that.setChecked(that.checkids); //搜索 that.elem.find('.layui-tree-search').on('keyup', function () { var input = $(this) , val = input.val() , pack = input.nextAll() , arr = []; //遍历所有的值 pack.find('.' + ELEM_TEXT).each(function () { var entry = $(this).parents('.' + ELEM_ENTRY); //若值匹配,加一个类以作标识 if ($(this).html().indexOf(val) != -1) { arr.push($(this).parent()); var select = function (div) { div.addClass('layui-tree-searchShow'); //向上父节点渲染 if (div.parent('.' + ELEM_PACK)[0]) { select(div.parent('.' + ELEM_PACK).parent('.' + ELEM_SET)); }; }; select(entry.parent('.' + ELEM_SET)); }; }); //根据标志剔除 pack.find('.' + ELEM_ENTRY).each(function () { var parent = $(this).parent('.' + ELEM_SET); if (!parent.hasClass('layui-tree-searchShow')) { parent.addClass(HIDE); }; }); if (pack.find('.layui-tree-searchShow').length == 0) { that.elem.append(that.elemNone); }; //节点过滤的回调 options.onsearch && options.onsearch({ elem: arr }); }); //还原搜索初始状态 that.elem.find('.layui-tree-search').on('keydown', function () { $(this).nextAll().find('.' + ELEM_ENTRY).each(function () { var parent = $(this).parent('.' + ELEM_SET); parent.removeClass('layui-tree-searchShow ' + HIDE); }); if ($('.layui-tree-emptyText')[0]) $('.layui-tree-emptyText').remove(); }); }; //得到选中节点 Class.prototype.getChecked = function () { var that = this , options = that.config , checkId = [] , checkData = []; //遍历节点找到选中索引 that.elem.find('.layui-form-checked').each(function () { checkId.push($(this).prev()[0].value); }); //遍历节点 var eachNodes = function (data, checkNode) { layui.each(data, function (index, item) { layui.each(checkId, function (index2, item2) { if (item.id == item2) { var cloneItem = $.extend({}, item); delete cloneItem.children; checkNode.push(cloneItem); if (item.children) { cloneItem.children = []; eachNodes(item.children, cloneItem.children); } return true } }); }); }; eachNodes($.extend({}, options.data), checkData); return checkData; }; //设置选中节点 Class.prototype.setChecked = function (checkedId) { var that = this , options = that.config; //初始选中 that.elem.find('.' + ELEM_SET).each(function (i, item) { var thisId = $(this).data('id') , input = $(item).children('.' + ELEM_ENTRY).find('input[same="layuiTreeCheck"]') , reInput = input.next(); //若返回数字 if (typeof checkedId === 'number') { if (thisId == checkedId) { if (!input[0].checked) { reInput.click(); }; return false; }; } //若返回数组 else if (typeof checkedId === 'object') { layui.each(checkedId, function (index, value) { if (value == thisId && !input[0].checked) { reInput.click(); return true; } }); }; }); }; //记录所有实例 thisModule.that = {}; //记录所有实例对象 thisModule.config = {}; //记录所有实例配置项 //重载实例 tree.reload = function (id, options) { var that = thisModule.that[id]; that.reload(options); return thisModule.call(that); }; //获得选中的节点数据 tree.getChecked = function (id) { var that = thisModule.that[id]; return that.getChecked(); }; //设置选中节点 tree.setChecked = function (id, checkedId) { var that = thisModule.that[id]; return that.setChecked(checkedId); }; //核心入口 tree.render = function (options) { var inst = new Class(options); return thisModule.call(inst); }; exports(MOD_NAME, tree); })