Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Table class for tables that, when too wide split all their cells into rows

Tags:

html

css

I am trying to figure out what HTML / CSS I need to get a certain breaking behavior depending on how much space is available. Basically, I want to have a long line of text automatically break at certain places, and all at one), when there is no longer space for it.

Example 1:

  • There is enough horizontal space for the entire line of text:

    enter image description here

  • There isn't enough horizontal space for the whole line (but even if there is space for the first two items, everything ends up on its own line) enter image description here

Example 2: This is the same as the previous example, yet it shows that I'd like to be able to use this in a nested fashion too:

  • There is enough horizontal space for the entire line of text:

    enter image description here

  • There is just not enough horizontal space for the entire line, so each cell goes onto its own line

    enter image description here

  • There is not enough horizontal space for the second cell

    enter image description here

I'm looking for a solution that uses only HTML and CSS (or only uses JavaScript very lightly) since my intended use-case is to use this pretty heavily in automatically generated HTML documentation pages.

I've come across examples like these, but I'm not sure I see how to make this work for subitems (and how to make this work without assigning a fixed size to each cell - I want to the cell-size to be determined by its content).

Clarfiying edit

Just bringing extra emphasis to a crucial part of the question which has been overlooked. I never want to find myself in a state where there are two items on one line and the third item on the next line. The three items should go straight from being all on one line to all on separate lines.

like image 226
Alec Avatar asked Dec 09 '17 18:12

Alec


4 Answers

As I have already stated in the comments, what the OP is looking for cannot be achieved without the use of Javascript, so here is what I came up with.

Initially we have a normal table structure, we calculate each table's width and apply a data-width attribute on each table - parent or nested.

On resize, as soon as each table is longer than the window width, it changes its contents display from table-cell to block, thus stacking all children.

As soon as the window width is expanded it is compared to each table's data-width attribute and when it fits, it expands.

edit: I thought for some reason it wouldn't work under some cases, but it actually works, so I remove the bloat!

HTML:

<div class="table">
  <div class="cell">
    Content 1
  </div>
  <div class="cell">
    <div class="table">
      <div class="cell">
        Content 1
      </div>
      <div class="cell">
        Cont
      </div>
      <div class="cell">
        Content 3
      </div>    
    </div>
  </div>
  <div class="cell">
    Content 3Content 3Content 3
  </div>

</div>

CSS:

.table {
  display: table;
  border: 1px solid #e4e5e7;
  white-space: nowrap;
}

.cell {
  display: table-cell;
  vertical-align: middle;
  padding: 10px;
  border: 1px solid #e4e5e7;
}

.cell .table {
  margin-bottom: -2px;
}

.table.wrap {
  white-space: normal;
}

.table.wrap > .cell {
  display: block;
}

Javascript:

$('.table').each(function(i) {
  tw = 0;
  $(this).children('.cell').each(function() {
    tw += $(this).outerWidth(true);
  });
  $(this).attr('data-width', tw);
});

$(window).on('load resize', function() {
  w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
  $('.table').each(function(i) {
    if ($(this).width() >= w) {
      $(this).addClass('wrap');
    } else if (w >= $(this).attr('data-width')) {
      $(this).removeClass('wrap');    
    }
  });
}).resize();

And here is a working fiddle

like image 83
scooterlord Avatar answered Oct 22 '22 16:10

scooterlord


Another variant with pure JS

window.addEventListener('load', collapse);
window.addEventListener('resize', collapse);

function collapse() {
  var parents = document.querySelectorAll('.parent');
  var children;
  var width;
  for (var i = 0; i < parents.length; i++) {
    parents[i].classList.remove('collapsed');
    width = 0;
    children = parents[i].children;
    for (var j = 0; j < children.length; j++) {
      width += children[j].offsetWidth;
    }
    if (width > parents[i].offsetWidth) {
      parents[i].classList.add('collapsed');
    }
  }
}
.parent,
.child {
  border: 1px solid gray;
  margin: 5px;
  padding: 5px;
}

.parent {
  display: flex;
  flex-wrap: wrap;
}

.collapsed {
  display: block;
}

.child {
  flex-grow: 1;
}
<div class="parent">
  <div class="child">Some thing one</div>
  <div class="parent child">
    <div class="child">Some inner</div>
    <div class="child">long thing</div>
    <div class="child">two</div>
  </div>
  <div class="child">Thing three</div>
</div>
like image 7
Ruslan Avatar answered Oct 22 '22 18:10

Ruslan


One approach is to use @media queries to alternate between

display: block

and

display: inline-block

Working Example:

.long-row {
display: inline-block;
width: 200px;
padding-left: 4px;
font-size: 16px;
line-height: 32px;
border: 1px solid rgb(0, 0, 0);
}

@media only screen and (min-width: 801px) {
    .long-row {
        display: inline-block;
        float: left;
    }
}

@media only screen and (max-width: 800px) {
    .long-row {
        display: block;
        border-top: none;
    }

    div:first-of-type.long-row {
        border-top: 1px solid rgb(0, 0, 0);
    }
}

@media only screen and (max-width: 600px) {
    .long-row {
    }

    .short-row {
        display: block;
        margin-left: -4px;
        padding-left: 4px;
        border-bottom: 1px solid rgb(0, 0, 0);
    }
    
    span:last-of-type.short-row {
        border-bottom: none;
    }
}
<div class="long-row">Some thing one</div>

<div class="long-row">
<span class="short-row">Some inner</span>
<span class="short-row">long thing</span>
<span class="short-row">two</span>
</div>

<div class="long-row">Thing three</div>
like image 1
Rounin - Glory to UKRAINE Avatar answered Oct 22 '22 18:10

Rounin - Glory to UKRAINE


Before getting to the script, there are couples of notes that should be considered :

At my actual state of my knowledge, this cannot be done using only CSS. So the question is how to do this using pure JavaScript with the best performance possible? And I've come up with some guidelines :

  • using for loops as they are the fastest using cache of the number of iterations
  • minimizing functions calls whenever possible

Avoiding :

  • recursive functions

  • [].foreach constructs

  • DOM access whenever possible

In addition to that it can be nested to any level

The idea:

The script takes all elements, force them into a single line, and see if there is not enough space, it make them displayed on top of each other, otherwise they stayed on the same line.

It does this every time the document's markup is loaded, and when the window is resized.

https://jsfiddle.net/nasdao/qzdtr9ym/

like image 1
user10089632 Avatar answered Oct 22 '22 16:10

user10089632