// Various widgets // Requires Prototype framework // TODO: When there will be several widgets, implement loading method inspired from scriptaculous // Constructor var Table = Class.create(); // Options Table.DefaultOptions = { // Table header index to sort by initialSortedCol: 1, secondSortCol: 1, secondSortColOrder: 'ASC', // ASC OR DESC // Look and feel showSortedCol: true, sortedColClassName: 'sortedColumn', alternateRows: true, alternateRowClassName: 'alternateRow' }; Table.prototype = { widgetName: "Table widget", version: "1.0", initialize: function(id, options) { // Get table if(typeof id == 'undefined') { alert('Table widget object needs an id'); } else { this.id = id; this.tableElt = $(id); } // This code is necessary for browsers that don't reflect the DOM constants // (like IE). if (document.ELEMENT_NODE == null) { document.ELEMENT_NODE = 1; document.TEXT_NODE = 3; } // Retrieve options this.options = Object.extend(Object.extend({},Table.DefaultOptions), options || {}); // Prepare table TODO still something to fix, should be able to remove some table cols this.prepareTable(); }, //----------------------------------------------------------------------------- // DEBUG METHOD : renders some debug info at bottom of //----------------------------------------------------------------------------- debug: function() { var ret = '
'; ret += '

Debug info

'; ret += '
';
    // Add info here
    ret += '

Widget : ' +this.widgetName +'

'; ret += '

Version : ' +this.version +'

'; ret += '
'; ret += '

Table id : ' +this.id +'

'; ret += '

Table element found? : ' +((this.tableElt) ? true : false) +'

'; ret += '
'; // Options ret += '

Options :
'; for(var i in this.options) { ret += ' ' +i +' : ' +this.options[i] +'
'; } ret += '

'; ret += '
'; var body = document.getElementsByTagName('body')[0]; if(!document.all) { // IE doesn't manage this !! new Insertion.Bottom(body, ret); } }, //----------------------------------------------------------------------------- // Prepare table to add callback on events, remove some columns, ... //----------------------------------------------------------------------------- prepareTable: function() { this.header = this.tableElt.tHead; this.footer = this.tableElt.tFoot; this.body = this.tableElt.tBodies[0]; // Add event observers var rows = this.body.rows; rows = $A(rows); rows.each( function(row) { Event.observe(row, 'mouseover', function(event){ row.className += " rowHover"; }); Event.observe(row, 'mouseout', function(event){ row.className = row.className.replace(new RegExp("[\s]*rowHover\\b"), ""); }); Event.observe(row, 'click', function(event){ if(row.className.match(new RegExp("[\s]*selectedRow\\b"))) { row.className = row.className.replace(new RegExp("[\s]*selectedRow\\b"), ""); } else { row.className += " selectedRow"; } // Check the box var aInputs = document.getElementsByClassName('checkbox', row); aInputs = $A(aInputs); aInputs.each(function(checkbox) { var trigger = Event.element(event); if(trigger.type != 'checkbox') { if(row.className.match(new RegExp("[\s]*selectedRow\\b"))) { checkbox.checked = true; } else { checkbox.checked = false; } } // Add multiple select on shift key press only if(event.shiftKey) {alert('shift key pressed')}; }); }); }); }, //----------------------------------------------------------------------------- // Sort tables row by header indexes (columns). //----------------------------------------------------------------------------- sortTable: function(col, rev) { // Get the table or table section to sort. var tableElt = this.body; // The first time this function is called for a given table, // set up an array of reverse sort flags. if (tableElt.reverseSort == null) { tableElt.reverseSort = new Array(); // Also, set the initially sorted column. tableElt.lastSortedColumn = this.options.initialSortedCol; } // If this column has not been sorted before, set the initial sort direction. if (tableElt.reverseSort[col] == null) { tableElt.reverseSort[col] = rev; } // If this column was the last one sorted, reverse its sort direction. if (col == tableElt.lastSortedColumn) { tableElt.reverseSort[col] = !tableElt.reverseSort[col]; } // Remember this column as the last one sorted. tableElt.lastSortedColumn = col; // Set the table display style to "none" - necessary for Netscape 6 // browsers. var oldDisplay = tableElt.style.display; tableElt.style.display = "none"; // Sort the rows based on the content of the specified column using a // selection sort. var tmpEl; var i, j; var minVal, minIdx; var testVal; var cmp; for(i = 0; i < tableElt.rows.length - 1; i++) { // Assume the current row has the minimum value. minIdx = i; minVal = this.getTextValue(tableElt.rows[i].cells[col]); // Search the rows that follow the current one for a smaller value. for (j = i + 1; j < tableElt.rows.length; j++) { testVal = this.getTextValue(tableElt.rows[j].cells[col]); cmp = this.compareValues(minVal, testVal); // Negate the comparison result if the reverse sort flag is set. if (tableElt.reverseSort[col]) { cmp = -cmp; } // Sort by the second column if those values are equal. if (cmp == 0 && col != this.options.secondSortCol) { cmp = this.compareValues(this.getTextValue(tableElt.rows[minIdx].cells[this.options.secondSortCol]), this.getTextValue(tableElt.rows[j].cells[this.options.secondSortCol])); if(this.options.secondSortColOrder == 'DESC') { cmp = -cmp; } } // If this row has a smaller value than the current minimum, remember its // position and update the current minimum value. if (cmp > 0) { minIdx = j; minVal = testVal; } } // Ok we have the row with the smallest value. Remove it from the // table and insert it before the current row. if (minIdx > i) { tmpEl = tableElt.removeChild(tableElt.rows[minIdx]); tableElt.insertBefore(tmpEl, tableElt.rows[i]); } } // Apply some css rules depending on options params. this.makePretty(tableElt, col); // Restore the table's display style. tableElt.style.display = oldDisplay; // In case a link was clicked, to disable it return false; }, //----------------------------------------------------------------------------- // Functions to get and compare values during a sort. //----------------------------------------------------------------------------- getTextValue: function(el) { var i; var s; // Find and concatenate the values of all text nodes contained within the // element. s = ""; for (i = 0; i < el.childNodes.length; i++) { if (el.childNodes[i].nodeType == document.TEXT_NODE) { s += el.childNodes[i].nodeValue; } else if (el.childNodes[i].nodeType == document.ELEMENT_NODE && el.childNodes[i].tagName == "BR") { s += " "; } else { // Use recursion to get text within sub-elements. s += this.getTextValue(el.childNodes[i]); } } return this.normalizeString(s); }, compareValues: function(v1, v2) { var f1, f2; // If the values are numeric, convert them to floats. f1 = parseFloat(v1); f2 = parseFloat(v2); if (!isNaN(f1) && !isNaN(f2)) { v1 = f1; v2 = f2; } // Compare the two values. if (v1 == v2) { return 0; } if (v1 > v2) { return 1; } return -1; }, // TODO: This could be replaced by String.strip(), and another addition // to replace multiple whites spaces // Regular expressions for normalizing white space. whiteSpaceEnds: new RegExp("^\\s*|\\s*$", "g"), whiteSpaceMult: new RegExp("\\s\\s+", "g"), normalizeString: function(s) { s = s.replace(new RegExp("\\s\\s+", "g"), " "); // Collapse any multiple whites space. s = s.replace(new RegExp("^\\s*|\\s*$", "g"), ""); // Remove leading or trailing white space. return s; }, //----------------------------------------------------------------------------- // Function to update the table appearance after a sort. //----------------------------------------------------------------------------- makePretty: function(tableElt, col) { var i, j; var rowEl, cellEl; // Set style classes on each row to alternate their appearance. for (i = 0; i < tableElt.rows.length; i++) { rowEl = tableElt.rows[i]; if(this.options.alternateRows) { rowEl.className = rowEl.className.replace(new RegExp("[\s]*" +this.options.alternateRowClassName +"\\b"), ""); if (i % 2 == 0) { rowEl.className += " " + this.options.alternateRowClassName; rowEl.className = this.normalizeString(rowEl.className); } } if(this.options.showSortedCol) { // Set style classes on each column (other than the name column) to // highlight the one that was sorted. for (j = 1; j < tableElt.rows[i].cells.length; j++) { cellEl = rowEl.cells[j]; cellEl.className = cellEl.className.replace(new RegExp("[\s]*" +this.options.sortedColClassName +"\\b"), ""); if (j == col) { cellEl.className += " " + this.options.sortedColClassName; cellEl.className = this.normalizeString(cellEl.className); } } } } // Find the table header and highlight the column that was sorted. var el = tableElt.parentNode.tHead; rowEl = el.rows[el.rows.length - 1]; // Set style classes for each column as above. for (i = 0; i < rowEl.cells.length; i++) { cellEl = rowEl.cells[i]; cellEl.className = cellEl.className.replace(new RegExp("[\s]*" +this.options.sortedColClassName +"\\b"), ""); cellEl.className = cellEl.className.replace(new RegExp("[\s]*" +"sortedDesc|sortedAsc" +"\\b"), ""); // Highlight the header of the sorted column. if (i == col) { if(this.options.showSortedCol) { cellEl.className += " " + this.options.sortedColClassName; } (tableElt.reverseSort[col]) ? cellEl.className += " sortedDesc" : cellEl.className += " sortedAsc"; cellEl.className = this.normalizeString(cellEl.className); } } }, // Unused currently toggleSelectedRow: function(row) { if(this.previousSelectedRow != null) { this.previousSelectedRow.className = ""; } row.className = "selectedRow"; this.previousSelectedRow = row; } }; // To fix //----------------------- // PREPARE TABLE //----------------------- // prepareTable: function() { // this.header = this.tableElt.tHead; // this.footer = this.tableElt.tFoot; // this.rows = this.tableElt.rows; // // In thead // this.rows.colIndexToAdd = 0; // // for(var i = 0; i < this.header.rows[0].cells.length; i++) { // headerCell = this.header.rows[0].cells[i]; // // check for colspan // for(var j in headerCell.attributes) { // if(headerCell.attributes[j].nodeName == 'colspan') { // this.rows.colIndexToAdd += headerCell.attributes[j].nodeValue - 1; // } // } // if(headerCell.className.match(new RegExp('jsHide'))) { // headerCell.parentNode.removeChild(headerCell); // } // } // // // In tbody // for(var i = 1; i < this.rows.length; i++) { // // Skip ^ first row which is header // var bodyRow = this.rows[i]; // // Execute on rows // var oldBodyCellsLength = bodyRow.cells.length; // for(var j = 0; j < oldBodyCellsLength; j++) { // // var rowCell = bodyRow.cells[j]; // //alert('j = ' +j +' cellule = ' +rowCell.innerHTML +' longueur = ' +bodyRow.cells.length); // if(rowCell.className.match(new RegExp('jsHide'))) { // rowCell.parentNode.removeChild(rowCell); // oldBodyCellsLength -= 1; // } // } // } // }