/**************************************** TableSorter component - version 1.15. *****************************************/ // Constructor. // [optional] - specifies a particular table on a page. This can be either a value related to // a specific tag or simply the numerical order of the table // on the page (i.e. 1, 2, ..., n). function TableSorter() { // These DOM constants aren't defined by some browsers (namely, IE), so define them here. if (document.ELEMENT_NODE == null) { document.ELEMENT_NODE = 1; document.TEXT_NODE = 3; } // Private member variables. var _ASC_ = '5'; // Pseudo-constant var _DESC_ = '6'; // Pseudo-constant var m_tid = ((arguments.length > 0) ? arguments[0] : 1); var m_default = false; var m_lastCol = null; var m_lastSeq = 'A'; var m_initialized = false; var m_tBodies = null; var m_spanElement = null; // Private Methods. // Mutators. var setDefault = function() { m_default = true; } var resetDefault = function() { m_default = false; } var setLastCol = function(col) { m_lastCol = col; } var setLastSeq = function() { m_lastSeq = (isAscending() ? 'D' : 'A'); } var resetLastSeq = function() { m_lastSeq = 'A'; } var setDefaultSeq = function(seq) { m_lastSeq = seq; } var setInitialized = function() { m_initialized = true; } var setTBodiesRef = function(ref) { m_tBodies = ref; } var setSpanElement = function(element) { m_spanElement = element; } // Accessors. var getTableBinding = function() { return m_tid; } var isDefault = function() { return m_default; } var getLastCol = function() { return m_lastCol; } var isAscending = function() { return ((m_lastSeq == 'A') ? true : false); } var isInitialized = function() { return m_initialized; } var getTBodiesRef = function() { return m_tBodies; } var getSpanElement = function() { if (!m_spanElement) { createIndicatorSpan(); } return m_spanElement; } // JavaScript doesn't understand commas in numbers. Remove all of the commas before comparison. var removeCommas = function(arg) { return arg.replace(/\,/g, ""); } // Remove any spaces from the number string. This will usually be in the form of leading spaces. var removeSpaces = function(arg) { return arg.replace(/\s/g, ""); } // Processes negative numbers marked with "( )" by temporarily "removing" the parentheses from // the sort value. It then make s the number negative using a standard "-" sign. In the case of // currency, the minus sign is inserted between the currency symbol and the value. var replaceParens = function(val, type) { if (val.charAt(0) == "(") { val = val.replace(/[\(\)]/g, ""); if (type == 'currency') { val = (val.charAt(0) + "-" + val.substr(1)); } else { val = "-" + val; } } return val; } // Recurses upward until it finds the
tag. If it does not find a
tag before // it finds the tag (meaning no table is defined or is ill-defined), it exits. // If it finds a proper table defintion, it returns a reference to the
tag containing // the clicked column.. // Note: XHTML specifies that ALL tables properly specify the , (optional), and // structure. The methods in this object will not work unless the table is "well-formed". var getTableRef = function(colRef) { var tableRef = colRef.parentNode; while (tableRef.nodeName.toLowerCase() != "table") { tableRef = tableRef.parentNode; if (tableRef.nodeName.toLowerCase() == "body") { tableRef = null; break; } } return tableRef; } // Uses the bound table ID to get a reference to the actual table, either by ID or sequence. var getTableRefByID = function(tid) { var tableRef = document.getElementById(tid); if (!tableRef) { var tables = document.getElementsByTagName('table'); if (tables) { tableRef = ((tid <= tables.length) ? tables[tid - 1] : null); } } return tableRef; } // Returns a reference to a collection of tags for the table clicked. var getTableBodies = function(colRef) { var tableRef = getTableRef(colRef); return ((tableRef && tableRef.tBodies) ? tableRef.tBodies : null); } // This node gets created once and a reference is stored. After that, it is just moved // to the appropriate parent and re-attached. var createIndicatorSpan = function() { var newElement = document.createElement('span'); var newTextElement = document.createTextNode(""); newElement.setAttribute('className', 'indicator'); newElement.appendChild(newTextElement); setSpanElement(newElement); } // Sets an indicator on the sorted column to show the direction of the sort. var setIndicator = function(colRef) { var lastCol = getLastCol(); if (!lastCol) { colRef.appendChild(getSpanElement()); } else if (colRef != lastCol) { colRef.appendChild(lastCol.removeChild(lastCol.lastChild)); } colRef.lastChild.firstChild.nodeValue = ((isAscending()) ? _ASC_ : _DESC_); } // Used to get and retain a reference to the sortable rows area of a table so that it doesn't // need to be resolved everytime it's needed - the tBody reference is used extensively. This // method is called only once at the first sort request. var init = function(col) { var retVal = true; if (!isInitialized()) { retVal = false; var tBodies = getTableBodies(col); if (tBodies) { if (tBodies[0].rows.length > 1) { setTBodiesRef(tBodies[0]); setInitialized(); retVal = true; } } } return retVal; } // Retrieves the information from the table to sort. var readRows = function(col, type) { var rowSet = getTBodiesRef().rows; var numRows = rowSet.length; var rowData = new Array(numRows); for (var i = 0; i < numRows; i++) { var row = rowSet[i]; var val = getValueByType(row, col, type); if (val != null) { rowData[i] = { value: val, row: row }; } else { rowData = null; break; } } return rowData; } // Loop through the sorted row data and rewrite it into the table. var writeRows = function(data) { var tBody = getTBodiesRef(); var len = data.length; for (var i = 0; i < len; i++) { tBody.appendChild(data[i].row); } } // This method "compiles" the text information from a given node (data cell) for comparison. // It is generic and will "find" the proper information regardless of whether or not the table // structure undergoes changes. It calls itself recursively until the data is found. var getRawCellData = function(node) { var tag = (((arguments.length > 1) && arguments[1]) ? arguments[1].toLowerCase() : null); var children = node.childNodes; var len = children.length; var value = ""; for (var i = 0; i < len; i++) { switch (children[i].nodeType) { case document.ELEMENT_NODE : var child = children[i].nodeName.toLowerCase(); if (tag && (tag == 'src') && (child == 'img')) { value = children[i].src; } else if (tag && (tag == 'href') && (child == 'a')) { value = children[i].href; } else if (child == "br") { value += " "; } else { value += getRawCellData(children[i], tag); } break; case document.TEXT_NODE : value += children[i].nodeValue; break; default : break; } } return value; } // Traffic cop method for readability. Directs the processing to the proper method. var getValueByType = function(row, col, type) { var val = null; if ((type == 'number') || (type == 'currency') || (type == 'date')) { val = getNumberValue(row, col, type); } else if ((type == 'textcase') || (type == 'textignorecase') || (type == 'icon') || (type =='link') || (type == 'url')) { val = getTextValue(row, col, type); } else { alert("Invalid data type: '" + type.toUpperCase() + "' is not supported."); } return val; } // Processes numerical data. This method handles integer and decimal entries with both // positive and negative values (using "-" or "( )" to show negativity). // Also handles scientific notation and values containing commas. var getNumberValue = function(row, col, type) { var node = row.cells[col.cellIndex]; var value = getRawCellData(node); value = replaceParens(value, type); switch (type) { // Ignores the currency designation and sorts in numerical order. case 'currency' : value = value.substr(1); break; // Ignores the format and sorts in chronological order. Supports any formats // supported by java.text.SimpleDateFormat. Also supports "-" as separator. case 'date' : value = value.replace(/\-/g, "/"); value = (Date.parse(value)).toString(); break; default : break; } value = removeSpaces(removeCommas(value)); // This handles the case of "empty" data cells (Example: ). var re = /[\+\-]?(\d*\.?)?(\d+)(e[\+\-]?\d+)?/i; value = ((re.test(value)) ? Number(value) : Number.POSITIVE_INFINITY); return value; } // Compares alpha and alphanumeric data in ASCII order by default. Uppercase letters have // a lower ASCII value and will therefore appear prior to any lowercase in the sort order. // (e.g. 'Z', 'g', 'a', 'F' will be sorted ascending as 'F', 'Z', 'a', 'g'). // // Compares alpha and alphanumeric data with no regard for case. ('A' === 'a') for all // other text types. (e.g. 'Z', 'g', 'a', 'F' are sorted ascending as 'a', 'F', 'g', 'Z'). var getTextValue = function(row, col, type) { var node = row.cells[col.cellIndex]; var value = null; switch (type) { // Handles reading actual URL values in a hyperlink. Link data is handled by default // and needs no special processing. case 'url' : value = getRawCellData(node, 'href'); break; // Used to goup similar icons together. Uses only the image name itself, not // the entire file path. This allows images to be stored anywhere in the system // and still sort appropriately. case 'icon' : value = getRawCellData(node, 'src'); var pos = value.lastIndexOf('/'); value = (pos ? value.substr(pos + 1) : value); break; // All other text types are handled here (incl. link text). default : value = getRawCellData(node) break; } if (type != 'textcase') { value = value.toLowerCase(); } return value; } // This is a generic compare function used by the Array.sort() method. // It handles all currently supported data types. var getComparator = function() { return function compare(row1, row2) { var a = row1.value; var b = row2.value; // If comparing two "empty" numeric cells to each other, just assert they are equal. if ((a == Number.POSITIVE_INFINITY) && (b == Number.POSITIVE_INFINITY)) { return 0; } else if (isAscending()) { return ((a < b) ? -1 : ((a > b) ? 1 : 0)); } else { return ((b < a) ? -1 : ((b > a) ? 1 : 0)); } } } // Public Methods // This is a public interface to the TableSorter for setting intial defaults for a table // during load. This allows a table to be displayed intially, pre-sorted on a given column. // It has two required parameters and one optional parameter. // // defCol - the column number for the actual column in the table // colType - specifies the data type to sort. (see sortColumn() for details). // [optional] - specifies the default sort direction. (Values: 'D' or 'A'). // Defaults to 'A' (ascending) if third argument is omitted. this.setDefaultSort = function(defCol, colType) { setDefault(); if (arguments.length > 2) { setDefaultSeq((arguments[2].charAt(0).toUpperCase() == 'D') ? 'D' : 'A'); } // If the specified table is not found by ID or sequence, just return. var tableRef = getTableRefByID(getTableBinding()); if (tableRef) { var cols = tableRef.tHead.rows[0].cells; if (cols) { var colRef = ((defCol <= cols.length) ? cols[defCol - 1] : null); if (colRef) { this.sortColumn(colRef, colType); } } } resetDefault(); return; } // This is the public sorting interface to the TableSorter. It has two required parameters. // If the table is malformed (no ) or there is one row or less, nothing to sort. // // clickedCol - usually passed as 'this', is a pointer to the actual column in the table // colType - specifies the data type to sort. (Values: 'number', 'textcase', // 'textignorecase', 'currency', 'date', 'link', 'url', and 'icon') // More types can be added and new Comparators created if necessary (e.g. hybrids) // Defaults to null (case-inspecific text order). this.sortColumn = function (clickedCol, colType) { if (!init(clickedCol)) { return; } if (clickedCol == getLastCol()) { setLastSeq(); } else if (!isDefault()) { resetLastSeq(); } var rowData = readRows(clickedCol, colType); if (!rowData) { return; } rowData.sort(getComparator()); writeRows(rowData); setIndicator(clickedCol); setLastCol(clickedCol); } }