d5a899c5160879ab7198e4d8a468d0771349fb86 tdreszer Fri Dec 10 09:27:35 2010 -0800 Moved sort routines from hui.js to utils.js and rewrote them so they are not hgTrackUi subtrack list specific. Use table.sortable to make almost any table sortable diff --git src/hg/js/utils.js src/hg/js/utils.js index fb1e182..16254c8 100644 --- src/hg/js/utils.js +++ src/hg/js/utils.js @@ -957,15 +957,619 @@ $("<img src='../images/loading.gif'/>").css("position", "relative").css('left', imgLeft).css('top', imgTop).appendTo(overlay); return loadingId; } function hideLoadingImage(id) { $('#' + id).remove(); } function codonColoringChanged(name) { // Updated disabled state of codonNumbering checkbox based on current value of track coloring select. var val = $("select[name='" + name + ".baseColorDrawOpt'] option:selected").text(); $("input[name='" + name + ".codonNumbering']").attr('disabled', val == "OFF"); } + + +//////////// Sorting //////////// +// Sorting a table by columns relies upon the sortColumns structure + +// The sortColumns structure looks like: +//{ +// char * tags[]; // a list of field names in sort order (e.g. 'cell', 'shortLabel') +// boolean reverse[]; // the sort direction for each sort field +// int cellIxs[]; // The indexes of the columns in the table to be sorted +// boolean useAbbr[]; // Compare on Abbr or on innerHtml? +//} +// These 2 globals are used by setTimeout, so that rows can be hidden while sorting and javascript timeout is less likely +var gSortColumns; +var gTbody + +function tableSort(tbody,fnCompare,sortColumns) +{// Sorts table based upon rules passed in by function reference + // Expects tbody to not sort thead, but could take table + + // The sort routine available is javascript array.sort(), which cannot sort rows directly + // Until we have jQuery >=v1.4, we cannot easily convert tbody.rows[] inot a javascript array + // So we will make our own array, sort, then then walk through the table and reorder + // FIXME: Until better methods are developed, only sortOrder based sorts are supported and fnCompare is obsolete + + // Create array of the primary sort column's text + var cols = new Array(); + var trs = tbody.rows; + var cellIx = sortColumns.cellIxs[0]; + var useAbbr = sortColumns.useAbbr[0]; + $(trs).each(function(i) { + var th = this.cells[cellIx]; + if(useAbbr) + cols.push(th.abbr); + else + cols.push($(th).text()); + }); + + // Sort the array + cols.sort(); + if(sortColumns.reverse[0]) + cols.reverse(); + + // Now reorder the table + for(var cIx=0;cIx<cols.length;cIx++) { + trs = tbody.rows; + var match = false; + for(var rIx=0;rIx<trs.length;rIx++) { + var th = trs[rIx].cells[cellIx]; + if(useAbbr) + match = (th.abbr == cols[cIx]); + else + match = ($(th).text() == cols[cIx]); + if(match) { + tbody.appendChild(tbody.removeChild(trs[rIx])); // Always append to end in order + break; + } + } + } + + /* This is obslete and inefficient code. + var trs=0,moves=0; + var colOrder = new Array(); + var cIx=0; + var trTopIx,trCurIx,trBottomIx=tbody.rows.length - 1; + for(trTopIx=0;trTopIx < trBottomIx;trTopIx++) { + trs++; + var topRow = tbody.rows[trTopIx]; + for(trCurIx = trTopIx + 1; trCurIx <= trBottomIx; trCurIx++) { + var curRow = tbody.rows[trCurIx]; + var compared = fnCompare(topRow,curRow); + if (compared < 0) { + tbody.insertBefore(tbody.removeChild(curRow), topRow); + topRow = curRow; // New top! + moves++; + } + } + } + */ + gTbody=tbody; + gSortColumns=sortColumns; + setTimeout('tableSortFinish(gTbody,gSortColumns)',5); // Avoid javascript timeouts! +} + +function tableSortFinish(tbody,sortColumns) +{// Additional sort cleanup. + // This is in a separate function to allow calling with setTimeout() which will prevent javascript timeouts (I hope) + tableSetPositions(tbody); + if ($(tbody).hasClass('altColors')) + sortedTableAlternateColors(tbody,sortColumns); + $(tbody).show(); +} + +///// Following compare functions are not currentl;y used since sorting rows must be done indirectly +function trCompareColumnInnerHtml(tr1,tr2) // NOT USED and will not be until sorting directly by row is avaiable +{// Compares a set of columns based upon the contents of their first sortField's innerHTML + if (tr1.cells[gSortColumns.cellIxs[0]].innerHTML < tr2.cells[gSortColumns.cellIxs[0]].innerHTML) + return (gSortColumns.reverse[0] ? -1: 1); + else if (tr1.cells[gSortColumns.cellIxs[0]].innerHTML > tr2.cells[gSortColumns.cellIxs[0]].innerHTML) + return (gSortColumns.reverse[0] ? 1: -1); + return 0; +} + +function trCompareColumnAbbr(tr1,tr2) // NOT USED and will not be until sorting directly by row is avaiable +{// Compares a set of columns based upon the contents of their first sortField's abbr + if (tr1.cells[gSortColumns.cellIxs[0]].abbr < tr2.cells[gSortColumns.cellIxs[0]].abbr) + return (gSortColumns.reverse[0] ? -1: 1); + else if (tr1.cells[gSortColumns.cellIxs[0]].abbr > tr2.cells[gSortColumns.cellIxs[0]].abbr) + return (gSortColumns.reverse[0] ? 1: -1); + return 0; +} + +function trCompareByColumn(tr1,tr2) // NOT USED and will not be until sorting directly by row is avaiable +{// Compares a set of columns based upon the contents of their first sortField's abbr + if (sortColumns.useAbbr[0]) + return trCompareColumnAbbr(tr1,tr2); + else + return trCompareColumnInnerHtml(tr1,tr2); +} + +function tableSortByColumns(tbody,sortColumns) +{// Will sort the table based on the abbr values on a set of <TH> colIds + // Expects tbody to not sort thead, but could take table + $(tbody).hide(); + gTbody=tbody; + gSortColumns=sortColumns; + setTimeout('tableSort(gTbody,trCompareByColumn,gSortColumns)',5); // This allows hiding the rows while sorting! +} + +function trAlternateColors(tbody,rowGroup,cellIx) +{// Will alternate colors for visible table rows. + // If cellIx(s) provided then color changes when the column(s) abbr or els innerHtml changes + // If no cellIx is provided then alternates on rowGroup (5= change color 5,10,15,...) + // Expects tbody to not color thead, but could take table + var darker = false; // == false will trigger first row to be change color = darker + + if (arguments.length<3) { // No columns to check so alternate on rowGroup + + if (rowGroup == undefined || rowGroup == 0) + rowGroup = 1; + var curCount = 0; // Always start with a change + $(tbody).find('tr:visible').each( function(i) { + if (curCount == 0 ) { + curCount = rowGroup; + darker = (!darker); + } + //$(this).css('backgroundColor', curColor ); + if (darker) { + $(this).removeClass("bgLevel1"); + $(this).addClass( "bgLevel2"); + } else { + $(this).removeClass("bgLevel2"); + $(this).addClass( "bgLevel1"); + } + curCount--; + }); + + } else { + + var lastContent = "startWithChange"; + var cIxs = new Array(); + for(var aIx=2;aIx<arguments.length;aIx++) { // multiple columns + cIxs[aIx-2] = arguments[aIx]; + } + $(tbody).find('tr:visible').each( function(i) { + curContent = ""; + for(var ix=0;ix<cIxs.length;ix++) { + if (this.cells[cIxs[ix]]) { + curContent += (this.cells[cIxs[ix]].abbr != "" ? + this.cells[cIxs[ix]].abbr : + this.cells[cIxs[ix]].innerHTML ); + } + } + if (lastContent != curContent ) { + lastContent = curContent; + darker = (!darker); + } + //$(this).css('backgroundColor', curColor ); + if (darker) { + $(this).removeClass("bgLevel1"); + $(this).addClass( "bgLevel2"); + } else { + $(this).removeClass("bgLevel2"); + $(this).addClass( "bgLevel1"); + } + }); + } +} + +function sortedTableAlternateColors(tbody) +{ // Will alternate colors based upon sort columns (which may be passed in as second arg, or discovered) +// Expects tbody to not color thead, but could take table + var sortColumns; + if (arguments.length > 1) + sortColumns = arguments[1]; + else { + var table = tbody; + if ($(table).is('tbody')) + table = $(tbody).parent(); + sortColumns = new sortColumnsGetFromTable(table); + } + + if (sortColumns) { + if (sortColumns.cellIxs.length==1) + trAlternateColors(tbody,0,sortColumns.cellIxs[0]); + else if (sortColumns.cellIxs.length==2) + trAlternateColors(tbody,0,sortColumns.cellIxs[0],sortColumns.cellIxs[1]); + else // Three columns is plenty + trAlternateColors(tbody,0,sortColumns.cellIxs[0],sortColumns.cellIxs[1],sortColumns.cellIxs[2]); + } else { + trAlternateColors(tbody,5); // alternates every 5th row + } +} + +function sortOrderFromColumns(sortColumns) +{// Creates the trackDB setting entry sortOrder subGroup1=+ ... from a sortColumns structure + fields = new Array(); + for(var ix=0;ix < sortColumns.cellIxs.length;ix++) { + if (sortColumns.tags[ix] != undefined && sortColumns.tags[ix].length > 0) + fields[ix] = sortColumns.tags[ix] + "=" + (sortColumns.reverse[ix] ? "-":"+"); + else + fields[ix] = sortColumns.cellIxs[ix] + "=" + (sortColumns.reverse[ix] ? "-":"+"); + } + var sortOrder = fields.join(' '); + //warn("sortOrderFromColumns("+sortColumns.cellIxs.length+"):["+sortOrder+"]"); + return sortOrder; +} + +function sortOrderUpdate(table,sortColumns,addSuperscript) +{// Updates the sortOrder in a sortable table + if (addSuperscript == undefined) + addSuperscript = false; + if ($(table).is('tbody')) + table = $(table).parent(); + var tr = $(table).find('tr.sortable')[0]; + if (tr) { + //warn("sortOrderUpdate("+sortColumns.cellIxs.length+")"); + for(cIx=0;cIx<sortColumns.cellIxs.length;cIx++) { + var th = tr.cells[sortColumns.cellIxs[cIx]]; + $(th).each(function(i) { + // First remove old sort classes + var classList = $( this ).attr("class").split(" "); + if (classList.length < 2) // assertable + return; + classList = aryRemove(classList,"sortable"); + while( classList.length > 0 ) { + var class = classList.pop(); + if (class.indexOf("sort") == 0) + $(this).removeClass(class); + } + + // Now add current sort classes + $(this).addClass("sort"+(cIx+1)); + if (sortColumns.reverse[cIx]) + $(this).addClass("sortRev"); + + // update any superscript + sup = $(this).find('sup')[0]; + if (sup || addSuperscript) { + var content = (sortColumns.reverse[cIx] == false ? "↓":"↑"); + + if (sortColumns.cellIxs.length>1) { // Number only if more than one + if (cIx < 5) // Show numbering and direction only for the first 5 + content += (cIx+1); + else + content = ""; + } + + if (sup) + sup.innerHTML = content; + else + $(th).append("<sup>"+content+"</sup>"); + } + }); + } + // There may be a hidden input that gets updated to the cart + var inp = $(tr).find('input.sortOrder')[0]; + if (inp) + $(inp).val(sortOrderFromColumns(sortColumns)); + } +} + +function sortOrderFromTr(tr) +{// Looks up the sortOrder input value from a *.sortable header row of a sortable table + var inp = $(tr).find('input.sortOrder')[0]; + if (inp) + return $(inp).val(); + else { + // create something like "cellType=+ rep=+ protocol=+ treatment=+ factor=+ view=+" + var fields = new Array(); + var cells = $(tr).find('th.sortable'); + $(cells).each(function (i) { + var classList = $( this ).attr("class").split(" "); + if (classList.length < 2) // assertable + return; + classList = aryRemove(classList,"sortable"); + var reverse = false; + var sortIx = -1; + while( classList.length > 0 ) { + var class = classList.pop(); + if (class.indexOf("sort") == 0) { + if (class == "sortRev") + reverse = true; + else { + class = class.substring(4); // clip off the "sort" portion + var ix = parseInt(class); + if (ix != NaN) { + sortIx = ix; + } + } + } + } + if (sortIx >= 0) { + if (this.id != undefined && this.id.length > 0) + fields[sortIx] = this.id + "=" + (reverse ? "-":"+"); + else + fields[sortIx] = this.cellIndex + "=" + (reverse ? "-":"+"); + } + }); + if (fields.length > 0) { + if (fields[0] == undefined) + fields.shift(); // 1 based sort ix and 0 based fields ix + return fields.join(' '); + } + } + return ""; +} +function sortColumnsGetFromSortOrder(sortOrder) +{// Creates sortColumns struct (without cellIxs[]) from a trackDB.sortOrder setting string + this.tags = new Array(); + this.reverse = new Array(); + var fields = sortOrder.split(" "); // sortOrder looks like: "cell=+ factor=+ view=+" + while(fields.length > 0) { + var pair = fields.shift().split("="); // Take first and split into + if (pair.length == 2) { + this.tags.push(pair[0]); + this.reverse.push(pair[1] != '+'); + } + } +} +function sortColumnsGetFromTr(tr,silent) +{// Creates a sortColumns struct from the entries in the 'tr.sortable' heading row of a sortable table + this.inheritFrom = sortColumnsGetFromSortOrder; + var sortOrder = sortOrderFromTr(tr); + if (sortOrder.length == 0 && silent == undefined) { + warn("Unable to obtain sortOrder from sortable table."); // developer needs to know something is wrong + return; + } + + this.inheritFrom(sortOrder); + // Add an additional array + this.cellIxs = new Array(); + this.useAbbr = new Array(); + var ths = $(tr).find('th.sortable'); + for(var tIx=0;tIx<this.tags.length;tIx++) { + for(ix=0; ix<ths.length; ix++) { + if (ths[ix].id != undefined && ths[ix].id == this.tags[tIx]) + this.cellIxs[tIx] = ths[ix].cellIndex; + else if (ths[ix].cellIndex == this.tags[tIx]) + this.cellIxs[tIx] = ths[ix].cellIndex; + this.useAbbr = (ths[ix].abbr.length > 0); + } + } + if (this.cellIxs.length == 0 && silent == undefined) { + warn("Unable to find any sortOrder.cells for sortable table. ths.length:"+ths.length + " tags.length:"+this.tags.length + " sortOrder:["+sortOrder+"]"); + return; + } +} + +function sortColumnsGetFromTable(table) +{// Creates a sortColumns struct from the contents of a 'table.sortable' + this.inheritNow = sortColumnsGetFromTr; + var tr = $(table).find('tr.sortable')[0]; + //if (tr == undefined && debug) warn("Couldn't find 'tr.sortable' rows:"+table.rows.length); + this.inheritNow(tr); +} + + +function _tableSortOnButtonPressEncapsulated(anchor) +{// Updates the sortColumns struct and sorts the table when a column header has been pressed + // If the current primary sort column is pressed, its direction is toggled then the table is sorted + // If a secondary sort column is pressed, it is moved to the primary spot and sorted in fwd direction + var th=$(anchor).closest('th')[0]; // Note that anchor is <a href> within th, not th + var tr=$(th).parent(); + var theOrder = new sortColumnsGetFromTr(tr); + var oIx = th.cellIndex; + for(oIx=0;oIx<theOrder.cellIxs.length;oIx++) { + if (theOrder.cellIxs[oIx] == th.cellIndex) + break; + } + if (oIx == theOrder.cellIxs.length) { + warn("Failure to find '"+th.id+"' in sort columns."); // Developer must be warned that something is wrong with sortable table setup + return; + } + // assert(th.id == theOrder.tags[oIx] || th.id == undefined); + if (oIx > 0) { // Need to reorder + var newOrder = new sortColumnsGetFromTr(tr); + var nIx=0; // button pushed puts this 'tagId' column first in new order + newOrder.tags[nIx] = theOrder.tags[oIx]; + newOrder.reverse[nIx] = false; // When moving to the first position sort forward + newOrder.cellIxs[nIx] = theOrder.cellIxs[oIx]; + for(var ix=0;ix<theOrder.cellIxs.length;ix++) { + if (ix != oIx) { + nIx++; + newOrder.tags[nIx] = theOrder.tags[ix]; + newOrder.reverse[nIx] = theOrder.reverse[ix]; + newOrder.cellIxs[nIx] = theOrder.cellIxs[ix]; + } + } + theOrder = newOrder; + } else { // if (oIx == 0) { // need to reverse directions + theOrder.reverse[oIx] = (theOrder.reverse[oIx] == false); + } + var table=$(tr).closest("table.sortable")[0]; + if (table) { // assertable + sortOrderUpdate(table,theOrder); // Must update sortOrder first! + var tbody = $(table).find("tbody.sortable")[0]; + //if (tbody == undefined && debug) warn("Couldn't find 'tbody.sortable' 5"); + tableSortByColumns(tbody,theOrder); + } + return; + +} + +function tableSortOnButtonPress(anchor,tagId) +{ + var table = $( anchor ).closest("table.sortable")[0]; + if (table) { + waitOnFunction( _tableSortOnButtonPressEncapsulated, anchor, tagId); + } + return false; // called by link so return false means don't try to go anywhere +} + +function tableSortUsingSortColumns(table) // NOT USED +{// Sorts a table body based upon the marked columns + var columns = new sortColumnsGetFromTable(table); + tbody = $(table).find("tbody.sortable")[0]; + if (tbody) + tableSortByColumns(tbody,columns); +} + +function hintOverSortableColumnHeader(th) // NOT USED +{// Upodates the sortColumns struct and sorts the table when a column headder has been pressed + //th.title = "Click to make this the primary sort column, or toggle direction"; + //var tr=th.parentNode; + //th.title = "Current Sort Order: " + sortOrderFromTr(tr); +} + +function tableSetPositions(table) +{// Sets the value for the input.trPos of a table row. Typically this is a "priority" for a track + // This gets called by sort or dragAndDrop in order to allow the new order to affect hgTracks display + var inputs = $(table).find("input.trPos"); + $( inputs ).each( function(i) { + var tr = $( this ).closest('tr')[0]; + $( this ).val( $(tr).attr('rowIndex') ); + }); +} + +///// Following functions are for Sorting by priority +function trFindPosition(tr) +{ +// returns the position (*.priority) of a sortable table row + var inp = $(tr).find('input.trPos')[0]; + if (inp) + return $(inp).val(); + return 999999; +} + +function trComparePriority(tr1,tr2) // UNUSED FUNCTION +{ +// Compare routine for sorting by *.priority + var priority1 = trFindPosition(tr1); + var priority2 = trFindPosition(tr2); + return priority2 - priority1; +} + +function tablesSortAtStartup() +{// Called at startup if you want javascript to initialize and sort all your class='sortable' tables + // IMPORTANT: This function WILL ONLY sort by first column. + // If there are multiple sort columns, please presort the list for accurtacy!!! + var tables = $("table.sortable"); + $(tables).each(function(i) { + sortTableInitialize(this,true); // Will initialize superscripts + tableSortUsingSortColumns(this); + }); +} + +function sortTableInitialize(table,addSuperscript,altColors) +{// Called if you want javascript to initialize your class='sortable' table. + // A sortable table requires: + // TABLE.sortable: TABLE class='sortable' containing a THEAD header and sortable TBODY filled with the rows to sort. + // THEAD.sortable: (NOTE: created if not found) The THEAD can contain multiple rows must contain: + // TR.sortable: exactly 1 header TH (table row) class='sortable' which will declare the sort columnns: + // TH.sortable: 1 or more TH (table column headers) with class='sortable sort1 [sortRev]' (or sort2, sort3) declaring sort order and whether reversed + // e.g. <TH id='factor' class='sortable sortRev sort3' nowrap>...</TH> (this means that factor is currently the third sort column and reverse sorted) + // (NOTE: If no TH.sortable is found, then every th in the TR.sortable will be converted for you and will be in sort1,2,3 order.) + // ONCLICK: Each TH.sortable must call tableSortOnButtonPress(this) directly or indirectly in the onclick event : + // e.g. <TH id='factor' class='sortable sortRev sort3' nowrap title='Sort list on this column' onclick="return tableSortOnButtonPress(this);"> + // (NOTE: If no onclick function is found in a TH.sortable, then it will automatically be added.) + // SUP: Each TH.sortable *may* contain a <sup> which will be filled with an up or down arrow and the column's sort order: e.g. <sup>↓2</sup> + // (NOTE: If no sup is found but addSuperscript is requested, then they will be added.) + // TBODY.sortable: (NOTE: created if not found) The TBODY class='sortable' contains the table rows that get sorted: + // TBODY->TR & ->TD: Each row contains a TD for each sortable column. The innerHTML (entire contents) of the cell will be used for sorting. + // TRICK: You can use the 'abbr' field to subtly alter the sortable contents. Otherwise sorts on td contents ($(td).text()). + // Use the abbr field to make case-insensitive sorts or force exceptions to alpha-text order (e.g. ZCTRL vs Control forcing controls to bottom) + // e.g. <TD id='wgEncodeBroadHistoneGm12878ControlSig_factor' nowrap abbr='ZCTRL' align='left'>Control</TD> + // IMPORTANT: You must add abbr='use' to the TH.sortable definitions. + // Finally if you want the tableSort to alternate the table row colors (using #FFFEE8 and #FFF9D2) then TBODY.sortable should also have class 'altColors' + // NOTE: This class can be added by using the altColors option to this function + // + // PRESERVING TO CART: To send the sort column on a form 'submit', the header tr (TR.sortable) needs a named hidden input of class='sortOrder' as: + // e.g.: <INPUT TYPE=HIDDEN NAME='wgEncodeBroadHistone.sortOrder' class='sortOrder' VALUE="factor=- cell=+ view=+"> + // AND each sortable column header (TH.sortable) must have id='{name}' which is the name of the sortable field (e.g. 'factor', 'shortLabel') + // The value preserves the column sort order and direction based upon the id={name} of each sort column. + // In the example, while 'cell' may be the first column, the table is currently reverse ordered by 'factor', then by cell and view. + // And to send the sorted row orders on form 'submit', each TBODY->TR will need a named hidden input field of class='trPos': + // e.g. <INPUT TYPE=HIDDEN NAME='wgEncodeHaibTfbsA549ControlPcr2xDexaRawRep1.priority' class='trPos' VALUE="2"> + // A reason to preserve the order in ther cart is if the order will affect other cgis. For instance: sort subtracks and see that order in the hgTracks image. + + if ($(table).hasClass('sortable') == false) { + warn('Table is not sortable'); + return; + } + var tr = $(table).find('tr.sortable')[0]; + if(tr == undefined) { + tr = $(table).find('tr')[0]; + if(tr == undefined) { + warn('Sortable table has no rows'); + return; + } + $(tr).addClass('sortable'); + //warn('Made first row tr.sortable'); + } + if ($(table).find('tr.sortable').length != 1) { + warn('sortable table contains more than 1 header row declaring sort columns.'); + return; + } + + // If not TBODY is found, then create, wrapping all but those already in a thead + tbody = $(table).find('tbody')[0]; + if(tbody == undefined) { + trs = $(table).find('tr').not('thead tr'); + $(trs).wrapAll("<TBODY class='sortable' />") + tbody = $(table).find('tbody')[0]; + //warn('Wrapped all trs not in thead.sortable in tbody.sortable'); + } + if ($(tbody).hasClass('sortable') == false) { + $(tbody).addClass('sortable'); + //warn('Added sortable class to tbody'); + } + if(altColors != undefined && $(tbody).hasClass('altColors') == false) { + $(tbody).addClass('altColors'); + //warn('Added altColors class to tbody.sortable'); + } + $(tbody).hide(); + + // If not THEAD is found, then create, wrapping first row. + thead = $(table).find('thead')[0]; + if(thead == undefined) { + $(tr).wrapAll("<THEAD class='sortable' />") + thead = $(table).find('thead')[0]; + $(thead).insertBefore(tbody); + //warn('Wrapped tr.sortable with thead.sortable'); + } + if ($(thead).hasClass('sortable') == false) { + $(thead).addClass('sortable'); + //warn('Added sortable class to thead'); + } + + var sortColumns = new sortColumnsGetFromTr(tr,"silent"); + if (sortColumns == undefined || sortColumns.cellIxs.length == 0) { + // could mark all columns as sortable! + $(tr).find('th').each(function (ix) { + $(this).addClass('sortable'); + $(this).addClass('sort'+(ix+1)); + //warn("Added class='sortable sort"+(ix+1)+"' to th:"+this.innerHTML); + }); + sortColumns = new sortColumnsGetFromTr(tr,"silent"); + if (sortColumns == undefined || sortColumns.cellIxs.length == 0) { + warn("sortable table's header row contains no sort columns."); + return; + } + } + // Can wrap all columnn headers with link + $(tr).find("th.sortable").each(function (ix) { + //if ( $(this).queue('click').length == 0 ) { + if ( $(this).attr('onclick') == undefined ) { + $(this).click( function () { tableSortOnButtonPress(this);} ); + } + if ( $(this).attr('title').length == 0) { + $(this).attr('title',"Sort list on '" + $(this).text() + "'." ); + } + }) + // Now update all of those cells + sortOrderUpdate(table,sortColumns,addSuperscript); + + // Alternate colors if requested + if(altColors != undefined) + sortedTableAlternateColors(tbody); + + // Finally, make visible + $(tbody).show(); +} +