Skip to content

Commit

Permalink
Existing table content for templating (#300)
Browse files Browse the repository at this point in the history
* feature: allow headers to be empty

* feature: existing table content for templating

* fix: feature regression
  • Loading branch information
g105b authored Nov 16, 2021
1 parent 6b06a89 commit e4e2208
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 5 deletions.
88 changes: 83 additions & 5 deletions src/TableBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<int, array<int, string>>|array<int, array<int|string, string|array<int, mixed>>> $tableData
* @param Element $context
Expand All @@ -24,6 +32,8 @@ public function bindTableData(
$context = $context->documentElement;
}

$this->initBinders();

$tableArray = [$context];
if(!$context instanceof HTMLTableElement) {
$tableArray = [];
Expand Down Expand Up @@ -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";

Expand All @@ -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
);
}
}
}
Expand Down Expand Up @@ -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
);
}
}
}
77 changes: 77 additions & 0 deletions test/phpunit/TableBinderTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace Gt\DomTemplate\Test;

use Gt\Dom\HTMLElement\HTMLInputElement;
use Gt\Dom\HTMLElement\HTMLTableCellElement;
use Gt\Dom\HTMLElement\HTMLTableElement;
use Gt\Dom\HTMLElement\HTMLTableRowElement;
Expand Down Expand Up @@ -373,4 +374,80 @@ public function testBindTableData_documentContext():void {

self::assertCount(4, $document->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"));
}
}
}
}
49 changes: 49 additions & 0 deletions test/phpunit/TestFactory/DocumentTestFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,55 @@ class DocumentTestFactory {
</div>
HTML;

const HTML_TABLE_ID_NAME_CODE = <<<HTML
<!doctype html>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Code</th>
<th>Delete</th>
</tr>
</thead>
</table>
HTML;

const HTML_TABLE_EXISTING_CELLS = <<<HTML
<!doctype html>
<table>
<thead>
<tr>
<th>Delete</th>
<th data-table-key="id">ID</th>
<th data-table-key="name">Name</th>
<th data-table-key="code">Code</th>
<th>Flag</th>
</tr>
</thead>
<tbody>
<tr data-template>
<td data-bind:class=":deleted">
<form method="post">
<input type="hidden" name="id" data-bind:value="@name" />
<button name="do" value="delete">Delete</button>
</form>
</td>
<td></td>
<td></td>
<td></td>
<td>
<form method="post">
<input type="hidden" name="id" data-bind:value="@name" />
<button name="do" value="flag">Flag</button>
</form>
</td>
</tr>
</tbody>
</table>
HTML;


const HTML_LIST_TEMPLATE = <<<HTML
<!doctype html>
<ul>
Expand Down

0 comments on commit e4e2208

Please sign in to comment.