stacktable jQuery plugin for stacking tables on small screens
stacktable.js
The purpose of stacktable.js is to give you an easy way of converting wide tables to a format that will work better on small screens. It creates a copy of the table that is converted into a 2-column key/value format.
I havent actually tested this one yet but it looks like a nice way to stack tables on smaller screens and devices.
On testing this, it seems to just stack the table, which makes sense from the name of the script. However you can do this with css, so not sure if this is really worth a whole plugin.
Update: 9 Feb 2022 - It seems my old demo of this was not working, so fixed up the demo. Also as seen on the git page, the author now recommends a CSS only solution rather than using javascript for this.
The demo seems to also break, so Ill mark this one as officially broken and move onto the CSS Only Table Stacking Solution.
HTML
<table id="responsive-example-table" class="large-only stacktable" cellspacing="0">
<tbody>
<tr align="left">
<th width="30%">Stuff</th>
<th width="12%">Rate</th>
<th width="12%">Amount</th>
<th width="12%">Points</th>
<th width="12%">Number</th>
<th width="18%">Type</th>
<th width="12%">Name</th>
</tr>
<tr>
<td>Something</td>
<td>3.375%</td>
<td>$123.12</td>
<td>1.125</td>
<td>4,000</td>
<td>Potato</td>
<td>Paul</td>
</tr>
<tr>
<td>Something Else</td>
<td>2.750%</td>
<td>$345.23</td>
<td>5</td>
<td>180</td>
<td>Spaceship</td>
<td>Skippy</td>
</tr>
<tr>
<td colspan="7" class="sub">Subgroup Header</td>
</tr>
<tr>
<td>Another Thing</td>
<td>3.375%</td>
<td>$563.12</td>
<td>222</td>
<td>60</td>
<td>Gym Shoe</td>
<td>George</td>
</tr>
<tr>
<td>Thing w/Less Stuff</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>Harmonica</td>
<td>Helga</td>
</tr>
<tr>
<td>Last Thing</td>
<td></td>
<td></td>
<td></td>
<td>4</td>
<td>Meaning of Life</td>
<td></td>
</tr>
</tbody>
</table>
CSS
.stacktable { width: 100%; }
.st-head-row { padding-top: 1em; }
.st-head-row.st-head-row-main { font-size: 1.5em; padding-top: 0; }
.st-key { width: 49%; text-align: right; padding-right: 1%; }
.st-val { width: 49%; padding-left: 1%; }
/* RESPONSIVE EXAMPLE */
.stacktable.large-only { display: table; }
.stacktable.small-only { display: none; }
@media (max-width: 800px) {
.stacktable.large-only { display: none; }
.stacktable.small-only { display: table; }
}
Scripts
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
Javascript
/**
* stacktable.js
* Author & copyright (c) 2012: John Polacek
* CardTable by: Justin McNally (2015)
* MIT license
*
* Page: http://johnpolacek.github.com/stacktable.js
* Repo: https://github.com/johnpolacek/stacktable.js/
*
* jQuery plugin for stacking tables on small screens
* Requires jQuery version 1.7 or above
*
*/
;(function($) {
$.fn.cardtable = function(options) {
var $tables = this,
defaults = {headIndex:0},
settings = $.extend({}, defaults, options),
headIndex;
// checking the "headIndex" option presence... or defaults it to 0
if(options && options.headIndex)
headIndex = options.headIndex;
else
headIndex = 0;
return $tables.each(function() {
var $table = $(this);
if ($table.hasClass('stacktable')) {
return;
}
var table_css = $(this).prop('class');
var $stacktable = $('<div></div>');
if (typeof settings.myClass !== 'undefined') $stacktable.addClass(settings.myClass);
var markup = '';
var $caption, $topRow, headMarkup, bodyMarkup, tr_class;
$table.addClass('stacktable large-only');
$caption = $table.find(">caption").clone();
$topRow = $table.find('>thead>tr,>tbody>tr,>tfoot>tr,>tr').eq(0);
// avoid duplication when paginating
$table.siblings().filter('.small-only').remove();
// using rowIndex and cellIndex in order to reduce ambiguity
$table.find('>tbody>tr').each(function() {
// declaring headMarkup and bodyMarkup, to be used for separately head and body of single records
headMarkup = '';
bodyMarkup = '';
tr_class = $(this).prop('class');
// for the first row, "headIndex" cell is the head of the table
// for the other rows, put the "headIndex" cell as the head for that row
// then iterate through the key/values
$(this).find('>td,>th').each(function(cellIndex) {
if ($(this).html() !== ''){
bodyMarkup += '<tr class="' + tr_class +'">';
if ($topRow.find('>td,>th').eq(cellIndex).html()){
bodyMarkup += '<td class="st-key">'+$topRow.find('>td,>th').eq(cellIndex).html()+'</td>';
} else {
bodyMarkup += '<td class="st-key"></td>';
}
bodyMarkup += '<td class="st-val '+$(this).prop('class') +'">'+$(this).html()+'</td>';
bodyMarkup += '</tr>';
}
});
markup += '<table class=" '+ table_css +' stacktable small-only"><tbody>' + headMarkup + bodyMarkup + '</tbody></table>';
});
$table.find('>tfoot>tr>td').each(function(rowIndex,value) {
if ($.trim($(value).text()) !== '') {
markup += '<table class="'+ table_css + ' stacktable small-only"><tbody><tr><td>' + $(value).html() + '</td></tr></tbody></table>';
}
});
$stacktable.prepend($caption);
$stacktable.append($(markup));
$table.before($stacktable);
});
};
$.fn.stacktable = function(options) {
var $tables = this,
defaults = {headIndex:0,displayHeader:true},
settings = $.extend({}, defaults, options),
headIndex;
// checking the "headIndex" option presence... or defaults it to 0
if(options && options.headIndex)
headIndex = options.headIndex;
else
headIndex = 0;
return $tables.each(function() {
var table_css = $(this).prop('class');
var $stacktable = $('<table class="'+ table_css +' stacktable small-only"><tbody></tbody></table>');
if (typeof settings.myClass !== 'undefined') $stacktable.addClass(settings.myClass);
var markup = '';
var $table, $caption, $topRow, headMarkup, bodyMarkup, tr_class, displayHeader;
$table = $(this);
$table.addClass('stacktable large-only');
$caption = $table.find(">caption").clone();
$topRow = $table.find('>thead>tr,>tbody>tr,>tfoot>tr').eq(0);
displayHeader = $table.data('display-header') === undefined ? settings.displayHeader : $table.data('display-header');
// using rowIndex and cellIndex in order to reduce ambiguity
$table.find('>tbody>tr, >thead>tr').each(function(rowIndex) {
// declaring headMarkup and bodyMarkup, to be used for separately head and body of single records
headMarkup = '';
bodyMarkup = '';
tr_class = $(this).prop('class');
// for the first row, "headIndex" cell is the head of the table
if (rowIndex === 0) {
// the main heading goes into the markup variable
if (displayHeader) {
markup += '<tr class=" '+tr_class +' "><th class="st-head-row st-head-row-main" colspan="2">'+$(this).find('>th,>td').eq(headIndex).html()+'</th></tr>';
}
} else {
// for the other rows, put the "headIndex" cell as the head for that row
// then iterate through the key/values
$(this).find('>td,>th').each(function(cellIndex) {
if (cellIndex === headIndex) {
headMarkup = '<tr class="'+ tr_class+'"><th class="st-head-row" colspan="2">'+$(this).html()+'</th></tr>';
} else {
if ($(this).html() !== ''){
bodyMarkup += '<tr class="' + tr_class +'">';
if ($topRow.find('>td,>th').eq(cellIndex).html()){
bodyMarkup += '<td class="st-key">'+$topRow.find('>td,>th').eq(cellIndex).html()+'</td>';
} else {
bodyMarkup += '<td class="st-key"></td>';
}
bodyMarkup += '<td class="st-val '+$(this).prop('class') +'">'+$(this).html()+'</td>';
bodyMarkup += '</tr>';
}
}
});
markup += headMarkup + bodyMarkup;
}
});
$stacktable.prepend($caption);
$stacktable.append($(markup));
$table.before($stacktable);
});
};
$.fn.stackcolumns = function(options) {
var $tables = this,
defaults = {},
settings = $.extend({}, defaults, options);
return $tables.each(function() {
var $table = $(this);
var $caption = $table.find(">caption").clone();
var num_cols = $table.find('>thead>tr,>tbody>tr,>tfoot>tr').eq(0).find('>td,>th').length; //first table <tr> must not contain colspans, or add sum(colspan-1) here.
if(num_cols<3) //stackcolumns has no effect on tables with less than 3 columns
return;
var $stackcolumns = $('<table class="stacktable small-only"></table>');
if (typeof settings.myClass !== 'undefined') $stackcolumns.addClass(settings.myClass);
$table.addClass('stacktable large-only');
var tb = $('<tbody></tbody>');
var col_i = 1; //col index starts at 0 -> start copy at second column.
while (col_i < num_cols) {
$table.find('>thead>tr,>tbody>tr,>tfoot>tr').each(function(index) {
var tem = $('<tr></tr>'); // todo opt. copy styles of $this; todo check if parent is thead or tfoot to handle accordingly
if(index === 0) tem.addClass("st-head-row st-head-row-main");
var first = $(this).find('>td,>th').eq(0).clone().addClass("st-key");
var target = col_i;
// if colspan apply, recompute target for second cell.
if ($(this).find("*[colspan]").length) {
var i =0;
$(this).find('>td,>th').each(function() {
var cs = $(this).attr("colspan");
if (cs) {
cs = parseInt(cs, 10);
target -= cs-1;
if ((i+cs) > (col_i)) //out of current bounds
target += i + cs - col_i -1;
i += cs;
} else {
i++;
}
if (i > col_i)
return false; //target is set; break.
});
}
var second = $(this).find('>td,>th').eq(target).clone().addClass("st-val").removeAttr("colspan");
tem.append(first, second);
tb.append(tem);
});
++col_i;
}
$stackcolumns.append($(tb));
$stackcolumns.prepend($caption);
$table.before($stackcolumns);
});
};
}(jQuery));
Stuff | Rate | Amount | Points | Number | Type | Name |
---|---|---|---|---|---|---|
Something | 3.375% | $123.12 | 1.125 | 4,000 | Potato | Paul |
Something Else | 2.750% | $345.23 | 5 | 180 | Spaceship | Skippy |
Subgroup Header | ||||||
Another Thing | 3.375% | $563.12 | 222 | 60 | Gym Shoe | George |
Thing w/Less Stuff | Harmonica | Helga | ||||
Last Thing | 4 | Meaning of Life |
External Link for stacktable jQuery plugin for stacking tables on small screens