/** @Name:tree 树组件 @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(layui._typeof(item) === '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'; } //点击产生的回调 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); })