Posted in js
16111
6:20 am, August 31, 2018
 

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

View Statistics
This Week
403
This Month
1478
This Year
3816

No Items Found.

Add Comment
Type in a Nick Name here
 
Other Items in js
jqueryui date selector with examples and code with custom date formatting fancybox youtube showing video links in a lightbox - updated with fixed code parts fix for Uncaught (in promise) Error: reCAPTCHA placeholder element must be empty enable tinymce on a target textarea by id Generate Random Whole Numbers with JavaScript Function Generate Random Fractions with JavaScript jquery document ready make clickable element with clickable class using getElementById and innerHTML to change the html of content no jquery preserve tabs in textarea when tab key is pressed jqueryui includes css and js using vue and json data jqueryui date selector with examples and code jquery accordion fancybox youtube showing video links in a lightbox jquery jqueryui vue script includes get select option form value with jquery show the year with js jquery clone and append elements stacktable jQuery plugin for stacking tables on small screens load content with jquery find all elements add class jquery digital clock with jquery using regex with replace to replace all instances of something in a string random string generator guid set and check a cookie using js cookie validate email address from string check length of element jquery access hacker news json firebase api via jquery testing chartjs clipboard copy js tiny mce editor tinymce do something later with settimeout or loop with setinterval validate form data using javascript to check required html elements mithril testing parallax js scroll testing change the water colour in google maps for an already initialised map scrollbar replacement simplebar load google sheet data into json string with jquery change favicon with jquery add this Slick Slider Carousel change the window title flems embed in url detect window scroll position jquery truncate string using jquery round number with js google map with overlay data
Related Search Terms
Search Code
Search Code by entering your search text above.
Welcome

This is my test area for webdev. I keep a collection of code here, mostly for my reference. Also if i find a good link, i usually add it here and then forget about it. more...

Subscribe to weekly updates about things i have added to the site or thought interesting during the last week.

You could also follow me on twitter or not... does anyone even use twitter anymore?

If you found something useful or like my work, you can buy me a coffee here. Mmm Coffee. ☕

❤️👩‍💻🎮

🪦 2000 - 16 Oct 2022 - Boots
Random Quote
There is a qualitative and quantitative difference between a day that begins with a little exercise, a book, meditation, a good meal, a thoughtful walk, and the start of a day that begins with a smartphone in bed.
Unknown
Random CSS Property

negative

When defining custom counter styles, the negative descriptor lets you alter the representations of negative counter values, by providing a way to specify symbols to be appended or prepended to the counter representation when the value is negative.
negative (@counter-style) css reference