From e4e22089c3174b69211707405c0f4a662e2aa8ce Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Tue, 16 Nov 2021 13:01:46 +0000 Subject: [PATCH] Existing table content for templating (#300) * feature: allow headers to be empty * feature: existing table content for templating * fix: feature regression --- src/TableBinder.php | 88 +++++++++++++++++-- test/phpunit/TableBinderTest.php | 77 ++++++++++++++++ .../TestFactory/DocumentTestFactory.php | 49 +++++++++++ 3 files changed, 209 insertions(+), 5 deletions(-) diff --git a/src/TableBinder.php b/src/TableBinder.php index eefd92b..101c119 100644 --- a/src/TableBinder.php +++ b/src/TableBinder.php @@ -10,6 +10,14 @@ use Stringable; class TableBinder { + public function __construct( + private ?TemplateCollection $templateCollection = null, + private ?ElementBinder $elementBinder = null, + private ?HTMLAttributeBinder $htmlAttributeBinder = null, + private ?HTMLAttributeCollection $htmlAttributeCollection = null, + private ?PlaceholderBinder $placeholderBinder = null + ) {} + /** * @param array>|array>> $tableData * @param Element $context @@ -24,6 +32,8 @@ public function bindTableData( $context = $context->documentElement; } + $this->initBinders(); + $tableArray = [$context]; if(!$context instanceof HTMLTableElement) { $tableArray = []; @@ -71,12 +81,23 @@ public function bindTableData( $tbody = $table->createTBody(); } + $templateCollection = $this->templateCollection + ?? new TemplateCollection($context->ownerDocument); + foreach($tableData as $rowData) { - $tr = $tbody->insertRow(); + try { + $trTemplate = $templateCollection->get($tbody); + /** @var HTMLTableRowElement $tr */ + $tr = $trTemplate->insertTemplate(); + } + catch(TemplateElementNotFoundInContextException) { + $tr = $tbody->insertRow(); + } + /** @var int|string|null $firstKey */ $firstKey = key($rowData); - foreach($allowedHeaders as $allowedHeader) { + foreach($allowedHeaders as $headerIndex => $allowedHeader) { $rowIndex = array_search($allowedHeader, $headerRow); $cellTypeToCreate = "td"; @@ -90,12 +111,50 @@ public function bindTableData( } } else { - $columnValue = $rowData[$rowIndex]; + if(false === $rowIndex) { + $columnValue = ""; + } + else { + $columnValue = $rowData[$rowIndex]; + } + } + + if($headerIndex < $tr->cells->length - 1) { + if(false === $rowIndex) { + continue; + } + + $cellElement = $tr->cells[$headerIndex]; + } + else { + $cellElement = $tr->ownerDocument->createElement($cellTypeToCreate); } - $cellElement = $tr->ownerDocument->createElement($cellTypeToCreate); $cellElement->textContent = $columnValue ?? ""; - $tr->appendChild($cellElement); + + if(!$cellElement->parentElement) { + $tr->appendChild($cellElement); + } + } + + foreach($rowData as $index => $value) { + $headerRowIndex = $index; + if(!is_int($index)) { + $headerRowIndex = null; + foreach($tableData as $tableDataIndex => $tableDatum) { + if($index === key($tableDatum)) { + $headerRowIndex = $tableDataIndex; + break; + } + } + } + + $key = $headerRow[$headerRowIndex]; + $this->elementBinder->bind( + $key, + $value, + $tr + ); } } } @@ -175,4 +234,23 @@ private function normaliseTableData(iterable $bindValue):array { return $normalised; } + + private function initBinders():void { + if(!$this->htmlAttributeBinder) { + $this->htmlAttributeBinder = new HTMLAttributeBinder(); + } + if(!$this->htmlAttributeCollection) { + $this->htmlAttributeCollection = new HTMLAttributeCollection(); + } + if(!$this->placeholderBinder) { + $this->placeholderBinder = new PlaceholderBinder(); + } + if(!$this->elementBinder) { + $this->elementBinder = new ElementBinder( + $this->htmlAttributeBinder, + $this->htmlAttributeCollection, + $this->placeholderBinder + ); + } + } } diff --git a/test/phpunit/TableBinderTest.php b/test/phpunit/TableBinderTest.php index 127a71d..6bc49bb 100644 --- a/test/phpunit/TableBinderTest.php +++ b/test/phpunit/TableBinderTest.php @@ -1,6 +1,7 @@ querySelectorAll("table tr")); } + + public function testBindTableData_emptyHeader():void { + $sut = new TableBinder(); + $tableData = [ + ["ID", "Name", "Code"], + ]; + for($i = 1; $i <= 10; $i++) { + $name = "Thing $i"; + array_push($tableData, [$i, $name, md5($name)]); + } + + $document = DocumentTestFactory::createHTML(DocumentTestFactory::HTML_TABLE_ID_NAME_CODE); + $sut->bindTableData($tableData, $document); + + /** @var HTMLTableElement $table */ + $table = $document->querySelector("table"); + /** @var HTMLTableRowElement $theadRow */ + $theadRow = $table->tHead->rows[0]; + self::assertCount(4, $theadRow->cells); + self::assertSame("Delete", $theadRow->cells[3]->textContent); + + /** @var HTMLTableSectionElement $tbody */ + $tbody = $table->tBodies[0]; + /** @var HTMLTableRowElement $row */ + foreach($tbody->rows as $rowIndex => $row) { + foreach($row->cells as $cellIndex => $cell) { + $expected = $tableData[$rowIndex + 1][$cellIndex] ?? ""; + self::assertSame((string)$expected, $cell->textContent); + } + } + } + + public function testBindTableData_existingBodyRow():void { + $tableData = [ + ["id", "code", "name", "deleted"], + ]; +// 3, 6 and 9 will be marked as "Deleted". + for($i = 1; $i <= 10; $i++) { + $name = "Thing $i"; + array_push($tableData, [$i, md5($name), $name, $i % 3 === 0]); + } + + $document = DocumentTestFactory::createHTML(DocumentTestFactory::HTML_TABLE_EXISTING_CELLS); + $sut = new TableBinder(); + + $sut->bindTableData($tableData, $document); + + /** @var HTMLTableSectionElement $tbody */ + $tbody = $document->querySelector("table tbody"); + + $headers = array_shift($tableData); + + /** @var HTMLTableRowElement $tr */ + foreach($tbody->rows as $rowIndex => $tr) { + $rowData = array_combine($headers, $tableData[$rowIndex]); + + self::assertSame((string)$rowData["id"], $tr->cells[1]->textContent); + self::assertSame((string)$rowData["name"], $tr->cells[2]->textContent); + self::assertSame((string)$rowData["code"], $tr->cells[3]->textContent); + + /** @var HTMLInputElement $input */ + $input = $tr->cells[0]->querySelector("input"); + self::assertSame((string)$rowData["id"], $input->value); + + /** @var HTMLInputElement $input */ + $input = $tr->cells[4]->querySelector("input"); + self::assertSame((string)$rowData["id"], $input->value); + + if(($rowIndex + 1) % 3 === 0) { + self::assertTrue($tr->cells[0]->classList->contains("deleted")); + } + else { + self::assertFalse($tr->cells[0]->classList->contains("deleted")); + } + } + } } diff --git a/test/phpunit/TestFactory/DocumentTestFactory.php b/test/phpunit/TestFactory/DocumentTestFactory.php index c5f82d5..274de4f 100644 --- a/test/phpunit/TestFactory/DocumentTestFactory.php +++ b/test/phpunit/TestFactory/DocumentTestFactory.php @@ -177,6 +177,55 @@ class DocumentTestFactory { HTML; + const HTML_TABLE_ID_NAME_CODE = << + + + + + + + + + +
IDNameCodeDelete
+HTML; + + const HTML_TABLE_EXISTING_CELLS = << + + + + + + + + + + + + + + + + + + + +
DeleteIDNameCodeFlag
+
+ + +
+
+
+ + +
+
+HTML; + + const HTML_LIST_TEMPLATE = <<