Fossil SCM
Add javascript to ticket reports tables so that clicking on column headers causes the table to be sorted by that header. Clicking again reverses the sort order.
Commit
c43da4fcd14505533961bd8b5b07747e4c495222
Parent
e08073d33366052…
1 file changed
+67
-4
+67
-4
| --- src/report.c | ||
| +++ src/report.c | ||
| @@ -694,11 +694,11 @@ | ||
| 694 | 694 | } |
| 695 | 695 | } |
| 696 | 696 | |
| 697 | 697 | /* The first time this routine is called, output a table header |
| 698 | 698 | */ |
| 699 | - @ <tr> | |
| 699 | + @ <thead><tr> | |
| 700 | 700 | zTid = 0; |
| 701 | 701 | for(i=0; i<nArg; i++){ |
| 702 | 702 | char *zName = azName[i]; |
| 703 | 703 | if( i==pState->iBg ) continue; |
| 704 | 704 | if( pState->iNewRow>=0 && i>=pState->iNewRow ){ |
| @@ -716,11 +716,11 @@ | ||
| 716 | 716 | } |
| 717 | 717 | } |
| 718 | 718 | if( g.perm.Write && zTid ){ |
| 719 | 719 | @ <th> </th> |
| 720 | 720 | } |
| 721 | - @ </tr> | |
| 721 | + @ </tr></thead><tbody> | |
| 722 | 722 | } |
| 723 | 723 | if( azArg==0 ){ |
| 724 | 724 | @ <tr><td colspan="%d(pState->nCol)"> |
| 725 | 725 | @ <i>No records match the report criteria</i> |
| 726 | 726 | @ </td></tr> |
| @@ -914,10 +914,71 @@ | ||
| 914 | 914 | rc = sqlite3_finalize(pStmt); |
| 915 | 915 | fossil_free(azVals); |
| 916 | 916 | return rc; |
| 917 | 917 | } |
| 918 | 918 | |
| 919 | +/* | |
| 920 | +** Output Javascript code that will enables sorting of the table with | |
| 921 | +** the id zTableId by clicking. | |
| 922 | +** | |
| 923 | +** The javascript is derived from: | |
| 924 | +** | |
| 925 | +** http://www.webtoolkit.info/sortable-html-table.html | |
| 926 | +** | |
| 927 | +*/ | |
| 928 | +static void output_table_sorting_javascript(const char *zTableId){ | |
| 929 | + @ <script> | |
| 930 | + @ function SortableTable(tableEl){ | |
| 931 | + @ this.tbody = tableEl.getElementsByTagName('tbody'); | |
| 932 | + @ this.sort = function (cell) { | |
| 933 | + @ var column = cell.cellIndex; | |
| 934 | + @ this.sortIndex = column; | |
| 935 | + @ var newRows = new Array(); | |
| 936 | + @ for (j = 0; j < this.tbody[0].rows.length; j++) { | |
| 937 | + @ newRows[j] = this.tbody[0].rows[j]; | |
| 938 | + @ } | |
| 939 | + @ newRows.sort(this.sortText); | |
| 940 | + @ if (cell.getAttribute("sortdir") == 'down') { | |
| 941 | + @ newRows.reverse(); | |
| 942 | + @ cell.setAttribute('sortdir','up'); | |
| 943 | + @ } else { | |
| 944 | + @ cell.setAttribute('sortdir','down'); | |
| 945 | + @ } | |
| 946 | + @ for (i=0;i<newRows.length;i++) { | |
| 947 | + @ this.tbody[0].appendChild(newRows[i]); | |
| 948 | + @ } | |
| 949 | + @ } | |
| 950 | + @ this.sortText = function(a,b) { | |
| 951 | + @ var i = thisObject.sortIndex; | |
| 952 | + @ aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase(); | |
| 953 | + @ bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase(); | |
| 954 | + @ if(aa==bb) return 0; | |
| 955 | + @ if(aa<bb) return -1; | |
| 956 | + @ return 1; | |
| 957 | + @ } | |
| 958 | + @ var thisObject = this; | |
| 959 | + @ var x = tableEl.getElementsByTagName('thead'); | |
| 960 | + @ if(!(this.tbody && this.tbody[0].rows && this.tbody[0].rows.length>0)){ | |
| 961 | + @ return; | |
| 962 | + @ } | |
| 963 | + @ if(x && x[0].rows && x[0].rows.length > 0) { | |
| 964 | + @ var sortRow = x[0].rows[0]; | |
| 965 | + @ } else { | |
| 966 | + @ return; | |
| 967 | + @ } | |
| 968 | + @ for (var i=0; i<sortRow.cells.length; i++) { | |
| 969 | + @ sortRow.cells[i].sTable = this; | |
| 970 | + @ sortRow.cells[i].onclick = function () { | |
| 971 | + @ this.sTable.sort(this); | |
| 972 | + @ return false; | |
| 973 | + @ } | |
| 974 | + @ } | |
| 975 | + @ } | |
| 976 | + @ var t = new SortableTable(gebi("%s(zTableId)")); | |
| 977 | + @ </script> | |
| 978 | +} | |
| 979 | + | |
| 919 | 980 | |
| 920 | 981 | /* |
| 921 | 982 | ** WEBPAGE: /rptview |
| 922 | 983 | ** |
| 923 | 984 | ** Generate a report. The rn query parameter is the report number |
| @@ -992,22 +1053,24 @@ | ||
| 992 | 1053 | "%s/tktnew", g.zTop); |
| 993 | 1054 | } |
| 994 | 1055 | style_header(zTitle); |
| 995 | 1056 | output_color_key(zClrKey, 1, |
| 996 | 1057 | "border=\"0\" cellpadding=\"3\" cellspacing=\"0\" class=\"report\""); |
| 997 | - @ <table border="1" cellpadding="2" cellspacing="0" class="report"> | |
| 1058 | + @ <table border="1" cellpadding="2" cellspacing="0" class="report" | |
| 1059 | + @ id="reportTable"> | |
| 998 | 1060 | sState.rn = rn; |
| 999 | 1061 | sState.nCount = 0; |
| 1000 | 1062 | report_restrict_sql(&zErr1); |
| 1001 | 1063 | sqlite3_exec_readonly(g.db, zSql, generate_html, &sState, &zErr2); |
| 1002 | 1064 | report_unrestrict_sql(); |
| 1003 | - @ </table> | |
| 1065 | + @ </tbody></table> | |
| 1004 | 1066 | if( zErr1 ){ |
| 1005 | 1067 | @ <p class="reportError">Error: %h(zErr1)</p> |
| 1006 | 1068 | }else if( zErr2 ){ |
| 1007 | 1069 | @ <p class="reportError">Error: %h(zErr2)</p> |
| 1008 | 1070 | } |
| 1071 | + output_table_sorting_javascript("reportTable"); | |
| 1009 | 1072 | style_footer(); |
| 1010 | 1073 | }else{ |
| 1011 | 1074 | report_restrict_sql(&zErr1); |
| 1012 | 1075 | sqlite3_exec_readonly(g.db, zSql, output_tab_separated, &count, &zErr2); |
| 1013 | 1076 | report_unrestrict_sql(); |
| 1014 | 1077 |
| --- src/report.c | |
| +++ src/report.c | |
| @@ -694,11 +694,11 @@ | |
| 694 | } |
| 695 | } |
| 696 | |
| 697 | /* The first time this routine is called, output a table header |
| 698 | */ |
| 699 | @ <tr> |
| 700 | zTid = 0; |
| 701 | for(i=0; i<nArg; i++){ |
| 702 | char *zName = azName[i]; |
| 703 | if( i==pState->iBg ) continue; |
| 704 | if( pState->iNewRow>=0 && i>=pState->iNewRow ){ |
| @@ -716,11 +716,11 @@ | |
| 716 | } |
| 717 | } |
| 718 | if( g.perm.Write && zTid ){ |
| 719 | @ <th> </th> |
| 720 | } |
| 721 | @ </tr> |
| 722 | } |
| 723 | if( azArg==0 ){ |
| 724 | @ <tr><td colspan="%d(pState->nCol)"> |
| 725 | @ <i>No records match the report criteria</i> |
| 726 | @ </td></tr> |
| @@ -914,10 +914,71 @@ | |
| 914 | rc = sqlite3_finalize(pStmt); |
| 915 | fossil_free(azVals); |
| 916 | return rc; |
| 917 | } |
| 918 | |
| 919 | |
| 920 | /* |
| 921 | ** WEBPAGE: /rptview |
| 922 | ** |
| 923 | ** Generate a report. The rn query parameter is the report number |
| @@ -992,22 +1053,24 @@ | |
| 992 | "%s/tktnew", g.zTop); |
| 993 | } |
| 994 | style_header(zTitle); |
| 995 | output_color_key(zClrKey, 1, |
| 996 | "border=\"0\" cellpadding=\"3\" cellspacing=\"0\" class=\"report\""); |
| 997 | @ <table border="1" cellpadding="2" cellspacing="0" class="report"> |
| 998 | sState.rn = rn; |
| 999 | sState.nCount = 0; |
| 1000 | report_restrict_sql(&zErr1); |
| 1001 | sqlite3_exec_readonly(g.db, zSql, generate_html, &sState, &zErr2); |
| 1002 | report_unrestrict_sql(); |
| 1003 | @ </table> |
| 1004 | if( zErr1 ){ |
| 1005 | @ <p class="reportError">Error: %h(zErr1)</p> |
| 1006 | }else if( zErr2 ){ |
| 1007 | @ <p class="reportError">Error: %h(zErr2)</p> |
| 1008 | } |
| 1009 | style_footer(); |
| 1010 | }else{ |
| 1011 | report_restrict_sql(&zErr1); |
| 1012 | sqlite3_exec_readonly(g.db, zSql, output_tab_separated, &count, &zErr2); |
| 1013 | report_unrestrict_sql(); |
| 1014 |
| --- src/report.c | |
| +++ src/report.c | |
| @@ -694,11 +694,11 @@ | |
| 694 | } |
| 695 | } |
| 696 | |
| 697 | /* The first time this routine is called, output a table header |
| 698 | */ |
| 699 | @ <thead><tr> |
| 700 | zTid = 0; |
| 701 | for(i=0; i<nArg; i++){ |
| 702 | char *zName = azName[i]; |
| 703 | if( i==pState->iBg ) continue; |
| 704 | if( pState->iNewRow>=0 && i>=pState->iNewRow ){ |
| @@ -716,11 +716,11 @@ | |
| 716 | } |
| 717 | } |
| 718 | if( g.perm.Write && zTid ){ |
| 719 | @ <th> </th> |
| 720 | } |
| 721 | @ </tr></thead><tbody> |
| 722 | } |
| 723 | if( azArg==0 ){ |
| 724 | @ <tr><td colspan="%d(pState->nCol)"> |
| 725 | @ <i>No records match the report criteria</i> |
| 726 | @ </td></tr> |
| @@ -914,10 +914,71 @@ | |
| 914 | rc = sqlite3_finalize(pStmt); |
| 915 | fossil_free(azVals); |
| 916 | return rc; |
| 917 | } |
| 918 | |
| 919 | /* |
| 920 | ** Output Javascript code that will enables sorting of the table with |
| 921 | ** the id zTableId by clicking. |
| 922 | ** |
| 923 | ** The javascript is derived from: |
| 924 | ** |
| 925 | ** http://www.webtoolkit.info/sortable-html-table.html |
| 926 | ** |
| 927 | */ |
| 928 | static void output_table_sorting_javascript(const char *zTableId){ |
| 929 | @ <script> |
| 930 | @ function SortableTable(tableEl){ |
| 931 | @ this.tbody = tableEl.getElementsByTagName('tbody'); |
| 932 | @ this.sort = function (cell) { |
| 933 | @ var column = cell.cellIndex; |
| 934 | @ this.sortIndex = column; |
| 935 | @ var newRows = new Array(); |
| 936 | @ for (j = 0; j < this.tbody[0].rows.length; j++) { |
| 937 | @ newRows[j] = this.tbody[0].rows[j]; |
| 938 | @ } |
| 939 | @ newRows.sort(this.sortText); |
| 940 | @ if (cell.getAttribute("sortdir") == 'down') { |
| 941 | @ newRows.reverse(); |
| 942 | @ cell.setAttribute('sortdir','up'); |
| 943 | @ } else { |
| 944 | @ cell.setAttribute('sortdir','down'); |
| 945 | @ } |
| 946 | @ for (i=0;i<newRows.length;i++) { |
| 947 | @ this.tbody[0].appendChild(newRows[i]); |
| 948 | @ } |
| 949 | @ } |
| 950 | @ this.sortText = function(a,b) { |
| 951 | @ var i = thisObject.sortIndex; |
| 952 | @ aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase(); |
| 953 | @ bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase(); |
| 954 | @ if(aa==bb) return 0; |
| 955 | @ if(aa<bb) return -1; |
| 956 | @ return 1; |
| 957 | @ } |
| 958 | @ var thisObject = this; |
| 959 | @ var x = tableEl.getElementsByTagName('thead'); |
| 960 | @ if(!(this.tbody && this.tbody[0].rows && this.tbody[0].rows.length>0)){ |
| 961 | @ return; |
| 962 | @ } |
| 963 | @ if(x && x[0].rows && x[0].rows.length > 0) { |
| 964 | @ var sortRow = x[0].rows[0]; |
| 965 | @ } else { |
| 966 | @ return; |
| 967 | @ } |
| 968 | @ for (var i=0; i<sortRow.cells.length; i++) { |
| 969 | @ sortRow.cells[i].sTable = this; |
| 970 | @ sortRow.cells[i].onclick = function () { |
| 971 | @ this.sTable.sort(this); |
| 972 | @ return false; |
| 973 | @ } |
| 974 | @ } |
| 975 | @ } |
| 976 | @ var t = new SortableTable(gebi("%s(zTableId)")); |
| 977 | @ </script> |
| 978 | } |
| 979 | |
| 980 | |
| 981 | /* |
| 982 | ** WEBPAGE: /rptview |
| 983 | ** |
| 984 | ** Generate a report. The rn query parameter is the report number |
| @@ -992,22 +1053,24 @@ | |
| 1053 | "%s/tktnew", g.zTop); |
| 1054 | } |
| 1055 | style_header(zTitle); |
| 1056 | output_color_key(zClrKey, 1, |
| 1057 | "border=\"0\" cellpadding=\"3\" cellspacing=\"0\" class=\"report\""); |
| 1058 | @ <table border="1" cellpadding="2" cellspacing="0" class="report" |
| 1059 | @ id="reportTable"> |
| 1060 | sState.rn = rn; |
| 1061 | sState.nCount = 0; |
| 1062 | report_restrict_sql(&zErr1); |
| 1063 | sqlite3_exec_readonly(g.db, zSql, generate_html, &sState, &zErr2); |
| 1064 | report_unrestrict_sql(); |
| 1065 | @ </tbody></table> |
| 1066 | if( zErr1 ){ |
| 1067 | @ <p class="reportError">Error: %h(zErr1)</p> |
| 1068 | }else if( zErr2 ){ |
| 1069 | @ <p class="reportError">Error: %h(zErr2)</p> |
| 1070 | } |
| 1071 | output_table_sorting_javascript("reportTable"); |
| 1072 | style_footer(); |
| 1073 | }else{ |
| 1074 | report_restrict_sql(&zErr1); |
| 1075 | sqlite3_exec_readonly(g.db, zSql, output_tab_separated, &count, &zErr2); |
| 1076 | report_unrestrict_sql(); |
| 1077 |