1 /*
  2  * ====================================================================
  3  * About Sarissa: http://dev.abiss.gr/sarissa
  4  * ====================================================================
  5  * Sarissa table utils are dependent on sarissa.js and are used for 
  6  * stuff like table sorting.
  7  * @version ${project.version}
  8  * @author: Copyright 2004-2008 Emmanouil Batsis, mailto: mbatsis at users full stop sourceforge full stop net
  9  * ====================================================================
 10  * Licence
 11  * ====================================================================
 12  * Sarissa is free software distributed under the GNU GPL version 2 (see <a href="gpl.txt">gpl.txt</a>) or higher, 
 13  * GNU LGPL version 2.1 (see <a href="lgpl.txt">lgpl.txt</a>) or higher and Apache Software License 2.0 or higher 
 14  * (see <a href="asl.txt">asl.txt</a>). This means you can choose one of the three and use that if you like. If 
 15  * you make modifications under the ASL, i would appreciate it if you submitted those.
 16  * In case your copy of Sarissa does not include the license texts, you may find
 17  * them online in various formats at <a href="http://www.gnu.org">http://www.gnu.org</a> and 
 18  * <a href="http://www.apache.org">http://www.apache.org</a>.
 19  *
 20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 
 21  * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
 22  * WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE 
 23  * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
 24  * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
 26  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 27  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 28  */
 29 
 30 /**
 31  * Sort the table data based on the column corresponding to the given TH element (clickedElem).
 32  * @memberOf Sarissa
 33  * @param {Node} clickedElem the table heading (<code>th</code>) initiating the sort.
 34  * @param {Function} iFunc the custom sort function if needed. Default (null) is case-sensitive sort.
 35  * You can also use <code>Sarissa.SORT_IGNORE_CASE</code>, <code>Sarissa.SORT_DATE_US</code>, 
 36  * and <code>Sarissa.SORT_DATE_EU</code>
 37  * @param {boolean} bSkipCache whether to skip the data cache and read table data all over again. Setting this
 38  * to <code>true</code> means the cache for the table, if it exists, will not be updated either. Defaul is <code>false</code>
 39  * @param {Function} oCallbac a callback function to be executed when the table is 
 40  * sorted and updated. The callback function may be used for effects for example. The parameters 
 41  * passed to the callback are the table as a DOM node and the sort column index (zero based <code>int</code>)
 42  * @requires Sarissa sarissa.js
 43  */
 44 Sarissa.sortHtmlTableData = function(clickedElem, iFunc, bSkipCache, oCallbac){
 45 	// get the table
 46 	var oTbl = clickedElem.parentNode.parentNode;
 47 	while(oTbl.nodeName.toLowerCase() != "table"){
 48 	    oTbl = oTbl.parentNode;
 49 	}
 50 	// we need a table ID for the cache
 51 	if(!oTbl.id){
 52 		oTbl.id = "SarissaTable"+ (Sarissa.tableIdGenCount++);
 53 	}
 54 	// the column to sort on
 55 	var iColIndex = clickedElem.cellIndex;
 56 	var matrix;
 57 	// use the cache if available and permitted
 58 	if(!bSkipCache && Sarissa.tableDataCache[oTbl.id]){
 59 		matrix = Sarissa.tableDataCache[oTbl.id];
 60 	}
 61 	else{
 62 		// read table, skip any rows containing headings, cache if permitted
 63 		matrix = this.getArrayFromTableData(oTbl, null, null, "th");
 64 		if(!bSkipCache){
 65 			Sarissa.tableDataCache[oTbl.id] = matrix;
 66 		}
 67 	}
 68 	// init state persistence as needed
 69 	if(!Sarissa.tableColumnSortStates[oTbl.id]){
 70 		Sarissa.tableColumnSortStates[oTbl.id] = [];
 71 	}
 72 	// build a array to sort from the specific column data, adding 
 73 	// original index info as a suffix
 74 	var sortedColumn = [];
 75 	for(var i=0; i < matrix.length;i++){
 76 		sortedColumn[i] = Sarissa.stripTags(matrix[i][iColIndex]) + "_mbns_" + i;
 77 	}
 78 	// sort the array
 79 	if(iFunc){
 80 		sortedColumn.sort(iFunc);
 81 	}
 82 	else{
 83 		sortedColumn.sort();
 84 	}
 85 	// persist column state
 86 	var sortOrder = Sarissa.tableColumnSortStates[oTbl.id][iColIndex];
 87 	if(sortOrder != "asc"){
 88 		Sarissa.tableColumnSortStates[oTbl.id][iColIndex] = "asc";
 89 	}
 90 	else{
 91 		sortedColumn.reverse();
 92 		Sarissa.tableColumnSortStates[oTbl.id][iColIndex] = "desc";
 93 	}
 94 	// create the sorted matrix based on sortedColumn
 95 	var sortedMatrix = [];
 96 	for(var j=0; j < matrix.length; j++){
 97 		var indexItem = sortedColumn[j];
 98 		var iRow = indexItem.substring(indexItem.indexOf("_mbns_")+6, indexItem.length);
 99 		sortedMatrix[j] = [];
100 		for(var k=0; k < matrix[j].length; k++){
101 			sortedMatrix[j][k] = matrix[iRow][k];
102 		}
103 	}
104 	// update table data, skipping rows with headings
105 	this.updateTableData(oTbl, sortedMatrix, null, null, "th");
106 	if(oCallbac){
107 		oCallbac(oTbl, iColIndex);	
108 	}
109 };
110 
111 /**
112  * Used for generating table IDs, which are required for the cache and sort state persistance 
113  * @memberOf Sarissa
114  * @private
115  */
116 Sarissa.tableIdGenCount = 0;
117 
118 /**
119  * Used for persisting sort state per table column
120  * @memberOf Sarissa
121  * @private
122  */
123 Sarissa.tableColumnSortStates = [];
124 
125 /**
126  * Used for caching table data.
127  * @memberOf Sarissa
128  */
129 Sarissa.tableDataCache = [];
130 
131 /**
132  * Keep track of the cache size. The length property is not for associative arrays 
133  * and I really dont want to add 50 lines and implement a PseudoHashMap right now :-)
134  * @memberOf Sarissa
135  * @private
136  */
137 Sarissa.tableDataCacheSize = 0;
138 
139 /**
140  * The table data cache size, used for sorting HTML tables. You can change it, default is 5 (tables). When a  
141  * table is cached exceeding the cache size, the oldest entry is disgarded from the cache.
142  * @memberOf Sarissa
143  */
144 Sarissa.tableDataCacheMaxSize = 5;
145 
146 /**
147  * Updates the cache, discards oldest entry if cache size is exceeded.
148  * @memberOf Sarissa
149  * @private
150  */
151 Sarissa.tableDataCachePut = function(sTableId, oArr){
152 	if(Sarissa.tableDataCacheSize.length >= Sarissa.tableDataCacheMaxSize){
153 		Sarissa.tableDataCache.shift();
154 		Sarissa.tableDataCacheSize--;
155 	}
156 	Sarissa.tableDataCache[sTableId] = oArr;
157 	Sarissa.tableDataCacheSize++;
158 };
159 /**
160  * Updates the cache of a specific table by reposition a column in the cached data.
161  * This is usefull if you use DHTML to visually reposition columns and need to 
162  * synchronize the cache.
163  * @memberOf Sarissa
164  * @private
165  */
166 Sarissa.tableDataCacheMoveColumn = function(sTableId, oldColumnIndex, newColumnIndex){	
167 	var oldMatrix = Sarissa.tableDataCache[sTableId];
168 	var newMatrix = [];
169 	// iterate rows
170 	var oldRow, movedColumn, newRow;
171 	for(var i=0; i<oldMatrix.length; i++){
172 		oldRow = oldMatrix[i];
173 		movedColumn = oldRow.splice(oldColumnIndex, 1);
174 		newRow = [];
175 		// reposition column value
176 		for(var j=0;j<oldArr.length;J++){
177 			if(j == newColumnIndex){
178 				newRow.put(movedColumn);
179 			}
180 			newRow.put(oldRow[j]);
181 		}
182 		newMatrix[i] = newRow;
183 	}
184 	Sarissa.tableDataCache[sTableId] = newMatrix;
185 };
186 
187 /**
188  * Function for case-insensitive sorting or simple comparison. Can be used as 
189  * a parameter to <code>Array.sort()</code>.
190  * @memberOf Sarissa
191  * @param a a string
192  * @param b a string
193  * @return -1, 0 or 1 depending on whether <code>a</code> is "less than", equal or "greater than" <code>b</code>
194  */
195 Sarissa.SORT_IGNORE_CASE = function(a, b){
196   var strA = a.toLowerCase(),
197       strB = b.toLowerCase();
198   if(strA < strB) return -1;
199   else if(strA > strB) return 1;
200   else return 0;
201 };
202 
203 /**
204  * Function for comparing US dates. Can be used as 
205  * a parameter to <code>Array.sort()</code>.
206  * @memberOf Sarissa
207  * @param a a string
208  * @param b a string
209  * @return -1, 0 or 1 depending on whether <code>a</code> is "less than", equal or "greater than" <code>b</code>
210  */
211 Sarissa.SORT_DATE_US = function(a, b){
212 	var datA = new Date(a.substring(0, a.lastIndexOf("_mbns_"))),
213 		datB = new Date(b.substring(0, b.lastIndexOf("_mbns_")));
214 	if(datA < datB)	return -1;
215 	else if(datA > datB) return 1;
216     else return 0;
217     
218 };
219 
220 /**
221  * Function for comparing EU dates. Can be used as 
222  * a parameter to <code>Array.sort()</code>.
223  * @memberOf Sarissa
224  * @param a a string
225  * @param b a string
226  * @return -1, 0 or 1 depending on whether <code>a</code> is "less than", equal or "greater than" <code>b</code>
227  */
228 Sarissa.SORT_DATE_EU = function(a, b){
229 	var strA = a.substring(0, a.lastIndexOf("_mbns_")).split("/"), 
230 		strB = b.substring(0, b.lastIndexOf("_mbns_")).split("/"),
231 		datA = new Date(strA[2], strA[1], strA[0]), 
232 		datB = new Date(strB[2], strB[1], strB[0]);
233 	if(datA < datB) return -1;
234 	else if(datA > datB) return 1;
235     else return 0;
236 };
237 
238 /**
239  * Get the data of the given element as a two-dimensional array. The 
240  * given XML or HTML Element must match the structure of an HTML table, 
241  * although element names may be different.
242  * @memberOf Sarissa
243  * @param oElem an HTML or XML table. The method works out of the box 
244  * for <code>table</code>, <code>tbody</code>, <code>thead</code> 
245  * or <code>tfooter</code> elements. For custom XML tables, the 
246  * <code>sRowName</code> <code>sCellName</code> must be used.
247  * @param sRowName the row element names. Default is <code>tr</code>
248  * @param sCellName the row element names. Default is <code>td</code>
249  * @param sHeadingName the heading element names. If you use this, rows with 
250  * headings will be <strong>skipped</strong>. To skip headings when reading 
251  * HTML tables use <code>th</code>
252  * @param bStripTags whether to strip markup from cell contents. Default is <code>false</code>
253  * @return a two-dimensional array with the data found in the given element's rows
254  */
255 Sarissa.getArrayFromTableData = function(oElem, sRowName, sCellName, sHeadingName, bStripTags){
256 	if(!sRowName){
257 		sRowName = "tr"
258 	}
259 	if(!sCellName){
260 		sCellName = "td"
261 	}
262 	if(!sHeadingName){
263 		sHeadingName = "th"
264 	}
265 	var rows = oElem.getElementsByTagName(sRowName);
266 	var matrix = [];
267 	for(var i=0, j=0; i < rows.length; i++) {
268 		// skip rows with headings
269 		var row = rows[i];
270 		if((!sHeadingName) || row.getElementsByTagName(sHeadingName).length == 0){
271 			matrix[j] = [];
272 			var cells = row.getElementsByTagName(sCellName);
273 			for(var k=0; k < cells.length; k++){
274 				matrix[j][k] = bStripTags ? Sarissa.stripTags(cells[k].innerHTML) : cells[k].innerHTML;
275 			}
276 			j++;
277 		}
278 	}
279 	return matrix;
280 };
281 
282 /**
283  * Update the data of the given element using the giventwo-dimensional array as a source. The 
284  * given XML or HTML Element must match the structure of an HTML table.
285  * @memberOf Sarissa
286  * @param oElem an HTML or XML table. The method works out of the box 
287  * for <code>table</code>, <code>tbody</code>, <code>thead</code> 
288  * or <code>tfooter</code> elements. For custom XML tables, the 
289  * <code>sRowName</code> <code>sCellName</code> must be used.
290  * @param sRowName the row element names. Default is <code>tr</code>
291  * @param sCellName the row element names. Default is <code>td</code>
292  * @param sHeadingName the heading element names. If you use this, rows with 
293  * headings will be <strong>skipped</strong>. To skip headings when reading 
294  * HTML tables use <code>th</code>
295  */
296 Sarissa.updateTableData = function(oElem, newData, sRowName, sCellName, sHeadingName){
297 	if(!sRowName){
298 		sRowName = "tr"
299 	}
300 	if(!sCellName){
301 		sCellName = "td"
302 	}
303 	var rows = oElem.getElementsByTagName(sRowName);
304 	for(var i=0, j=0; i < newData.length && j < rows.length; j++){
305 		// skip rows with headings
306 		var row = rows[j];
307 		if((!sHeadingName) || row.getElementsByTagName(sHeadingName).length == 0){
308 			var cells = row.getElementsByTagName(sCellName);
309 			for(var k=0; k < cells.length; k++){
310 				cells[k].innerHTML = newData[i][k];
311 			}
312 			i++;
313 		}
314 	}
315 };
316 
317 
318