Skip to content

feat: HTML Table data keys synchronize order with Heading keys #7409

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Apr 10, 2023
43 changes: 40 additions & 3 deletions system/View/Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ class Table
*/
public $function;

/**
* Order each inserted row by heading keys
*/
public bool $rowKeysSyncWithHeadingKeys = false;

/**
* Set the template from the table config file if it exists
*
Expand Down Expand Up @@ -161,7 +166,8 @@ public function makeColumns($array = [], $columnLimit = 0)

// Turn off the auto-heading feature since it's doubtful we
// will want headings from a one-dimensional array
$this->autoHeading = false;
$this->autoHeading = false;
$this->rowKeysSyncWithHeadingKeys = false;

if ($columnLimit === 0) {
return $array;
Expand Down Expand Up @@ -207,7 +213,38 @@ public function setEmpty($value)
*/
public function addRow()
{
$this->rows[] = $this->_prepArgs(func_get_args());
$tmpRow = $this->_prepArgs(func_get_args());

if ($this->rowKeysSyncWithHeadingKeys && ! empty($this->heading)) {
// each key has an index
$keyIndex = array_flip(array_keys($this->heading));

// figure out which keys need to be added
$missingKeys = array_diff_key($keyIndex, $tmpRow);

// Remove all keys which don't exist in $keyIndex
$tmpRow = array_filter($tmpRow, static fn ($k) => array_key_exists($k, $keyIndex), ARRAY_FILTER_USE_KEY);

// add missing keys to row, but use $this->emptyCells
$tmpRow = array_merge($tmpRow, array_map(fn ($v) => ['data' => $this->emptyCells], $missingKeys));

// order keys by $keyIndex values
uksort($tmpRow, static fn ($k1, $k2) => $keyIndex[$k1] <=> $keyIndex[$k2]);
}
$this->rows[] = $tmpRow;

return $this;
}

/**
* Set to true if each row column should be synced by keys defined in heading.
*
* If a row has a key which does not exist in heading, it will be filtered out
* If a row does not have a key which exists in heading, the field will stay empty
*/
public function setSyncRowKeysWithHeadingKeys(bool $orderByKey): Table
{
$this->rowKeysSyncWithHeadingKeys = $orderByKey;

return $this;
}
Expand Down Expand Up @@ -436,7 +473,7 @@ protected function _setFromArray($data)
}

foreach ($data as &$row) {
$this->rows[] = $this->_prepArgs($row);
$this->addRow($row);
}
}

Expand Down
53 changes: 53 additions & 0 deletions tests/system/View/TableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,59 @@ public function testInvalidCallback()

$this->assertStringContainsString('<td>Fred</td><td><strong>Blue</strong></td><td>Small</td>', $generated);
}

/**
* @dataProvider orderedColumnUsecases
*/
public function testAddRowAndGenerateWithOrderedColumns(array $heading, array $row, string $expectContainsString): void
{
$this->table->setHeading($heading);
$this->table->setSyncRowKeysWithHeadingKeys(true);
$this->table->addRow($row);

$generated = $this->table->generate();

$this->assertStringContainsString($expectContainsString, $generated);
}

/**
* @dataProvider orderedColumnUsecases
*/
public function testGenerateDataWithOrderedColumns(array $heading, array $row, string $expectContainsString): void
{
$this->table->setHeading($heading);
$this->table->setSyncRowKeysWithHeadingKeys(true);

$generated = $this->table->generate([$row]);

$this->assertStringContainsString($expectContainsString, $generated);
}

public function orderedColumnUsecases(): array
{
return [
'reorder example #1' => [
'heading' => ['id' => 'ID', 'name' => 'Name', 'age' => 'Age'],
'row' => ['name' => 'Max', 'age' => 30, 'id' => 5],
'expectContainsString' => '<td>5</td><td>Max</td><td>30</td>',
],
'reorder example #2' => [
'heading' => ['id' => 'ID', 'age' => 'Age', 'name' => 'Name'],
'row' => ['name' => 'Fred', 'age' => 30, 'id' => 5],
'expectContainsString' => '<td>5</td><td>30</td><td>Fred</td>',
],
'2 col heading, 3 col data row' => [
'heading' => ['id' => 'ID', 'name' => 'Name'],
'row' => ['name' => 'Fred', 'age' => 30, 'id' => 5],
'expectContainsString' => '<td>5</td><td>Fred</td>',
],
'3 col heading, 2 col data row' => [
'heading' => ['id' => 'ID', 'age' => 'Age', 'name' => 'Name'],
'row' => ['name' => 'Fred', 'id' => 5],
'expectContainsString' => '<td>5</td><td></td><td>Fred</td>',
],
];
}
}

// We need this for the _set_from_db_result() test
Expand Down
35 changes: 35 additions & 0 deletions user_guide_src/source/outgoing/table.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,32 @@ to the Table constructor:

.. literalinclude:: table/008.php

Synchronizing row columns with heading
======================================

.. versionadded:: 4.4.0

``setSyncRowKeysWithHeadingKeys(true)`` enables that each data value
is placed in the same column as defined in ``setHeading()`` if an
associative array was used as parameter. This is especially useful
when dealing with data loaded via REST API where the order is not to
your liking, or if the API returned too much data.

If a data row contains a ``key`` which does not exist the heading,
the value will be filtered out. Vise versa if a data row does not have key
mentioned in heading, it places an empty cell for that spot.

.. literalinclude:: table/019.php

.. important:: You must call ``setSyncRowKeysWithHeadingKeys(true)`` and
``setHeading([...])`` before adding any rows via ``addRow([...])`` where
the rearrangement of columns takes place.

You get the same result by using the result array is input in ``generate()``

.. literalinclude:: table/020.php


***************
Class Reference
***************
Expand Down Expand Up @@ -188,3 +214,12 @@ Class Reference
Example

.. literalinclude:: table/018.php

.. php:method:: setSyncRowKeysWithHeadingKeys(bool $orderByKey)

:returns: Table instance (method chaining)
:rtype: Table

Enables each row column to be ordered by heading keys. This gives
more control of how data is displayed in the final table. Make
sure to set this value before calling the first ``addRow()`` method.
40 changes: 40 additions & 0 deletions user_guide_src/source/outgoing/table/019.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

$table = new \CodeIgniter\View\Table();

$table->setHeading(['name' => 'Name', 'color' => 'Color', 'size' => 'Size'])
->setSyncRowKeysWithHeadingKeys(true)
->addRow(['color' => 'Blue', 'name' => 'Fred', 'size' => 'Small'])
->addRow(['size' => 'Large', 'age' => '24', 'name' => 'Mary'])
->addRow(['color' => 'Green']);

echo $table->generate();
?>

<!-- Generates a table with this prototype: -->
<table border="0" cellpadding="4" cellspacing="0">
<thead>
<tr>
<th>Name</th>
<th>Color</th>
<th>Size</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fred</td>
<td>Blue</td>
<td>Small</td>
</tr>
<tr>
<td>Mary</td>
<td></td>
<td>Large</td>
</tr>
<tr>
<td></td>
<td>Green</td>
<td></td>
</tr>
</tbody>
</table>
24 changes: 24 additions & 0 deletions user_guide_src/source/outgoing/table/020.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

$data = [
[
'color' => 'Blue',
'name' => 'Fred',
'size' => 'Small',
],
[
'size' => 'Large',
'age' => '24',
'name' => 'Mary',
],
[
'color' => 'Green',
],
];

$table = new \CodeIgniter\View\Table();

$table->setHeading(['name' => 'Name', 'color' => 'Color', 'size' => 'Size'])
->setSyncRowKeysWithHeadingKeys(true);

echo $table->generate($data);