Freeze table header while allowing dynamic widths - html

The solutions I've seen for freezing a table header row is by creating another table with just the header, making the first table's header hidden. This works only if the widths are pre set. But I want my table's columns to be sized according to their contents. I also want the table to not have a fixed height, because I want to use it across devices, and want to use all available space (minus reserved one for a header)

The approach to this is similar to your other question. Except in this case, the <tbody> is the viewport, and we'll need to give its rows display: table, so their widths can be manually set.
I've added some JS to essentially clone the top rows with the most content and append them as visually hidden rows to <thead> to help get it to the right width dynamically. I've also re-tuned the CSS. Note layout still breaks if <th> content is longer than <td> content.
$(function() {
var $table = $('table');
var $body = $table.find('tbody');
var rowTexts = $body.find('tr').map(function() {
return $(this).text().trim();
}).toArray().sort(function(a, b) {
return (a.length > b.length) ? -1 : (a.length < b.length) ? 1 : 0;
});
if (rowTexts.length > 5) {
rowTexts = rowTexts.slice(0, 5);
}
var $head = $table.find('thead');
rowTexts.forEach(function(text) {
var $hiddenRow = $body.find('tr:contains(' + text + ')').clone().addClass('hidden');
$head.append($hiddenRow);
});
});
body {
height: 100%;
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
text-align: left;
margin-top: 44px;
width: 100%;
}
td,
th {
border-bottom: 1px solid black;
height: 44px;
line-height: 44px;
padding: 0 1em;
}
tr.hidden td {
border: 0;
height: 0;
line-height: 0;
overflow: hidden;
}
thead {
display: table;
position: fixed;
top: 0;
width: 100%;
}
thead tr:not(.hidden) {
background: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Key that's longer than usual</td>
<td>Value</td>
</tr>
<tr>
<td>Key</td>
<td>Value that's longer than usual</td>
</tr>
<tr>
<td>Key</td>
<td>Value</td>
</tr>
<tr>
<td>Key</td>
<td>Value</td>
</tr>
<tr>
<td>Key</td>
<td>Value</td>
</tr>
<tr>
<td>Key</td>
<td>Value</td>
</tr>
<tr>
<td>Key</td>
<td>Value</td>
</tr>
<tr>
<td>Key</td>
<td>Value</td>
</tr>
<tr>
<td>Key</td>
<td>Value</td>
</tr>
<tr>
<td>Key</td>
<td>Value</td>
</tr>
<tr>
<td>Key</td>
<td>Value</td>
</tr>
<tr>
<td>Last Key</td>
<td>Last Value</td>
</tr>
</tbody>
</table>

Related

Implement Word freeze functionality in HTML table

As I am a beginner in designing and have not much idea about CSS manipulating so need some help.
I have one HTML page and want to implement the same functionality of freeze as we are using in Word.
Here I am having the table and want to freeze the first 4 columns so it doesn't move and stay sticky and the last column to show the total and only allow in between columns to move with scrolling bar.
NOTE: The table is dynamic and rows and columns will be dynamic. Here is my code [https://jsfiddle.net/padhiyarmahavirsinh/b51k20sr/3/][2]
The basic idea is to make two tables, give one a fixed height and add a scroll bar. You're gonna have to tweak it for your site
//not part of styling, just to get the total
const total = document.querySelector("table:last-of-type tr td:last-child");
const money = document.querySelectorAll("table:first-of-type tr td:last-child");
let sum = 0;
money.forEach(({
innerText
}) => {
sum += parseInt(innerText);
});
total.innerText = sum;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
table {
border-collapse: collapse;
width: 125px;
display: flex;
overflow: hidden;
}
table:first-of-type {
height: 150px;
overflow-y: scroll;
}
td,
th {
border: 1px solid grey;
text-align: center;
}
td {
height: 40px;
width: 50px;
}
<table>
<tr>
<th>name</th>
<th>money</th>
</tr>
<tr>
<td>a</td>
<td>7923</td>
</tr>
<tr>
<td>b</td>
<td>7981</td>
</tr>
<tr>
<td>c</td>
<td>3724</td>
</tr>
<tr>
<td>d</td>
<td>4234</td>
</tr>
<tr>
<td>e</td>
<td>12939</td>
</tr>
<tr>
<td>f</td>
<td>3247</td>
</tr>
<tr>
<td>g</td>
<td>5798</td>
</tr>
<tr>
<td>h</td>
<td>5324</td>
</tr>
</table>
<table>
<tr>
<td>total</td>
<td>0</td>
</tr>
</table>

Bootstrap4- sticky first row and column for grid/flex layout

can anyone give me an idea of how to approach the attached grid/table layout using bootstrap4 flex/grid (must be responsive)?
It's like a table with sticky first row and sticky first column as navigation items.
I tried using w- & h- but it's not responsive without media query. Would like to know the best solution on bootstrap4.
Thanks a lot! :)
(Pardon my English...)
Freeze First Row & First Column
However, to get this behavior for both first row and first column, you need to separate the first row, first column, and first cell from the table, and then continuously set the position of these elements based on the scrolled position of the table body, upon a scroll event.
$(document).ready(function() {
$('tbody').scroll(function(e) {
$('thead').css("left", -$("tbody").scrollLeft());
$('thead th:nth-child(1)').css("left", $("tbody").scrollLeft()-5);
$('tbody td:nth-child(1)').css("left", $("tbody").scrollLeft()-5);
});
});
body {
margin: 0;
}
th, td {
text-align: center;
background-color: white
}
table {
position: relative;
width: 400px;
overflow: hidden;
}
thead {
position: relative;
display: block;
width: 400px;
overflow: visible;
}
thead th {
min-width: 80px;
height: 40px;
}
thead th:nth-child(1) {
position: relative;
display: block;
height: 40px;
padding-top: 20px;
}
tbody {
position: relative;
display: block;
width: 400px;
height: 90px;
overflow: scroll;
}
tbody td {
min-width: 80px;
}
tbody tr td:nth-child(1) {
position: relative;
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Tanggal</th>
<th>Judul Pekerjaan</th>
<th>Deskripsi</th>
<th>Level</th>
<th>Category</th>
<th>Severity</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>1 May 2017</td>
<td>Satu</td>
<td>Satu</td>
<td>5</td>
<td>Lorem</td>
<td>Ipsum</td>
</tr>
<tr>
<td>2</td>
<td>2 May 2017</td>
<td>Dua</td>
<td>Dua</td>
</tr>
<tr>
<td>3</td>
<td>3 May 2017</td>
<td>Tiga</td>
<td>Tiga</td>
</tr>
<tr>
<td>3</td>
<td>3 May 2017</td>
<td>Tiga</td>
<td>Tiga</td>
</tr>
<tr>
<td>2</td>
<td>2 May 2017</td>
<td>Dua</td>
<td>Dua</td>
</tr>
<tr>
<td>3</td>
<td>3 May 2017</td>
<td>Tiga</td>
<td>Tiga</td>
</tr>
<tr>
<td>3</td>
<td>3 May 2017</td>
<td>Tiga</td>
<td>Tiga</td>
</tr>
</tbody>
</table>

How to make <tr>'s height of position:fixed column follow its content's height?

Currently, I have a table in my page and I am trying to make the first column freeze. The freeze column works fine using position: fixed for that particular column.
However there is another problem. When the content has more than one row, the <tr> doesn't extend its height based on its content. Therefore, is there any solution for this?
Here is my demo:
https://jsfiddle.net/yusrilmaulidanraji/ckfdubsf/121/
#table-wrapper {
width: 95%;
float: left;
overflow-x: scroll;
background: #ddd;
}
table {
background: #fff;
width: 1200px;
text-align: center;
overflow: hidden;
position: relative;
}
table thead tr th {
width: 15em;
}
table thead tr th:first-child,
table tbody tr td:first-child {
top: auto;
left: 0.5;
position: fixed;
width: 6em;
}
table thead tr th:nth-child(2),
table tbody tr td:nth-child(2) {
padding-left: 7em;
/*to show second column behind the first*/
}
<button id="left">←</button>
<button id="right">→</button>
<div id="table-wrapper">
<table border="1">
<thead>
<tr>
<th>Heading1</th>
<th>Heading2</th>
<th>Heading3</th>
<th>Heading4</th>
<th>Heading5</th>
</tr>
</thead>
<tbody>
<tr>
<td>1<br/>asdasdada</td>
<td>12</td>
<td>13</td>
<td>14</td>
<td>15</td>
</tr>
<tr>
<td>2<br/>asdasdada<br/>asdasdada</td>
<td>22</td>
<td>23</td>
<td>24</td>
<td>25</td>
</tr>
<tr>
<td>3<br/>asdasdada</td>
<td>32</td>
<td>33</td>
<td>34</td>
<td>35</td>
</tr>
<tr>
<td>4<br/>asdasdada<br/>asdasdada<br/>asdasdada<br/>asdasdada<br/>asdasdada</td>
<td>42</td>
<td>43</td>
<td>44</td>
<td>45</td>
</tr>
<tr>
<td>5<br/>asdasdada</td>
<td>52</td>
<td>53</td>
<td>54</td>
<td>55</td>
</tr>
</tbody>
</table>
</div>
You can go through all td fixed elements and then set those heights to default td elements.
$('table tbody tr td:first-child').each(function(){
var height = $(this).height();
$(this).parent().find('td').height(height);
});
Working example https://jsfiddle.net/ckfdubsf/122/
I ended up by using #feesar answer, however I had another issue regarding performance. Therefore, Here is the final result for my case:
https://jsfiddle.net/yusrilmaulidanraji/ckfdubsf/124/
// Adjust the th and td's height.
// Improve the performance by using native js + for loop.
var firstHeader = $('#table-wrapper th:first-child');
firstHeader[0].style.height = firstHeader[0].parentNode.offsetHeight + "px";
var firstColumn = $('#table-wrapper td:first-child');
for (var i = 0; i < firstColumn.length; i++) {
firstColumn[i].parentNode.style.height = firstColumn[i].offsetHeight + "px";
}
The logic and the result are the same, but it has a better performance. Hopefully, it can help.
In terms of performance, I think you will be best off simply cloning the first table column and painting it above the "real" one, like this:
var $overlayTable = $("#table-wrapper table").clone().addClass("overlay");
$overlayTable.find("tr > *:not(:first-child)").remove();
$overlayTable.appendTo("#table-wrapper");
$(window).on("scroll", function () {
$overlayTable.css("left", $(window).scrollLeft() + "px");
});
#table-wrapper {
position: relative;
}
#table-wrapper table {
text-align: center;
table-layout: fixed;
overflow-x: scroll;
width: 1200px;
border-collapse: collapse;
}
#table-wrapper table tr > * {
margin-left: 150px;
width: auto;
}
#table-wrapper table tr > *:first-child {
width: 6em;
}
#table-wrapper table.overlay {
position: absolute;
top: 0;
color: red;
background-color: white;
width: 6em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>
<button id="left">←</button>
<button id="right">→</button>
</p>
<div id="table-wrapper">
<table border="1">
<thead>
<tr>
<th>Heading1</th>
<th>Heading2</th>
<th>Heading3</th>
<th>Heading4</th>
<th>Heading5</th>
</tr>
</thead>
<tbody>
<tr>
<td>1<br/>asdasdada</td>
<td>12</td>
<td>13</td>
<td>14</td>
<td>15</td>
</tr>
<tr>
<td>2<br/>asdasdada<br/>asdasdada</td>
<td>22</td>
<td>23</td>
<td>24</td>
<td>25</td>
</tr>
<tr>
<td>3<br/>asdasdada</td>
<td>32</td>
<td>33</td>
<td>34</td>
<td>35</td>
</tr>
<tr>
<td>4<br/>asdasdada<br/>asdasdada<br/>asdasdada<br/>asdasdada<br/>asdasdada</td>
<td>42</td>
<td>43</td>
<td>44</td>
<td>45</td>
</tr>
<tr>
<td>5<br/>asdasdada</td>
<td>52</td>
<td>53</td>
<td>54</td>
<td>55</td>
</tr>
</tbody>
</table>
</div>

issue on scrollable table

I have a code to display the content of table as scrollable. But i am not sure on how to do scroll only on body not with head. Currently it happening for whole table.
Please help me.
tbody {
display: block;
}
tbody {
height: 300px;
/* Just for the demo */
overflow-y: auto;
/* Trigger vertical scroll */
overflow-x: hidden;
/* Hide the horizontal scroll */
}
<table cellspacing="0" id="itemtable" align="center">
<tr>
<th> SLno</th>
<th>Item name</th>
<th>Item code</th>
</tr>
<tr>
<td>1</td>
<td>icecream</td>
<td>100</td>
</tr>
<tr>
<td>2</td>
<td>biscuits</td>
<td>200</td>
</tr>
</table>
First of all you need your thead of the table to be separate from the rest of your table by either applying position: relative; or absolute.
Apply display: block to your tbody so that you have a block level element which you can then apply height and overflow properties to have a desirable scrollable table.
Refer code:
table {
position: relative;
width: 700px;
background-color: #aaa;
overflow: hidden;
border-collapse: collapse;
}
/*thead*/
thead {
position: relative;
display: block;
/*seperates the header from the body allowing it to be positioned*/
width: 700px;
overflow: visible;
}
thead th {
background-color: #99a;
min-width: 120px;
height: 36px;
min-height: 36px;
border: 1px solid #222;
}
thead th:nth-child(1) {
/*first cell in the header*/
position: relative;
display: block;
background-color: #88b;
}
/*tbody*/
tbody {
display: block;
width: 700px;
height: 239px;
overflow-y: auto;
}
tbody td {
background-color: #bbc;
min-width: 120px;
border: 1px solid #222;
height: 36px;
min-height: 36px;
}
<body>
<table>
<thead>
<tr>
<th>Name</th>
<th>Town</th>
<th>County</th>
<th>Age</th>
<th>Profession</th>
<th>Anual Income</th>
<th>Matital Status</th>
<th>Children</th>
</tr>
</thead>
<tbody>
<tr>
<td>John Smith</td>
<td>Macelsfield</td>
<td>Cheshire</td>
<td>52</td>
<td>Brewer</td>
<td>£47,000</td>
<td>Married</td>
<td>2</td>
</tr>
<tr>
<td>Jenny Jones</td>
<td>Threlkeld</td>
<td>Cumbria</td>
<td>34</td>
<td>Shepherdess</td>
<td>£28,000</td>
<td>Single</td>
<td>0</td>
</tr>
<tr>
<td>Peter Frampton</td>
<td>Avebury</td>
<td>Wiltshire</td>
<td>57</td>
<td>Musician</td>
<td>£124,000</td>
<td>Married</td>
<td>4</td>
</tr>
<tr>
<td>Simon King</td>
<td>Malvern</td>
<td>Worchestershire</td>
<td>48</td>
<td>Naturalist</td>
<td>£65,000</td>
<td>Married</td>
<td>2</td>
</tr>
<tr>
<td>Lucy Diamond</td>
<td>St Albans</td>
<td>Hertfordshire</td>
<td>67</td>
<td>Pharmasist</td>
<td>Retired</td>
<td>Married</td>
<td>3</td>
</tr>
<tr>
<td>Austin Stevenson</td>
<td>Edinburgh</td>
<td>Lothian</td>
<td>36</td>
<td>Vigilante</td>
<td>£86,000</td>
<td>Single</td>
<td>Unknown</td>
</tr>
<tr>
<td>Wilma Rubble</td>
<td>Bedford</td>
<td>Bedfordshire</td>
<td>43</td>
<td>Housewife</td>
<td>N/A</td>
<td>Married</td>
<td>1</td>
</tr>
<tr>
<td>Kat Dibble</td>
<td>Manhattan</td>
<td>New York</td>
<td>55</td>
<td>Policewoman</td>
<td>$36,000</td>
<td>Single</td>
<td>1</td>
</tr>
<tr>
<td>Henry Bolingbroke</td>
<td>Bolingbroke</td>
<td>Lincolnshire</td>
<td>45</td>
<td>Landowner</td>
<td>Lots</td>
<td>Married</td>
<td>6</td>
</tr>
<tr>
<td>Alan Brisingamen</td>
<td>Alderley</td>
<td>Cheshire</td>
<td>352</td>
<td>Arcanist</td>
<td>A pile of gems</td>
<td>Single</td>
<td>0</td>
</tr>
</tbody>
</table>
</body>

td colspan width not working

table
{
width: 100%;
}
td:first-child
{
width: 100px;
}
<table>
<thead>
<tr>
<td colspan="2">A</td>
<td>B</td>
<td>C</td>
</tr>
</thead>
</table>
I do not quite understand why the first td is not 100px wide and how can I do it exactly 100px with no JS?
You don't need to remove the colspan to get this to work, as other's are suggesting, although that might be a valid/better idea, if it is not needed (in your example it isn't, since there is only one row, but perhaps this is for another situation where you need it)
You can use table-layout: fixed; to get it to respect your value.
I also added padding: 0; , to get the exact with down from 102px to 100px, just for neatness sake:
table
{
width: 100%;
table-layout: fixed;
}
td {
padding: 0;
}
td:first-child
{
width: 100px;
}
<table>
<thead>
<tr>
<td colspan="2">A</td>
<td>B</td>
<td>C</td>
</tr>
</thead>
</table>
JSFiddle Remove colspan=2 this multiplies your width value from your css.
HTML
<table>
<thead>
<tr>
<td>A</td>
<td>B</td>
<td>C</td>
</tr>
</thead>
</table>
CSS
table
{
width: 100%;
}
td:first-child
{
width: 100px;
}
This should work :
table
{
width: 100%;
}
tr td:first-child
{
width: 100px;
}
<table>
<thead>
<tr>
<td>A</td>
<td>B</td>
<td>C</td>
</tr>
</thead>
</table>