Skip to content
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

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

Merged
merged 14 commits into from
Apr 10, 2023
45 changes: 42 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 $syncRowsWithHeading = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a need to make this public?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All other properties are public, I will keep it for consistency

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All other properties seems to be wrong, but changing them is breaking changes...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paulbalandan

  1. public for current consistency
  2. change only this property to protected or private

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like this new property to be private to enforce API encapsulation. We can increase visibility later if needed.
For the others, we can retain them as public to prevent breaking changes.

kenjis marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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->syncRowsWithHeading = false;

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

if ($this->syncRowsWithHeading && ! 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
*
* @return Table
rumpfc marked this conversation as resolved.
Show resolved Hide resolved
*/
rumpfc marked this conversation as resolved.
Show resolved Hide resolved
public function setSyncRowsWithHeading(bool $orderByKey)
{
$this->syncRowsWithHeading = $orderByKey;

return $this;
}
Expand Down Expand Up @@ -436,7 +475,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 testAddRowAndGenerateOrderedColumns(array $heading, array $row, string $expectContainsString): void
{
$this->table->setHeading($heading);
$this->table->setSyncRowsWithHeading(true);
$this->table->addRow($row);

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

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

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

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

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

public function orderedColumnUsecases(): iterable
{
yield from [
'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
1 change: 1 addition & 0 deletions user_guide_src/source/changelogs/v4.4.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Others
See :ref:`controller-default-method-fallback` for details.
- **Filters:** Now you can use Filter Arguments with :ref:`$filters property <filters-filters-filter-arguments>`.
- **Request:** Added ``IncomingRequest::setValidLocales()`` method to set valid locales.
- **Table:** Addedd ``Table::setSyncRowsWithHeading()`` method to synchronize row columns with heading. See :ref:`table-sync-rows-with-headings` for details.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- **Table:** Addedd ``Table::setSyncRowsWithHeading()`` method to synchronize row columns with heading. See :ref:`table-sync-rows-with-headings` for details.
- **Table:** Added ``Table::setSyncRowsWithHeading()`` method to synchronize row columns with headings. See :ref:`table-sync-rows-with-headings` for details.


Message Changes
***************
Expand Down
37 changes: 37 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,34 @@ to the Table constructor:

.. literalinclude:: table/008.php

.. _table-sync-rows-with-headings:

Synchronizing Rows with Headings
================================

.. versionadded:: 4.4.0

The ``setSyncRowsWithHeading(true)`` method 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 that is not present in the heading, its value is
filtered. Conversely, if a data row does not have a key listed in the heading,
an empty cell will be placed in its place.

.. literalinclude:: table/019.php

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

Using an array as input to ``generate()`` produces the same result:

.. literalinclude:: table/020.php


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

.. literalinclude:: table/018.php

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

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

Enables each row data key to be ordered by heading keys. This gives
more control of data being displaced in the correct column. 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'])
->setSyncRowsWithHeading(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'])
->setSyncRowsWithHeading(true);

echo $table->generate($data);