├── README.md ├── plugin.info.txt ├── screen.css ├── script.js └── syntax.php /README.md: -------------------------------------------------------------------------------- 1 | sortablejs 2 | ========== 3 | 4 | JavaScript-based (client-side) DokuWiki table sorting plugin. In semi-maintenance mode now - only bugfixes and feature requests via PRs are handled. 5 | 6 | DokuWiki homepage at https://www.dokuwiki.org/plugin:sortablejs 7 | 8 | For server-side sorting (required for DW2PDF etc.) or more robust sort facilities, use @samwilson 's dokuwiki sorter (based on https://github.com/Mottie/tablesorter): https://github.com/samwilson/dokuwiki_sortablejs 9 | 10 | Cross-plugin compatibility is outside the scope of this repo. 11 | 12 | If reporting bugs, please provide a valid reproduction scenario for any non-trivial case. 13 | -------------------------------------------------------------------------------- /plugin.info.txt: -------------------------------------------------------------------------------- 1 | base sortablejs 2 | author vaxquis 3 | email spamove@gmail.com 4 | date 2023-04-20 5 | name sortablejs 6 | desc Allow sorting tables by columns 7 | url https://github.com/FyiurAmron/sortablejs 8 | -------------------------------------------------------------------------------- /screen.css: -------------------------------------------------------------------------------- 1 | div.sortable thead tr.row0 th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sorttable_nosort):after { 2 | content: " \25B4\25BE" 3 | } 4 | 5 | div.sortable thead tr.row0 th.sorttable_sorted:after { 6 | content: " \25BE" 7 | } 8 | 9 | div.sortable thead tr.row0 th.sorttable_sorted_reverse:after { 10 | content: " \25B4" 11 | } 12 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | /** 2 | Based on code from http://www.kryogenix.org/code/browser/sorttable/ by Stuart Langridge 3 | (distributed under the conditions of MIT licence from http://www.kryogenix.org/code/browser/licence.html). 4 | Includes open-source contributions from other people 5 | (see https://github.com/FyiurAmron/sortablejs/graphs/contributors for more details). 6 | Maintainers: 7 | 2007-2016 oiv (Otto Vainio at otto@valjakko.net) 8 | 2016-? vaxquis AKA FyiurAmron (spamove@gmail.com) 9 | */ 10 | 11 | var stIsIE = /*@cc_on!@*/false; 12 | //var tableid = 0; 13 | 14 | var sorttable = { 15 | reinit: function () { 16 | arguments.callee.done = true; 17 | 18 | if ( !document.createElement || !document.getElementsByTagName ) { 19 | return; 20 | } 21 | 22 | var elems = document.getElementsByTagName( "table" ); 23 | var elem; 24 | for( var i = 0; i < elems.length; i++ ) { 25 | elem = elems[i]; 26 | if ( jQuery(elem).hasClass("sortable") ) { 27 | sorttable.makeSortable( elem ); 28 | } 29 | } 30 | elems = document.getElementsByTagName( "div" ); 31 | for( var i = 0; i < elems.length; i++ ) { 32 | elem = elems[i]; 33 | if ( jQuery(elem).hasClass("sortable") ) { 34 | sorttable.makeSortableDiv( elem ); 35 | } 36 | } 37 | }, 38 | init: function () { 39 | if ( arguments.callee.done ) { 40 | return; 41 | } 42 | sorttable.reinit(); 43 | }, 44 | makeSortableDiv: function ( div ) { 45 | var childTables = div.getElementsByTagName( "table" ); 46 | var elem; 47 | for( var i = 0; i < childTables.length; i++ ) { 48 | elem = childTables[i]; 49 | var colid = div.className; 50 | var patt1 = /\bcol_\d+_[a-z]+/gi; 51 | var overs = []; 52 | if ( colid.search( patt1 ) !== -1 ) { 53 | var overrides = colid.match( patt1 ); 54 | for( var i = 0; i < overrides.length; i++ ) { 55 | var entry = overrides[i]; 56 | if ( entry !== "" ) { 57 | try { 58 | var tmp = entry.split( "_" ); 59 | var ind = tmp[1]; 60 | var val = tmp[2]; 61 | overs[ind] = val; 62 | } catch( e ) { 63 | } 64 | } 65 | } 66 | colid = colid.replace( patt1, '' ); 67 | } 68 | var patt2 = /\bsortbottom_?\d?/gi; 69 | var bottoms = 0; 70 | if ( colid.search( patt2 ) !== -1 ) { 71 | var bs = colid.match( patt2 ); 72 | try { 73 | var tmp = bs[0].split( "_" ); 74 | //var ind = tmp[1]; 75 | var val = 1; 76 | if ( tmp.length > 1 ) { 77 | val = tmp[1]; 78 | } 79 | bottoms = val; 80 | } catch( e ) { 81 | } 82 | } 83 | var patt2ph = /\bthreephase/gi; 84 | var ph2 = true; 85 | if ( colid.search( patt2ph ) !== -1 ) { 86 | ph2 = false; 87 | } 88 | 89 | sorttable.makeSortable( elem, overs, bottoms, ph2 ); 90 | var pattdefault = /\bsortr?\d\d?/gi; 91 | if ( colid.search( pattdefault ) !== -1 ) { 92 | var mi = colid.match( pattdefault ); 93 | colid = mi[0].replace( 'sort', '' ); 94 | if ( colid !== '' ) { 95 | colid = colid.trim(); 96 | } 97 | var revs = false; 98 | if ( colid.search( /\br/ ) !== -1 ) { 99 | revs = true; 100 | colid = colid.replace( 'r', '' ); 101 | } 102 | sorttable.defaultSort( elem, colid, revs ); 103 | } 104 | } 105 | }, 106 | defaultSort: function ( table, colid, revs ) { 107 | var havetHead = table.tHead; 108 | var sindex = 1; 109 | if ( havetHead ) { 110 | sindex = 0; 111 | } 112 | var theadrow = table.rows[0].cells; 113 | colid--; 114 | var colname = "col" + colid; 115 | // remove sorttable_sorted classes 116 | var thiscell = false; 117 | for( var i = 0; i < theadrow.length; i++ ) { 118 | var cell = theadrow[i]; 119 | var colclass = cell.className; 120 | var classname = colclass.split( " " ); 121 | if ( classname[0] === colname ) { 122 | thiscell = cell; 123 | } 124 | } 125 | if ( thiscell === false ) { 126 | return; 127 | } 128 | // build an array to sort. This is a Schwartzian transform thing, 129 | // i.e., we "decorate" each row with the actual sort key, 130 | // sort based on the sort keys, and then put the rows back in order 131 | // which is a lot faster because you only do getInnerText once per row 132 | var row_array = []; 133 | var col = thiscell.sorttable_columnindex; 134 | var rows = thiscell.sorttable_tbody.rows; 135 | for( var j = sindex; j < rows.length; j++ ) { 136 | row_array[row_array.length] = [sorttable.getInnerText( rows[j].cells[col] ), rows[j]]; 137 | } 138 | /* If you want a stable sort, uncomment the following line */ 139 | //sorttable.shaker_sort(row_array, this.sorttable_sortfunction); 140 | /* and comment out this one */ 141 | row_array.sort( thiscell.sorttable_sortfunction ); 142 | 143 | var tb = thiscell.sorttable_tbody; 144 | for( var jj = 0; jj < row_array.length; jj++ ) { 145 | tb.appendChild( row_array[jj][1] ); 146 | } 147 | 148 | if ( revs ) { 149 | sorttable.reverse( thiscell.sorttable_tbody, sindex ); 150 | jQuery(thiscell).addClass( "sorttable_sorted_reverse" ); 151 | } else { 152 | jQuery(thiscell).addClass( "sorttable_sorted" ); 153 | } 154 | }, 155 | makeSortable: function ( table, overrides, bottoms, ph2 ) { 156 | // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as 157 | // "total" rows, for example). This is B&R, since what you're supposed 158 | // to do is put them in a tfoot. So, if there are sortbottom rows, 159 | // for backwards compatibility, move them to tfoot (creating it if needed). 160 | 161 | var sortbottomrows = []; 162 | if ( bottoms > 0 ) { 163 | var frombottom = table.rows.length - bottoms; 164 | for( var i = table.rows.length - 1; i >= frombottom; i-- ) { 165 | sortbottomrows[sortbottomrows.length] = table.rows[i]; 166 | } 167 | if ( sortbottomrows ) { 168 | var tfo; 169 | if ( table.tFoot === null ) { 170 | // table doesn't have a tfoot. Create one. 171 | tfo = document.createElement( 'tfoot' ); 172 | table.appendChild( tfo ); 173 | } 174 | for( var ii = sortbottomrows.length - 1; ii >= 0; ii-- ) { 175 | tfo.appendChild( sortbottomrows[ii] ); 176 | } 177 | //delete sortbottomrows; 178 | } 179 | } 180 | // work through each column and calculate its type 181 | var havetHead = table.tHead; 182 | var sindex = 1; 183 | if ( havetHead ) { 184 | sindex = 0; 185 | } 186 | var headrow = table.rows[0].cells; 187 | 188 | for( var i = 0; i < headrow.length; i++ ) { 189 | // manually override the type with a sorttable_type attribute 190 | var colOptions = ""; 191 | if ( overrides[i + 1] ) 192 | { 193 | colOptions = overrides[i + 1]; 194 | } 195 | if ( colOptions.match( /\bnosort\b/ ) ) { 196 | jQuery(headrow[i]).addClass("sorttable_nosort"); 197 | } else { // skip this col 198 | var mtch = colOptions.match( /\b[a-z0-9]+\b/ ); 199 | var override; 200 | if ( mtch ) { 201 | override = mtch[0]; 202 | } 203 | if ( mtch && typeof sorttable["sort_" + override] === 'function' ) { 204 | headrow[i].sorttable_sortfunction = sorttable["sort_" + override]; 205 | } else { 206 | headrow[i].sorttable_sortfunction = sorttable.guessType( table, i ); 207 | } 208 | 209 | // make it clickable to sort 210 | headrow[i].sorttable_columnindex = i; 211 | headrow[i].sorttable_tbody = table.tBodies[0]; 212 | headrow[i].sindex = sindex; 213 | 214 | jQuery( headrow[i] ).click( function () { 215 | var theadrow = this.parentNode; 216 | var jqt = jQuery( this ); 217 | if ( jqt.hasClass( "sorttable_sorted" ) ) { 218 | // if we're already sorted by this column, just reverse the table 219 | sorttable.reverse( this.sorttable_tbody, this.sindex ); 220 | jqt.removeClass( "sorttable_sorted" ); 221 | jqt.addClass( "sorttable_sorted_reverse" ); 222 | return; 223 | } 224 | if ( jqt.hasClass( "sorttable_sorted_reverse" ) ) { 225 | if ( !ph2 ) { 226 | sorttable.original_order( this.sorttable_tbody, this.sindex ); 227 | var list = theadrow.childNodes; 228 | for( var i = 0; i < list.length; i++ ) { 229 | var cell = list[i]; 230 | if ( cell.nodeType === 1 ) { // an element 231 | var cc = jQuery( cell ); 232 | cc.removeClass( "sorttable_sorted" ); 233 | cc.removeClass( "sorttable_sorted_reverse" ); 234 | } 235 | } 236 | return; 237 | } else { 238 | // if we're already sorted by this column in reverse, just re-reverse the table 239 | sorttable.reverse( this.sorttable_tbody, this.sindex ); 240 | jqt.removeClass( "sorttable_sorted_reverse" ); 241 | jqt.addClass( "sorttable_sorted" ); 242 | return; 243 | } 244 | } 245 | 246 | // remove sorttable_sorted classes 247 | var list = theadrow.childNodes; 248 | for( var i = 0; i < list.length; i++ ) { 249 | var cell = list[i]; 250 | if ( cell.nodeType === 1 ) { // an element 251 | var cc = jQuery( cell ); 252 | cc.removeClass( "sorttable_sorted" ); 253 | cc.removeClass( "sorttable_sorted_reverse" ); 254 | } 255 | } 256 | jqt.addClass( "sorttable_sorted" ); 257 | 258 | // build an array to sort. This is a Schwartzian transform thing, 259 | // i.e., we "decorate" each row with the actual sort key, 260 | // sort based on the sort keys, and then put the rows back in order 261 | // which is a lot faster because you only do getInnerText once per row 262 | var row_array = []; 263 | var col = this.sorttable_columnindex; 264 | var rows = this.sorttable_tbody.rows; 265 | sindex = this.sindex; 266 | for( var j = sindex; j < rows.length; j++ ) { 267 | row_array[row_array.length] = [sorttable.getInnerText( rows[j].cells[col] ), rows[j]]; 268 | } 269 | /* If you want a stable sort, uncomment the following line */ 270 | //sorttable.shaker_sort(row_array, this.sorttable_sortfunction); 271 | /* and comment out this one */ 272 | row_array.sort( this.sorttable_sortfunction ); 273 | 274 | var tb = this.sorttable_tbody; 275 | for( var j3 = 0; j3 < row_array.length; j3++ ) { 276 | tb.appendChild( row_array[j3][1] ); 277 | } 278 | 279 | //delete row_array; 280 | } ); 281 | } 282 | } 283 | }, 284 | guessType: function ( table, column ) { 285 | // guess the type of a column based on its first non-blank row 286 | var textCnt = 0; 287 | var numCnt = 0; 288 | var dateCnt = 0; 289 | var ipCnt = 0; 290 | 291 | for( var i = 0; i < table.tBodies[0].rows.length; i++ ) { 292 | var text = sorttable.getInnerText( table.tBodies[0].rows[i].cells[column] ); 293 | if ( text !== "" ) { 294 | if ( text.match( /^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$/ ) ) { 295 | ipCnt++; 296 | } else if ( text.match( /^[\-\+]?.?\d*[\d,.]?\d+.?$/ ) ) { 297 | numCnt++; 298 | } else if ( !isNaN( new Date( text ).getTime() ) ) { 299 | dateCnt++; 300 | } else { // not a date (nor IP nor number) 301 | textCnt++; 302 | } 303 | } 304 | } 305 | if ( textCnt > numCnt && textCnt > ipCnt && textCnt > dateCnt ) 306 | return sorttable.sort_alpha; 307 | if ( numCnt > ipCnt && numCnt > dateCnt ) 308 | return sorttable.sort_numeric; 309 | if ( ipCnt > dateCnt ) 310 | return sorttable.sort_ipaddr; 311 | return sorttable.sort_date; 312 | }, 313 | getInnerText: function ( node ) { 314 | // gets the text we want to use for sorting for a cell. 315 | // strips leading and trailing whitespace. 316 | // this is *not* a generic getInnerText function; it's special to sorttable. 317 | // for example, you can override the cell text with a customkey attribute. 318 | // it also gets .value for fields. 319 | if ( !node ) { 320 | return ''; 321 | } 322 | var hasInputs = ( typeof node.getElementsByTagName === "function" ) && 323 | node.getElementsByTagName( "input" ).length; 324 | if ( node.getAttribute( "sorttable_customkey" ) !== null ) { 325 | return node.getAttribute( "sorttable_customkey" ); 326 | } else if ( typeof node.textContent !== "undefined" && !hasInputs ) { 327 | return node.textContent.replace( /^\s+|\s+$/g, '' ); 328 | } else if ( typeof node.innerText !== "undefined" && !hasInputs ) { 329 | return node.innerText.replace( /^\s+|\s+$/g, '' ); 330 | } else if ( typeof node.text !== "undefined" && !hasInputs ) { 331 | return node.text.replace( /^\s+|\s+$/g, '' ); 332 | } else { 333 | switch ( node.nodeType ) { 334 | case 3: 335 | return ( node.nodeName.toLowerCase() === "input" ) ? node.value.replace( /^\s+|\s+$/g, '' ) : ''; 336 | case 4: 337 | return node.nodeValue.replace( /^\s+|\s+$/g, '' ); 338 | case 1: 339 | case 11: 340 | var innerText = ''; 341 | for( var i = 0; i < node.childNodes.length; i++ ) { 342 | innerText += sorttable.getInnerText( node.childNodes[i] ); 343 | } 344 | return innerText.replace( /^\s+|\s+$/g, '' ); 345 | default: 346 | return ''; 347 | } 348 | } 349 | }, 350 | reverse: function ( tbody, sindex ) { 351 | // reverse the rows in a tbody 352 | var newrows = []; 353 | for( var i = sindex; i < tbody.rows.length; i++ ) { 354 | newrows[newrows.length] = tbody.rows[i]; 355 | } 356 | for( var i = newrows.length - 1; i >= 0; i-- ) { 357 | tbody.appendChild( newrows[i] ); 358 | } 359 | //delete newrows; 360 | }, 361 | original_order: function ( tbody, isindex ) { 362 | // build an array to sort. This is a Schwartzian transform thing, 363 | // i.e., we "decorate" each row with the actual sort key, 364 | // sort based on the sort keys, and then put the rows back in order 365 | // which is a lot faster because you only do getInnerText once per row 366 | var row_array = []; 367 | var rows = tbody.rows; 368 | var sindex = isindex; 369 | for( var j = sindex; j < rows.length; j++ ) { 370 | row_array[row_array.length] = [rows[j].className, rows[j]]; 371 | } 372 | /* If you want a stable sort, uncomment the following line */ 373 | //sorttable.shaker_sort(row_array, this.sorttable_sortfunction); 374 | /* and comment out this one */ 375 | row_array.sort( sorttable.sort_alpha ); 376 | 377 | var tb = tbody; 378 | for( var j3 = 0; j3 < row_array.length; j3++ ) { 379 | tb.appendChild( row_array[j3][1] ); 380 | } 381 | 382 | //delete row_array; 383 | }, 384 | /* sort functions 385 | each sort function takes two parameters, a and b 386 | you are comparing a[0] and b[0] */ 387 | sort_ipaddr: function ( a, b ) { 388 | var aa = a[0].split( ".", 4 ); 389 | var bb = b[0].split( ".", 4 ); 390 | var resulta = aa[0] * 0x1000000 + aa[1] * 0x10000 + aa[2] * 0x100 + aa[3] * 1; 391 | var resultb = bb[0] * 0x1000000 + bb[1] * 0x10000 + bb[2] * 0x100 + bb[3] * 1; 392 | return resulta - resultb; 393 | }, 394 | sort_numeric: function ( a, b ) { 395 | if ( a[0] === "" ) { 396 | return -1; 397 | } 398 | if ( b[0] === "" ) { 399 | return 1; 400 | } 401 | var aa = parseFloat( a[0].replace( ",", "." ).replace( /[^0-9.\-]/g, "" ) ); 402 | if ( isNaN( aa ) ) { 403 | aa = Number.NEGATIVE_INFINITY; 404 | } 405 | var bb = parseFloat( b[0].replace( ",", "." ).replace( /[^0-9.\-]/g, "" ) ); 406 | if ( isNaN( bb ) ) { 407 | bb = Number.NEGATIVE_INFINITY; 408 | } 409 | return aa - bb; 410 | }, 411 | sort_alpha: function ( a, b ) { 412 | return a[0].localeCompare( b[0] ); 413 | }, 414 | sort_date: function ( a, b ) { 415 | var aa = new Date( a[0] ), bb = new Date( b[0] ); 416 | return ( aa > bb ) - ( aa < bb ); 417 | }, 418 | shaker_sort: function ( list, comp_func ) { 419 | // A stable sort function to allow multi-level sorting of data 420 | // see: http://en.wikipedia.org/wiki/Cocktail_sort 421 | // thanks to Joseph Nahmias 422 | var b = 0; 423 | var t = list.length - 1; 424 | var swap = true; 425 | var q; 426 | 427 | while( swap ) { 428 | swap = false; 429 | for( var i = b; i < t; ++i ) { 430 | if ( comp_func( list[i], list[i + 1] ) > 0 ) { 431 | q = list[i]; 432 | list[i] = list[i + 1]; 433 | list[i + 1] = q; 434 | swap = true; 435 | } 436 | } // for 437 | t--; 438 | 439 | if ( !swap ) { 440 | break; 441 | } 442 | 443 | for( var i = t; i > b; --i ) { 444 | if ( comp_func( list[i], list[i - 1] ) < 0 ) { 445 | q = list[i]; 446 | list[i] = list[i - 1]; 447 | list[i - 1] = q; 448 | swap = true; 449 | } 450 | } // for 451 | b++; 452 | 453 | } // while(swap) 454 | } 455 | 456 | }; 457 | 458 | if ( typeof ( window.addEvent ) !== "undefined" ) { 459 | window.addEvent( window, "load", sorttable.init ); 460 | } else { 461 | jQuery( function () { 462 | sorttable.init(); 463 | } ); 464 | } 465 | -------------------------------------------------------------------------------- /syntax.php: -------------------------------------------------------------------------------- 1 | Lexer->addEntryPattern( ']*>(?=.*?)', $mode, 'plugin_sortablejs' ); 36 | } 37 | 38 | function postConnect() { 39 | $this->Lexer->addExitPattern( '', 'plugin_sortablejs' ); 40 | } 41 | 42 | function handle( $match, $state, $pos, Doku_Handler $handler ) { 43 | 44 | switch ( $state ) { 45 | case DOKU_LEXER_ENTER : 46 | $match = substr( $match, 9, -1 ); 47 | $match = trim( $match ); 48 | $scl = ""; 49 | if ( strlen( $match ) > 0 ) { 50 | $scl = $this->__validateOptions( $match ); 51 | } 52 | return array( $state, $scl ); 53 | case DOKU_LEXER_UNMATCHED : 54 | return array( $state, $match ); 55 | case DOKU_LEXER_EXIT : 56 | return array( $state, "" ); 57 | } 58 | return array(); 59 | } 60 | 61 | function render( $mode, Doku_Renderer $renderer, $data ) { 62 | list($state, $match) = $data; 63 | if ( $mode == 'xhtml' ) { 64 | switch ( $state ) { 65 | case DOKU_LEXER_ENTER : 66 | $renderer->doc .= "
"; 67 | break; 68 | case DOKU_LEXER_UNMATCHED : 69 | $instructions = p_get_instructions( $match ); 70 | foreach( $instructions as $instruction ) { 71 | call_user_func_array( array( &$renderer, $instruction[0] ), $instruction[1] ); 72 | } 73 | 74 | break; 75 | case DOKU_LEXER_EXIT : 76 | $renderer->doc .= "
"; 77 | break; 78 | } 79 | return true; 80 | } else if ( $mode == 'odt' ) { 81 | switch ( $state ) { 82 | case DOKU_LEXER_ENTER : 83 | // In ODT, tables must not be inside a paragraph. Make sure we 84 | // closed any opened paragraph 85 | $renderer->p_close(); 86 | break; 87 | case DOKU_LEXER_UNMATCHED : 88 | $instructions = array_slice( p_get_instructions( $match ), 1, -1 ); 89 | foreach( $instructions as $instruction ) { 90 | call_user_func_array( array( &$renderer, $instruction[0] ), $instruction[1] ); 91 | } 92 | break; 93 | case DOKU_LEXER_EXIT : 94 | //$renderer->p_open(); 95 | // DO NOT re-open the paragraph, it would cause an error if the table is the last content on a page 96 | break; 97 | } 98 | return true; 99 | } 100 | return false; 101 | } 102 | 103 | function __validateOptions( $opts ) { 104 | if ( empty( $opts ) ) { 105 | return ""; 106 | } 107 | $ret = ""; 108 | $oa = explode( " ", $opts ); 109 | foreach( $oa as $opt ) { 110 | $explodedOption = explode( "=", $opt ); 111 | $c = $explodedOption[0]; 112 | $v = $explodedOption[1] ?? null; 113 | if ( $c == "sumrow" ) { 114 | $c = $v; 115 | $v = "sumrow"; 116 | if ( $c == "" ) { 117 | $c = "1"; 118 | } 119 | } else if ( $c == "3phase" ) { 120 | $v = $c; 121 | $c = ""; 122 | } 123 | if ( $v != null ) { 124 | $cmpr = $v; 125 | } else { 126 | if ( preg_match( '/r?\d*/', $c, $matches ) ) { 127 | $cmpr = 'sort'; 128 | } 129 | } 130 | switch ( $cmpr ) { 131 | case '3phase': 132 | $ret .= " threephase"; 133 | break; 134 | case 'nosort': 135 | $ret .= " col_".$c."_nosort"; 136 | break; 137 | case 'numeric': 138 | $ret .= " col_".$c."_numeric"; 139 | break; 140 | case 'date': 141 | $ret .= " col_".$c."_date"; 142 | break; 143 | case 'alpha': 144 | case 'text': 145 | $ret .= " col_".$c."_alpha"; 146 | break; 147 | case 'sort': 148 | $ret .= ' sort'.$opt; 149 | break; 150 | case 'sumrow': 151 | $ret .= ' sortbottom_'.$c; 152 | break; 153 | } 154 | } 155 | return $ret; 156 | } 157 | } 158 | --------------------------------------------------------------------------------