HetaJs

Virtual Scroll

codeplay-vscroll.js

	var Vscroll = function(options,dataList){
		if(!options){
			alert("Required options");
			return;
		}
		
		this.options = options;
		//this.id = options.id;
		this.originalData = dataList;			//실데이터
 
		this.ReLoad(dataList,options.viewH,options.lineH);
		this.viewport = options.viewport;
		this.content = options.content;	
		this.Events.onStart && this.Events.onStart(options,this);		//$$$ viewport와 content를 받음.
 
		this.viewport.scroll((function(obj){	//jquery
			return	function(){obj.Scroll();}
		})(this));
 
		this.Init();
	};
 
	Vscroll.prototype = {
		Init : function(){
			this.viewport.css({height:this.viewH,overflow:"auto"});		//jquery
			this.content.css({height:this.h,position:"relative",overflow:"hidden"});			//jquery
			this.Scroll();
		},

		ReLoad : function(list, vh, lh){		
			this.dataList = list;			//실데이터
			this.viewH = (vh != null)?vh:this.viewH; // 보이는 부분 높이
			this.lineH = (lh != null)?lh:this.lineH; // 행 높이
 
			this.lh = Math.ceil(this.viewH / this.lineH);	//라인 갯수.
			this.tot = list.length;
			this.vHeight = this.tot * this.lineH ; // 가상 높이 (+를 해서 아래 공백을 넣을수 있음.)
			this.h = this.tot * this.lineH; // 실제 스크롤 가능 높이
			this.pageH = this.h / this.lineH; // 페이지 높이
			this.pageCnt = Math.ceil(this.vHeight / this.pageH); // 페이지 개수
			this.overlaid = (this.vHeight - this.h) / (this.pageCnt - 1); // "넘어가는" 계수
			this.page = 0; // 현재 페이지
			this.offset=0; // 현재 페이지 위치
			this.prevScrollTop = 0;
			this.rows = {}; // 캐시된 행 노드
 
		},
		// vo에 해당하는 이름으로 검색어를 넣어주면 됩니다.	 {name:"해당값"} 검색시 사용
		Filter : function(searchVo,isNotLowerCase){
			this._RemoveAllRows();
			var filterData = this._findData(searchVo,false,isNotLowerCase);
			this.ReLoad(filterData);	//재설정
			this.Init();
		},
 
		// selected value를 가져옴 {name:"해당값"}
		FindData : function(searchVo){
			if(!searchVo) return [];
			return this._findData(searchVo,true,true);	//1 true는 equal검색. 2 true는 lowercase검색안하기.
		},
		FindFilteredData : function(searchVo){
			if(!searchVo) return [];
			return this._findFilteredData(searchVo,true);	//true는 equal검색.
			
			//필터 결과
//			var resultList = this.originalData.filter(function(item){
//				for(var i in searchVo){
//					if(item[i] == searchVo[i]){
//						return true;
//					}
//				}
//			});
		},
		// vo에 해당하는 이름으로 검색어를 넣어주면 됩니다.	 {selected:false}
		ChangeAllData : function(changeVo){
 
			if(!changeVo) return;
 
			//원본 값을 수정.
			this.originalData.filter(function(item,i){
				for(var v in changeVo){
					item[v] = changeVo[v];	//값을 모두 넣는다.
				}
			});
 
			//복사본 값을 수정.
			this.dataList.filter(function(item,i){
				for(var v in changeVo){
					item[v] = changeVo[v];	//값을 모두 넣는다.
				}
			});
 
			//다시그리기
			this._ReDraw();
		},
		
		// 필터 적용된것만 수정. (dataList를 바꾸되.. original의 동일 데이터를 함께 바꾼다.)	 {selected:false}
		ChangeAllFilteredData : function(changeVo){

			if(!changeVo) return;

			var originalList = this.originalData;

			//복사본 값을 수정.
			this.dataList.filter(function(item,i){
				for(var v in changeVo){
					item[v] = changeVo[v];	//값을 모두 넣는다.

					//_idx가 없으면 원본 그대로임.
					originalList[((item._idx != null ) ? item._idx : i)][v] = changeVo[v];	//원본도 수정한다.		// filtered본에는 _idx로 original의 index를갖고 있음. 
				}
			});
			//다시그리기
			this._ReDraw();
		},

		// vo에 해당하는 이름으로 검색어를 넣어주면 됩니다.	 {key:{name:한글},val:{selected:true}}
		ChangeData : function(changeVo){

			var keyVo =  changeVo.key;
			var valVo =  changeVo.val;
			if(!keyVo) return;
			if(!valVo) return;

			//원본 값을 수정.
			this.originalData.filter(function(item,i){
				for(var k in keyVo){				//{name:한글}
					if(!keyVo[k]) break;
					if(item[k] == keyVo[k]){		//1개라도 일치하면
						for(var v in valVo){		//{selected:true}
							item[v] = valVo[v];	//값을 모두 넣는다.
						}
						break;	//for나가기
					}
				}
			});
 
			//복사본 값을 수정.
			this.dataList.filter(function(item,i){
				for(var k in keyVo){
					if(!keyVo[k]) break;//{name:한글}
					if(item[k] == keyVo[k]){		//1개라도 일치하면
						for(var v in valVo){		//{selected:true}
							item[v] = valVo[v];	//값을 모두 넣는다.
						}
						break;	//for나가기
					}
				}
			});

			//다시그리기
			//this._ReDraw();
		},
		// 필터 결과 중에서 검색하기.
		_findFilteredData : function(searchVo,isEquals){
			for(var i in searchVo) if(searchVo[i] == '') delete searchVo[i];	//공백 지우기.
			//필터 결과
			var resultList = this.dataList.filter(function(item){
				var failcnt = 0;
				for(var i in searchVo){
					if(!item[i]){
						failcnt++;
						continue;
					}
					if(isEquals){
						if(item[i] != searchVo[i]){
							failcnt++;
						}
					}else{
						if(item[i].indexOf(searchVo[i]) == -1){
							failcnt++;
						}
					}
				}
				if(failcnt == 0) return true;
			});
			return resultList;
		},
		//해당 조회 조건으로 찾는다. {selected:false}
		_findData : function(searchVo,isEquals,isNotLowerCase){
			for(var i in searchVo) if(searchVo[i] == '') delete searchVo[i];	//공백 지우기.
			var filterData = [];
			if(searchVo == null ){
				filterData = this.originalData;
			}else{
				for (var k = 0; k < this.originalData.length; k++) {
					var item = this.originalData[k];
					var failcnt = 0;
					for(var i in searchVo){
						if(!item[i]){
							failcnt++;
							continue;
						}
						//소문자 통일 여부.
						var oriVal = item[i];
						var tarVal = searchVo[i];
						if(!isNotLowerCase){		//소문자로 통일.
							oriVal = (""+item[i]).toLowerCase();
							tarVal = (""+searchVo[i]).toLowerCase();
						}
						if(isEquals){
							if(oriVal != tarVal){
								failcnt++;
							}
						}else{
							if(oriVal.indexOf(tarVal) == -1){
								failcnt++;
							}
						}
					}
					if(failcnt == 0){
						item._idx = k; 	//original의 index;
						filterData.push(item);						
					}
				}
			}
			return filterData;
		},
		_JumpScroll : function(){		//순간이동 스크롤을 빨리하면 일루 빠짐.
			var scrollTop = this.viewport.scrollTop();
			this.page = Math.floor(scrollTop * ((this.vHeight-this.viewH) / (this.h-this.viewH)) * (1/this.pageH));
			this.offset = Math.round(this.page * this.overlaid);
			this.prevScrollTop = scrollTop;
			this._RemoveAllRows();
		},
		_NearScroll : function(){
			var scrollTop = this.viewport.scrollTop();
			// 다음 페이지
			if (scrollTop + this.offset > (this.page+1)*this.pageH) {
				this.page++;
				this.offset = Math.round(this.page * this.overlaid);
				this.viewport.scrollTop(this.prevScrollTop = scrollTop - this.overlaid);
				this._RemoveAllRows();
			}
			// 이전 페이지
			else if (scrollTop + this.offset < this.page*this.pageH) {
				this.page--;
				this.offset = Math.round(this.page * this.overlaid);
				this.viewport.scrollTop(this.prevScrollTop = scrollTop + this.overlaid);
				this._RemoveAllRows();
			}
			else{
				this.prevScrollTop = scrollTop;
			}
		},
		_RemoveAllRows : function() {
			for (var i in this.rows) {
				this.rows[i].remove();		//객체 지우기.
				delete this.rows[i];
			}
		},
		_RenderViewport : function() {
			// 보이는 부분 + 버퍼 를 계산한다.
			var y = this.viewport.scrollTop() + this.offset,
			buffer = this.viewH,	//*0.6,
			top = Math.floor((y-buffer)/this.lineH),	bottom = Math.ceil((y+this.viewH+buffer)/this.lineH);
 
			//부드럽게 보일려면 *0.5를 높여준다. 
			top = Math.max(0,top);		//상단
			bottom = Math.min(this.tot,bottom);	//하단
 
			// 더이상 보이지 않는 부분을 제거한다.
			for (var i in this.rows) {
				if (i < top || i > bottom) {
					this.rows[i].remove();	//jquery
//					this.Events.onRemoveRow && this.Events.onRemoveRow(this.rows[i],i);	
					delete this.rows[i];
				}
			}
			// 새 행을 넣는다.
			for (var i=top; i < bottom; i++) {
				if (!this.rows[i]){
					var css = {
						position: "absolute",
						top: i*this.lineH - this.offset,
						height: this.lineH,
						index : i
					};
					this.rows[i] = this.Events.onDraw(css,this.dataList[i],this.options);	//만든거.	//여기서 에러
				}
			}
			//다그리고 호출.
			try{
				this.Events.onDrawAfter(this.options,this);
			}catch(e){}
		},
		//화면만 갱신
		_ReDraw : function(){
//			this.rows = {};
			this._RemoveAllRows();
			this._RenderViewport();
		},
		//스크롤 동작 이벤트
		Scroll : function(){
			var scrollTop = this.viewport.scrollTop();
			if (Math.abs(scrollTop-this.prevScrollTop) > this.viewH){	//jump
				this._JumpScroll();
			}else {
				this._NearScroll();
			}
			this.Events.onScroll && this.Events.onScroll(this);		//$$$외부 호출.
			this._RenderViewport();
		}
	};

	

	//multi select

	Vscroll.prototype.Events = {
		onStart : function(options,obj){
			//미리 체크 항목 넣기.
			if(!options.selectedItems) return;
			obj.originalData.filter(function(item,i){
				for(var i in options.selectedItems){
					if(item.CODE == options.selectedItems[i]){
						item.selected = true;
						break;
					}
				}
			});
		},
		onScroll : function(obj){},	//페이지 표시 가능.
		onDraw : function(css, row, options){
			return $("
") .css(css) .text("row " + row.CODE) .appendTo(options.content); }, onDrawAfter : function(options,obj){ }, onRemoveRow :function(rowObj){} }; var MultiSelect = { init : function(){ var dataList = []; for(var i = 0 ; i < 50000 ; i++){ dataList[i] = {name :"한글"+i ,code:"testCode"+i }; } this.vscrolling(dataList); }, vscrolling : function(dataList){ Vscroll.prototype.Events.onDraw = function(css, row, opts){ return $("
") .css(css) .text("row " + row.name) .appendTo(opts.content); }; this.viewport1 = new Vscroll({viewport : $("#viewport") ,content : $("#content"),viewH:500,lineH:30},dataList); }, //검색어 입력 changeFilter : function(obj,objname){ if(obj == "") this[objname].Filter(null); else this[objname].Filter({name:obj.value}); } }; $(function() { MultiSelect.init(); });