Fossil

Check-in [8533365e44]
Login

Check-in [8533365e44]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add icons on sortable table headers to show which columns are sortable and which is the current sort column. This code is inspired by the brlist-visual-enhancement branch, but is a completely new implementation.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 8533365e44fab97b6e2375eb42d9ca22ac033fd0
User & Date: drh 2015-01-06 01:06:55.570
Context
2015-01-06
13:14
Add the --verbose option to the clone command. ... (check-in: 5e7d3effa3 user: drh tags: trunk)
06:46
Extend verbose option to clone operations as well. ... (Closed-Leaf check-in: 7421f9263c user: andybradford tags: pending-review)
01:06
Add icons on sortable table headers to show which columns are sortable and which is the current sort column. This code is inspired by the brlist-visual-enhancement branch, but is a completely new implementation. ... (check-in: 8533365e44 user: drh tags: trunk)
2015-01-05
22:39
Fix typo in a new #define. ... (check-in: c761af63c0 user: mistachkin tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/branch.c.
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
@   (SELECT uuid FROM blob WHERE rid=tagxref.rid)
@  FROM tagxref, tag, event
@ WHERE tagxref.tagid=tag.tagid
@   AND tagxref.tagtype>0
@   AND tag.tagname='branch'
@   AND event.objid=tagxref.rid
@ GROUP BY 1
@ ORDER BY %d DESC;
;

/*
** This is the new-style branch-list page that shows the branch names
** together with their ages (time of last check-in) and whether or not
** they are closed or merged to another branch.
**
** Control jumps to this routine from brlist_page() (the /brlist handler)
** if there are no query parameters.
*/
static void new_brlist_page(void){
  Stmt q;
  double rNow;
  int orderByMtime = P("mtime")!=0;
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  style_header("Branches");
  login_anonymous_available();
  
  assert( orderByMtime==0 || orderByMtime==1 );
  db_prepare(&q, brlistQuery/*works-like:"%d"*/, 2-orderByMtime);
  rNow = db_double(0.0, "SELECT julianday('now')");
  @ <div class="brlist"><table id="branchlisttable">
  @ <thead><tr>
  @ <th>Branch Name</th>
  @ <th>Age</th>
  @ <th>Checkins</th>
  @ <th>Status</th>







|













<





<
|







322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342

343
344
345
346
347

348
349
350
351
352
353
354
355
@   (SELECT uuid FROM blob WHERE rid=tagxref.rid)
@  FROM tagxref, tag, event
@ WHERE tagxref.tagid=tag.tagid
@   AND tagxref.tagtype>0
@   AND tag.tagname='branch'
@   AND event.objid=tagxref.rid
@ GROUP BY 1
@ ORDER BY 2 DESC;
;

/*
** This is the new-style branch-list page that shows the branch names
** together with their ages (time of last check-in) and whether or not
** they are closed or merged to another branch.
**
** Control jumps to this routine from brlist_page() (the /brlist handler)
** if there are no query parameters.
*/
static void new_brlist_page(void){
  Stmt q;
  double rNow;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  style_header("Branches");
  login_anonymous_available();
  

  db_prepare(&q, brlistQuery/*works-like:""*/);
  rNow = db_double(0.0, "SELECT julianday('now')");
  @ <div class="brlist"><table id="branchlisttable">
  @ <thead><tr>
  @ <th>Branch Name</th>
  @ <th>Age</th>
  @ <th>Checkins</th>
  @ <th>Status</th>
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
    }else{
      @ <td></td>
    }
    @ </tr>
  }
  @ </tbody></table></div>
  db_finalize(&q);
  output_table_sorting_javascript("branchlisttable","tkktx");
  style_footer();
}

/*
** WEBPAGE: brlist
** Show a list of branches
** Query parameters:







|







377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
    }else{
      @ <td></td>
    }
    @ </tr>
  }
  @ </tbody></table></div>
  db_finalize(&q);
  output_table_sorting_javascript("branchlisttable","tkktx",2);
  style_footer();
}

/*
** WEBPAGE: brlist
** Show a list of branches
** Query parameters:
Changes to src/report.c.
920
921
922
923
924
925
926
927
928
929


930
931
932
933
934
935
936
937
938
939
940





941
942
943
944




945
946
947

948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963

964
965
966
967
968
969
970



















971
972
973
974
975
976
977
  return rc;
}

/*
** Output Javascript code that will enables sorting of the table with
** the id zTableId by clicking.
**
** The javascript is derived from:
**
**     http://www.webtoolkit.info/sortable-html-table.html


**
** This variation allows column types to be expressed using the second
** argument.  Each character of the second argument represent a column.
**
**       t      Sort by text
**       n      Sort numerically
**       k      Sort by the data-sortkey property
**       x      This column is not sortable
**
** If there are fewer characters in zColumnTypes[] than their are columns,
** the all extra columns assume type "t" (text).





**
** Clicking on the same column header twice in a row inverts the sort.
*/
void output_table_sorting_javascript(const char *zTableId, const char *zColumnTypes){




  @ <script>
  @ function SortableTable(tableEl,columnTypes){
  @   this.tbody = tableEl.getElementsByTagName('tbody');

  @   this.sort = function (cell) {
  @     var column = cell.cellIndex;
  @     var sortFn;
  @     switch( cell.sortType ){
  @       case "n":  sortFn = this.sortNumeric;  break;
  @       case "t":  sortFn = this.sortText;     break;
  @       case "k":  sortFn = this.sortKey;      break;
  @       case "x":  return;
  @     }
  @     this.sortIndex = column;
  @     var newRows = new Array();
  @     for (j = 0; j < this.tbody[0].rows.length; j++) {
  @        newRows[j] = this.tbody[0].rows[j];
  @     }
  @     if( this.sortIndex==this.prevColumn ){
  @       newRows.reverse();

  @     }else{
  @       newRows.sort(sortFn);
  @       this.prevColumn = this.sortIndex;
  @     }
  @     for (i=0;i<newRows.length;i++) {
  @       this.tbody[0].appendChild(newRows[i]);
  @     }



















  @   }
  @   this.sortText = function(a,b) {
  @     var i = thisObject.sortIndex;
  @     aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
  @     bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
  @     if(aa==bb) return 0;
  @     if(aa<bb) return -1;







|


>
>











>
>
>
>
>



|
>
>
>
>

|

>














|

>


|




>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
  return rc;
}

/*
** Output Javascript code that will enables sorting of the table with
** the id zTableId by clicking.
**
** The javascript was originally derived from:
**
**     http://www.webtoolkit.info/sortable-html-table.html
**
** But there have been extensive modifications.
**
** This variation allows column types to be expressed using the second
** argument.  Each character of the second argument represent a column.
**
**       t      Sort by text
**       n      Sort numerically
**       k      Sort by the data-sortkey property
**       x      This column is not sortable
**
** If there are fewer characters in zColumnTypes[] than their are columns,
** the all extra columns assume type "t" (text).
**
** The third parameter is the column that was initially sorted (using 1-based
** column numbers, like SQL).  Make this value 0 if none of the columns are
** initially sorted.  Make the value negative if the column is initially sorted
** in reverse order.
**
** Clicking on the same column header twice in a row inverts the sort.
*/
void output_table_sorting_javascript(
  const char *zTableId,      /* ID of table to sort */
  const char *zColumnTypes,  /* String for column types */
  int iInitSort              /* Initially sorted column. Leftmost is 1. 0 for NONE */
){
  @ <script>
  @ function SortableTable(tableEl,columnTypes,initSort){
  @   this.tbody = tableEl.getElementsByTagName('tbody');
  @   this.columnTypes = columnTypes;
  @   this.sort = function (cell) {
  @     var column = cell.cellIndex;
  @     var sortFn;
  @     switch( cell.sortType ){
  @       case "n":  sortFn = this.sortNumeric;  break;
  @       case "t":  sortFn = this.sortText;     break;
  @       case "k":  sortFn = this.sortKey;      break;
  @       case "x":  return;
  @     }
  @     this.sortIndex = column;
  @     var newRows = new Array();
  @     for (j = 0; j < this.tbody[0].rows.length; j++) {
  @        newRows[j] = this.tbody[0].rows[j];
  @     }
  @     if( this.sortIndex==Math.abs(this.prevColumn)-1 ){
  @       newRows.reverse();
  @       this.prevColumn = -this.prevColumn;
  @     }else{
  @       newRows.sort(sortFn);
  @       this.prevColumn = this.sortIndex+1;
  @     }
  @     for (i=0;i<newRows.length;i++) {
  @       this.tbody[0].appendChild(newRows[i]);
  @     }
  @     this.setHdrIcons();
  @   }
  @   this.setHdrIcons = function() {
  @     var arrowdiv = this.hdrRow.getElementsByClassName("sortarrow");
  @     while( arrowdiv[0] ){
  @        arrowdiv[0].parentNode.removeChild(arrowdiv[0]);
  @     }
  @     for (var i=0; i<this.hdrRow.cells.length; i++) {
  @       if( this.columnTypes[i]=='x' ) continue;
  @       if( this.prevColumn==i+1 ){
  @         arrow = "\u2b07";
  @       }else if( this.prevColumn==(-1-i) ){
  @         arrow = "\u2b06";
  @       }else{
  @         arrow = "\u21f3";
  @       }
  @       this.hdrRow.cells[i].innerHTML +=
  @             "<span class='sortarrow'>" + arrow + "</div>";
  @     }
  @   }
  @   this.sortText = function(a,b) {
  @     var i = thisObject.sortIndex;
  @     aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
  @     bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
  @     if(aa==bb) return 0;
  @     if(aa<bb) return -1;
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006


1007
1008

1009
1010
1011
1012
1013
1014
1015
1016

1017
1018
1019
1020
1021
1022
1023
1024
1025
  @     var i = thisObject.sortIndex;
  @     aa = a.cells[i].getAttribute("data-sortkey");
  @     bb = b.cells[i].getAttribute("data-sortkey");
  @     if(aa==bb) return 0;
  @     if(aa<bb) return -1;
  @     return 1;
  @   }
  @   var thisObject = this;
  @   var prevColumn = -1;
  @   var x = tableEl.getElementsByTagName('thead');
  @   if(!(this.tbody && this.tbody[0].rows && this.tbody[0].rows.length>0)){
  @     return;
  @   }
  @   if(x && x[0].rows && x[0].rows.length > 0) {
  @     var sortRow = x[0].rows[0];
  @   } else {
  @     return;
  @   }


  @   for (var i=0; i<sortRow.cells.length; i++) {
  @     if( columnTypes[i]=='x' ) continue;

  @     sortRow.cells[i].sTable = this;
  @     sortRow.cells[i].style.cursor = "pointer";
  @     sortRow.cells[i].sortType = columnTypes[i] || 't';
  @     sortRow.cells[i].onclick = function () {
  @       this.sTable.sort(this);
  @       return false;
  @     }
  @   }

  @ }
  @ var t = new SortableTable(gebi("%s(zTableId)"),"%s(zColumnTypes)");
  @ </script>
}


/*
** WEBPAGE: /rptview
**







<
<





|



>
>
|

>
|
|
|
|




>

|







1021
1022
1023
1024
1025
1026
1027


1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
  @     var i = thisObject.sortIndex;
  @     aa = a.cells[i].getAttribute("data-sortkey");
  @     bb = b.cells[i].getAttribute("data-sortkey");
  @     if(aa==bb) return 0;
  @     if(aa<bb) return -1;
  @     return 1;
  @   }


  @   var x = tableEl.getElementsByTagName('thead');
  @   if(!(this.tbody && this.tbody[0].rows && this.tbody[0].rows.length>0)){
  @     return;
  @   }
  @   if(x && x[0].rows && x[0].rows.length > 0) {
  @     this.hdrRow = x[0].rows[0];
  @   } else {
  @     return;
  @   }
  @   var thisObject = this;
  @   this.prevColumn = initSort;
  @   for (var i=0; i<this.hdrRow.cells.length; i++) {
  @     if( columnTypes[i]=='x' ) continue;
  @     var hdrcell = this.hdrRow.cells[i];
  @     hdrcell.sTable = this;
  @     hdrcell.style.cursor = "pointer";
  @     hdrcell.sortType = columnTypes[i] || 't';
  @     hdrcell.onclick = function () {
  @       this.sTable.sort(this);
  @       return false;
  @     }
  @   }
  @   this.setHdrIcons()
  @ }
  @ var t = new SortableTable(gebi("%s(zTableId)"),"%s(zColumnTypes)",%d(iInitSort));
  @ </script>
}


/*
** WEBPAGE: /rptview
**
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
    report_unrestrict_sql();
    @ </tbody></table>
    if( zErr1 ){
      @ <p class="reportError">Error: %h(zErr1)</p>
    }else if( zErr2 ){
      @ <p class="reportError">Error: %h(zErr2)</p>
    }
    output_table_sorting_javascript("reportTable","");
    style_footer();
  }else{
    report_restrict_sql(&zErr1);
    db_exec_readonly(g.db, zSql, output_tab_separated, &count, &zErr2);
    report_unrestrict_sql();
    cgi_set_content_type("text/plain");
  }







|







1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
    report_unrestrict_sql();
    @ </tbody></table>
    if( zErr1 ){
      @ <p class="reportError">Error: %h(zErr1)</p>
    }else if( zErr2 ){
      @ <p class="reportError">Error: %h(zErr2)</p>
    }
    output_table_sorting_javascript("reportTable","",0);
    style_footer();
  }else{
    report_restrict_sql(&zErr1);
    db_exec_readonly(g.db, zSql, output_tab_separated, &count, &zErr2);
    report_unrestrict_sql();
    cgi_set_content_type("text/plain");
  }
Changes to src/timeline.c.
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
    const char *zAvgLabel = includeMonth ? "month" : "year";
    int nAvg = iterations ? (nEventTotal/iterations) : 0;
    @ <br><div>Total events: %d(nEventTotal)
    @ <br>Average per active %s(zAvgLabel): %d(nAvg)
    @ </div>
  }
  if( !includeMonth ){
    output_table_sorting_javascript("statsTable","tnx");
  }
}

/*
** Implements the "byuser" view for /reports.
*/
static void stats_report_by_user(){







|







2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
    const char *zAvgLabel = includeMonth ? "month" : "year";
    int nAvg = iterations ? (nEventTotal/iterations) : 0;
    @ <br><div>Total events: %d(nEventTotal)
    @ <br>Average per active %s(zAvgLabel): %d(nAvg)
    @ </div>
  }
  if( !includeMonth ){
    output_table_sorting_javascript("statsTable","tnx",-1);
  }
}

/*
** Implements the "byuser" view for /reports.
*/
static void stats_report_by_user(){
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
    if(!nCount) continue /* arguable! Possible? */;
    else if(!nSize) nSize = 1;
    rowClass = ++nRowNumber % 2;
    nEventTotal += nCount;
    @<tr class='row%d(rowClass)'>
    @ <td>
    @ <a href="?view=bymonth&user=%h(zUser)&type=%c((char)statsReportType)">%h(zUser)</a>
    @ </td><td>%d(nCount)</td>
    @ <td>
    @ <div class='statistics-report-graph-line'
    @  style='width:%d(nSize)%%;'>&nbsp;</div>
    @ </td>
    @</tr>
    /*
      Potential improvement: calculate the min/max event counts and
      use percent-based graph bars.
    */
  }
  @ </tbody></table>
  db_finalize(&query);
  output_table_sorting_javascript("statsTable","tnx");
}

/*
** Implements the "byweekday" view for /reports.
*/
static void stats_report_day_of_week(){
  Stmt query = empty_Stmt;







|












|







2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
    if(!nCount) continue /* arguable! Possible? */;
    else if(!nSize) nSize = 1;
    rowClass = ++nRowNumber % 2;
    nEventTotal += nCount;
    @<tr class='row%d(rowClass)'>
    @ <td>
    @ <a href="?view=bymonth&user=%h(zUser)&type=%c((char)statsReportType)">%h(zUser)</a>
    @ </td><td data-sortkey='%08x(-nCount)'>%d(nCount)</td>
    @ <td>
    @ <div class='statistics-report-graph-line'
    @  style='width:%d(nSize)%%;'>&nbsp;</div>
    @ </td>
    @</tr>
    /*
      Potential improvement: calculate the min/max event counts and
      use percent-based graph bars.
    */
  }
  @ </tbody></table>
  db_finalize(&query);
  output_table_sorting_javascript("statsTable","tkx",2);
}

/*
** Implements the "byweekday" view for /reports.
*/
static void stats_report_day_of_week(){
  Stmt query = empty_Stmt;
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
    @ <div class='statistics-report-graph-line'
    @  style='width:%d(nSize)%%;'>&nbsp;</div>
    @ </td>
    @</tr>
  }
  @ </tbody></table>
  db_finalize(&query);
  output_table_sorting_javascript("statsTable","ntnx");
}


/*
** Helper for stats_report_by_month_year(), which generates a list of
** week numbers. zTimeframe should be either a timeframe in the form YYYY
** or YYYY-MM.







|







2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
    @ <div class='statistics-report-graph-line'
    @  style='width:%d(nSize)%%;'>&nbsp;</div>
    @ </td>
    @</tr>
  }
  @ </tbody></table>
  db_finalize(&query);
  output_table_sorting_javascript("statsTable","ntnx",1);
}


/*
** Helper for stats_report_by_month_year(), which generates a list of
** week numbers. zTimeframe should be either a timeframe in the form YYYY
** or YYYY-MM.
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
    cgi_printf("</tbody></table>");
    if(total){
      int nAvg = iterations ? (total/iterations) : 0;
      cgi_printf("<br><div>Total events: %d<br>"
                 "Average per active week: %d</div>",
                 total, nAvg);
    }
    output_table_sorting_javascript("statsTable","tnx");
  }
}

/*
** WEBPAGE: reports
**
** Shows activity reports for the repository.







|







2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
    cgi_printf("</tbody></table>");
    if(total){
      int nAvg = iterations ? (total/iterations) : 0;
      cgi_printf("<br><div>Total events: %d<br>"
                 "Average per active week: %d</div>",
                 total, nAvg);
    }
    output_table_sorting_javascript("statsTable","tnx",-1);
  }
}

/*
** WEBPAGE: reports
**
** Shows activity reports for the repository.