Skip to content

Commit

Permalink
Merge pull request #7409 from rumpfc/feature-view-table-order
Browse files Browse the repository at this point in the history
feat: HTML Table data keys synchronize order with Heading keys
  • Loading branch information
kenjis authored Apr 10, 2023
2 parents 95a6be9 + 57fc049 commit d073d41
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 3 deletions.
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
*/
private bool $syncRowsWithHeading = 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->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 $this
*/
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 @@ -91,6 +91,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:** Added ``Table::setSyncRowsWithHeading()`` method to synchronize row columns with headings. See :ref:`table-sync-rows-with-headings` for details.
- **Error Handling:** Now you can use :ref:`custom-exception-handlers`.

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);

0 comments on commit d073d41

Please sign in to comment.