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