├── jquery.tablecards.css ├── README.md └── jquery.tablecards.js /jquery.tablecards.css: -------------------------------------------------------------------------------- 1 | .tablecard-col, .tablecard-show th, .tablecard-show td { display: none; } 2 | .tablecard-show th.tablecard-mode-card, .tablecard-show td.tablecard-mode-card { display: table-cell; } 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jQuery Responsive Table Cards 2 | ============================= 3 | 4 | This jQuery plugin generates cards to convert wide tables into one or two column tables on smaller screen sizes by injecting a new column into each row of the table and using CSS to switch between table and card modes. 5 | 6 | When generating cards, each row (including thead and tfoot) is processed using a string-based template. This makes for highly efficient delivery of naturally tabular data to all devices but only generate the extra column on smaller displays. 7 | 8 | Combined with [TableBodyScroll](https://github.com/cubiclesoft/jquery-tablebodyscroll), traditional tables can be displayed on all devices in a neat, compact format. 9 | 10 | [![Donate](https://cubiclesoft.com/res/donate-shield.png)](https://cubiclesoft.com/donate/) [![Discord](https://img.shields.io/discord/777282089980526602?label=chat&logo=discord)](https://cubiclesoft.com/product-support/github/) 11 | 12 | Usage 13 | ----- 14 | 15 | ```html 16 | 17 | 18 | 49 | ``` 50 | 51 | There are several live examples under "Add Entry" in the [Admin Pack Demo](http://barebonescms.com/demos/admin_pack/admin.php). 52 | 53 | Limitations 54 | ----------- 55 | 56 | Due to the way Responsive Table Cards works, the 'id' attribute may not be used in any part of the table's contents. Use 'class' and 'data-' instead. Doing so can be more semantic that way anyway. 57 | 58 | Attached events on elements are also lost during card creation. This is unavoidable. Listen for the 'tablecards:mode' event on the table to trigger the first time this plugin switches to card mode. 59 | 60 | Options 61 | ------- 62 | 63 | The following options may be passed to TableCards: 64 | 65 | * width - An integer to set the threshold after which to switch to and from card mode OR null to automatically determine the width later if the table exceeds its parent's width (Default is null). 66 | * extracols - An array containing the columns to not hide in the header (Default is []). 67 | * tokenstart - A string containing the prefix to use for the templates to reference column numbers (Default is '%'). 68 | * head - A string or an array of strings containing the templates to use in rotation for each row of the 'thead's (Default is ' '). 69 | * body - A string or an array of strings containing the templates to use in rotation for each row of the 'tbody's (Default is ' '). 70 | * foot - A string or an array of strings containing the templates to use in rotation for each row of the 'tfoot's (Default is ' '). 71 | * postinit - A callback function to run after each TableCards initialization is run (Default is null). 72 | 73 | To destroy the instance and restore the table to its initial state, simply call: `$('#mytable').TableCards('destroy');` 74 | 75 | Events 76 | ------ 77 | 78 | The following custom events may be listened for: 79 | 80 | * tablecards:mode - Notifies after switching the mode. An optional parameter is passed containing a string with the new mode - either 'card' or 'table'. 81 | 82 | The following custom events may be manually triggered: 83 | 84 | * tablecards:resize - Notifies TableCards that the table has been resized. 85 | * tablecards:datachanged - Notifies TableCards that the data in the table has changed and to reset any width calculations at the first available opportunity. 86 | -------------------------------------------------------------------------------- /jquery.tablecards.js: -------------------------------------------------------------------------------- 1 | // jQuery plugin to convert tables to responsive cards via templates. 2 | // (C) 2017 CubicleSoft. All Rights Reserved. 3 | 4 | (function($) { 5 | $.fn.TableCards = function(options) { 6 | // Reset TableCards (e.g. card template changes). 7 | this.each(function() { 8 | var $this = $(this); 9 | 10 | $this.removeClass('tablecards'); 11 | $this.removeClass('tablecard-show'); 12 | $this.children('thead, tbody, tfoot').children('tr').children('th, td').each(function() { 13 | $this2 = $(this); 14 | 15 | if ($this2.hasClass('tablecard-col')) $this2.remove(); 16 | else $this2.removeClass('tablecard-mode-card'); 17 | }); 18 | 19 | $this.off('tablecards:resize'); 20 | $this.off('tablecards:datachanged'); 21 | }); 22 | 23 | if (typeof(options) === 'string' && options === 'destroy') return this; 24 | 25 | var settings = $.extend({}, $.fn.TableCards.defaults, options); 26 | 27 | // Allows rotation through input templates per row. 28 | if (typeof(settings.head) === 'string') settings.head = [settings.head]; 29 | if (typeof(settings.body) === 'string') settings.body = [settings.body]; 30 | if (typeof(settings.foot) === 'string') settings.foot = [settings.foot]; 31 | 32 | // Tokenize the templates. 33 | var ParseTemplate = function(templatestr) { 34 | if (typeof(templatestr) === 'function') return templatestr; 35 | 36 | var template = []; 37 | var tokens = templatestr.split(settings.tokenstart); 38 | template.push(tokens.shift()); 39 | var zero = '0'.charCodeAt(0); 40 | var nine = '9'.charCodeAt(0); 41 | for (var x = 0; x < tokens.length; x++) 42 | { 43 | var str = tokens[x]; 44 | 45 | if (str.length == 0) template.push(settings.tokenstart); 46 | else 47 | { 48 | var x2; 49 | for (x2 = 0; x2 < str.length && str.charCodeAt(x2) >= zero && str.charCodeAt(x2) <= nine; x2++) 50 | { 51 | } 52 | 53 | if (x2 > 0) template.push(parseInt(str.substr(0, x2), 10)); 54 | 55 | template.push(str.substr(x2)); 56 | } 57 | } 58 | 59 | return template; 60 | }; 61 | 62 | var ParseTemplates = function(templates) { 63 | var result = []; 64 | 65 | for (var x = 0; x < templates.length; x++) result.push(ParseTemplate(templates[x])); 66 | 67 | return result; 68 | }; 69 | 70 | // Apply a template to one or more rows of data and generate card columns. 71 | var ApplyTemplates = function(trs, templates, tag, mainbody) { 72 | var num = 0; 73 | 74 | trs.each(function() { 75 | var template = templates[num]; 76 | 77 | num++; 78 | if (num >= templates.length) num = 0; 79 | 80 | var html = ''; 81 | 82 | if (typeof(template) === 'function') html += template(); 83 | else 84 | { 85 | var children = $(this).children('th, td'); 86 | 87 | for (var x = 0; x < template.length; x++) 88 | { 89 | if (typeof(template[x]) === 'string') html += template[x]; 90 | else if (template[x] > 0 && template[x] <= children.length) html += $(children.get(template[x] - 1)).html(); 91 | } 92 | } 93 | 94 | $(this).append('<' + tag + ' class="tablecard-col' + (mainbody ? ' tablecard-mode-card' : '') + '">' + html + ''); 95 | }); 96 | }; 97 | 98 | // Add a class to always show specified columns when in card mode. 99 | var AddCardClass = function(trs) { 100 | if (settings.extracols.length) 101 | { 102 | trs.first().children('th.tablecard-col, td.tablecard-col').addClass('tablecard-mode-card'); 103 | 104 | for (var x = 0; x < settings.extracols.length; x++) 105 | { 106 | var col = settings.extracols[x]; 107 | if (typeof(col) !== 'number') continue; 108 | 109 | if (col > 0) 110 | { 111 | trs.each(function() { 112 | $(this).children('th, td').slice(col - 1, col).addClass('tablecard-mode-card'); 113 | }); 114 | } 115 | } 116 | } 117 | }; 118 | 119 | var headtemplates = null, bodytemplates, foottemplates; 120 | 121 | var Init = function($this) { 122 | if (!headtemplates) 123 | { 124 | headtemplates = ParseTemplates(settings.head); 125 | bodytemplates = ParseTemplates(settings.body); 126 | foottemplates = ParseTemplates(settings.foot); 127 | } 128 | 129 | // Add a class so that CSS can make any necessary adjustments. 130 | $this.addClass('tablecards'); 131 | 132 | // Alter table header. 133 | var theads = $this.children('thead'); 134 | if (!theads.length) 135 | { 136 | $this.prepend(''); 137 | theads = $this.children('thead'); 138 | } 139 | var headtrs = theads.children('tr'); 140 | ApplyTemplates(headtrs, headtemplates, 'th', false); 141 | AddCardClass(headtrs); 142 | 143 | // Alter table body. 144 | var tbodys = $this.children('tbody'); 145 | var bodytrs = tbodys.children('tr'); 146 | ApplyTemplates(bodytrs, bodytemplates, 'td', true); 147 | AddCardClass(bodytrs); 148 | 149 | // Alter table footer (if any). 150 | var tfoots = $this.children('tfoot'); 151 | var foottrs = tfoots.children('tr'); 152 | ApplyTemplates(foottrs, foottemplates, 'td', false); 153 | AddCardClass(foottrs); 154 | }; 155 | 156 | return this.each(function() { 157 | var $this = $(this); 158 | var minwidth = settings.width; 159 | var resetminwidth = false; 160 | var currmode = 'table'; 161 | var initialized = false; 162 | 163 | var HandleResize = function(e) { 164 | var tablewidth = $this.outerWidth(); 165 | if (tablewidth < 1) return; 166 | 167 | var parentwidth = $this.parent().innerWidth(); 168 | 169 | if (!minwidth && currmode === 'table') 170 | { 171 | if (tablewidth > parentwidth + 1) minwidth = tablewidth; 172 | } 173 | 174 | if (minwidth) 175 | { 176 | if ((parentwidth < minwidth || tablewidth > parentwidth + 1) && currmode === 'table') 177 | { 178 | if (!initialized) 179 | { 180 | Init($this); 181 | initialized = true; 182 | } 183 | 184 | $this.addClass('tablecard-show'); 185 | if (!settings.extracols.length) $this.addClass('tablecard-show-nohead'); 186 | currmode = 'card'; 187 | 188 | //console.log('mode = ' + currmode + ', parentwidth = ' + parentwidth + ', minwidth = ' + minwidth + ', tablewidth = ' + tablewidth); 189 | 190 | setTimeout(function() { $this.trigger('tablecards:mode', currmode); }, 0); 191 | } 192 | else if (parentwidth > minwidth + 1 && currmode === 'card') 193 | { 194 | $this.removeClass('tablecard-show'); 195 | if (!settings.extracols.length) $this.removeClass('tablecard-show-nohead'); 196 | currmode = 'table'; 197 | 198 | //console.log('mode = ' + currmode + ', parentwidth = ' + parentwidth + ', minwidth = ' + minwidth + ', tablewidth = ' + tablewidth); 199 | 200 | if (resetminwidth) 201 | { 202 | minwidth = settings.width; 203 | resetminwidth = false; 204 | 205 | setTimeout(HandleResize, 20); 206 | } 207 | 208 | setTimeout(function() { $this.trigger('tablecards:mode', currmode); }, 0); 209 | } 210 | } 211 | }; 212 | 213 | $this.on('tablecards:resize', HandleResize); 214 | setTimeout(HandleResize, 0); 215 | 216 | $this.on('tablecards:datachanged', function() { 217 | if (currmode === 'card') resetminwidth = true; 218 | else 219 | { 220 | minwidth = settings.width; 221 | setTimeout(HandleResize, 0); 222 | } 223 | }); 224 | 225 | if (settings.postinit) settings.postinit(this, settings); 226 | }); 227 | }; 228 | 229 | $.fn.TableCards.defaults = { 230 | 'width' : null, 231 | 'extracols' : [], 232 | 'tokenstart' : '%', 233 | 'head' : ' ', 234 | 'body' : ' ', 235 | 'foot' : ' ', 236 | 'postinit' : null 237 | }; 238 | }(jQuery)); 239 | --------------------------------------------------------------------------------