基于jquery的树形列表TreeGrid

因项目近期特殊需要,自己实现了一个TreeGrid  javascript组件。其中一些思想是从网络借鉴的,一些来自于开源框架easyui。现将其公布出来,兴许可以帮助一些朋友实现一些奇葩需求。 js组件中注释很详尽,代码结构也很简单。

该组件实现的功能:

1.展示树列表,左边是树,右边是表

2.提供勾选框,可以通过接口获取选中数据

3.每个子列表都有自己的分页器

4.支持本地数据展示,和远程数据延迟加载

5.每个值可以有自身的转换器formatter

6.支持行单击事件

7.支持拖拽改变列宽

8.加载时的loading效果


效果图:



js代码如下:


(function($){

	//工具方法
	function request(url,restricts){
		if(restricts==""){
			restricts = {};
		}
		if(typeof(restricts)!="object"){
			console.error("requestParam should be Ojbect");
		}
		var jsonData = null;
		$.ajax({
			type:'POST',
			data:restricts,
			async:false,
			url:url,
			success:function(data,success){
				if(success){
					jsonData = eval('('+data+')');
				}else{
					console.error('fail to call '+url);
				}
			}
		});
		
		return jsonData;
	}

	function getParentId(id){
		return id.substring(0,id.lastIndexOf('_'));
	}

	function getCurrentLevel(id){
		var pid = getParentId(id);
		var levelStr = $('#'+pid).attr('level');
		if( levelStr==undefined ){
			levelStr = 0;
		}
		return parseInt(levelStr)+1;
	}

	

	var methods = {

		init:function(options){
			var config = $.extend({}, this.TreeGrid.defaults, options);
			 
			return this.each(function() {
                var $this = $(this);
                $this.TreeGrid('createContainer', config)
					.TreeGrid('drawHeader', config)
					.TreeGrid('drawData', config)
					.TreeGrid('bindEvent', config)
					.TreeGrid('bindCheckboxEvent', config);
					
            });
		},
		
		//创建容器
		createContainer:function(config){
			var $context = this;
			$context.css({width:config.width,height:config.height});
			//先清除工作(可能之前有残留)
			$context.find('.TreeGrid-inner').remove();
			$context.removeClass('TreeGrid');
			//正式构造
			$context.addClass('TreeGrid');
			//创建遮罩容器
			$context.append('<div class="TreeGrid-inner"></div>');
			var $inner = $context.find('.TreeGrid-inner');
			var id = config.id || "TreeGrid"+$.TreeGrid.COUNT++;
			$inner.append("<table id='"+id+"' />");
			var $table = $("#"+id);
			$table.attr('cellspacing',0);
			$table.attr('cellpadding',0);
			
			
			//对每一个符合条件的jquery对象(this即选择的div),对其执行以下函数
			return $context.each(function(){
				
			});
		},
		
		//画表头
		drawHeader:function(config){
			var id = config.id+'H';
			$(this).find("table").append("<tr id='"+id+"' />");
			var $th = $('#'+id);
			$th.addClass('header');
			$th.attr('height',config.headerHeight);
			$th.attr('level',0);
			
			if(config.showCheckbox ){
				//第一列要用来显示checkbox
				$th.append("<td><input type='checkbox' trid='"+id+"' /></td>");
			}

			var cols = config.columns;
			for(i=0;i<cols.length;i++){
				var col = cols[i];
				$th.append("<td />");
				var $td = $th.find('td:last');
				$td.attr('align',(col.headerAlign || config.headerAlign) );
				$td.css('width',(col.width || "") );
				$td.append(col.headerText || "");
			}
			return this.each(function(){
				if(config.columnWidthResizable){
					$(this).TreeGrid('resizeHeaderWidth',config,$th);
				}
				
			});
		},
		
		//改变列宽
		resizeHeaderWidth:function(config,$th){
			var $context = this;
			var resizable = false;//当前位置是可以开始改变宽度的
			var resizing = false;//表明正在拖动改变大小
			var begin = 0;
			var $resizeTarget = null;

			//当鼠标滑动到边界,指针发生改变
			$th.mousemove(function(e){
				var $target = $(e.target);
				var x = e.pageX;//鼠标位置的左边距
				var offset = $target.offset();
				var left = offset.left;//元素整体偏移量
				var allWidth = left + $target.outerWidth();//td右侧边线的左边距
				if(x>allWidth-config.cursorRange){
					resizable = true;
					$target.css('cursor','e-resize');
				}else{
					resizable = false;
					$target.css("cursor", "default");
				}

			});
			
			//触发
			$th.mousedown(function(e){
				
				if(resizable){
					$resizeTarget = $(e.target);
					var $inner = $context.find('.TreeGrid-inner');
					$inner.append("<div class='table-resize-proxy'></div>");
					var $proxy = $context.find('.table-resize-proxy');
					$proxy.css({left : e.pageX-$context.offset().left+2,display : 'block'});
					begin = e.pageX;
					resizing = true;
				}	
			});
			
			//辅助线移动
			$context.mousemove(function(e){
				if(resizing){
					var $proxy = $context.find('.table-resize-proxy');
					$proxy.css({left : e.pageX-$context.offset().left,display : 'block'});
				}
			});

			$context.mouseup(function(e){
				
				if(resizing){
					//设置新宽度
					var changedWith = e.pageX-begin;
					var newWidth = $resizeTarget.width()+changedWith;
					$resizeTarget.width(newWidth);
					//清理工作
					$context.find('.table-resize-proxy').remove();
					resizing = false;
					$resizeTarget = null;
					begin = 0;
					
				}
			});	
		},

		//画数据
		drawData : function(config){
			var $context = this;
			var rows = [];
			//本地有数据用本地的,没有则远程获取
			if(config.data){
				rows = config.data;
			}else if(config.requestUrl!=''){
				rows = request(config.requestUrl,config.requestParam);
			}else{
				console.error('pls config the data source');
			}
			
			//表头即为整体的根
			var rootId = config.id+'H';
			//表头中存放了所有数据,为了保持规律:在所有的父节点中都能找到子节点的数据
			$context.find('#'+rootId).data('data',{children:rows});
			return $context.each(function(){
				$(this).TreeGrid('drawDataRecursive', config,rootId,rows,config.displayLevel);
			});
		},
		
		//递归将rows画在parentId下  displayLevel级别之前的都要显示
		drawDataRecursive:function(config,parentId,rows,displayLevel){
			var $context = this;

			var prevTrId = parentId;
			
			var count = rows.length;
			if(config.pagination){
				//只画前 pageNum 行
				count = count<config.pageNum ? count:config.pageNum;
			}
			for(var i=0; i<count; i++){
				var id = parentId + "_" + i;
				var row = rows[i];
				
				prevTrId = $context.TreeGrid('drawTableTr',config,id,row,prevTrId,'dataTr',displayLevel );
				//递归画子树
				if(row.children && row.children.length>0 ){
					prevTrId = $context.TreeGrid('drawDataRecursive', config,id, row.children,displayLevel);
				}
			}
			//分页器
			if(config.pagination){
				var id = parentId + "_" + count;
				prevTrId = $context.TreeGrid('drawTableTr',config,id,row,prevTrId,'paginationTr',displayLevel );
			}
			return prevTrId;
		},
		
		

		//画tr  prevTrId:该行的前一行id;  displayLevel:该级之前的节点都要显示
		drawTableTr:function(config,id,row,prevTrId,trCls,displayLevel){
			var $context = this;
			$context.find('#'+prevTrId).after("<tr id="+id+" />");
			var $tr = $('#'+id);
			var pid = getParentId(id);
			var currentLevel = getCurrentLevel(id);
			$tr.attr('level',currentLevel);
			$tr.attr('pid',pid );
			$tr.attr('rowIndex',config.rownum++);
			$tr.data('data',row);
			$tr.addClass(trCls);
			var openStatus = "N";
			var display = "none";
			if(currentLevel<displayLevel) openStatus = "Y";//级别<displayLevel的节点,都为展开状态
			if(currentLevel<=displayLevel) display = ""; //级别<=displayLevel的节点,都要显示
			
			$tr.attr('openStatus',openStatus);
			$tr.css('display',display);
			
			if(trCls=='dataTr'){
				$context.TreeGrid('drawDataTd',config,$tr);
			}else if(trCls=='paginationTr'){
				$context.TreeGrid('drawPaginationTd',config,$tr);
			}
			
			return id;
		},

		//画td
		drawDataTd:function(config,$tr){
			var treeColumnIndex = config.treeColumnIndex;
			var columns = config.columns;
			var row = $tr.data('data');
			var trid = $tr.attr('id');
			var currentLevel = $tr.attr('level');
			var openStatus = $tr.attr('openStatus');

			if(config.showCheckbox ){
				//第一列要用来显示checkbox
				$tr.append("<td><input type='checkbox' /></td>");
				$tr.find("input[type='checkbox']").attr('trid',trid);
			}

			for(var j=0;j<columns.length;j++){
				var col = columns[j];
				$tr.append("<td />");
				var $td = $tr.find('td:last');
				$td.attr('align',(col.dataAlign || config.dataAlign) );

				
				//层次缩进
				if(j==treeColumnIndex){
					$td.css('text-indent',parseInt(config.indentation)*(currentLevel-1) );
					$td.append('<span />');
					var $img = $td.find('span');
					$img.attr('trid',trid);
					//如果是延迟加载则只要求有children属性即可,否则要求children.length>0
					if((config.delayLoad&&row.children) || (row.children&&row.children.length>0) ){
						$img.addClass('folder');
						var nodeClass = (openStatus=="Y")? "nodeOpen" : "nodeClose";
						$img.addClass(nodeClass);
					}else{
						$img.addClass('image_nohand');
						$img.addClass('nodeLeaf');
					}
				}
				
				var displayData = row[col.dataField] || "";
				//该字段有转换器
				if(col.converter){
					displayData = col.converter.call(this,displayData);
				}
				$td.append(displayData);

			}
		},

		//画分页器
		drawPaginationTd:function(config,$tr){
			var level = parseInt( $tr.attr('level') );
			
			
			$tr.append("<td />");
			var $td = $tr.find('td:last');
			$td.attr('align','right');
			$td.css('padding-right',(level-1)*20);
			//$td.css('border','none');

			$td.addClass('pagination');
			var colspan = config.columns.length;
			if(config.showCheckbox){
				colspan += 1;
			}
			$td.attr('colspan',colspan);
			$td.append("<span class='first'></span>");
			$td.append("<span class='prev'></span>");
			$td.append("<span class='page'><input /></span>");
			$td.append("/<span class='pageCount'></span>");
			$td.append("<span class='next'></span>");
			$td.append("<span class='last'></span>");
			this.TreeGrid('bindPaginationEvent',config,$tr);
		},

		//用rows重画parentId下面的数据
		reloadData:function(config,parentId,rows){
			var $context = this;
			var parentLevel = $context.find('#'+parentId).attr('level');
			var currentLevel = parseInt(parentLevel)+1;
			//删除所有子项
			$context.TreeGrid('deleteDataRecursive',config,parentId);
			//重画所有子项
			$context.TreeGrid('drawDataRecursive', config,parentId,rows,currentLevel);
		},
		
		//递归删除parentId的所有子项
		deleteDataRecursive:function(config,parentId){
			var $context = this;
			$context.TreeGrid('markDeleteTr',config,parentId);
			$context.find('.deleteTr').remove();
		},
		
		//将所有需要删除的行标记出
		markDeleteTr:function(config,parentId){
			var $context = this;
			var $trs = $context.find("tr[pid="+ parentId +"]");
			
			for(var i=0; i<$trs.length;i++){
				var $tr = $($trs[i]);
				$tr.addClass('deleteTr');
				$context.TreeGrid('markDeleteTr',config,$tr.attr('id'));
			}
		},
		
		//子节点分页事件
		bindPaginationEvent:function(config,$tr){
			var $context = this;
			var pid = $tr.attr('pid');
			var $parent = $context.find('#'+pid)

			var $first = $tr.find('span.first');
			var $prev = $tr.find('span.prev');
			var $page = $tr.find('span.page input');
			var $pageCount = $tr.find('span.pageCount');
			var $next = $tr.find('span.next');
			var $last = $tr.find('span.last');
			
			//从父节点上获取子节点的当前页
			var currentPage = $parent.data('currentPage');
			if(currentPage == undefined ){
				currentPage = 1;
			}

			//从父节点上获取所有子节点
			var allDatas = $parent.data('data').children;
			var pageNum = config.pageNum;
			var totalCount = allDatas.length;
			var pageCount = Math.ceil( totalCount/pageNum );
			$page.val(currentPage);
			$pageCount.text(pageCount);
			
			//给分页器加禁止的样式
			if(currentPage<=1){
				$first.addClass('disabled');
				$prev.addClass('disabled');
			}
			if(currentPage>=pageCount){
				$next.addClass('disabled');
				$last.addClass('disabled');
			}

			$prev.click(function(){
				if(currentPage<=1){
					return;
				}
				//将子页面 页码存放于父节点
				$parent.data('currentPage',currentPage-1);
				$context.TreeGrid('reloadData',config,pid,getPageContent(currentPage-1) );
				
			});

			$next.click(function(){
				if(currentPage>=pageCount){
					return;
				}
				$parent.data('currentPage',currentPage+1);
				$context.TreeGrid('reloadData',config,pid,getPageContent(currentPage+1) );
			});

			$first.click(function(){
				if(currentPage<=1){
					return;
				}
				$parent.data('currentPage',1);
				$context.TreeGrid('reloadData',config,pid,getPageContent(1) );			
			});

			$last.click(function(){
				if(currentPage>=pageCount){
					return;
				}
				$parent.data('currentPage',pageCount);
				$context.TreeGrid('reloadData',config,pid,getPageContent(pageCount) );				
			});

			$page.change(function(){
				var index = $page.val();
				var reg = new RegExp("^[0-9]*$");
				if(!reg.test(index)){
					$page.val(currentPage);
					return;
				}
				index = parseInt(index);
				if(index>pageCount){
					index = pageCount;
				}else if(index<1){
					index = 1;
				}
				$parent.data('currentPage',index);
				$context.TreeGrid('reloadData',config,pid,getPageContent(index) );				
			});


			function getPageContent(index){
				var rows = [];
				var begin = (index-1<0?0:index-1)*pageNum;
				var end = begin+pageNum<totalCount-1 ? begin+pageNum : totalCount-1;
				for(var i=begin;i<=end;i++){
					rows.push(allDatas[i]);
				}
				return rows;
			}
		},
		

		bindEvent:function(config){
			
			var $context = this;

			//对数据行增加悬浮样式,因为数据行有可能是动态增加的,因而采用如下写法
			if(config.showHoverCss){
				$context.find("tr.dataTr").die().live({
					mouseenter:function(){
						if($(this).hasClass("header")) return;
						$(this).addClass("row_hover");
					},
					mouseleave:function(){
						$(this).removeClass("row_hover");
					}
				});
			}
			
			var timer = null;//区分单双击
			//bind click to <tr>
			$context.find("tr.dataTr").die().live("click", function(){
				var $this = $(this);
				$context.find("tr").removeClass("row_active");
				$this.addClass("row_active");
				
				var id = $this.attr('id');
				var data = $this.data("data");

				if(config.itemClick){
					config.itemClick(id,data);
				}
				
			});


			//bind click to image
			$context.find("span.folder").die().live("click", function(){
				var trid = $(this).attr("trid"); 
				var $tr = $context.find("#" + trid);
				var isOpen = $tr.attr("openStatus");
				var statusAfterClick = (isOpen == "Y") ? "N" : "Y";//当前为打开状态则关闭
				$tr.attr("openStatus", statusAfterClick);
				
				if(statusAfterClick == "N"){ //隐藏子节点
					$tr.find("span.folder").removeClass("nodeOpen").addClass("nodeClose");
					$context.find("tr[id^=" + trid + "_]").css("display", "none");
				}else{ //显示子节点
					$tr.find("span.folder").removeClass("nodeClose").addClass("nodeOpen");
					$context.TreeGrid("showNextLevelRecursive",config,trid);
				}
				//阻止事件冒泡
				return false;
			});

			return this.each(function(){
				
			});
		},
		
		//给checkbox绑定事件
		bindCheckboxEvent:function(config){
			if(!config.showCheckbox){
				return;
			}
			var $context = this;
			$context.on('click',"input[type='checkbox'][trid]",function(event){
				//阻止事件冒泡
				event.stopPropagation();
				var $ck = $(this);
				var checked = this.checked;
				var trid = $ck.attr('trid');
				
				checkRecursive(trid);
				uncheckParentRecursive(trid);

				//勾选或不勾选父节点,所有子节点跟着变化
				function checkRecursive(pid){
					var $trs = $context.find("tr[pid='"+pid+"']");
					for( var i=0;i<$trs.length;i++ ){
						$($trs[i]).find("input[type='checkbox'][trid]").attr('checked',checked);
						checkRecursive($($trs[i]).attr('id'));
					}
				}

				//取消勾选子节点父节点取消
				function uncheckParentRecursive(id){
					var $tr = $context.find('#'+id);
					var pid = $tr.attr('pid');
					if(!checked && pid!=undefined ){
						var $ptr = $context.find('#'+pid);
						$ptr.find("input[type='checkbox'][trid]").attr('checked',checked);
						uncheckParentRecursive($ptr.attr('id'));
					}
				}
			});
		},

		//递归显示数据
		showNextLevelRecursive:function(config,parentId){
			var $context = this;
			
			var $parentTr = $context.find("#" + parentId);
			var isOpen = $parentTr.attr("openStatus");
			//只有当前行处于打开状态才可以显示下一行
			if(isOpen == "Y"){
				//找出所有trid的子行
				var nextTrs = $context.find("tr[pid=" + parentId + "]");
				if(nextTrs.length>0){
					for(var i=0;i<nextTrs.length;i++){
						var next = $(nextTrs[i]);
						next.css("display", "");
						this.TreeGrid("showNextLevelRecursive",config,next.attr('id'));
					}
				}else if(config.delayLoad && nextTrs.length==0){
					//延迟加载且当前没有数据
					var rows = [];
					if(config.onDelayLoadData){
						//显示loading样式
						$context.TreeGrid('showLoading',config);
						//调用用户自定义的延迟加载获取数据
						rows = config.onDelayLoadData($parentTr.data("data"));
						$context.find('.loading').remove();
						if(!rows){
							console.error('onDelayLoadData get nothing from remote');
							return;
						}
						$parentTr.data('data').children = rows;
					}
					var currentLevel = parseInt($parentTr.attr('level'))+1;
					$context.TreeGrid('drawDataRecursive', config,parentId,rows,currentLevel );
					
				}
			}
		},

		showLoading:function(config){
			var $context = this;
			$context.append('<div class="loading">loading...</div>');
			var $loading = $context.find('.loading');
			var containerWidth = $context.outerWidth();
			var containerHeight = $context.outerHeight();
			$loading.css('left',containerWidth/2-10);
			$loading.css('top',containerHeight/2-5);
			
			return this;
		},
		
		//获取勾选上的节点数据
		getCheckedRows:function(){
			var $context = this;
			//注意排除表头
			var $cks = $context.find("tr.dataTr input[type='checkbox'][trid]:checked");
			var result = [];
			for(var i=0;i<$cks.length;i++){
				var trid = $($cks[i]).attr('trid');
				result.push($('#'+trid).data());
			}
			return result;
		}


	};


	$.fn.TreeGrid = function(method) {
        if (methods[method]) {
            return methods[ method ].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        } else {
            $.error('Method with name ' + method + ' does not exists for jQuery.TreeGrid');
        }
    };

	
	$.TreeGrid = {};
	$.TreeGrid.COUNT = 1;


	$.fn.TreeGrid.defaults = {
		id:'T',
		width:'100%',
		headerAlign: 'center',
		headerHeight: '25',
		dataAlign: 'center',
		indentation: '20',
		displayLevel:1,//页面默认看到所有的一级节点
		treeColumnIndex:0,//默认第0列是树
		rownum:0,
		showHoverCss:true,
		itemClick: function(id,data){},

		showCheckbox:false,
		
		//remote props
		requestUrl:"",
		requestParam:"",
		
		//delay load data
		delayLoad:false,
		onDelayLoadData:function(data){},
		//pager
		pagination:true,
		pageNum:5,
		
		//resize column width
		columnWidthResizable:true,
		cursorRange:3  //在td的右侧3像素内能识别到左右滑动鼠标
	};



})(jQuery)


附件中带了有demo

文件位置

http://download.csdn.net/detail/williamxww1/9559555


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值