3
\$\begingroup\$

I wrote this PHP script to dynamically generate a topic header layout using CSS grid. The $columns array defines each column’s properties—like its CSS class, size, content, and how many rows or columns it spans. The script then processes these definitions to calculate grid placement and generate the necessary CSS styles.

I designed it to be flexible so that third-party scripts can easily add new columns or rows just by modifying the $columns array. However, since the script involves complex grid calculations, I’d appreciate a second pair of eyes—especially from someone better at math—to verify that the row and column positioning works as intended.

/** * Topic header column definitions. * * @var array $columns * Each column has: * - `class`: The CSS class name. * - `content`: The displayed content. * - `size`: The column width in CSS grid format. * - `rowspan`: Number of rows the column should span. * - `colspan`: Number of columns the column should span. * - `row_number`: The row in which the element starts (1-based). */ $columns = [ 'icon' => [ 'class' => 'topic_icon', 'content' => ['header' => ''], 'size' => '2em', 'rowspan' => 2, 'colspan' => 1, 'row_number' => 1, ], 'info' => [ 'class' => 'info', 'content' => ['header' => '{subject} / {starter}'], 'size' => '1fr', 'rowspan' => 1, 'colspan' => 2, 'row_number' => 1, ], 'author' => [ 'class' => 'topic_author', 'content' => ['header' => ''], 'size' => 'auto', 'rowspan' => 1, 'colspan' => 1, 'row_number' => 2, ], 'stats' => [ 'class' => 'topic_stats', 'content' => ['header' => '{replies} / {views}'], 'size' => '10%', 'rowspan' => 2, 'colspan' => 1, 'row_number' => 1, ], 'lastpost' => [ 'class' => 'lastpost', 'content' => ['header' => '{last_post}'], 'size' => '28%', 'rowspan' => 2, 'colspan' => 1, 'row_number' => 1, ], ]; $grid_rows = []; $num_rows = 1; $grid_sizes = []; $row_numbers = []; $indexed_grid_areas = []; $column_index = 0; // Tracks column position $column_numbers = []; $prev_name = ''; foreach ($columns as $name => $column) { $row_index = $column['row_number'] - 1; // Convert to zero-based index if (!isset($grid_rows[$row_index])) { $grid_rows[$row_index] = []; } for ($i = 0; $i < $column['colspan']; $i++) { for ($y = 0; $y < $column['rowspan']; $y++) { $current_row = $row_index + $y; $row_numbers[$current_row][$name] = ($column_numbers[$row_index] ?? 0); if ($row_numbers[$current_row][$name] === 0) { $row_numbers[$current_row][$name] = (get_adjacent_value($row_numbers[$current_row], $name) ?? -1) + 1; } $column_index = $row_numbers[$current_row][$name]; $grid_rows[$current_row][$column_index + $i] = $name; } // Add column sizes only if we're on the first row. if ($column['row_number'] === 1) { $grid_sizes[] = $column['size']; } } // Move to the next available column //~ $column_index = $row_numbers[$row_index][$column['colspan']; $column_numbers[$row_index] = ($column_numbers[$row_index] ?? $row_numbers[$row_index][$name]) + $column['colspan']; $num_rows = max($num_rows, $row_index + $column['rowspan']); $prev_name = $name; } var_export($row_numbers); // Fill empty grid areas. for ($y = 0; $y < $num_rows; $y++) { for ($i = 0, $n = count($grid_sizes); $i < $n; $i++) { $indexed_grid_areas[$y][$i] = $grid_rows[$y][$i] ?? '.'; } } // Convert rows into CSS grid template areas. $grid_areas_str = implode('" "', array_map(fn($r) => implode(' ', $r), $indexed_grid_areas)); echo ' <style>'; foreach ($columns as $name => $column) { echo ' .' . $column['class'] . ' { grid-area: ' . $name . '; }'; } echo ' #topic_header, .topic_container { --grid-template-columns:' . implode(' ', $grid_sizes) . '; --grid-template-areas: "' . $grid_areas_str . '"; } </style>'; /** * Get the previous or next value in an associative array based on a given key. * * @param array $array The associative array. * @param mixed $current_key The key to search for. * @param string $direction "before" to get the previous value, "after" to get the next value. * * @return mixed|null The found value if exists, otherwise null. */ function get_adjacent_value(array $array, $current_key, string $direction = "before") { reset($array); $previous_value = null; while (key($array) !== null) { $key_in_loop = key($array); $value = current($array); if ($direction === "before" && $key_in_loop === $current_key) { return $previous_value; } next($array); if ($direction === "after" && $key_in_loop === $current_key) { return current($array) !== false ? current($array) : null; } $previous_value = $value; } return null; } 

Output

 <style> .topic_icon { grid-area: icon; } .info { grid-area: info; } .topic_author { grid-area: author; } .topic_stats { grid-area: stats; } .lastpost { grid-area: lastpost; } #topic_header, .topic_container { --grid-template-columns:2em 1fr 1fr 10% 28%; --grid-template-areas: "icon info info stats lastpost" "icon author . stats lastpost"; } </style> 
\$\endgroup\$
1
  • \$\begingroup\$I, like you, am a non-mathematician finding myself doing lots of mathematical things. My basic advice would be to simplify everything to the fewest parts, always write clear tests, and likewise develop inverse functions. Then you can 'round-trip' what you are doing.\$\endgroup\$
    – Konchog
    CommentedMar 6 at 11:50

1 Answer 1

1
\$\begingroup\$

The problem with your script is misalignment in grid column positioning due to incorrect column index calculations. The script doesn’t properly track where each column should start, causing gaps and misplacements in the grid-template-areas. This happens because $column_index and $row_numbers aren't correctly incremented when handling colspan and rowspan.

\$\endgroup\$

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.