/** @Name:layui.tree 树组件 @Author:贤心---weidy在此基础上重新调整了内容 @License:MIT */ layui.define(['jquery','util'], function(exports){ "use strict"; var $ = layui.$ ,hint = layui.hint(); var enterSkin = 'layui-tree-enter'; /** * 初始化树的对象 * @constructor */ var Tree = function(){ this.icon = {//图标 arrow: ['', ''] //箭头 ,checkbox: ['', ''] //复选框 ,radio: ['', ''] //单选框 ,branch: ['', ''] //父节点 ,leaf: '' //叶节点 }, this.loadType = {//加载方式 all:'all',//一次展开全部 node:'node'//一次展开一级 }, this.emptyText = "没有数据" }; /*** * 初始化某些对象 */ Tree.prototype.privateInitObject = function(){ var that = this; if(!that.config) that.config = {};//配置 if(!that.cacheData) that.cacheData = {};//缓存数据 if(!that.allCheck) that.allCheck = {};//勾选的数据 if(!that.getAllData) that.getAllData = {};//勾选的数据 if(!that.allSelectedData) that.allSelectedData = {};//单击选中的数据 }; /** * 树的初始化, * @param filter 过滤器 * @param elem 根元素 * @param options 配置信息 */ Tree.prototype.init = function(filter,elem,options){ var that = this; that.privateInitObject(); that.setConfig(filter,elem,options); that.clearCheckData(filter); elem.addClass('layui-box layui-tree'); //添加tree样式 if(elem.attr("layui-filter") && elem.attr("layui-filter").indexOf(filter) < 0){ var f = elem.attr("layui-filter"); elem.attr("layui-filter",filter); } if(options.skin){//皮肤 elem.addClass('layui-tree-skin-'+ options.skin); } if(options.url || options.data){//使用后台来加载的 that.load(filter,elem,options.parentValue,false); }else{//直接显示 that.showTree(filter,elem); that.on(filter,elem); } if(options.showSearch) { that.syncLayuiTreeFilter(filter,elem); } }; /** * 设置配置信息 * @param filter 过滤器 * @param elem //树展示的根节点 * @param options */ Tree.prototype.setConfig = function(filter,elem,options){ var that = this; that.config[filter]= { elem:elem, options:options }; }; /*** * 获取配置信息 * @param filter 过滤器 * @returns {elem:根元素,options:配置信息} */ Tree.prototype.getConfig = function(filter){//获取配置文件 var that = this; return that.config[filter]; }; /** * 获取根元素 * @param filter 过滤器 * @returns {*} elem元素对象 */ Tree.prototype.getRootElem =function(filter){ var that = this; var config = that.getConfig(filter); if(config){ return config.elem; }else{ return false; } }; /*** * 获取配置信息 * @param filter 过滤器 * @returns {*} */ Tree.prototype.getOptions = function(filter){ var that = this; var config = that.getConfig(filter); if(config){ return config.options; }else{ return false; } }; Tree.prototype.setOptions = function(filter,options){ var that = this; var config = that.getConfig(filter); if(config){ that.setConfig(filter,config.elem,options); } }; /** * 保存缓存数据 * @param filter 过滤器 * @param parenValue 上级值 * @param data 当前值 */ Tree.prototype.setCacheData=function(filter,data){ var that = this; if( !that.cacheData[filter]){ that.cacheData[filter] = {}; } that.cacheData[filter][data.oid] = data; //全部平铺,如果是想获取结构化的内容,单独的方法里提供 }; /** * 根据主键获取数据,不包含下级 * @param filter 过滤器 * @param oid 主键 */ Tree.prototype.getDataByOid=function(filter,oid){ var that = this; var cacheData = that.cacheData[filter]; if(!cacheData) return false; if(layui.util.isNull(oid)){ return cacheData; }else{ return cacheData[oid]; } }; /** * 获取当前节点的子节点 * @param filter 过滤器 * @param oid 当前节点的主键 */ Tree.prototype.getChildDataByOid=function(filter,oid){ var that = this; //TODO 未来测试一下使用jquery来查询doc快,还是直接遍历所有的数据快 weidy@2018-04-10 var elem = that.getRootElem(filter); var data = that.getDataByOid(filter,oid); if(layui.util.isNull(oid) || !data){ return data?[data]:false;//直接返回全部 } var nodeCity = elem.find('cite[oid="' + oid + '"]').first(); var hasChildrenOid = []; if(nodeCity){ var allChildrenElem = nodeCity.parent().parent().children("ul").first().children("li");//使用children只会找第一级 if(allChildrenElem){ layui.each(allChildrenElem,function(_index,record){ try{ var childOid = record.children("a").first().children("").first().attr("oid"); if(childOid){ hasChildrenOid.push(childOid); } }catch(e){ layui.util.showDebugMsg(e); } }); } } var childData = []; if(hasChildrenOid.length >0){ layui.each(hasChildrenOid,function(_index,record){ var tempData = that.getDataByOid(filter,record); if(tempData) childData.push(tempData); }); } return childData; }; /*** * 获取当前节点的上级节点 * @param filter 过滤器 * @param oid 本节点的主键 * @return false或者上级对象数据 */ Tree.prototype.getParentDataByOid = function(filter,oid){ var that =this; var elem = that.getElemByItem(filter,oid); if(!elem){ var parentElem = elem.parent().parent().children("li").first(); if(parentElem){ var data = that.getDataByOid(filter,that.getOidByElem(parentElem)); return data?data:false; } } return false; }; /** * 重新加载整个树 * @param filter 过滤器 * @param options 新增的配置 */ Tree.prototype.reload = function(filter,options){ var that = this; var rootElem = that.getRootElem(filter); var oldOptions = that.getOptions(filter); if(options) {oldOptions = $.extend(oldOptions,options)}; that.setConfig(filter,rootElem,oldOptions); //清除所有的数据和html元素 rootElem.children().remove(); delete that.cacheData[filter]; that.load(filter,rootElem); that.getAllData = {}; that.allCheck = {}; that.allSelectedData={}; if(oldOptions.showSearch) { that.syncLayuiTreeFilter(filter,rootElem); } }; /** * 重新加载某个节点 * @param filter 过滤器 * @param oid 节点主键 * @param callback 回调函数 */ Tree.prototype.reloadNode = function(filter,nodeOid,callback,parentBtmType){ var that = this; var elem = that.getElemByItem(filter,nodeOid); //没办法,需要将所有下级都重新加载,因为有可能直属下级都发生了变化 //先获取所有的下级的主键 var allChildrenOid = []; elem.children("ul").find("cite").each(function(){ var record = $(this); allChildrenOid.push(record.attr("oid")); }); layui.each(allChildrenOid,function(_index,record){//删除数据 if(that.cacheData[filter] && that.cacheData[filter][record]) delete that.cacheData[filter][record]; }); //var parentElem = elem.parent().parent().first(); elem.children("ul").remove();//删除元素 that.load(filter,elem,nodeOid,true,callback,parentBtmType); }; /** * 通过后台来加载 * @param filter 过滤器 * @param elem 上级元素 * @param parentValue 上级值 * @param isAppend 是否追加 */ Tree.prototype.load = function(filter,elem,parentValue,isAppend,callback,parentBtmType) { var that = this; var options = that.getOptions(filter); if (options.url) { if (!options.extraParams) { options.extraParams = {}; } var queryParams = {}; if (!options.loadType) { options.loadType = that.loadType.node; } queryParams['queryAllLevel'] = options.loadType == that.loadType.node ? false : true;//默认逐级展开 queryParams['loadType'] = options.loadType; queryParams['multipleSelect'] = ("true" == options.isMuti || options.isMuti) ? true : false; queryParams['isMuti'] = options.extraParams['multipleSelect']; if (options.where) { for (var key in options.where) { queryParams[key] = options.where[key]; } } if (options.extraParams) { for (var key in options.extraParams) { queryParams[key] = options.extraParams[key];//老的方式 //queryParams['conditionMap["' + key + '"]'] = options.extraParams[key];//新的方式 } } if ($webUtil.isNotNull(parentValue)) { queryParams['parentOid'] = parentValue.indexOf('@vcitreesep@') > -1 ? parentValue.split('@vcitreesep@')[1] : parentValue; queryParams['parentValue'] = queryParams['parentOid']; } if ($webUtil.isNotNull(parentBtmType)) { queryParams['parentBtmName'] = parentBtmType; queryParams['parentBtmType'] = parentBtmType; } if (options.rootParams && !options.rootNodeLoaded) { for (var key in options.rootParams) { queryParams[key] = options.rootParams[key]; } options.rootNodeLoaded = true; that.setOptions(filter, options); } if (options.conditionParams) { //说明是扩展属性 for (var key in options.conditionParams) { queryParams['conditionMap["' + key + '"]'] = options.conditionParams[key];//新的方式 } } if (options.treeExtandParams) { //说明是扩展属性 for (var key in options.treeExtandParams) { queryParams['extandParamsMap["' + key + '"]'] = options.treeExtandParams[key];//新的方式 } } if (options.sourceDataParams) { //说明是扩展属性 for (var key in options.sourceDataParams) { if (key && key.constructor === Object) return; queryParams['sourceData["' + key + '"]'] = options.sourceDataParams[key];//新的方式 } } if (options.replaceParams) { //说明是扩展属性 for (var key in options.replaceParams) { if (key && key.constructor === Object) return; queryParams['replaceMap["' + key + '"]'] = options.replaceParams[key];//新的方式 } } if (options.remoteSort && options.initSort) { queryParams['order'] = options.initSort.type;//方法 queryParams['sort'] = options.initSort.field;//字段 } $webUtil.ajax(options.requestType ? options.requestType : 'get', options.url, queryParams, function (result) { if (result && $webUtil.isNotNull(result.msg)) {//以前的接口可能没有msg属性 $webUtil.showErrorMsg(result.msg); return; } var tempNode = null; if (result && result.success && "treeData" in result) { tempNode = result.treeData; } else { //兼容以前的方式 tempNode = result; } if (!isAppend) {//不是追加,说明得将内容放进去 //把数据缓存起来,这样后面在获取所有的数据的时候可以获取 that.showTree(filter, elem, tempNode); that.on(filter, elem); } else { var ul = that.addChildNodeEl(filter, elem, true,tempNode); that.showTree(filter, ul, tempNode, true); that.on(filter, ul); } if(options.loadCallback && !isAppend){ options.loadCallback(filter,tempNode); } if(options.loadAllCallback){ options.loadAllCallback(filter,tempNode); } if(callback){ callback(filter,elem,tempNode); } }, function (xhr, error) { $webUtil.showErrorMsg("加载树的时候出现了错误,options=" + JSON.stringify(options)); }, options.backPath ? options.backPath : null,options.noProgress); return; } else if (options.data) { that.showTree(filter, elem, options.data); that.on(filter, elem); if (options.loadCallback && !isAppend) { options.loadCallback(filter, tempNode); } if (options.loadAllCallback) { options.loadAllCallback(filter, tempNode); } if (callback) { callback(filter, elem, tempNode); } } }; /** * 当有子节点的时候,给子节点添加必要的元素 * @param elem 当前的节点 * @returns {*|HTMLElement} */ Tree.prototype.addChildNodeEl = function(filter,elem,isExpand,children){ var that = this; var ul = $(''); elem.append(ul); var nodeI = that.getNodeI(elem);//找到节点的元素 nodeI.removeClass("layui-tree-leaf"); nodeI.addClass("layui-tree-branch"); //nodeI.html(that.icon.branch[1]); if(children && children.length>0) { var item = that.getDataByOid(filter, that.getOidByElem(filter, elem)); item.leaf = false; that.getAllData[item.oid] = item; that.allCheck[filter] && (that.allCheck[filter][item.oid] = item); that.allSelectedData[filter] = item; that.setCacheData(filter, item) } that.on(filter,ul); return ul; }; /** * 核心方法--树的展示,包括追加节点和直接显示所有节点 * @param elem * @param children */ Tree.prototype.showTree = function(filter,elem, children,isAppend) { var that = this, options = that.getOptions(filter); var nodes = children || options.nodes; if ((!nodes || nodes.length == 0) && !isAppend) { elem.append('
' + that.emptyText + '
'); } else { $('.emptyDiv', elem).remove() } layui.each(nodes, function (index, item) { //处理属性映射 weidy@2018-03-16 if (!item.expanded) item.spread = item.expanded; else item.spread = false; if (isAppend) item.spread = true; if (!item.name) item.name = item.text; if (item.checked || options.isMuti) { options.check = 'checkbox'; } that.setCacheData(filter, item); var hasChild = item.children && item.children.length > 0; var isLeaf = item.leaf; var li = $(['
  • ' //展开箭头 , function () { return isLeaf || hasChild ? '' + ( item.spread ? that.icon.arrow[0] : that.icon.arrow[1] ) + '' : ''; }() //复选框/单选框 , function () { return options.check ? ( '' + ( options.check === 'checkbox' ? that.icon.checkbox[0] : ( options.check === 'radio' ? that.icon.radio[0] : '' ) ) + (item.checked == true ? that.icon.checkbox[1] : '') + '' ) : ''; }() //节点 , function () { return '' + ('' + ( hasChild ? ( '' //item.spread ? that.icon.branch[1] : that.icon.branch[0] ) : ''//that.icon.leaf ) + '') //节点图标 //2021-6-17 wangting 增加templet显示 + ('' + (options.templet ? function () { return typeof options.templet === 'function' ? options.templet(item) : layui.laytpl($(options.templet).html() || String(item.name)).render(item) }() : (item.name || '未命名')) + ''); }() , '
  • '].join('')); //如果有子节点,则递归继续生成树 if (hasChild) { var ul = that.addChildNodeEl(filter, li, true); that.showTree(filter, ul, item.children, true); } elem.append(li); //触发点击节点回调 typeof options.click === 'function' && that.click(filter, li, options); //伸展节点--自动展开节点 that.spread(filter, li); //拖拽节点 options.drag && that.drag(filter, li); //复选框点击事件 if (options.check) { that.checkClick(filter, li); } if (item.checked == true) { that.getAllData[item.oid] = item; that.allCheck[filter] = {}; that.allCheck[filter][item.oid] = item; } }); if (options.done && !isAppend) { options.done(filter, children, elem); } }; /** * 复选框点击事件处理 * @param filter 过滤器 * @param elem 点击li * @param item 该对象 */ Tree.prototype.checkClick = function(filter,elem){ var that = this, options = that.getOptions(filter); var item = that.getDataByOid(filter,that.getOidByElem(filter,elem)); elem.children('.layui-tree-check').first().on('click',function(e){ layui.stope(e); if($(this).hasClass("layui-tree-checked")){ $(this).next().next().find('.layui-tree-check').removeClass("layui-tree-checked"); $(this).next().next().find('.layui-tree-check').html(that.icon.checkbox[0]); // 父级 // $(this).parent().parent().prev().prev().removeClass("layui-tree-checked"); // $(this).parent().parent().prev().prev().html(that.icon.checkbox[0]); $(this).removeClass("layui-tree-checked"); $(this).html(that.icon.checkbox[0]); delete that.allCheck[filter][item.oid]; // 子集是否全部取消选中 var allChildren = $(this).parent().siblings().find('.layui-tree-checked'); var childrenLen = [] if(allChildren.length == 0){ $(this).parent().parent().prev().prev().removeClass("layui-tree-checked"); $(this).parent().parent().prev().prev().html(that.icon.checkbox[0]); if(item.parentId != null){ delete that.getAllData[item.parentId]; if(that.cacheData[filter][item.parentId].parentId != null) { if( $(".check_"+that.cacheData[filter][item.parentId].parentId).parent().children('ul').children("li").children(".layui-tree-checked").length == 0) { $(this).parent().parent().parent().parent().prev().prev().removeClass("layui-tree-checked"); $(this).parent().parent().parent().parent().prev().prev().html(that.icon.checkbox[0]); delete that.getAllData[that.cacheData[filter][item.parentId].parentId]; } } } } if(item.children && item.children.length>0) { for(var i = 0;i0 ) { delete that.getAllData[item.children[i].oid]; for(var j = 0;j0 ) { for(var i = 0;i0 ) { that.getAllData[item.children[i].oid] = item.children[i]; for(var j = 0;j'); e.preventDefault(); $('.' + dragStr)[0] || $('body').append(treeMove); var dragElem = $('.' + dragStr)[0] ? $('.' + dragStr) : treeMove; (dragElem).addClass('layui-show').html(move.from.elem.children('a').html()); dragElem.css({ left: e.pageX + 10 ,top: e.pageY + 10 }) } }).on('mouseup', function(){ var move = that.move; if(move.from){ move.from.elem.children('a').removeClass(enterSkin); move.to && move.to.elem.children('a').removeClass(enterSkin); that.move = {}; $('.' + dragStr).remove(); } }); } }; //拖拽节点 Tree.prototype.move = {}; Tree.prototype.drag = function(filter,elem){ var that = this, options = that.getOptions(filter); var a = elem.children('a'), mouseenter = function(){ var othis = $(this), move = that.move; if(move.from){ move.to = { item: item ,elem: elem }; othis.addClass(enterSkin); } }; a.on('mousedown', function(){ var move = that.move move.from = { item: item ,elem: elem }; }); a.on('mouseenter', mouseenter).on('mousemove', mouseenter) .on('mouseleave', function(){ var othis = $(this), move = that.move; if(move.from){ delete move.to; othis.removeClass(enterSkin); } }); }; /*** * 销毁 * @param filter */ Tree.prototype.destory = function(filter){ var that = this; var rootEl = that.getRootElem(filter); that.clearCheckData(filter); rootEl.remove(); if(that.allSelectedData && that.allSelectedData[filter]) delete that.allSelectedData[filter]; if(that.cacheData && that.cacheData[filter]) delete that.cacheData[filter]; if(that.config && that.config[filter]) delete that.config[filter]; }; Tree.prototype.syncLayuiTreeFilter = function (treeId,treeElem,callback) { treeElem.prepend(''); var filterElem = treeElem.find("input[name='fast_search_select_value']"); if (!filterElem.length || !filterElem.length) { return; } filterElem.keydown(function (event) { if (event && event.keyCode == 13) { var that = this; var value = $(that).val().trim(); var HIDE = 'layui-hide'; var hintClass = 'search_hit'; // 先恢复现场 treeElem.find('.' + HIDE).removeClass(HIDE); treeElem.find('.layui-tree-checked').click(); treeElem.find('.' + hintClass).removeClass(hintClass).each(function (index, item) { item = $(item); item.html(item.data('textOld')).data('textOld', null); }); // 如果有值筛选开始 if (value) { layui.each(treeElem.find('cite'), function (index, elem) { elem = $(elem); var textTemp = elem.text(); if (textTemp.indexOf(value) === -1) { // 不存在就隐藏 elem.closest('li').addClass(HIDE); } else { // 命中就添加一个class elem.addClass(hintClass) .data('textOld', textTemp) .html(textTemp.replace(new RegExp(value, 'g'), '' + value + '')); if (textTemp == value) { elem.closest("a").prev().click(); } } }); layui.each(treeElem.find('.' + hintClass), function (index, elem) { elem = $(elem); elem.parents('li').removeClass(HIDE); elem.parents('ul').each(function (i, item) { if (!$(item).hasClass('layui-show')) { $(item).parent('li').find('>i').click(); } }); elem.parents('ul').parent('li').removeClass(HIDE); if (elem.prev("i").hasClass("layui-tree-branch")) { elem.parent("a").next("ul").find("li").removeClass(HIDE) } }); } typeof callback === 'function' && callback.call(that, treeElem, filterElem, treeElem.find('.' + hintClass).length); return false; } layui.stope(event); }); }; //暴露接口---weidy修改为统一的风格 var tree = new Tree(); exports('tree',tree); });