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