Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make overflown flexbox element to have width of its children?

Tags:

html

css

flexbox

I've been trying to ceate table-like grid with scroll using Flexbox. no horizontal scroll

Most of it works okay, but is there any way to force rows to have width of their content when horizontal scrolling is on?

horizontal scroll on

As you can see, every even row have white background, so it's easy to spot that there's a problem with width.

http://jsbin.com/fedisozafe/embed?html,css,output

$('.tbody').on('scroll', function(e) {
    $(this).siblings().scrollLeft($(this).scrollLeft());
});

$('.tbody').perfectScrollbar();

$(window).on('resize', function(e) {
    $('.tbody')
      .perfectScrollbar('update')
      .siblings()
        .scrollLeft($(this).scrollLeft());
});


angular.module('app', [])
    .controller('testController', [
        '$scope',
        function($scope) {
            this.n = 5;
            $scope.$watch(() => this.n, () => this.collection = _.range(this.n));
        }
    ]);
* {
    box-sizing: border-box;
    border-collapse: collapse;
}
.table {
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
    border: solid 1px red;
}
.table > * {
    border-top: solid 1px transparent;
    border-bottom: solid 1px transparent;
}
.thead {
    border-bottom: solid 1px red;
}
.tfoot {
    border-top: solid 1px red;
}
.thead {
    flex: none;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    order: -1;
    background-color: lightgoldenrodyellow;
}
.tbody {
    position: relative;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    justify-content: space-between;
    background-color: lightgray;
}
.tfoot {
    flex: none;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    order: 1;
    background-color: lightblue;
}
.tr {
    display: flex;
    flex: 1 0 20px;
    justify-content: space-between;
    align-items: center;
}
.tr:nth-child(2n) {
    background-color: white;
}
.td,
.th {
    flex: 1 0 60px;
    display: inline-flex;
}
.th {
    font-weight: bold;
}
<!doctype html>
<html ng-app="app">
    <head>
        <meta charset="utf-8">
        <title></title>
        <meta name="viewport" content="width=device-width">

		<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>
		<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular.min.js"></script>
		<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.5.1/lodash.min.js"></script>
		<script src="//cdnjs.cloudflare.com/ajax/libs/jquery.perfect-scrollbar/0.6.10/js/min/perfect-scrollbar.jquery.min.js"></script>

		<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery.perfect-scrollbar/0.6.10/css/perfect-scrollbar.min.css"/>
	</head>
	<body ng-controller="testController as ctrl">
	<input type="number" ng-model="ctrl.n" ng-max="100" />
	<div style="display: flex">
		<div style="flex: 1 1 auto">
			<table class="table" style="width: 40vw; height: 40vh">
				<tbody class="tbody" style="height: calc(100% - 60px)">
					<tr class="tr" ng-repeat="item in ctrl.collection" style="min-width: 200px">
						<th class="th">{{item}}</th>
						<th class="td" style="flex-basis: 200px">{{item}}</th>
						<th class="td">{{item}}</th>
						<th class="td">{{item}}</th>
						<th class="td">{{item}}</th>
					</tr>
				</tbody>
				<thead class="thead">
					<tr class="tr">
						<td class="th">A</td>
						<td class="th" style="flex-basis: 200px">B</td>
						<td class="th">C</td>
						<td class="th">D</td>
						<td class="th">E</td>
					</tr>
				</thead>
				<tfoot class="tfoot">
					<tr class="tr">
						<th class="th">A</th>
						<th class="th" style="flex-basis: 200px">B</th>
						<th class="th">C</th>
						<th class="th">D</th>
						<th class="th">E</th>
					</tr>
				</tfoot>
			</table>
		</div>
		<div style="flex: 1 1 auto">
			<div class="table" style="width: 40vw; height: 40vh">
				<div class="tbody" style="height: calc(100% - 60px)">
				<div class="tr" ng-repeat="item in ctrl.collection">
					<div class="th">{{item}}</div>
					<div class="td" style="flex-basis: 200px">{{item}}</div>
					<div class="td">{{item}}</div>
					<div class="td">{{item}}</div>
					<div class="td">{{item}}</div>
				</div>
				</div>
				<div class="thead">
				<div class="tr">
					<div class="th">A</div>
					<div class="th" style="flex-basis: 200px">B</div>
					<div class="th">C</div>
					<div class="th">D</div>
					<div class="th">E</div>
				</div>
				</div>
				<div class="tfoot">
				<div class="tr">
					<div class="th">A</div>
					<div class="th" style="flex-basis: 200px">B</div>
					<div class="th">C</div>
					<div class="th">D</div>
					<div class="th">E</div>
				</div>
				</div>
			</div>
		</div>
	</div>

	</body>
</html>

Any ideas?


EDIT 1

It's important that rows calculate their width based on its cells, not the other way round. In an ideal world row would grow when space is available (and allow cells to grow as well), but would not shrink - that's why scrolling is enabled.

I'm aware that there's a possibility it can't be done. I've spent quite some time on it, but I still cling to hope that I'm just not as clever as you guys.

like image 953
m1gu3l Avatar asked Mar 01 '16 16:03

m1gu3l


1 Answers

There were no mistakes, HTML, CSS, and JS (I'll assume Angular and loDash are good to go.) all good. So OP just needed a way to shrink wrap the rows. The rows extended in weird looking overflow? There were some changes to the CSS, but they were more for aesthetics and small adjustments. The main style changes are as follows:

    .tr {
      display: flex;
      min-width: -moz-fit-content;
      min-width: -webkit-fit-content;
      min-width: fit-content;
      flex: 1 0 20px;
      justify-content: flex-end;
    } 

fit-content is an old but little known property still in it's experimental stage. What it basically does is it will make an element wrap it's content in a perfect fit. So if you apply it to a parent element, the parent will be as wide as it's content and it will increase and decrease with the content.

- Result: The overflow was gone but `tr` did not extend.

Removed the flex-direction: column so it would default to flex-direction: row I figured that a tr flowing in a vertical direction would probably stop short and abruptly.

- Result: The `tr` extended, but the `td`s and `th`s were not aligned.

justify-content: flex-end applied in order to shift the columns to the right recalling the problem was on the right side of the table.

- Result: http://jsbin.com/godoqi/3/edit?css,output

table-layout: fixed was also applied to the table so there's more control over column widths.

- Result: See previous result.

jsBin

Snippet

$('.tbody').on('scroll', function(e) {
  $(this).siblings().scrollLeft($(this).scrollLeft());
});

$('.tbody').perfectScrollbar();

$(window).on('resize', function(e) {
  $('.tbody')
    .perfectScrollbar('update')
    .siblings()
    .scrollLeft($(this).scrollLeft());
});


angular.module('app', [])
  .controller('testController', [
    '$scope',
    function($scope) {
      this.n = 5;
      $scope.$watch(() => this.n, () => this.collection = _.range(this.n));
    }
  ]);
* {
  box-sizing: border-box;
}
.table {
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  border: solid 1px red;
  border-collapse: collapse;
  table-layout: fixed;
}
.table > * {
  border-top: solid 1px transparent;
  border-bottom: solid 1px transparent;
}
.thead {
  border-bottom: solid 1px red;
}
.tfoot {
  border-top: solid 1px red;
}
.thead {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  order: -1;
  background-color: lightgoldenrodyellow;
}
.tbody {
  position: relative;
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-content: space-between;
  background-color: lightgray;
}
.tfoot {
  flex: none;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  order: 1;
  background-color: lightblue;
  text-align: center;
}
.tr {
  display: flex;
  min-width: -moz-fit-content;
  min-width: -webkit-fit-content;
  min-width: fit-content;
  flex: 1 0 20px;
  justify-content: flex-end;
}
.tr:nth-child(2n) {
  background-color: white;
}
.td,
.th {
  flex: 0 1 60px;
  text-align: center;
}
.th {
  font-weight: bold;
}
.ng-not-empty {
  text-align: center;
  width: 8ex;
}
<!doctype html>
<html ng-app="app">

<head>
  <meta charset="utf-8">
  <title></title>
  <meta name="viewport" content="width=device-width">

  <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.5.1/lodash.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/jquery.perfect-scrollbar/0.6.10/js/min/perfect-scrollbar.jquery.min.js"></script>

  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery.perfect-scrollbar/0.6.10/css/perfect-scrollbar.min.css" />
</head>

<body ng-controller="testController as ctrl">
  <input type="number" ng-model="ctrl.n" ng-max="100" />
  <div style="display: flex">
    <div style="flex: 1 1 auto">
      <table class="table" style="width: 40vw; height: 40vh">
        <tbody class="tbody" style="height: calc(100% - 60px)">
          <tr class="tr" ng-repeat="item in ctrl.collection" style="min-width: 200px">
            <th class="th">{{item}}</th>
            <th class="td" style="flex-basis: 200px">{{item}}</th>
            <th class="td">{{item}}</th>
            <th class="td">{{item}}</th>
            <th class="td">{{item}}</th>
          </tr>
        </tbody>
        <thead class="thead">
          <tr class="tr">
            <td class="th">A</td>
            <td class="th" style="flex-basis: 200px">B</td>
            <td class="th">C</td>
            <td class="th">D</td>
            <td class="th">E</td>
          </tr>
        </thead>
        <tfoot class="tfoot">
          <tr class="tr">
            <th class="th">A</th>
            <th class="th" style="flex-basis: 200px">B</th>
            <th class="th">C</th>
            <th class="th">D</th>
            <th class="th">E</th>
          </tr>
        </tfoot>
      </table>
    </div>
    <div style="flex: 1 1 auto">
      <div class="table" style="width: 40vw; height: 40vh">
        <div class="tbody" style="height: calc(100% - 60px)">
          <div class="tr" ng-repeat="item in ctrl.collection">
            <div class="th">{{item}}</div>
            <div class="td" style="flex-basis: 200px">{{item}}</div>
            <div class="td">{{item}}</div>
            <div class="td">{{item}}</div>
            <div class="td">{{item}}</div>
          </div>
        </div>
        <div class="thead">
          <div class="tr">
            <div class="th">A</div>
            <div class="th" style="flex-basis: 200px">B</div>
            <div class="th">C</div>
            <div class="th">D</div>
            <div class="th">E</div>
          </div>
        </div>
        <div class="tfoot">
          <div class="tr">
            <div class="th">A</div>
            <div class="th" style="flex-basis: 200px">B</div>
            <div class="th">C</div>
            <div class="th">D</div>
            <div class="th">E</div>
          </div>
        </div>
      </div>
    </div>
  </div>

</body>

</html>
like image 140
zer00ne Avatar answered Oct 31 '22 02:10

zer00ne