1. 程式人生 > >javascript實現動態模態繫結grid

javascript實現動態模態繫結grid

<html>
<head>
	<style type="text/css">
		.grid{border:1px solid #808080; border-spacing:0; width:500px; border-collapse:collapse}
		.grid th,.grid td{border:0; text-align:center;}
		.grid tr{height:25px;line-height:25px;}
		.grid tr.odd{background:#d0d0d0}
		.grid .btn{width:80px; text-align:center}		
	</style>
	
	<script type="text/javascript">
		(function(){
			//
			var util = {
				next:function next(ele){
					if(ele){
						var n = ele.nextSibling;
						if(n && n.nodeType === 1){
							return n;
						}
						return next(n);
					}
				},
				parseJSON:function(str){
					if(typeof JSON !== "undefined"){
						return JSON.parse(str);
					}
					return eval("("+str+")");
				},
				parseArray:function(obj){
					if(!obj){
						return obj;
					}
					var result = [];
					if(typeof obj.length !== "undefined"){
						try{
							var arr = Array.prototype.slice.call(obj,0);
							result = arr;
						}catch(e){
							for(var i=0;i<obj.length;i++){
								try{
									var target = obj[i];
								}catch(e){
									if(obj.item){
										var target = this.item(i);//nodeList
									}
								}
								if(typeof target !== "undefined"){
									result.push(target);
									delete target;
								}
							}
						}
					}
					return result;
				},
				isFunction:function(fn){
					return typeof fn === "function";
				},
				trim:function(str){
					if(typeof str !== "string"){
						return str;
					}
					return str.replace(/^\s+|\s+$/g,"");
				},
				offset:function offset(ele){
					var result = {top:0,left:0};
					if(!ele || ele.nodeType !== 1){
						return result;
					}
					result.top = Number(ele.offsetTop || (ele.style.top || "0").replace(/[^\d]+$/,""));
					result.left = Number(ele.offsetLeft || (ele.style.left || "0").replace(/[^\d]+$/,""));
					if(ele.parentNode){
						var r = offset(ele.parentNode);
						result.top += r.top;
						result.left += r.left;
					}
					return result;
				}
			};
			
			//事件處理
			var Event = {
				on:function(eventType,fn){
					var element = this;
					if(this.addEventListener){
						this.addEventListener(eventType,fn,false);
					}else if(this.attachEvent){
						 //將事件緩衝到該標籤上,已解決this指向window(現fn內this指向element)和移除匿名事件問題						 
						var _EventRef='_'+eventType+'EventRef';
						if(!element[_EventRef]){
							element[_EventRef]=[];
						}
						var _EventRefs=element[_EventRef];
						var index;
						for(index in _EventRefs){
							if(_EventRefs[index]['realFn']==fn){
								return;
							}
						}
						var nestFn=function(){
							fn.apply(element,arguments);
						};
						element[_EventRef].push({'realFn':fn,'nestFn':nestFn});
						element.attachEvent('on'+eventType,nestFn);
					}					
				},
				remove:function(eventType,fn){			
					var element = this;		
					if(this.removeEventListener){
						this.removeEventListener(eventType,fn,false);
					}else if(this.detachEvent){
						var _EventRef='_'+eventType+'EventRef';
						if(!element[_EventRef]){
							element[_EventRef]=[];
						}
						var _EventRefs=element[_EventRef]
						var index;
						var nestFn;
						for(index in _EventRefs){
							if(_EventRefs[index]['realFn']==fn){
								nestFn=_EventRefs[index]['nestFn'];
								if(index==_EventRefs.length-1){
									element[_EventRef]=_EventRefs.slice(0,index);
								}else{
									element[_EventRef]=_EventRefs.slice(0,index).concat(_EventRefs.slice(index+1,_EventRefs.length-1));
								}
								break;
							}
						}
						if(nestFn){
							element.detachEvent('on'+eventType,nestFn);
						}
					}
				}
			};
			
			//extend
			(function(){
				//刪除陣列中指定下標出的元素
				Array.prototype.remove = function(index){
					var o = this[index];
					if(typeof o !== "undefined"){
						if(index == 0){
							this.shift();
						}else if(index === this.length - 1){
							this.pop();
						}else{
							var arr1 = this.slice(0,index);
							var arr2 = this.slice(index+1);
							this.length = 0;
							this.concat(arr1,arr2);
						}
					}
				}
				//刪除陣列中所有的元素
				Array.prototype.clear = function(){
					this.length = 0;
				}
				//each
				Array.prototype.each = function(fn){
					if(!util.isFunction(fn)){
						return;
					}
					for(var i=0;i<this.length;i++){
						if(typeof this[i] !== "undefined"){
							fn.call(this[i],i);
						}
					}
				}
				
				//
				var collection = this.collection = function(){
					this.__data = {};
					this.length = 0;
				}
				collection.prototype = {
					add:function(obj){
						this.__data[this.length++] = obj;
					},
					get:function(index){
						return this.__data[index];
					},
					remove:function(index){
						var obj = this.__data[index];
						if(typeof obj !== "undefined"){
							this.length--;
						}
						delete this.__data[index];
					},
					clear:function(){
						this.__data = {};
						this.length = 0;
					},
					each:function(fn){
						var index = 0;
						for(var k in this.__data){
							if(k && typeof this.__data[k] !== "undefined"){
								fn.call(this.__data[k],index++);
							}
						}
					},
					toArray:function(){
						var arr = [];
						this.each(function(){
							arr.push(this);
						});
						return arr;
					}
				};
			})();
			//
			var grid = this.grid = function(table, options){
				grid.prototype._init.call(this,table,options);
			}
			grid.prototype = {
				_init:function(table, options){
					if(typeof table === "undefined" || !table){
						throw "table is undefined or null";
					}
					if(table.nodeType !== 1 || !/^table$/i.test(table.tagName)){
						throw "table must be 'table' element.";
					}
					table.guid = ++grid.guid;
					this.__cache = {};
					var self = this;
					var header = this.__cache["header"] = loadHeader();//header
					this.__root = header.parentNode;
					var templateRow = this.__cache["template"] = loadTemplate();//模板行
					this.__cache["dataFormat"] = loadDataFormat();//資料模板
					this.__cache["dataRows"] = new collection();//資料行
					this.__cache["customCache"] = new collection();//使用者快取資料
					this.__editRow = null;//當前編輯行
					this.__saveContainer = createSaveButton();//儲存按鈕
					this.__cache["commandHandles"] = {//command handels
						removeRow:function(){
							var rowIndex = this.getAttribute("index");
							self.removeRow.apply(self,[rowIndex]);
						},
						newLine:function(){
							self.newLine();
						}
					};
					this.__regCommand = function(commandName,row){ //註冊command
						if(row){
							var arg = row.getAttribute("index");
							this.setAttribute("index",arg || false);
						}
						this.commandName = commandName;
						Event.remove.call(this,"click",exec);
						Event.on.call(this,"click",exec);
					}
					this.__removeRowCallback = function(){ //改變行的背景樣式
						var rows = this.__cache["dataRows"];
						var customCache = this.__cache["customCache"];
						var arr = rows.toArray(),dataArr=[];
						var rowIndex,row,data;
						
						rows.clear();
						arr.each(function(i){
							rowIndex = this.getAttribute("index");
							data = customCache.get(rowIndex);
							dataArr.push(data);
							this.setAttribute("index",i.toString());
							rows.add(this);
							if( i % 2 == 1){//基數行
								if(!/\sodd\s|\sodd$/g.test(this.className)){
									this.className = (this.className || "") + " odd";
								}
							}else if(/\sodd\s|\sodd$/g.test(this.className)){
								this.className = this.className.replace(/\sodd\s|\sodd$/g," ");
							}
							i++;
						});
						
						customCache.clear();
						dataArr.each(function(){
							customCache.add(this);
						});
					}					
					
					//事件處理
					options = options || {};
					this.onDataBinding = options.onDataBinding || this.onDataBinding;
					this.onRowBinding = options.onRowBinding || this.onRowBinding;
					this.onRowBinded = options.onRowBinded || this.onRowBinded;					
					
					function loadHeader(){
						var tr = table.firstChild;
						if(tr && tr.nodeType != 1){
							tr = util.next(tr);
						}
						if(!/tr/i.test(tr.tagName)){ //如果第一個元素不是tr,則瀏覽器支援tbody
							tr = tr.firstChild;
							if(tr.nodeType != 1){
								tr = util.next(tr);
							}
						}
						return tr;
					}
					
					function loadTemplate(){
						tr = util.next(header);//獲取模板行
						return tr;
					}
					
					function loadDataFormat(){
						var nodes = templateRow.childNodes,ele,data,result = {},attr;
						nodes = util.parseArray(nodes);
						nodes.each(function(i){
							ele = this;
							if(ele && ele.nodeType == 1){
								attr = ele.data || ele.getAttribute("data");
								if(attr){
									data = util.parseJSON(attr);
									ele.field = data.field;
									result[ele.field] = data;
								}
							}
						});						
						return result;
					}
					
					function createSaveButton(){
						var div = document.createElement("div");
						div.style.position = "absolute";
						div.style.display = "none";
						div.style.width = "auto";
						var btn = document.createElement("button");
						btn.innerHTML = btn.innerText = btn.textContent = btn.value = "Save";
						try{
							btn.type = "button";
						}catch(e){
							btn.setAttribute("type","button");
						}
						div.appendChild(btn);
						
						var btnCancel = document.createElement("button");
						btnCancel.innerHTML = btnCancel.innerText = btnCancel.textContent = btnCancel.value = "Cancel";
						try{
							btnCancel.type = "button";
						}catch(e){
							btnCancel.setAttribute("type","button");
						}
						div.appendChild(btnCancel);
						
						document.body.appendChild(div);
						Event.on.call(btn,"click",function(){
							self.save();
						});
						Event.on.call(btnCancel,"click",function(){
							div.style.display = "none";
							if(self.__editRow){
								self.__editRow.parentNode.removeChild(self.__editRow);
								self.__editRow = null;
							}
						});
						
						return div;
					}
					
					function exec(){
						if(self.__editRow){//如果當前處於編輯模式,則禁用所有command
							return;
						}
						var commandName = this.commandName;
						var handler = self.__cache["commandHandles"][commandName];						
						if(handler){
							handler.call(this);
						}
					}
					
					//去除模板行
					templateRow.parentNode.removeChild(templateRow);
					
					//處理表格中的command事件
					var elements = header.getElementsByTagName("*");
					elements = util.parseArray(elements);
					elements.each(function(){
						if(this.nodeType === 1){
							var commandName = this.command || this.getAttribute("command");
							if(commandName){
								self.__regCommand.call(this,commandName,header);
							}
						}
					});
				},
				//bangding
				bind:function(data){
					this.clear();
					if(data && data.length > 0){
						var self = this;
						data.each(function(){
							self.append(this);
						});
					}
				},
				//清理表,刪除所以除header以外的資料行
				clear:function(){
					var rows = this.__cache["dataRows"],row;
					rows.each(function(){
						row = this;
						if(row){
							row.parentNode.removeChild(row);
						}
					});
					rows.clear();//清理rows
				},
				//刪除指定的行
				removeRow:function(rowIndex){
					var rows = this.__cache["dataRows"];
					var row = rows.get(rowIndex);
					if(row){
						var data = this.__cache["customCache"][rowIndex];
						row.parentNode.removeChild(row);
						rows.remove(rowIndex);
						//通知使用者資料行被移除						
						if(util.isFunction(this.onRowRemoved)){
							this.onRowRemoved(data,row);
						}
					}
					this.__removeRowCallback();
				},
				//新增 行 
				append:function(data){
					if(!data){
						return ;
					}
					var template = this.__cache["template"];
					var rows = this.__cache["dataRows"];
					var rowIndex = rows.length;
					var tr = template.cloneNode();
					var customCache = this.__cache["customCache"];
					customCache.add(data);
					//將資料行新增到table
					this.__root.appendChild(tr);
					var self = this;
					var td,//資料單元格
						dataFormat,//資料格式化器
						value;//單元格中的給定的資料
					tr.setAttribute("index",rowIndex.toString());
					//更改樣式
					if(rowIndex % 2 == 1){
						tr.className = (tr.className || "") + " odd";
					}
					//通知 行資料繫結開始
					if(util.isFunction(this.onRowBinding)){
						this.onRowBinding(rowIndex,tr);
					}
					
					var templateTD = template.firstChild;
					while(templateTD){
						td = templateTD.cloneNode(true);
						tr.appendChild(td);
						if(td.nodeType == 1 && templateTD.field){
							dataFormat = this.__cache["dataFormat"][templateTD.field];
							td.removeAttribute("data");
							td.field = templateTD.field;
							value = data[dataFormat.field];
							//通知單元格資料繫結事件
							value = this.onDataBinding(dataFormat.field,value,td,data);
							if(value !== false){//如果返回false,則不用做賦值操作
								switch(dataFormat.render){
									case "innerHTML":
										td.innerHTML = typeof value == "undefined" || value == null ? "" : value;
										break;
									case "innerText":
									default:
										td.innerText = td.textContent = typeof value == "undefined" || value == null ? "" : value;
										break;
								}
							}
						}
						templateTD = templateTD.nextSibling;
					}
					rows.add(tr);
					
					//處理command
					var elements = tr.getElementsByTagName("*"),ele,attr;
					elements = util.parseArray(elements);
					elements.each(function(){
						ele = this;
						if(ele.nodeType == 1 && (ele.command || ele.getAttribute("command"))){
							attr = ele.command || ele.getAttribute("command");
							self.__regCommand.call(ele,attr,tr);
						}
					});
					
					//通知 行資料繫結完成
					if(util.isFunction(this.onRowBinded)){
						this.onRowBinded(rowIndex,tr);
					}
				},
				//手動產生新的輸入行
				newLine:function(){
					if(this.__editRow){//如果當前有存在編輯行,則直接返回,每次最多限制編輯一行資料
						return;
					}
					var template = this.__cache["template"] ;
					var row = this.__editRow = template.cloneNode(false);
					var templateTD = template.firstChild;
					var textareaList = [];
											
					while(templateTD){
						td = templateTD.cloneNode(true);
						row.appendChild(td);
						if(td.nodeType == 1){
							if(templateTD.field){
								td.field = templateTD.field;
								td.innerHTML = "";
								var dataFormat = this.__cache["dataFormat"][templateTD.field];
								var textarea = null;
								if(dataFormat.render == "innerHTML"){
									textarea = document.createElement("textarea");
								}else{
									textarea = document.createElement("input");
									textarea.type = "text";
								}
								textarea.style.display = "none";
								td.appendChild(textarea);
								textareaList.push(textarea);
							}
						}
						templateTD = templateTD.nextSibling;
					}
					//將資料行新增到table
					this.__root.appendChild(row);
					
					var height = row.offsetHeight,
						width = row.offsetWidth,
						offset = util.offset(row);
						
					textareaList.each(function(){
						this.style.height = (0.8 * height) + "px";
						this.style.width = (0.8 * this.parentNode.offsetWidth) + "px";
						this.style.display = "";
					});
					
					var left = offset.left + width + 5;
					var top = offset.top;
					this.__saveContainer.style.top = top + "px";
					this.__saveContainer.style.left = left + "px";
					this.__saveContainer.style.height = this.__saveContainer.style.lineHeight = height + "px";
					this.__saveContainer.style.display = "block";
				},
				//儲存手動產生的資料行資料
				save:function(){
					if(!this.__editRow){
						return;
					}
					
					var row = this.__editRow;
					var td = row.firstChild;
					var data = {};
					while(td){
						if(td.nodeType === 1 && td.field){
							var dataFormat = this.__cache["dataFormat"][td.field];
							var textarea = null;
							if(dataFormat.render == "innerHTML"){
								textarea = td.getElementsByTagName("textarea")[0];
							}else{
								textarea = td.getElementsByTagName("input")[0];
							}
							value = textarea.value;
							switch(dataFormat.dataType){
								case "number":
									value = util.trim(value);
									value = Number(value.length == 0 ? 0 : value);
									break;
								default:
									break;
							}
							data[td.field] = value;
						}
						td = td.nextSibling;
					}
					this.__editRow.parentNode.removeChild(this.__editRow);
					this.__editRow = null;
					this.__saveContainer.style.display = "none";
					
					//通知使用者正在儲存資料
					if(util.isFunction(this.onSaving)){
						this.onSaving(data);
					}
					
					this.append(data);
				},
				getRowData:function(rowIndex){
					return this.__cache["customCache"].get(rowIndex);
				},
				
				//資料繫結到指定cell時的事件
				onDataBinding:function(field,value,cell,data){
					return value;
				},
				//當資料行繫結開始時的事件
				onRowBinding:function(rowIndex, row){
				},
				//當資料行繫結完成時的事件
				//@param row {DOM element tr} 
				onRowBinded:function(rowIndex, row){
				},
				//當編輯的資料被儲存時的事件
				onSaving:function(data){
				},
				//當資料行被移除時的通知事件
				onRowRemoved:function(data,row){
				}
			};
			
			grid.guid = 0;
		})();
		
		
	</script>
</head>

<body>
	<table id="table_demo" class="grid">
		<tr class="odd">
			<th>ID</th>
			<th>Name</th>
			<th>Descpription</th>
			<th><button type="button" command="newLine" class="btn">New Line</button></th>
		</tr>
		<tr>
			<td data='{"field":"id","dataType":"number","render":"innerText"}'>1</td>
			<td data='{"field":"name","dataType":"string","render":"innerText"}'>WorkingService</td>
			<td data='{"field":"description","dataType":"string","render":"innerHTML"}'>WorkingService</td>
			<td>
				<button type="button" command="removeRow" class="btn">Delete</button>
			</td>
		</tr>
	</table>
	<script type="text/javascript">
		var table = document.getElementById("table_demo");
		var g = new grid(table,{
			onDataBinding:function(field,value){
				return value;
			},
			onRowBinded:function(rowIndex,row){}
		});
		g.bind([
			{id:0,name:"kilin"},
			{id:1,name:"kilin1"},
			{id:2,name:"kilin2"},
			{id:3,name:"kilin3"}
		]);
	</script>
</body>
</html>