Jump to content

User:Splarka/summabletables.js

From Wikipedia, the free encyclopedia
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/* Summable tables, version [0.0.1a]
Originally from http://en.wikipedia.org/wiki/User:Splarka/summabletables.js
 
Framework for automatically calculating totals for table columns.

-------------------------------- Control classes --------------------------------
  CLASS                 TAGS    EFFECT
 summable              table   Defines summable table
 summable-row-skip     tr      Defines rows to skip calculating on
 summable-row-result   tr      Defines result/output rows (optional)
 summable-row-cols     tr      Defines the row defining the columns (optional)
 summable-col          td/th   Defines a summable column (summable-row-cols only)

Usage:
* COLSPAN AND ROWSPAN WILL PROBABLY BREAK SHIT, except on skipped rows
* Usually defining a summable-row-cols isn't necessary, unless you want to use a colspan on the top row.
** All cells in summable-row-cols with summable-col are determined to define a sortable column (numerically).
*** Using colspan will screw this up. 
**** Possibly this could be worked around, but rowspan couldn't without storing the whole damn table, ugh.
* Leaving off a defined summable-row-result allows non-javascript users to not see an empty row.
** If generating a result row, and if the first cell isn't summable, the word 'Total' is inserted there.
*** This can be localised via: var summableTotalText = 'Whatever';

Notes:
* Takes the contents of the cell, strips everything that isn't numeric (0 to 9, plus, minus, period)
** Only concession is space, anything after a valid value and a space is ignored, eg "a 5" is 5 but "7 5" is 7.
** This version only works with period-delimited fractions currently.
*** Probably could use some weird JS-global definable formatnum rules. Meh. Sortable is horrible for i18n too.
** Doesn't support exponents or dates or any weird shit yet. Should ignore currency signs.
* Should work fine with sortable tables (needs testing).
* Uses .getElementsByTagName to find 'tr' (doublechecking depth), but .childNodes to get TD/TH
** Is that naughty?

Minimal example:

{| class="summable" border="1"
|- class="summable-row-skip"
|| Name
|class="summable-col"| Bob
|class="summable-col"| Joe
|class="summable-col"| Eve
|class="summable-col"| Ted
|- 
|| Game 1 || 5 || 7 || 0 || -1
|-
|| Game 2 || 1 || 2 || 4 || 3
|-
|| Game 3 || 7 || 2 || 6 || 2
|-
|| Game 4 || 0 || 3 || 12 || 2
|- class="summable-row-result"
|| Totals || || || ||
|}

*/

// debug
appendCSS('.summable-counted {background-color:#ffbbff;}'
 + '.summable-col {background-color:#ffffbb;}'
 + '.summable-generated-result {background-color:#bbffff;}'
)


// init tables, find them and give them IDs, and scan them for summable bits
function sumtables_init() {
  var idnum = 0;
  var tables = getElementsByClassName(document, 'table', 'summable');
    for (var i=0;i<tables.length;i++) {
      if (!tables[i].id) {
        tables[i].setAttribute('id','summable_table_id_' + idnum);
        ++idnum;
      }
    sumtables_sum(tables[i]);
  }
}
$(sumtables_init);

//Do the main addition
function sumtables_sum(table) {

  // we only want to go one level deep, so lets shift focus to the tbody
  var tbody = table.getElementsByTagName('tbody');
  if(tbody && tbody.length > 0) table = tbody[0]

  // set up fillable arrays and globals
  var tr = table.getElementsByTagName('tr');
  if(tr.length == 0) return
  var trsummable = [];
  var trresult = [];
  var trdef = tr[0];
  var cellsummable = [];
  var totaltxt = window.summableTotalText || 'Total';

  // iterate over all the rows, looking for summable, defining, skippable, and the result
  for(var i=0;i<tr.length;i++) {
    if(tr[i].parentNode != table) continue    //make sure we're only one level deep, don't grab child table elements
    var classes = ' ' + tr[i].className + ' ';
    if(classes.indexOf(' summable-row-skip ') != -1) {
      // just skip
    } else if(classes.indexOf(' summable-row-result ') != -1) {
      trresult.push(tr[i]);
    } else {
      trsummable.push(tr[i]);
    }
    // use this row for the defintions, otherwise use the first row by default
    if(classes.indexOf(' summable-row-cols ') != -1) trdef = tr[i]
  }
  if(trsummable == 0) return

  // lets look for class="summable-col", and flag these colums as summable
  var dcell = getTableCells(trdef);
  for(var i=0;i<dcell.length;i++) {
    var classes = ' ' + dcell[i].className + ' ';
    if(classes.indexOf(' summable-col ') != -1) {
      cellsummable[i] = 0;
    }
  }

  // iterate over the summable rows and find the summable columns, keep a running count
  for(var i=0;i<trsummable.length;i++) {
    var cell = getTableCells(trsummable[i]);
    for(var j=0;j<cell.length;j++) {    
      if(typeof cellsummable[j] != 'undefined') {
        cell[j].className += ' summable-counted';
        var txt = getInnerText(cell[j]);
        // normalize minus signs, and strip everything not number related
        txt = txt.replace(/[\u2010\u2011\u2012\u2013\u2014\u2015\u2212]/g,'-');    
        txt = txt.replace(/[^0-9+. -]/g,'');
        cellsummable[j] += parseFloat(txt);
      }
    }
  }

  // if no summable-row-result was found, just create one at the bottom
  if(trresult.length == 0) {
    var trr = document.createElement('tr');
    trr.setAttribute('class','summable-row-result sortbottom');
    trr.style.fontWeight = 'bold';
    var cell = getTableCells(trdef);
    for(var j=0;j<cell.length;j++) {    
      var td = document.createElement('td');
      // if the first cell isn't sortable, insert 'Total' literally
      if(typeof cellsummable[j] == 'undefined' && j == 0) {
        td.appendChild(document.createTextNode(totaltxt));
      }
      trr.appendChild(td);
    }
    table.appendChild(trr);
    trresult = [trr];
  }

  // generate the results
  for(var i=0;i<trresult.length;i++) {
    var cell = getTableCells(trresult[i]);
    for(var j=0;j<cell.length;j++) {    
      if(typeof cellsummable[j] != 'undefined') {
        //empty this cell and
        while(cell[j].firstChild) cell[j].removeChild(cell[j].firstChild)
        var span = document.createElement('span');
        span.setAttribute('class','summable-generated-result');
        // convert to string, and regex for parseFloat inaccuracies, parseFloat(.4+.2) => 0.6000000000000001
        var txt = '' + cellsummable[j];
        txt = txt.replace(/\.?(\d)\1{10,}\d?$/,''); 
        span.appendChild(document.createTextNode(txt));
        cell[j].appendChild(span);
      }
    }    
  }
}

// helper function, get any first generation <td> or <th>
function getTableCells(row) {
  var tdth = [];
  var cell = row.childNodes
  for(var i=0;i<cell.length;i++) {
    if(cell[i].tagName == 'TD' || cell[i].tagName == 'TH') {
      tdth.push(cell[i]);
    }
  }
  return tdth;
}