Author: CrawfordCurrie Date: 2007-10-20 08:02:25 -0500 (Sat, 20 Oct 2007) New Revision: 15355 Added: twiki/branches/MAIN/twikiplugins/EditRowPlugin/pub/TWiki/EditRowPlugin/TableSort_src.js Modified: twiki/branches/MAIN/twikiplugins/EditRowPlugin/data/TWiki/EditRowPlugin.txt twiki/branches/MAIN/twikiplugins/EditRowPlugin/lib/TWiki/Plugins/EditRowPlugin.pm twiki/branches/MAIN/twikiplugins/EditRowPlugin/lib/TWiki/Plugins/EditRowPlugin/Table.pm twiki/branches/MAIN/twikiplugins/EditRowPlugin/lib/TWiki/Plugins/EditRowPlugin/TableCell.pm twiki/branches/MAIN/twikiplugins/EditRowPlugin/pub/TWiki/EditRowPlugin/TableSort.js Log: Item4861: rework the rules for associating tag with adjacent table Item4862: avoid expanding TWiki variables on edit Modified: twiki/branches/MAIN/twikiplugins/EditRowPlugin/data/TWiki/EditRowPlugin.txt =================================================================== --- twiki/branches/MAIN/twikiplugins/EditRowPlugin/data/TWiki/EditRowPlugin.txt 2007-10-19 23:52:03 UTC (rev 15354) +++ twiki/branches/MAIN/twikiplugins/EditRowPlugin/data/TWiki/EditRowPlugin.txt 2007-10-20 13:02:25 UTC (rev 15355) @@ -37,8 +37,13 @@ Works like the !EditTablePlugin (it uses exactly the same =%EDITTABLE= tags) and you can use it to edit the entire table, or just one row at a time. -Add =%<nop>EDITTABLE{...}%= on the line before the first row of an existing table to make it editable. Parameters: +A =%<nop>EDITTABLE{...}%= in the topic will be associated with the next table +in the topic. If there is no existing table, or another +=%<nop>EDITTABLE{...}%= is seen before the next table, then a new table will +be created. +Parameters: + * ==format== - The format of the cells in a row of the table. The format is defined like a table row, where the cell data specify the type for each cell. For example, =format="| text,16 | label |"=. Cells can be any of @@ -217,6 +222,7 @@ | Copyright: | © 2007 Wind<nop>River Inc. | | License: | [[http://www.gnu.org/licenses/gpl.html][GPL (Gnu General Public License)]] | | Change History: | <!-- versions below in reverse order --> | +| 20 Oct 2007 | Bugs:Item4853: support %EDITCELL Bugs:Item4861: rework the rules for associating tag with adjacent table Bugs:Item4862: avoid expanding TWiki variables on edit | | 18 Oct 2007 | Bugs:Item4834: added automatic save on row add Bugs:Item4853: added %EDITCELL support Bugs:Item4651: fixed validation of date change | | 26 Sep 2007 | Bugs:Item4696 Keep table in full-table edit mode after a row is added Bugs:Item4651 add JS to handle navigating away with pending changes (requires BehaviourContrib) | | 10 Sep 2007 | Bugs:Item4552 Can use row buttons to delete the header, even though headderrows is set Bugs:Item4565 "row" parameter not supported Bugs:Item4567 header parameter not correctly handled Bugs:Item4602 Header row gets eaten | Modified: twiki/branches/MAIN/twikiplugins/EditRowPlugin/lib/TWiki/Plugins/EditRowPlugin/Table.pm =================================================================== --- twiki/branches/MAIN/twikiplugins/EditRowPlugin/lib/TWiki/Plugins/EditRowPlugin/Table.pm 2007-10-19 23:52:03 UTC (rev 15354) +++ twiki/branches/MAIN/twikiplugins/EditRowPlugin/lib/TWiki/Plugins/EditRowPlugin/Table.pm 2007-10-20 13:02:25 UTC (rev 15355) @@ -23,8 +23,9 @@ # Returns an array of lines, with those lines that represent editable # tables plucked out and replaced with references to table objects sub parseTables { - my ($text, $topic, $web, $meta, $urps) = @_; + my ($text, $web, $topic, $meta, $urps) = @_; my $active_table = undef; + my $hasRows = 0; my @tables; my $nTables = 0; my $disable = 0; @@ -51,6 +52,9 @@ $line = "$openRow$line"; $openRow = undef; } + + # Process an EDITTABLE. The tag will be associated with the + # next table encountered in the topic. if (!$disable && $line =~ s/(%EDITTABLE{(.*?)}%)// ) { my $spec = $1; my $attrs = new TWiki::Attrs($2); @@ -104,10 +108,12 @@ } $active_table = new TWiki::Plugins::EditRowPlugin::Table( - $nTables, 1, $spec, $attrs, $_[2], $_[1]); + $nTables, 1, $spec, $attrs, $web, $topic); push(@tables, $active_table); + $hasRows = 0; next; } + elsif (!$disable && $line =~ /^\s*\|/) { if ($line =~ s/\\$//) { # Continuation @@ -124,38 +130,51 @@ my $attrs => new TWiki::Attrs(''); $active_table = new TWiki::Plugins::EditRowPlugin::Table( - $nTables, 0, $line, $attrs, $_[2], $_[1]); + $nTables, 0, $line, $attrs, $web, $topic); push(@tables, $active_table); } - # Note use of -1 on the split so we don't lose empty columns - my @cols = split(/\|/, $line, -1); + # Note use of LIMIT=-1 on the split so we don't lose empty columns + my @cols; + if (length($line)) { + @cols = split(/\|/, $line, -1); + } else { + # Splitting an EXPR that evaluates to the empty string always + # returns the empty list, regardless of the LIMIT specified. + push(@cols, ''); + } my $row = new TWiki::Plugins::EditRowPlugin::TableRow( $active_table, scalar(@{$active_table->{rows}}) + 1, $precruft, $postcruft, \@cols); push(@{$active_table->{rows}}, $row); + $hasRows = 1; next; } - $active_table = undef; + elsif (!$disable && $hasRows) { + # associated table has been terminated + $active_table = undef; + } + push(@tables, $line); } - # Legacy: add a header if the header param is defined and the table - # has no rows. foreach my $t (@tables) { - if (ref($t) eq 'TWiki::Plugins::EditRowPlugin::Table' && - !scalar(@{$t->{rows}}) && - defined($t->{attrs}->{header})) { - my $line = $t->{attrs}->{header}; - my $precruft = ''; - $precruft = $1 if $line =~ s/^(\s*\|)//; - my $postcruft = ''; - $postcruft = $1 if $line =~ s/(\|\s*)$//; - my @cols = split(/\|/, $line, -1); - my $row = new TWiki::Plugins::EditRowPlugin::TableRow( - $t, 1, $precruft, $postcruft, \@cols); - push(@{$t->{rows}}, $row); + if (UNIVERSAL::isa($t, 'TWiki::Plugins::EditRowPlugin::Table')) { + if (!scalar(@{$t->{rows}}) && + defined($t->{attrs}->{header})) { + # Legacy: add a header if the header param is defined and + # the table has no rows. + my $line = $t->{attrs}->{header}; + my $precruft = ''; + $precruft = $1 if $line =~ s/^(\s*\|)//; + my $postcruft = ''; + $postcruft = $1 if $line =~ s/(\|\s*)$//; + my @cols = split(/\|/, $line, -1); + my $row = new TWiki::Plugins::EditRowPlugin::TableRow( + $t, 1, $precruft, $postcruft, \@cols); + push(@{$t->{rows}}, $row); + } } } Modified: twiki/branches/MAIN/twikiplugins/EditRowPlugin/lib/TWiki/Plugins/EditRowPlugin/TableCell.pm =================================================================== --- twiki/branches/MAIN/twikiplugins/EditRowPlugin/lib/TWiki/Plugins/EditRowPlugin/TableCell.pm 2007-10-19 23:52:03 UTC (rev 15354) +++ twiki/branches/MAIN/twikiplugins/EditRowPlugin/lib/TWiki/Plugins/EditRowPlugin/TableCell.pm 2007-10-20 13:02:25 UTC (rev 15355) @@ -59,7 +59,8 @@ sub renderForDisplay { my ($this, $colDefs, $isHeader) = @_; my $colDef = $colDefs->[$this->{number} - 1] || $defCol; - my $text = $this->{text} || '-'; + my $text = $this->{text}; + $text = '-' unless defined($text); if (!$this->{isHeader} && !$this->{isFooter}) { if ($colDef->{type} eq 'row') { Modified: twiki/branches/MAIN/twikiplugins/EditRowPlugin/lib/TWiki/Plugins/EditRowPlugin.pm =================================================================== --- twiki/branches/MAIN/twikiplugins/EditRowPlugin/lib/TWiki/Plugins/EditRowPlugin.pm 2007-10-19 23:52:03 UTC (rev 15354) +++ twiki/branches/MAIN/twikiplugins/EditRowPlugin/lib/TWiki/Plugins/EditRowPlugin.pm 2007-10-20 13:02:25 UTC (rev 15355) @@ -13,6 +13,7 @@ $NO_PREFS_IN_TOPIC = 1; my $pluginName = 'EditRowPlugin'; +my $USE_SRC = ''; sub initPlugin { my( $topic, $web, $user, $installWeb ) = @_; @@ -27,23 +28,50 @@ TWiki::Func::registerRESTHandler('save', \&save); $headed = 0; + if (TWiki::Func::getPreferencesValue('EDITROWPLUGIN_DEBUG')) { + $USE_SRC = '_src'; + } + # Plugin correctly initialized return 1; } -# Handler run when viewing a topic +sub beforeCommonTagsHandler { + my ($text, $topic, $web, $meta) = @_; + + if (_process($text, $web, $topic, $meta)) { + $_[0] = $text; + } +} + +# The handler has to be run from both beforeCommonTagsHandler and +# commonTagsHandler, because beforeCommonTagsHandler allows us to +# process tables before TWiki variables in their data are expanded, +# while the second call allos us to handle tables that have been +# included from other topics. Both handlers only fire when the topic +# text contains %EDITTABLE, thus constraining the problem. sub commonTagsHandler { - # my ( $text, $topic, $web, $included, $meta ) = @_; + my ($text, $topic, $web, $included, $meta) = @_; + if (_process($text, $web, $topic, $meta)) { + $_[0] = $text; + } +} + +sub _process { + my ($text, $web, $topic, $meta) = @_; + + return 0 unless $text =~ /%EDITTABLE{.*}%/; + my $context = TWiki::Func::getContext(); - return unless $context->{view}; + return 0 unless $context->{view}; unless ($headed) { - $headed = 1; + $headed = 1; # recursion block my $header = '<script type="text/javascript" src="'; $header .= TWiki::Func::getPubUrlPath().'/'. TWiki::Func::getTwikiWebname(). - '/EditRowPlugin/TableSort.js"></script>'; + '/EditRowPlugin/TableSort$USE_SRC.js"></script>'; $header .= <<STYLE; <style> .erpSort { @@ -55,18 +83,12 @@ } my $query = TWiki::Func::getCgiQuery(); - return unless $query; + return 0 unless $query; - # SMELL: hack to get around not having a proper topic object model - my $meta = $_[4] || $context->{can_render_meta}; - return unless $meta; + return 0 if TWiki::Func::getPreferencesFlag('EDITROWPLUGIN_DISABLE'); - return if TWiki::Func::getPreferencesFlag('EDITROWPLUGIN_DISABLE'); - - my ($topic, $web) = ($_[1], $_[2]); - require TWiki::Plugins::EditRowPlugin::Table; - return if $@; + return 0 if $@; my $vars = $query->Vars(); my $urps = {}; @@ -74,13 +96,11 @@ $urps->{$key} = $value if $key =~ /^erp_/; } - my $endsWithNewline = ($_[0] =~ /\n$/)?1:0; + my $endsWithNewline = ($text =~ /\n$/) ? 1 : 0; my $content = TWiki::Plugins::EditRowPlugin::Table::parseTables( - @_, $urps); + $text, $web, $topic, $meta, $urps); - $_[0] =~ s/\\\n//gs; - $urps->{erp_active_table} ||= 0; $urps->{erp_active_row} ||= 0; @@ -93,7 +113,7 @@ # Without change access, there is no way you can edit. if (!TWiki::Func::checkAccessPermission( 'CHANGE', TWiki::Func::getWikiName(), - $_[0], $_[1], $_[2], $meta)) { + $text, $topic, $web, $meta)) { $displayOnly = 1; } @@ -148,7 +168,7 @@ HEAD } TWiki::Func::addToHEAD('EDITROWPLUGIN_JSVETO', <<HEAD); -<script type='text/javascript' src='$pub/$web/EditRowPlugin/twiki.js'></script> +<script type='text/javascript' src='$pub/$web/EditRowPlugin/twiki$USE_SRC.js'></script> HEAD }; if ($@) { @@ -156,7 +176,11 @@ } } - $_[0] = join("\n", @$content).($endsWithNewline?"\n":'') if $hasTables; + if ($hasTables) { + $_[0] = join("\n", @$content).($endsWithNewline?"\n":''); + return 1; + } + return 0; } # Replace content with a marker to prevent it being munged by TWiki Modified: twiki/branches/MAIN/twikiplugins/EditRowPlugin/pub/TWiki/EditRowPlugin/TableSort.js =================================================================== --- twiki/branches/MAIN/twikiplugins/EditRowPlugin/pub/TWiki/EditRowPlugin/TableSort.js 2007-10-19 23:52:03 UTC (rev 15354) +++ twiki/branches/MAIN/twikiplugins/EditRowPlugin/pub/TWiki/EditRowPlugin/TableSort.js 2007-10-20 13:02:25 UTC (rev 15355) @@ -1,364 +1,49 @@ -// Code from http://www.brainjar.com/ -// License GPL 2 -// Extended by Crawford Currie Copyright (C) 2007 http://c-dot.co.uk -//----------------------------------------------------------------------------- -// sortTable(id, col, rev) -// -// tblEl - an element anywhere in the table -// rev - If true, the column is sorted in reverse (descending) order -// initially. -// headrows - number of rows in table header (unsorted) -// footrows number of rows in table footer (unsorted) -// -// Automatically detects and sorts data types; numbers and dates - -function sortTable(el, rev, headrows, footrows) { - - // Search up to find the containing TD or TH - var tdEl = el; - while (tdEl != null && - tdEl.tagName.toUpperCase() != "TD" && - tdEl.tagName.toUpperCase() != "TH") { - tdEl = tdEl.parentNode; - } - if (tdEl == null) { - return; - } - - // Continue up to the TR - var trEl = tdEl; - while (trEl != null && trEl.tagName.toUpperCase() != "TR") { - trEl = trEl.parentNode; - } - if (trEl == null) { - return; - } - - // Continue to search up to find the containing table - var tblEl = trEl; - while (tblEl != null && tblEl.tagName.toUpperCase() != "TABLE") { - tblEl = tblEl.parentNode; - } - if (tblEl == null) { - return; - } - - // Now work out the column index - var col = 0; - var i = 0; - while (i < trEl.childNodes.length) { - if (trEl.childNodes[i].tagName != null) { - if (trEl.childNodes[i] == tdEl) - break; - col++; - } - i++; - } - if (i == trEl.childNodes.length) { - return null; - } - - // Find the TBODY, and work out the number of rows - var tblBody = null; - var gotBody = false; - for (var i = 0; i < tblEl.childNodes.length; i++) { - var tn = tblEl.childNodes[i].tagName; - if (tn != null) - tn = tn.toUpperCase(); - if (tn == "THEAD") { - // Bloody TablePlugin generates footer rows in the THEAD! - if (gotBody) - footrows -= tblEl.childNodes[i].rows.length; - else - headrows -= tblEl.childNodes[i].rows.length; - } - else if (tn == "TBODY") { - tblBody = tblEl.childNodes[i]; - gotBody = true; - } - else if (tn == "TFOOT") { - footrows -= tblEl.childNodes[i].rows.length; - } - } - - // The first time this function is called for a given table, set up an - // array of reverse sort flags. - if (tblEl.reverseSort == null) { - tblEl.reverseSort = new Array(); - // Also, assume the team name column is initially sorted. - tblEl.lastColumn = 1; - } - - // If this column has not been sorted before, set the initial sort direction. - if (tblEl.reverseSort[col] == null) - tblEl.reverseSort[col] = rev; - - // If this column was the last one sorted, reverse its sort direction. - if (col == tblEl.lastColumn) - tblEl.reverseSort[col] = !tblEl.reverseSort[col]; - - // Remember this column as the last one sorted. - tblEl.lastColumn = col; - - // Set the table display style to "none" - necessary for Netscape 6 - // browsers. - var oldDsply = tblEl.style.display; - tblEl.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; - - var start = (headrows > 0 ? headrows : 0); - var end = tblBody.rows.length - (footrows > 0 ? footrows : 0); - for (i = start; i < end - 1; i++) { - - // Assume the current row has the minimum value. - minIdx = i; - minVal = getTextValue(tblBody.rows[i].cells[col]); - - // Search the rows that follow the current one for a smaller value. - for (j = i + 1; j < end; j++) { - testVal = getTextValue(tblBody.rows[j].cells[col]); - cmp = compareValues(minVal, testVal); - // Negate the comparison result if the reverse sort flag is set. - if (tblEl.reverseSort[col]) - 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; - } - } - - // By now, we have the row with the smallest value. Remove it from the - // table and insert it before the current row. - if (minIdx > i) { - tmpEl = tblBody.removeChild(tblBody.rows[minIdx]); - tblBody.insertBefore(tmpEl, tblBody.rows[i]); - } - } - - // Make it look pretty. - // Not used, but kept for when TablePlugin uses classes. - //makePretty(tblBody, col); - - // Restore the table's display style. - tblEl.style.display = oldDsply; - - return false; -} - -//----------------------------------------------------------------------------- -// Functions to get and compare values during a sort. -//----------------------------------------------------------------------------- - -// 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; -} - -function getTextValue(el) { - - if (!el) - return ''; - - 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 += getTextValue(el.childNodes[i]); - - return normalizeString(s); -} - -var months = new Array(); -months["jan"] = 0; -months["feb"] = 1; -months["mar"] = 2; -months["apr"] = 3; -months["may"] = 4; -months["jun"] = 5; -months["jul"] = 6; -months["aug"] = 7; -months["sep"] = 8; -months["oct"] = 9; -months["nov"] = 10; -months["dec"] = 11; - -// "31 Dec 2003 - 23:59", -// "31-Dec-2003 - 23:59", -// "31/Dec/2003 - 23:59", -// "31/Dec/03 - 23:59", -var TWIKIDATE = new RegExp( - "^\\s*([0-3]?[0-9])[-\\s/]*" + - "(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)" + - "[-\\s/]*([0-9]{2}[0-9]{2}?)" + - "(\\s*(-\\s*)?([0-9]{2}):([0-9]{2}))?", "i"); -var RFC8601 = new RegExp( - "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" + - "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" + - "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?"); - -// Convert date/time to epoch seconds. Return 0 if a valid date -// wasn't found. -function s2d(s) { - // TWiki date/time - var d = s.match(TWIKIDATE); - if (d != null) { - var nd = new Date(); - nd.setDate(Number(d[1])); - nd.setMonth(months[d[2].toLowerCase()]); - if (d[3].length == 2) { - var year = d[3]; - // I'll be dead by the time this fails :-) - if (year > 59) - year += 1900; - else - year += 2000; - nd.setYear(year); - } else - nd.setYear(d[3]); - if (d[6] != null && d[6].length) - nd.setHours(d[6]); - if (d[7] != null && d[7].length) - nd.setMinutes(d[7]); - return nd.getTime(); - } - - // RFC8601 date/time - // (Paul Sowden, http://delete.me.uk/2005/03/iso8601.html) - var d = s.match(RFC8601); - if (d == null) - return 0; - - var offset = 0; - var date = new Date(d[1], 0, 1); - - if (d[3]) date.setMonth(d[3] - 1); - if (d[5]) date.setDate(d[5]); - if (d[7]) date.setHours(d[7]); - if (d[8]) date.setMinutes(d[8]); - if (d[10]) date.setSeconds(d[10]); - if (d[12]) date.setMilliseconds(Number("0." + d[12]) * 1000); - if (d[14]) { - offset = (Number(d[16]) * 60) + Number(d[17]); - offset *= ((d[15] == '-') ? 1 : -1); - } - - offset -= date.getTimezoneOffset(); - time = (Number(date) + (offset * 60 * 1000)); - return time; -} - -function compareValues(v1, v2) { - // if the values are both dates, convert them to epoch seconds - var d1 = s2d(v1); - if (d1) { - var d2 = s2d(v2); - if (d2) { - v1 = d1; - v2 = d2; - } - } else { - // If the values are numeric, convert them to floats. - var f1 = parseFloat(v1); - if (!isNaN(f1)) { - var f2 = parseFloat(v2); - if (!isNaN(f2)) { - v1 = f1; - v2 = f2; - } - } - } - // Compare the two values. - if (v1 == v2) - return 0; - if (v1 > v2) - return 1; - return -1; -} - -// Regular expressions for normalizing white space. -var whtSpEnds = new RegExp("^\\s*|\\s*$", "g"); -var whtSpMult = new RegExp("\\s\\s+", "g"); - -function normalizeString(s) { - - s = s.replace(whtSpMult, " "); // Collapse any multiple whites space. - s = s.replace(whtSpEnds, ""); // Remove leading or trailing white space. - - return s; -} - -//----------------------------------------------------------------------------- -// Functions to update the table appearance after a sort. -// Not used, but kept for when TablePlugin uses classes. -//----------------------------------------------------------------------------- - -/* -// Style class names. -var rowClsNm = "alternateRow"; -var colClsNm = "sortedColumn"; - -// Regular expressions for setting class names. -var rowTest = new RegExp(rowClsNm, "gi"); -var colTest = new RegExp(colClsNm, "gi"); - -function makePretty(tblEl, col) { - - var i, j; - var rowEl, cellEl; - - // Set style classes on each row to alternate their appearance. - for (i = 0; i < tblEl.rows.length; i++) { - rowEl = tblEl.rows[i]; - rowEl.className = rowEl.className.replace(rowTest, ""); - if (i % 2 != 0) - rowEl.className += " " + rowClsNm; - rowEl.className = normalizeString(rowEl.className); - // Set style classes on each column (other than the name column) to - // highlight the one that was sorted. - for (j = 2; j < tblEl.rows[i].cells.length; j++) { - cellEl = rowEl.cells[j]; - cellEl.className = cellEl.className.replace(colTest, ""); - if (j == col) - cellEl.className += " " + colClsNm; - cellEl.className = normalizeString(cellEl.className); - } - } - - // Find the table header and highlight the column that was sorted. - var el = tblEl.parentNode.tHead; - if (el) { - rowEl = el.rows[el.rows.length - 1]; - // Set style classes for each column as above. - for (i = 2; i < rowEl.cells.length; i++) { - cellEl = rowEl.cells[i]; - cellEl.className = cellEl.className.replace(colTest, ""); - // Highlight the header of the sorted column. - if (i == col) - cellEl.className += " " + colClsNm; - cellEl.className = normalizeString(cellEl.className); - } - } -} -*/ +function sortTable(el,rev,headrows,footrows){var tdEl=el;while(tdEl!=null&&tdEl.tagName.toUpperCase()!="TD"&&tdEl.tagName.toUpperCase()!="TH"){tdEl=tdEl.parentNode;} +if(tdEl==null){return;} +var trEl=tdEl;while(trEl!=null&&trEl.tagName.toUpperCase()!="TR"){trEl=trEl.parentNode;} +if(trEl==null){return;} +var tblEl=trEl;while(tblEl!=null&&tblEl.tagName.toUpperCase()!="TABLE"){tblEl=tblEl.parentNode;} +if(tblEl==null){return;} +var col=0;var i=0;while(i<trEl.childNodes.length){if(trEl.childNodes[i].tagName!=null){if(trEl.childNodes[i]==tdEl) +break;col++;} +i++;} +if(i==trEl.childNodes.length){return null;} +var tblBody=null;var gotBody=false;for(var i=0;i<tblEl.childNodes.length;i++){var tn=tblEl.childNodes[i].tagName;if(tn!=null) +tn=tn.toUpperCase();if(tn=="THEAD"){if(gotBody) +footrows-=tblEl.childNodes[i].rows.length;else +headrows-=tblEl.childNodes[i].rows.length;} +else if(tn=="TBODY"){tblBody=tblEl.childNodes[i];gotBody=true;} +else if(tn=="TFOOT"){footrows-=tblEl.childNodes[i].rows.length;}} +if(tblEl.reverseSort==null){tblEl.reverseSort=new Array();tblEl.lastColumn=1;} +if(tblEl.reverseSort[col]==null) +tblEl.reverseSort[col]=rev;if(col==tblEl.lastColumn) +tblEl.reverseSort[col]=!tblEl.reverseSort[col];tblEl.lastColumn=col;var oldDsply=tblEl.style.display;tblEl.style.display="none";var tmpEl;var i,j;var minVal,minIdx;var testVal;var cmp;var start=(headrows>0?headrows:0);var end=tblBody.rows.length-(footrows>0?footrows:0);for(i=start;i<end-1;i++){minIdx=i;minVal=getTextValue(tblBody.rows[i].cells[col]);for(j=i+1;j<end;j++){testVal=getTextValue(tblBody.rows[j].cells[col]);cmp=compareValues(minVal,testVal);if(tblEl.reverseSort[col]) +cmp=-cmp;if(cmp>0){minIdx=j;minVal=testVal;}} +if(minIdx>i){tmpEl=tblBody.removeChild(tblBody.rows[minIdx]);tblBody.insertBefore(tmpEl,tblBody.rows[i]);}} +tblEl.style.display=oldDsply;return false;} +if(document.ELEMENT_NODE==null){document.ELEMENT_NODE=1;document.TEXT_NODE=3;} +function getTextValue(el){if(!el) +return'';var i;var s;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 +s+=getTextValue(el.childNodes[i]);return normalizeString(s);} +var months=new Array();months["jan"]=0;months["feb"]=1;months["mar"]=2;months["apr"]=3;months["may"]=4;months["jun"]=5;months["jul"]=6;months["aug"]=7;months["sep"]=8;months["oct"]=9;months["nov"]=10;months["dec"]=11;var TWIKIDATE=new RegExp("^\\s*([0-3]?[0-9])[-\\s/]*"+ +"(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)"+ +"[-\\s/]*([0-9]{2}[0-9]{2}?)"+ +"(\\s*(-\\s*)?([0-9]{2}):([0-9]{2}))?","i");var RFC8601=new RegExp("([0-9]{4})(-([0-9]{2})(-([0-9]{2})"+ +"(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?"+ +"(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?");function s2d(s){var d=s.match(TWIKIDATE);if(d!=null){var nd=new Date();nd.setDate(Number(d[1]));nd.setMonth(months[d[2].toLowerCase()]);if(d[3].length==2){var year=d[3];if(year>59) +year+=1900;else +year+=2000;nd.setYear(year);}else +nd.setYear(d[3]);if(d[6]!=null&&d[6].length) +nd.setHours(d[6]);if(d[7]!=null&&d[7].length) +nd.setMinutes(d[7]);return nd.getTime();} +var d=s.match(RFC8601);if(d==null) +return 0;var offset=0;var date=new Date(d[1],0,1);if(d[3])date.setMonth(d[3]-1);if(d[5])date.setDate(d[5]);if(d[7])date.setHours(d[7]);if(d[8])date.setMinutes(d[8]);if(d[10])date.setSeconds(d[10]);if(d[12])date.setMilliseconds(Number("0."+d[12])*1000);if(d[14]){offset=(Number(d[16])*60)+Number(d[17]);offset*=((d[15]=='-')?1:-1);} +offset-=date.getTimezoneOffset();time=(Number(date)+(offset*60*1000));return time;} +function compareValues(v1,v2){var d1=s2d(v1);if(d1){var d2=s2d(v2);if(d2){v1=d1;v2=d2;}}else{var f1=parseFloat(v1);if(!isNaN(f1)){var f2=parseFloat(v2);if(!isNaN(f2)){v1=f1;v2=f2;}}} +if(v1==v2) +return 0;if(v1>v2) +return 1;return-1;} +var whtSpEnds=new RegExp("^\\s*|\\s*$","g");var whtSpMult=new RegExp("\\s\\s+","g");function normalizeString(s){s=s.replace(whtSpMult," ");s=s.replace(whtSpEnds,"");return s;} Added: twiki/branches/MAIN/twikiplugins/EditRowPlugin/pub/TWiki/EditRowPlugin/TableSort_src.js =================================================================== --- twiki/branches/MAIN/twikiplugins/EditRowPlugin/pub/TWiki/EditRowPlugin/TableSort_src.js (rev 0) +++ twiki/branches/MAIN/twikiplugins/EditRowPlugin/pub/TWiki/EditRowPlugin/TableSort_src.js 2007-10-20 13:02:25 UTC (rev 15355) @@ -0,0 +1,364 @@ +// Code from http://www.brainjar.com/ +// License GPL 2 +// Extended by Crawford Currie Copyright (C) 2007 http://c-dot.co.uk +//----------------------------------------------------------------------------- +// sortTable(id, col, rev) +// +// tblEl - an element anywhere in the table +// rev - If true, the column is sorted in reverse (descending) order +// initially. +// headrows - number of rows in table header (unsorted) +// footrows number of rows in table footer (unsorted) +// +// Automatically detects and sorts data types; numbers and dates + +function sortTable(el, rev, headrows, footrows) { + + // Search up to find the containing TD or TH + var tdEl = el; + while (tdEl != null && + tdEl.tagName.toUpperCase() != "TD" && + tdEl.tagName.toUpperCase() != "TH") { + tdEl = tdEl.parentNode; + } + if (tdEl == null) { + return; + } + + // Continue up to the TR + var trEl = tdEl; + while (trEl != null && trEl.tagName.toUpperCase() != "TR") { + trEl = trEl.parentNode; + } + if (trEl == null) { + return; + } + + // Continue to search up to find the containing table + var tblEl = trEl; + while (tblEl != null && tblEl.tagName.toUpperCase() != "TABLE") { + tblEl = tblEl.parentNode; + } + if (tblEl == null) { + return; + } + + // Now work out the column index + var col = 0; + var i = 0; + while (i < trEl.childNodes.length) { + if (trEl.childNodes[i].tagName != null) { + if (trEl.childNodes[i] == tdEl) + break; + col++; + } + i++; + } + if (i == trEl.childNodes.length) { + return null; + } + + // Find the TBODY, and work out the number of rows + var tblBody = null; + var gotBody = false; + for (var i = 0; i < tblEl.childNodes.length; i++) { + var tn = tblEl.childNodes[i].tagName; + if (tn != null) + tn = tn.toUpperCase(); + if (tn == "THEAD") { + // Bloody TablePlugin generates footer rows in the THEAD! + if (gotBody) + footrows -= tblEl.childNodes[i].rows.length; + else + headrows -= tblEl.childNodes[i].rows.length; + } + else if (tn == "TBODY") { + tblBody = tblEl.childNodes[i]; + gotBody = true; + } + else if (tn == "TFOOT") { + footrows -= tblEl.childNodes[i].rows.length; + } + } + + // The first time this function is called for a given table, set up an + // array of reverse sort flags. + if (tblEl.reverseSort == null) { + tblEl.reverseSort = new Array(); + // Also, assume the team name column is initially sorted. + tblEl.lastColumn = 1; + } + + // If this column has not been sorted before, set the initial sort direction. + if (tblEl.reverseSort[col] == null) + tblEl.reverseSort[col] = rev; + + // If this column was the last one sorted, reverse its sort direction. + if (col == tblEl.lastColumn) + tblEl.reverseSort[col] = !tblEl.reverseSort[col]; + + // Remember this column as the last one sorted. + tblEl.lastColumn = col; + + // Set the table display style to "none" - necessary for Netscape 6 + // browsers. + var oldDsply = tblEl.style.display; + tblEl.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; + + var start = (headrows > 0 ? headrows : 0); + var end = tblBody.rows.length - (footrows > 0 ? footrows : 0); + for (i = start; i < end - 1; i++) { + + // Assume the current row has the minimum value. + minIdx = i; + minVal = getTextValue(tblBody.rows[i].cells[col]); + + // Search the rows that follow the current one for a smaller value. + for (j = i + 1; j < end; j++) { + testVal = getTextValue(tblBody.rows[j].cells[col]); + cmp = compareValues(minVal, testVal); + // Negate the comparison result if the reverse sort flag is set. + if (tblEl.reverseSort[col]) + 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; + } + } + + // By now, we have the row with the smallest value. Remove it from the + // table and insert it before the current row. + if (minIdx > i) { + tmpEl = tblBody.removeChild(tblBody.rows[minIdx]); + tblBody.insertBefore(tmpEl, tblBody.rows[i]); + } + } + + // Make it look pretty. + // Not used, but kept for when TablePlugin uses classes. + //makePretty(tblBody, col); + + // Restore the table's display style. + tblEl.style.display = oldDsply; + + return false; +} + +//----------------------------------------------------------------------------- +// Functions to get and compare values during a sort. +//----------------------------------------------------------------------------- + +// 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; +} + +function getTextValue(el) { + + if (!el) + return ''; + + 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 += getTextValue(el.childNodes[i]); + + return normalizeString(s); +} + +var months = new Array(); +months["jan"] = 0; +months["feb"] = 1; +months["mar"] = 2; +months["apr"] = 3; +months["may"] = 4; +months["jun"] = 5; +months["jul"] = 6; +months["aug"] = 7; +months["sep"] = 8; +months["oct"] = 9; +months["nov"] = 10; +months["dec"] = 11; + +// "31 Dec 2003 - 23:59", +// "31-Dec-2003 - 23:59", +// "31/Dec/2003 - 23:59", +// "31/Dec/03 - 23:59", +var TWIKIDATE = new RegExp( + "^\\s*([0-3]?[0-9])[-\\s/]*" + + "(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)" + + "[-\\s/]*([0-9]{2}[0-9]{2}?)" + + "(\\s*(-\\s*)?([0-9]{2}):([0-9]{2}))?", "i"); +var RFC8601 = new RegExp( + "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" + + "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" + + "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?"); + +// Convert date/time to epoch seconds. Return 0 if a valid date +// wasn't found. +function s2d(s) { + // TWiki date/time + var d = s.match(TWIKIDATE); + if (d != null) { + var nd = new Date(); + nd.setDate(Number(d[1])); + nd.setMonth(months[d[2].toLowerCase()]); + if (d[3].length == 2) { + var year = d[3]; + // I'll be dead by the time this fails :-) + if (year > 59) + year += 1900; + else + year += 2000; + nd.setYear(year); + } else + nd.setYear(d[3]); + if (d[6] != null && d[6].length) + nd.setHours(d[6]); + if (d[7] != null && d[7].length) + nd.setMinutes(d[7]); + return nd.getTime(); + } + + // RFC8601 date/time + // (Paul Sowden, http://delete.me.uk/2005/03/iso8601.html) + var d = s.match(RFC8601); + if (d == null) + return 0; + + var offset = 0; + var date = new Date(d[1], 0, 1); + + if (d[3]) date.setMonth(d[3] - 1); + if (d[5]) date.setDate(d[5]); + if (d[7]) date.setHours(d[7]); + if (d[8]) date.setMinutes(d[8]); + if (d[10]) date.setSeconds(d[10]); + if (d[12]) date.setMilliseconds(Number("0." + d[12]) * 1000); + if (d[14]) { + offset = (Number(d[16]) * 60) + Number(d[17]); + offset *= ((d[15] == '-') ? 1 : -1); + } + + offset -= date.getTimezoneOffset(); + time = (Number(date) + (offset * 60 * 1000)); + return time; +} + +function compareValues(v1, v2) { + // if the values are both dates, convert them to epoch seconds + var d1 = s2d(v1); + if (d1) { + var d2 = s2d(v2); + if (d2) { + v1 = d1; + v2 = d2; + } + } else { + // If the values are numeric, convert them to floats. + var f1 = parseFloat(v1); + if (!isNaN(f1)) { + var f2 = parseFloat(v2); + if (!isNaN(f2)) { + v1 = f1; + v2 = f2; + } + } + } + // Compare the two values. + if (v1 == v2) + return 0; + if (v1 > v2) + return 1; + return -1; +} + +// Regular expressions for normalizing white space. +var whtSpEnds = new RegExp("^\\s*|\\s*$", "g"); +var whtSpMult = new RegExp("\\s\\s+", "g"); + +function normalizeString(s) { + + s = s.replace(whtSpMult, " "); // Collapse any multiple whites space. + s = s.replace(whtSpEnds, ""); // Remove leading or trailing white space. + + return s; +} + +//----------------------------------------------------------------------------- +// Functions to update the table appearance after a sort. +// Not used, but kept for when TablePlugin uses classes. +//----------------------------------------------------------------------------- + +/* +// Style class names. +var rowClsNm = "alternateRow"; +var colClsNm = "sortedColumn"; + +// Regular expressions for setting class names. +var rowTest = new RegExp(rowClsNm, "gi"); +var colTest = new RegExp(colClsNm, "gi"); + +function makePretty(tblEl, col) { + + var i, j; + var rowEl, cellEl; + + // Set style classes on each row to alternate their appearance. + for (i = 0; i < tblEl.rows.length; i++) { + rowEl = tblEl.rows[i]; + rowEl.className = rowEl.className.replace(rowTest, ""); + if (i % 2 != 0) + rowEl.className += " " + rowClsNm; + rowEl.className = normalizeString(rowEl.className); + // Set style classes on each column (other than the name column) to + // highlight the one that was sorted. + for (j = 2; j < tblEl.rows[i].cells.length; j++) { + cellEl = rowEl.cells[j]; + cellEl.className = cellEl.className.replace(colTest, ""); + if (j == col) + cellEl.className += " " + colClsNm; + cellEl.className = normalizeString(cellEl.className); + } + } + + // Find the table header and highlight the column that was sorted. + var el = tblEl.parentNode.tHead; + if (el) { + rowEl = el.rows[el.rows.length - 1]; + // Set style classes for each column as above. + for (i = 2; i < rowEl.cells.length; i++) { + cellEl = rowEl.cells[i]; + cellEl.className = cellEl.className.replace(colTest, ""); + // Highlight the header of the sorted column. + if (i == col) + cellEl.className += " " + colClsNm; + cellEl.className = normalizeString(cellEl.className); + } + } +} +*/ |