<!doctype html>
<html>
<head>
	<meta charset="utf-8" />

	<title>
		Filling In The Empty Cells When Using Grid-Auto-Flow: Dense In CSS Grid
	</title>

	<style type="text/css">

		body {
			margin: 20px 20px 20px 20px ;
		}

		div.grid {
			display: grid ;
			grid-auto-flow: dense ;
			grid-auto-rows: 100px ;
			grid-gap: 14px ;
			grid-template-columns: repeat( auto-fit, minmax( 100px, 1fr ) ) ;
		}

		div.grid__item {
			align-content: center ;
			background-color: #F0F0F0 ;
			border: 1px solid #333333 ;
			border-radius: 4px 4px 4px 4px ;
			display: grid ;
			justify-content: center ;
		}

		div.grid__item--new {
			background-color: gold ;
			font-weight: 600 ;
		}

		div.grid__item--row-span-2 { grid-row: span 2 ; }
		div.grid__item--row-span-3 { grid-row: span 3 ; }
		div.grid__item--row-span-4 { grid-row: span 4 ; }
		div.grid__item--column-span-2 { grid-column: span 2 ; }
		div.grid__item--column-span-3 { grid-column: span 3 ; }
		div.grid__item--column-span-4 { grid-column: span 4 ; }

	</style>
</head>
<body>

	<h1>
		Filling In The Empty Cells When Using Grid-Auto-Flow: Dense In CSS Grid
	</h1>

	<div class="grid">
		<!-- Filled dynamically. -->
	</div>

	<script type="text/javascript" src="../../vendor/lodash/lodash-4.17.2.min.js"></script>
	<script type="text/javascript">

		var grid = document.querySelector( "div.grid" );
		var currentHeight = -1;
		var resizeTimer = null;
		var randomSizes = generateRandomSizes();

		// Move 100 randomly-sized items into the grid.
		populateGrid( 100 );

		// Fill-in an empty cells that are left over after the "dense" auto-flow has
		// moved elements around.
		fillEmptyGridCells();

		// Wire window-resize events into subsequent calls to fill-in empty cells as a
		// newly-sized window will result in a different grid topology.
		setupResizeBinding();

		// --------------------------------------------------------------------------- //
		// --------------------------------------------------------------------------- //

		// I fill in the empty cells in the gird. I use a brute-force method that starts
		// adding new grid items until the HEIGHT of the grid changes. At that point, I
		// know the grid has been saturated.
		function fillEmptyGridCells() {

			var clientHeight = grid.clientHeight;

			// Since this gets called as part of the window-resize event, it's possible
			// that the resize didn't cause a HEIGHT-change, only a WIDTH-change. In that
			// case, we can just bail-out as there should be no new empty cells (since
			// the existing cells are based on fr - fractional - units and should extend
			// horizontally to fill space).
			if ( clientHeight === currentHeight ) {

				return;

			}

			// Before we fill in the empty cells, let's first remove any existing items
			// that were inserted during a previous attempt to fill-in empty cells. This
			// way, each resize event doesn't compound the collection of fixes.
			Array
				.from( grid.querySelectorAll( "div.grid__item--new" ) )
				.forEach(
					function( node ) {

						grid.removeChild( node );

					}
				)
			;

			// Keep track of the current un-altered grid height (so we can skip
			// insignificant window resize events in the future).
			currentHeight = grid.clientHeight;

			// Now, we're going to start iterating over our random-sizes collection and
			// start inserting grid items with the given, random size. We'll continue to
			// so until no new items can be inserted into the grid without affecting the
			// original height of the grid. At that point, we'll remove the last-inserted
			// grid item and know that the grid cells has been saturated.
			// --
			// NOTE: We know this will work because the random-sizes collection contains
			// a 1x1 grid area, which will ultimately be able to fill in any remaining
			// empty cells.
			var newItemIndex = 0;
			var fillerSizes = randomSizes.slice();
			var indicesToDelete = [];

			do {

				fillerSizes.forEach(
					function ( size, index ) {

						var node = document.createElement( "div" );
						node.classList.add(
							"grid__item",
							"grid__item--new",
							`grid__item--column-span-${ size.columns }`,
							`grid__item--row-span-${ size.rows }`
						);

						grid.appendChild( node );

						// If the height of the grid has not changed, then we know that
						// the inserted grid item fit into an empty cell.
						if ( grid.clientHeight === currentHeight ) {

							node.innerText = `New ${ ++newItemIndex }`;

						// If the HEIGHT OF THE GRID HAS CHANGED, then we know that the
						// inserted grid item did not fit cleanly into an empty cell.
						} else {

							// Remove the breaking item.
							grid.removeChild( node );

							// Let's also track this size as one to delete before the
							// next iteration. If it didn't fit this time, there's no
							// reason to think it will fit in the next iteration.
							indicesToDelete.push( index );

						}

					}
				);

				// Remove any random-size combinations that we know didn't get used in
				// previous iteration (which means there's no point in trying them in the
				// next iteration).
				for ( var i = ( indicesToDelete.length - 1 ) ; i >= 0 ; i-- ) {

					fillerSizes.splice( indicesToDelete[ i ], 1 );
					indicesToDelete.splice( i, 1 );

				}

			} while ( fillerSizes.length );

		}


		// I generate a randomly-sorted collection of grid-area sizes (column and row
		// span combinations). These will be used to fill-in the empty grid cells.
		function generateRandomSizes() {

			var sizes = [];

			for ( var columns = 4 ; columns >= 1 ; columns-- ) {
				for ( var rows = 4 ; rows >= 1 ; rows-- ) {

					sizes.push({
						columns: columns,
						rows: rows
					});

				}
			}

			return( _.shuffle( sizes ) );

		}


		// I populate the grid with the given number of randomly-sized items.
		function populateGrid( itemCount ) {

			for ( var i = 1 ; i <= itemCount ; i++ ) {

				var node = document.createElement( "div" );
				node.innerText = `Item ${ i }`;
				node.classList.add(
					"grid__item",
					`grid__item--column-span-${ _.random( 1, 4 ) }`,
					`grid__item--row-span-${ _.random( 1, 4 ) }`
				);

				grid.appendChild( node );

			}

		}


		// I wire the window-resize event into a call to fill in the empty grid cells.
		function setupResizeBinding() {

			window.addEventListener(
				"resize",
				_.debounce( fillEmptyGridCells, 200 ),
				false
			);

		}

	</script>

</body>
</html>