User:Splarka/summabletables.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
![]() | Documentation for this user script can be added at User:Splarka/summabletables. |
/* 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;
}