User:Splarka/summabletables.js

/* 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:



// 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 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 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 or 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; }