1 xq.RichTable = Class.create({
  2 	initialize: function(rdom, table) {
  3 		this.rdom = rdom;
  4 		this.table = table;
  5 	},
  6 	insertNewRowAt: function(tr, where) {
  7 		var row = this.rdom.createElement("TR");
  8 		var cells = tr.cells;
  9 		for(var i = 0; i < cells.length; i++) {
 10 			var cell = this.rdom.createElement(cells[i].nodeName);
 11 			this.rdom.correctEmptyElement(cell);
 12 			row.appendChild(cell);
 13 		}
 14 		return this.rdom.insertNodeAt(row, tr, where);
 15 	},
 16 	insertNewCellAt: function(cell, where) {
 17 		// collect cells;
 18 		var cells = [];
 19 		var x = this.getXIndexOf(cell);
 20 		var y = 0;
 21 		while(true) {
 22 			var cur = this.getCellAt(x, y);
 23 			if(!cur) break;
 24 			cells.push(cur);
 25 			y++;
 26 		}
 27 		
 28 		// insert new cells
 29 		for(var i = 0; i < cells.length; i++) {
 30 			var cell = this.rdom.createElement(cells[i].nodeName);
 31 			this.rdom.correctEmptyElement(cell);
 32 			this.rdom.insertNodeAt(cell, cells[i], where);
 33 		}
 34 	},
 35 	deleteRow: function(tr) {
 36 		return this.rdom.removeBlock(tr);
 37 	},
 38 	deleteCell: function(cell) {
 39 		if(!cell.previousSibling && !cell.nextSibling) {
 40 			this.rdom.deleteNode(this.table);
 41 			return;
 42 		}
 43 		
 44 		// collect cells;
 45 		var cells = [];
 46 		var x = this.getXIndexOf(cell);
 47 		var y = 0;
 48 		while(true) {
 49 			var cur = this.getCellAt(x, y);
 50 			if(!cur) break;
 51 			cells.push(cur);
 52 			y++;
 53 		}
 54 		
 55 		for(var i = 0; i < cells.length; i++) {
 56 			this.rdom.deleteNode(cells[i]);
 57 		}
 58 	},
 59 	getPreviousCellOf: function(cell) {
 60 		if(cell.previousSibling) return cell.previousSibling;
 61 		var adjRow = this.getPreviousRowOf(cell.parentNode);
 62 		if(adjRow) return adjRow.lastChild;
 63 		return null;
 64 	},
 65 	getNextCellOf: function(cell) {
 66 		if(cell.nextSibling) return cell.nextSibling;
 67 		var adjRow = this.getNextRowOf(cell.parentNode);
 68 		if(adjRow) return adjRow.firstChild;
 69 		return null;
 70 	},
 71 	getPreviousRowOf: function(row) {
 72 		if(row.previousSibling) return row.previousSibling;
 73 		var rowContainer = row.parentNode;
 74 		if(rowContainer.previousSibling && rowContainer.previousSibling.lastChild) return rowContainer.previousSibling.lastChild;
 75 		return null;
 76 	},
 77 	getNextRowOf: function(row) {
 78 		if(row.nextSibling) return row.nextSibling;
 79 		var rowContainer = row.parentNode;
 80 		if(rowContainer.nextSibling && rowContainer.nextSibling.firstChild) return rowContainer.nextSibling.firstChild;
 81 		return null;
 82 	},
 83 	getAboveCellOf: function(cell) {
 84 		var row = this.getPreviousRowOf(cell.parentNode);
 85 		if(!row) return null;
 86 		
 87 		var x = this.getXIndexOf(cell);
 88 		return row.cells[x];
 89 	},
 90 	getBelowCellOf: function(cell) {
 91 		var row = this.getNextRowOf(cell.parentNode);
 92 		if(!row) return null;
 93 		
 94 		var x = this.getXIndexOf(cell);
 95 		return row.cells[x];
 96 	},
 97 	getXIndexOf: function(cell) {
 98 		var row = cell.parentNode;
 99 		for(var i = 0; i < row.cells.length; i++) {
100 			if(row.cells[i] == cell) return i;
101 		}
102 		
103 		return -1;
104 	},
105 	getYIndexOf: function(cell) {
106 		var y = -1;
107 		
108 		// find y
109 		var group = row.parentNode;
110 		for(var i = 0; i <group.rows.length; i++) {
111 			if(group.rows[i] == row) {
112 				y = i;
113 				break;
114 			}
115 		}
116 		if(this.hasHeadingAtTop() && group.nodeName == "TBODY") y = y + 1;
117 		
118 		return y;
119 	},
120 	/**
121 	 * TODO: Not used. Delete or not?
122 	 */
123 	getLocationOf: function(cell) {
124 		var x = this.getXIndexOf(cell);
125 		var y = this.getYIndexOf(cell);
126 		return {x:x, y:y};
127 	},
128 	getCellAt: function(col, row) {
129 		var row = this.getRowAt(row);
130 		return (row && row.cells.length > col) ? row.cells[col] : null;
131 	},
132 	getRowAt: function(index) {
133 		if(this.hasHeadingAtTop()) {
134 			return index == 0 ? this.table.tHead.rows[0] : this.table.tBodies[0].rows[index - 1];
135 		} else {
136 			var rows = this.table.tBodies[0].rows;
137 			return (rows.length > index) ? rows[index] : null;
138 		}
139 	},
140 	getDom: function() {
141 		return this.table;
142 	},
143 	hasHeadingAtTop: function() {
144 		return !!(this.table.tHead && this.table.tHead.rows[0]);
145 	},
146 	hasHeadingAtLeft: function() {
147 		return this.table.tBodies[0].rows[0].cells[0].nodeName == "TH";
148 	},
149 	correctEmptyCells: function() {
150 		var cells = $A(this.table.getElementsByTagName("TH"));
151 		cells.push($A(this.table.getElementsByTagName("TD")));
152 		cells = cells.flatten();
153 		
154 		for(var i = 0; i < cells.length; i++) {
155 			if(this.rdom.isEmptyBlock(cells[i])) this.rdom.correctEmptyElement(cells[i])
156 		}
157 	}
158 });
159 
160 xq.RichTable.create = function(rdom, cols, rows, headerPositions) {
161 	if(["t", "tl", "lt"].include(headerPositions)) var headingAtTop = true
162 	if(["l", "tl", "lt"].include(headerPositions)) var headingAtLeft = true
163 
164 	var sb = []
165 	sb.push('<table class="datatable">')
166 	
167 	// thead
168 	if(headingAtTop) {
169 		sb.push('<thead><tr>')
170 		for(var i = 0; i < cols; i++) sb.push('<th></th>')
171 		sb.push('</tr></thead>')
172 		rows -= 1
173 	}
174 		
175 	// tbody
176 	sb.push('<tbody>')
177 	for(var i = 0; i < rows; i++) {
178 		sb.push('<tr>')
179 		
180 		for(var j = 0; j < cols; j++) {
181 			if(headingAtLeft && j == 0) {
182 				sb.push('<th></th>')
183 			} else {
184 				sb.push('<td></td>')
185 			}
186 		}
187 		
188 		sb.push('</tr>')
189 	}
190 	sb.push('</tbody>')
191 	
192 	sb.push('</table>')
193 	
194 	// create DOM element
195 	var container = rdom.createElement("div");
196 	container.innerHTML = sb.join("");
197 	
198 	// correct empty cells and return
199 	var rtable = new xq.RichTable(rdom, container.firstChild);
200 	rtable.correctEmptyCells();
201 	return rtable;
202 }