Thursday, December 21, 2017

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

Leave a Comment

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.

9 Answers

Answers 1

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: https://jsfiddle.net/scooterlord/g6d7w8pk/1/

Answers 2

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>

Answers 3

The three items should go straight from being all on one line to all on separate lines.

You need to set a query for that (choose the best breakpoint) and then set those items all in 3 separate rows

Here is a example how to do that using CSS grid layout using media query.

.grid-container {    display: grid;    grid-template-columns: repeat(3, auto);  }    .grid-container>div {    border: 1px solid #000;  }      /*middle step */    @media (max-width:992px) {    .grid-container {      grid-template-columns: auto;    }    .grid-container .grid-container {      grid-template-columns: repeat(3, auto);      border: 1px solid #000;    }  }      /* Final Step */    @media (max-width:768px) {    .grid-container .grid-container {      grid-template-columns: auto;    }    .grid-container>div {      width: -moz-max-content;      width: -webkit-max-content;      width: max-content    }  }
<div class="grid-container outer">    <div>Something One</div>    <div class="grid-container inner">      <div>Some inner</div>      <div>long thing</div>      <div>two</div>    </div>    <div>Thing three</div>  </div>

Answers 4

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>

Answers 5

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/

Cheers!

Answers 6

As I mentioned in my comment you can make use of flexbox, here is a demonstration of how you wanted. Hope, this helps.

.flex-container {    display: flex;    align-items: center;    flex-direction: row;    flex-wrap: wrap;    flex-flow: row wrap;    align-content: flex-end;  }    .flex-item {    background: gray;    color: white;    margin: 2px;    height: 80px;    text-align: center;    font-size: 30px;    border:3px solid black;  }
<div>  <div class="flex-container">    <div class="flex-item">Something One</div>    <div class="flex-container" style="border:2px solid black">       <div class="flex-item">Something 2</div>       <div class="flex-item">Something random3</div>       <div class="flex-item">Some 4</div>    </div>    <div class="flex-item">Something Five</div>  </div>

Answers 7

You can make it with using display:flex;

For example:

 .horz_cont {          display: flex;          align-items: center;          flex-direction: row;          flex-wrap: wrap;          flex-flow: row wrap;          align-content: flex-end;          border: 1px solid #000;          padding: 4px;      }            .horz_cont .horz_cont {         border: none;         padding: 0;      }            .item {          display: flex;          align-items: center;          justify-content: center;          margin: 4px;          padding: 4px;          height: 50px;          font-size: 34px;          font-weight: bold;          border: 1px solid #000;      }
<div class="horz_cont">    <div class="item">Some thing one</div>    <div class="horz_cont">       <div class="item">Some inner</div>       <div class="item">long thing</div>       <div class="item">two</div>    </div>    <div class="horz_cont">       <div class="item">Thing three</div>    </div>  </div>

Answers 8

You could try using media queries on the screen sizes, such that when the inner content of the middle flexbox exceeds its container, you can write CSS that only applies to that scenario.

It's important to also add the viewport tag to head of your html:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

Open the example in full screen and resize the viewport of the browser to see how the elements pop around.

I hope this is helpful to you.

Cheers!

.flex-container {    display: flex;    align-items: center;    flex-direction: row;    flex-wrap: wrap;    flex-flow: row wrap;    align-content: flex-end;  }  .flex-item, .inner-flex-item {    background: white;    color: black;    padding: 5px;    margin: 2px;    height: 100px;    text-align: center;    line-height: 100px;    font-size: 50px;    border:3px solid black;  }  @media screen and (max-width: 1180px) {    .flex-item {      width: 80%;  }  @media screen and (max-width: 640px) {    .inner-flex-item {      width: 80%;  }
<div>    <div class="flex-container">    <div class="flex-item">Something One</div>    <div class="flex-container" style="border:2px solid black">      <div class="inner-flex-item">Some inner</div>      <div class="inner-flex-item">long thing</div>      <div class="inner-flex-item">two</div>    </div>    <div class="flex-item">Thing three</div>  </div>

Answers 9

I don't know a way to do it at every screen size in pure css. I need a bit of javascript (easier with jquery)

  1. Those elements are inline blocks.
  2. For each group of siblings elements check if the top position of the last child is the same that top position of the first child (if so, they are in the same line).
  3. If not the same top position, add a class to make them blocks again (spite of inline-blocks).

Works well with n-nested levels and it's reasonably light javascript. (plus no media querys, any number of items per level, no loops with "trying to fit", probably the widest browser support...)

$(function() {   $('div.line:first-child ~ div.line:last-child').each(    function(){      if( $(this).position().top != $(this).siblings(":first-child").position().top ){        $(this).parent().addClass("breaks")      }    }   );  });
div{outline:1px solid red;}    .line{display:inline-block;}  .breaks > .line{display:block;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>  <div class="container">  <div class="line">Some thing one</div>  <div class="line">    <div class="line">Some inner</div>    <div class="line">longest thing</div>    <div class="line">two</div>  </div>  <div class="line">this is long Thing three</div>  </div>

EDIT: a second option with same logic, but using selector for the "group" of items -not the items themselves-, with better performance and no jquery

window.addEventListener('load', breaklines);    function breaklines() {    var groups = document.querySelectorAll('.group');    for (var i = 0; i < groups.length; i++) {      if(groups[i].children[0].offsetTop != groups[i].children[ groups[i].children.length - 1].offsetTop){        groups[i].classList.add('breaks');      }else{        groups[i].classList.remove('breaks');      }    }  }
div.group > div{outline:2px dotted red;}  div.group div.group > div{outline:2px solid green;}      .group > div {display:inline-block}  .group.breaks > div {display:block}
<div class="group">  <div>Some thing one</div>  <div class="group">    <div>Some inner</div>    <div>longest thing</div>    <div>two</div>  </div>  <div>this is long Thing three</div>  </div>

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment