All Downloads are FREE. Search and download functionalities are using the official Maven repository.

auto-code-ui.static.js.layui.extend.authtree.js Maven / Gradle / Ivy

There is a newer version: 1.0.8
Show newest version
/*
* @Author: Jeffrey Wang
* @Date:   2018-03-16 18:24:47
* @Version: v1.2.2
* @Last Modified by:   Jeffrey Wang
* @Last Modified time: 2019-01-09 17:48:22
*/
// 节点树
layui.define(['jquery', 'form'], function(exports){
	$ = layui.jquery;
	form = layui.form;
	var MOD_NANE = 'authtree';

	var obj = {
		// 渲染 + 绑定事件
		openIconContent: '',
		closeIconContent: '',
		// 表单类型 checkbox: 多选,radio:单选
		checkType: 'checkbox',
		// 选中、半选中、未选中
		checkedIconContent: '',
		halfCheckedIconContent: '',
		notCheckedIconContent: '',
		// 保存节点数据
		checkedNode : {},
		notCheckedNode : {},
		// 临时保存最新操作影响的节点
		lastCheckedNode : {},
		lastNotCheckedNode : {},
		// 已经渲染过的树,可用来获取配置,{ dst: {trees: '树的节点数据', opt: '配置'} }
		renderedTrees: {},
		// 使用 layui 的监听事件
		on: function(events, callback) {
			return layui.onevent.call(this, MOD_NANE, events, callback);
		},
		/**
		 * 渲染DOM并绑定事件
		 * @param  {[type]} dst       [目标ID,如:#test1]
		 * @param  {[type]} trees     [数据,格式:{}]
		 * @param  {[type]} inputname [上传表单名]
		 * @param  {[type]} layfilter [lay-filter的值]
		 * @param  {[type]} openall [是否展开全部]
		 * @return {[type]}           [description]
		 */
		render: function(dst, trees, opt) {
			// 表单名称配置
			var inputname = opt.inputname ? opt.inputname : 'menuids[]';
			opt.inputname = inputname;
			// lay-filter 配置
			var layfilter = opt.layfilter ? opt.layfilter : 'checkauth';
			opt.layfilter = layfilter;
			// 默认展开全部 配置
			var openall = opt.openall ? opt.openall : false;
			opt.openall = openall;
			// 双击展开此层配置
			var dblshow = opt.dblshow ? opt.dblshow : false;
			opt.dblshow = dblshow;
			// 双击时间差 - 不能设置过长,否则单击延迟很感人
			var dbltimeout = opt.dbltimeout ? opt.dbltimeout : 120;
			opt.dbltimeout = dbltimeout;
			// 默认展开有选中数据的层
			var openchecked = typeof opt.openchecked !== 'undefined' ? opt.openchecked : true;
			opt.openchecked = openchecked;
			// 自动取消选中
			var autoclose = typeof opt.autoclose !== 'undefined' ? opt.autoclose : true;
			opt.autoclose = autoclose;
			// 自动选择直属父级节点
			var autochecked = typeof opt.autochecked !== 'undefined' ? opt.autochecked : true;
			opt.autochecked = autochecked;
            // 是否隐藏左侧 单选/多选的选框 -- 特殊需求,一般用于单选树并且不用
            var hidechoose = typeof opt.hidechoose !== 'undefined' ? opt.hidechoose : false;
            opt.hidechoose = hidechoose;
			// 有子节点的前显字符配置
			opt.prefixChildStr = opt.prefixChildStr ? opt.prefixChildStr : '├─';
			// 单选、多选配置
			opt.checkType = opt.checkType ? opt.checkType : 'checkbox';
			this.checkType = opt.checkType;
			// 皮肤可选择
			opt.checkSkin = opt.checkSkin ? opt.checkSkin : 'primary';
			// 展开、折叠节点的前显字符配置
			opt.openIconContent = opt.openIconContent ? opt.openIconContent : '';
			this.openIconContent = opt.openIconContent;
			opt.closeIconContent = opt.closeIconContent ? opt.closeIconContent : '';
			this.closeIconContent = opt.closeIconContent;
			// 选中、半选中、未选中节点的图标配置
			opt.checkedIconContent = opt.checkedIconContent ? opt.checkedIconContent : '';
			this.checkedIconContent = opt.checkedIconContent;
			opt.halfCheckedIconContent = opt.halfCheckedIconContent ? opt.halfCheckedIconContent : '';
			this.halfCheckedIconContent = opt.halfCheckedIconContent;
			opt.notCheckedIconContent = opt.notCheckedIconContent ? opt.notCheckedIconContent : '';
			this.notCheckedIconContent = opt.notCheckedIconContent;

			// 不启用双击展开,单击不用延迟
			var dblisten = true;
			if (dblshow) {
				// 开启双击展开,双击事件默认为120s
			} else {
				// 未开启双击展开且 dbltimeout <= 0,则说明不用监听双击事件
				if (opt.dbltimeout <= 0) {
					dblisten = false;
				}
				dbltimeout = 0;
				// opt.dbltimeout = dbltimeout;
			}

			// 记录渲染过的树
			obj.renderedTrees[dst] = {trees: trees, opt: opt};

			$(dst).html(obj.renderAuth(trees, 0, {
				inputname: inputname,
				layfilter: layfilter,
				openall: openall,
				openchecked: openchecked,
				checkType: this.checkType,
				prefixChildStr: opt.prefixChildStr,
			}));
			obj.showChecked(dst);
			form.render();
			// 变动则存一下临时状态
			obj._saveNodeStatus(dst);

			// 开启自动宽度优化
			obj.autoWidth(dst);
			// 备注:如果使用form.on('checkbox()'),外部就无法使用form.on()监听同样的元素了(LAYUI不支持重复监听了)。
			// form.on('checkbox('+layfilter+')', function(data){
			// 	/*属下所有权限状态跟随,如果选中,往上走全部选中*/
			// 	var childs = $(data.elem).parent().next().find('input[type="checkbox"]').prop('checked', data.elem.checked);
			// 	if(data.elem.checked){
			// 		/*查找child的前边一个元素,并将里边的checkbox选中状态改为true。*/
			// 		$(data.elem).parents('.auth-child').prev().find('input[type="checkbox"]').prop('checked', true);
			// 	}
			// 	/*console.log(childs);*/
			// 	form.render('checkbox');
			// });
			
			// 解决单击和双击冲突问题的 timer 变量
			var timer = 0;
			$(dst).find('.auth-single:first').unbind('click').on('click', '.layui-form-checkbox,.layui-form-radio', function(event){
                // window.event? window.event.cancelBubble = true : event.stopPropagation();
				var that = this;
				clearTimeout(timer);
				// 双击判断需要的延迟处理
				timer = setTimeout(function(){
					var elem = $(that).prev();
					var checked = elem.is(':checked');

					if (autochecked) {
						if (checked) {
							/*查找child的前边一个元素,并将里边的checkbox选中状态改为true。*/
							elem.parents('.auth-child').prev().find('.authtree-checkitem:not(:disabled)[type="checkbox"]').prop('checked', true);
						}
						elem.parent().next().find('.authtree-checkitem:not(:disabled)[type="checkbox"]').prop('checked', checked);
					}
					if (autoclose) {
						if (checked) {
							// pass
						} else {
							// 自动关闭父级选中节点
							obj._autoclose($(that).parent());
						}
					}
					form.render('checkbox');
					form.render('radio');
					// 变动则存一下临时状态
					obj._saveNodeStatus(dst);
					// 触发 change 事件
					obj._triggerEvent(dst, 'change', {
						othis: $(that),
						oinput: elem,
						value: elem.val(),
					});
					obj.autoWidth(dst);
				}, dbltimeout);
				return false;
			});
			/*动态绑定展开事件*/
			$(dst).unbind('click').on('click', '.auth-icon', function(){
				obj.iconToggle(dst, this);
			});
			/*双击展开*/
            $(dst).find('.auth-single:first').unbind('dblclick').on('dblclick', '.layui-form-checkbox,.layui-form-radio', function(e){
                // 触发时间 > 0,才触发双击事件
                // opt.dbltimeout 是用户真实设定的超时时间,与 dbltimeout 不一样
                // if (opt.dbltimeout > 0) {
                    obj._triggerEvent(dst, 'dblclick', {
                    	othis: $(this),
                    	elem: $(this).prev(),
                    	value: $(this).prev().val(),
                    });
                // }
                if (dblshow) {
                    clearTimeout(timer);
                    obj.iconToggle(dst, $(this).prevAll('.auth-icon:first'));
                }
            }).on('selectstart', function(){
                // 屏蔽双击选中文字
                return false;
            });
		},
		// 自动关闭 - 如果兄弟节点均没选中,递归取消上级元素选中状态,传入的是 .auth-status 节点,递归 .auth-status 上级节点
		_autoclose: function(obj) {
			var single = $(obj).parent().parent();
			var authStatus = single.parent().prev();

			if (!authStatus.hasClass('auth-status')) {
				return false;
			}
			// 仅一层
			if (single.find('div>.auth-status>input.authtree-checkitem:not(:disabled)[type="checkbox"]:checked').length === 0) {
				authStatus.find('.authtree-checkitem:not(:disabled)[type="checkbox"]').prop('checked', false);
				this._autoclose(authStatus);
			}
		},
		// 以 icon 的维度,切换显示下级空间
		iconToggle: function(dst, iconobj){
			var origin = $(iconobj);
			var child = origin.parent().parent().find('.auth-child:first');
			if(origin.is('.active')){
				/*收起*/
				origin.removeClass('active').html(obj.closeIconContent);
				child.slideUp('fast');
			} else {
				/*展开*/
				origin.addClass('active').html(obj.openIconContent);
				child.slideDown('fast');
			}
			obj._triggerEvent(dst, 'deptChange');
			return false;
		},
		// 递归创建格式
		renderAuth: function(tree, dept, opt){
			var inputname = opt.inputname;
			var layfilter = opt.layfilter;
			var openall = opt.openall;
			var str = '
'; layui.each(tree, function(index, item){ var hasChild = (item.list&&(item.list.length || !$.isEmptyObject(item.list.length))) ? 1 : 0; // 注意:递归调用时,this的环境会改变! var append = hasChild ? obj.renderAuth(item.list, dept+1, opt) : ''; var openstatus = openall || (opt.openchecked && item.checked); // '+new Array(dept * 4).join(' ')+' str += '
'+ (hasChild?''+(openstatus?obj.openIconContent:obj.closeIconContent)+'':'')+ (dept > 0 ? (''+opt.prefixChildStr+' '):'')+ '
'+ '
'+append+'
' }); str += '
'; return str; }, /** * 显示到已选中的最高层级 * @param {[type]} dst [description] * @return {[type]} [description] */ showChecked: function(dst) { $(dst).find('.authtree-checkitem:checked').parents('.auth-child').show(); }, /** * 将普通列表无限递归转换为树 * @param {[type]} list [普通的列表,必须包括 opt.primaryKey 指定的键和 opt.parentKey 指定的键] * @param {[type]} opt [配置参数,支持 primaryKey(主键 默认id) parentKey(父级id对应键 默认pid) nameKey(节点标题对应的key 默认name) valueKey(节点值对应的key 默认id) checkedKey、disabledKey(节点是否选中的字段 默认checked,传入数组则判断主键是否在此数组中) startPid(第一层扫描的PID 默认0) currentDept(当前层 默认0) maxDept(最大递归层 默认100) childKey(递归完成后子节点对应键 默认list) deptPrefix(根据层级重复的前缀 默认'')] * @return {[type]} [description] */ listConvert: function(list, opt) { opt.primaryKey = opt.primaryKey ? opt.primaryKey : 'id'; opt.parentKey = opt.parentKey ? opt.parentKey : 'pid'; opt.startPid = opt.startPid ? opt.startPid : 0; opt.currentDept = opt.currentDept ? opt.currentDept : 0; opt.maxDept = opt.maxDept ? opt.maxDept : 100; opt.childKey = opt.childKey ? opt.childKey : 'list'; opt.checkedKey = opt.checkedKey ? opt.checkedKey : 'checked'; opt.disabledKey = opt.disabledKey ? opt.disabledKey : 'disabled'; opt.nameKey = opt.nameKey ? opt.nameKey : 'name'; opt.valueKey = opt.valueKey ? opt.valueKey : 'id'; return this._listToTree(list, opt.startPid, opt.currentDept, opt); }, // 实际的递归函数,将会变化的参数抽取出来 _listToTree: function(list, startPid, currentDept, opt) { if (opt.maxDept < currentDept) { return []; } var child = []; for (index in list) { // 筛查符合条件的数据(主键 = startPid) var item = list[index]; if (typeof item[opt.parentKey] !== 'undefined' && item[opt.parentKey] === startPid) { // 满足条件则递归 var nextChild = this._listToTree(list, item[opt.primaryKey], currentDept+1, opt); // 节点信息保存 var node = {}; if (nextChild.length > 0) { node[opt.childKey] = nextChild; } node['name'] = item[opt.nameKey]; node['value'] = item[opt.valueKey]; // 已选中节点的两种渲染方式 if (typeof opt.checkedKey === "string" || typeof opt.checkedKey === 'number') { node['checked'] = item[opt.checkedKey]; } else if(typeof opt.checkedKey === 'object') { if ($.inArray(item[opt.valueKey], opt.checkedKey) != -1) { node['checked'] = true; } else { node['checked'] = false; } } else { node['checked'] = false; } // 禁用节点的两种渲染方式 if (typeof opt.disabledKey === "string" || typeof opt.disabledKey === 'number') { node['disabled'] = item[opt.disabledKey]; } else if(typeof opt.disabledKey === 'object') { if ($.inArray(item[opt.valueKey], opt.disabledKey) != -1) { node['disabled'] = true; } else { node['disabled'] = false; } } else { node['disabled'] = false; } child.push(node); } } return child; }, /** * 将树转为单选可用的 select,如果后台返回列表数据,可以先转换为 tree * @param {[type]} tree [description] * @param {[type]} opt [description] * @return {[type]} [description] */ treeConvertSelect: function(tree, opt) { if (typeof tree.length !== 'number' || tree.length <= 0) { return []; } // 初始化层级 opt.currentDept = opt.currentDept ? opt.currentDept : 0; // 子节点列表的Key opt.childKey = opt.childKey ? opt.childKey : 'list'; // 值的key opt.valueKey = opt.valueKey ? opt.valueKey : 'value'; // 选中的key - 仅支持字符串 opt.checkedKey = opt.checkedKey ? opt.checkedKey : 'checked'; // 禁用的key opt.disabledKey = opt.disabledKey ? opt.disabledKey : 'disabled'; // 有子节点的前缀 opt.prefixChildStr = opt.prefixChildStr ? opt.prefixChildStr : '├─ '; // 没有子节点的前缀 opt.prefixNoChildStr = opt.prefixNoChildStr ? opt.prefixNoChildStr : '● '; // 树的深度影响的子节点数据 opt.prefixDeptStr = opt.prefixDeptStr ? opt.prefixDeptStr : ' '; // 如果第一列就存在没有子节点的情况,加的特殊前缀 opt.prefixFirstEmpty = opt.prefixFirstEmpty ? opt.prefixFirstEmpty : '  ' return this._treeToSelect(tree, opt.currentDept, opt); }, // 实际处理递归的函数 _treeToSelect: function(tree, currentDept, opt) { var ansList = []; var prefix = ''; for (var i = 0;i < currentDept; i++) { prefix += opt.prefixDeptStr; } for (index in tree) { var child_flag = 0; var item = tree[index]; if (opt.childKey in item && item[opt.childKey] && item[opt.childKey].length > 0) { child_flag = 1; } var name = item.name; if (child_flag) { name = opt.prefixChildStr + name; } else { if (currentDept > 1) { name = opt.prefixNoChildStr + name; } else { name = opt.prefixFirstEmpty + name; } } ansList.push({ name: prefix+name, value: item[opt.valueKey], checked: item[opt.checkedKey], disabled: item[opt.disabledKey], }); // 添加子节点 if (child_flag) { var child = this._treeToSelect(item[opt.childKey], currentDept + 1, opt); // apply 的骚操作,使用第二个参数可以用于合并两个数组 ansList.push.apply(ansList, child); } } return ansList; }, // 自动调整宽度以解决 form.render()生成元素兼容性问题,如果用户手动调用 form.render() 之后也需要调用此方法 autoWidth: function(dst) { var tree = this.getRenderedInfo(dst); var opt = tree.opt; $(dst).css({ 'whiteSpace': 'nowrap', 'maxWidth' : '100%', }); $(dst).find('.layui-form-checkbox,.layui-form-radio,.layui-form-audio').each(function(index, item){ if ($(this).is(':hidden')) { // 比较奇葩的获取隐藏元素宽度的手法,请见谅 $('body').append('
'+$(this).html()+'
'); $width = $('#layui-authtree-get-width').find('span').width() + $('#layui-authtree-get-width').find('i').width() + 29; $('#layui-authtree-get-width').remove(); } else { $width = $(this).find('span').width() + $(this).find('i').width() + 25; } $(this).width($width); // 隐藏 单选/多选的左侧选框隐藏 if (opt.hidechoose) { $(this).prevAll('i').css({ zIndex: 2, }); $(this).css({ position: 'relative' ,left: function () { return '-'+$(this).css('padding-left');// 避免点击抖动的骚操作 } }).find('i').hide(); } }); }, // 触发自定义事件 _triggerEvent: function(dst, events, other) { var tree = this.getRenderedInfo(dst); var origin = $(dst); if (tree) { var opt = tree.opt; var data = { opt: opt, dst: dst, othis: origin, }; if (other && typeof other === 'object') { data = $.extend(data, other); } // 支持 dst 和 用户的配置的 layfilter 监听 layui.event.call(origin, MOD_NANE, events+'('+dst+')', data); layui.event.call(origin, MOD_NANE, events+'('+opt.layfilter+')', data); } else { return false; } }, // 获取渲染过的信息 getRenderedInfo: function(dst) { return this.renderedTrees[dst]; }, // 动态获取最大深度 getMaxDept: function(dst){ var next = $(dst); var dept = 0; while(next.length && dept < 100000) { next = this._getNext(next); if (next.length) { dept++; } else { break; } } return dept; }, // 全选 checkAll: function(dst){ var origin = $(dst); origin.find('.authtree-checkitem:not(:disabled):not(:checked)').prop('checked', true); form.render('checkbox'); form.render('radio'); obj.autoWidth(dst); // 变动则存一下临时状态 obj._saveNodeStatus(dst); obj._triggerEvent(dst, 'change'); obj._triggerEvent(dst, 'checkAll'); }, // 全不选 uncheckAll: function(dst){ var origin = $(dst); origin.find('.authtree-checkitem:not(:disabled):checked').prop('checked', false); form.render('checkbox'); form.render('radio'); obj.autoWidth(dst); // 变动则存一下临时状态 obj._saveNodeStatus(dst); obj._triggerEvent(dst, 'change'); obj._triggerEvent(dst, 'uncheckAll'); }, // 显示整个树 showAll: function(dst) { this.showDept(dst, this.getMaxDept(dst)); }, // 关闭整颗树 closeAll: function(dst) { this.closeDept(dst, 1); }, // 切换整颗树的显示/关闭 toggleAll: function(dst) { if (this._shownDept(2)) { this.closeDept(dst); } else { this.showAll(dst); } }, // 显示到第 dept 层 showDept: function(dst, dept) { var next = $(dst); for(var i = 1; i < dept; i++) { next = this._getNext(next); if (next.length) { this._showSingle(next); } else { break; } } obj._triggerEvent(dst, 'deptChange', {dept: dept}); }, // 第 dept 层之后全部关闭 closeDept: function(dst, dept) { var next = $(dst); for(var i = 0; i < dept; i++){ next = this._getNext(next); } while(next.length) { this._closeSingle(next); next = this._getNext(next); } obj._triggerEvent(dst, 'deptChange', {dept: dept}); }, // 临时保存所有节点信息状态 _saveNodeStatus: function(dst){ var currentChecked = this.getChecked(dst); var currentNotChecked = this.getNotChecked(dst); // 保存新信息前,最新选择的信息 this.lastCheckedNode[dst] = this._getLastChecked(dst, currentChecked, currentNotChecked); this.lastNotCheckedNode[dst] = this._getLastNotChecked(dst, currentChecked, currentNotChecked); this.checkedNode[dst] = currentChecked; this.notCheckedNode[dst] = currentNotChecked; // console.log('保存节点信息', this.checkedNode[dst], this.notCheckedNode[dst], this.lastCheckedNode[dst], this.lastNotCheckedNode[dst]); }, // 判断某一层是否显示 _shownDept: function(dst, dept) { var next = $(dst); for(var i = 0; i < dept; i++){ next = this._getNext(next); } return !next.is(':hidden'); }, // 获取 _getNext: function(dst) { return $(dst).find('.auth-single:first>div>.auth-child'); }, // 显示某层 single _showSingle: function(dst) { layui.each(dst, function(index, item) { var origin = $(item).find('.auth-single:first'); var parentChild = origin.parent(); var parentStatus = parentChild.prev(); if (!parentStatus.find('.auth-icon').hasClass('active')) { parentChild.show(); // 显示上级的 .auth-child节点,并修改.auth-status的折叠状态 parentStatus.find('.auth-icon').addClass('active').html(obj.openIconContent); } }); }, // 关闭某层 single _closeSingle: function(dst) { var origin = $(dst).find('.auth-single:first'); var parentChild = origin.parent(); var parentStatus = parentChild.prev(); if (parentStatus.find('.auth-icon').hasClass('active')) { parentChild.hide(); // 显示上级的 .auth-child节点,并修改.auth-status的折叠状态 parentStatus.find('.auth-icon').removeClass('active').html(obj.closeIconContent); } }, // 获取选中叶子结点 getLeaf: function(dst){ var leafs = $(dst).find('.auth-leaf').parent().find('.authtree-checkitem:checked'); var data = []; leafs.each(function(index, item){ // console.log(item); data.push(item.value); }); // console.log(data); return data; }, // 获取所有节点数据 getAll: function(dst){ var inputs = $(dst).find('.authtree-checkitem'); var data = []; inputs.each(function(index, item){ data.push(item.value); }); // console.log(data); return data; }, // 获取最新选中(之前取消-现在选中) getLastChecked: function(dst) { return this.lastCheckedNode[dst] || []; }, // (逻辑)最新选中(之前取消-现在选中) _getLastChecked: function(dst, currentChecked, currentNotChecked) { var lastCheckedNode = currentChecked; var data = []; for (i in lastCheckedNode) { if ($.inArray(lastCheckedNode[i], this.notCheckedNode[dst]) != -1) { data.push(lastCheckedNode[i]); } } return data; }, // 获取所有选中的数据 getChecked: function(dst){ var inputs = $(dst).find('.authtree-checkitem:checked'); var data = []; inputs.each(function(index, item){ data.push(item.value); }); return data; }, // 获取最新取消(之前取消-现在选中) getLastNotChecked: function(dst) { return this.lastNotCheckedNode[dst] || []; }, // (逻辑)最新取消(之前选中-现在取消) _getLastNotChecked: function(dst, currentChecked, currentNotChecked) { var lastNotCheckedNode = currentNotChecked; var data = []; for (i in lastNotCheckedNode) { if ($.inArray(lastNotCheckedNode[i], this.checkedNode[dst]) != -1) { data.push(lastNotCheckedNode[i]); } } return data; }, // 获取未选中数据 getNotChecked: function(dst){ var inputs = $(dst).find('.authtree-checkitem:not(:checked)'); var data = []; inputs.each(function(index, item){ data.push(item.value); }); // console.log(data); return data; } }; exports('authtree', obj); });




© 2015 - 2024 Weber Informatics LLC | Privacy Policy