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
20f17aeb05940bccd8af63ad09a480c0281efaaa
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 |