Skip to content

Commit

Permalink
bindListCallback (#268)
Browse files Browse the repository at this point in the history
* build: upgrade to stable dom release

* test: bind objects in arrays with bindList

* wip: complex test for #261

* feature: bindListCallback allows callback to be called for each list item
closes #261

* feature: expose and test callback functions
closes #261
  • Loading branch information
g105b authored Oct 12, 2021
1 parent 1b89af7 commit d8006b5
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 7 deletions.
18 changes: 18 additions & 0 deletions src/DocumentBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,24 @@ public function bindList(
return $this->listBinder->bindListData($listData, $context, $templateName);
}

public function bindListCallback(
iterable $listData,
callable $callback,
?Element $context = null,
?string $templateName = null
):int {
if(!$context) {
$context = $this->document;
}

return $this->listBinder->bindListData(
$listData,
$context,
$templateName,
$callback
);
}

private function bind(
?string $key,
mixed $value,
Expand Down
24 changes: 18 additions & 6 deletions src/ListBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public function __construct(
public function bindListData(
iterable $listData,
Document|Element $context,
?string $templateName = null
?string $templateName = null,
?callable $callback = null,
):int {
if($context instanceof Document) {
$context = $context->documentElement;
Expand All @@ -36,7 +37,7 @@ public function bindListData(
$templateName
);

$binder = new ElementBinder();
$elementBinder = new ElementBinder();
$nestedCount = 0;
$i = -1;
foreach($listData as $listKey => $listItem) {
Expand All @@ -45,7 +46,7 @@ public function bindListData(

// If the $listItem's first value is iterable, then treat this as a nested list.
if($this->isNested($listItem)) {
$binder->bind(null, $listKey, $t);
$elementBinder->bind(null, $listKey, $t);
$nestedCount += $this->bindListData(
$listItem,
$t,
Expand All @@ -64,11 +65,22 @@ public function bindListData(
}

if($this->isKVP($listItem)) {
if($callback) {
$listItem = call_user_func(
$callback,
$t,
$listItem,
$listKey,
);
}

$elementBinder->bind(null, $listKey, $t);

foreach($listItem as $key => $value) {
$binder->bind($key, $value, $t);
$elementBinder->bind($key, $value, $t);

if($this->isNested($value)) {
$binder->bind(null, $key, $t);
$elementBinder->bind(null, $key, $t);
$nestedCount += $this->bindListData(
$value,
$t,
Expand All @@ -78,7 +90,7 @@ public function bindListData(
}
}
else {
$binder->bind(null, $listItem, $t);
$elementBinder->bind(null, $listItem, $t);
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/PlaceholderBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ public function bind(
);

foreach($xpathResult as $attributeOrText) {
/** @var Text|Attr $text */
$text = $attributeOrText;
if($text instanceof Attr) {
$text = $text->firstChild;
}

/** @var Text $text */
$placeholder = $text->splitText(
strpos($text->data, "{{")
Expand All @@ -41,7 +43,8 @@ public function bind(
);

$placeholderText = new PlaceholderText($placeholder);
if($key !== $placeholderText->getBindKey()) {
if((string)$key !== $placeholderText->getBindKey()) {
$text->parentNode->nodeValue = $text->wholeText;
continue;
}

Expand Down
160 changes: 160 additions & 0 deletions test/phpunit/DocumentBinderTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace Gt\DomTemplate\Test;

use DateInterval;
use Gt\Dom\Element;
use Gt\Dom\HTMLCollection;
use Gt\Dom\HTMLElement\HTMLButtonElement;
Expand All @@ -12,7 +13,9 @@
use Gt\DomTemplate\DocumentBinder;
use Gt\DomTemplate\IncompatibleBindDataException;
use Gt\DomTemplate\InvalidBindPropertyException;
use Gt\DomTemplate\ListBinder;
use Gt\DomTemplate\TableElementNotFoundInContextException;
use Gt\DomTemplate\TemplateCollection;
use Gt\DomTemplate\Test\TestFactory\DocumentTestFactory;
use PHPUnit\Framework\TestCase;
use stdClass;
Expand Down Expand Up @@ -546,4 +549,161 @@ public function testBindValue_callable():void {
$sut->bindValue(fn() => "test");
self::assertSame("test", $document->querySelector("output")->textContent);
}

public function testBindList_complexHTML():void {
$from = "Slitting Mill";
$to = "Clipstone";

$routesData = [
[
"duration" => new DateInterval("PT3H58M"),
"method" => "Train",
"steps" => [
"rtv471" => [
"time" => "07:02",
"location" => "Rugeley Trent Valley",
],
"ltv991" => [
"time" => "07:49",
"location" => "Lichfield Trent Valley",
],
"tem010" => [
"time" => "08:03",
"location" => "Tamworth",
],
"csy001" => [
"time" => "09:03",
"location" => "Chesterfield",
],
"ep090" => [
"time" => "09:42",
"location" => "Eastwood Park",
],
"mnn310" => [
"time" => "10:25",
"location" => "Mansfield",
],
"c0390" => [
"time" => "11:00",
"location" => "Clipstone",
]
]
], [
"duration" => new DateInterval("PT4H11M"),
"method" => "Bus",
"steps" => [
"stv472" => [
"time" => "06:20",
"location" => "Rugeley Trent Valley",
],
"ltv050" => [
"time" => "07:40",
"location" => "Lichfield City Centre",
],
"ltv921" => [
"time" => "08:00",
"location" => "Mosley Street",
],
"sd094" => [
"time" => "08:18",
"location" => "Burton-on-Trent"
],
"ng001" => [
"time" => "09:06",
"location" => "Nottingham",
],
"mnn310" => [
"time" => "10:01",
"location" => "Mansfield",
],
"c0353" => [
"time" => "10:31",
"location" => "Greendale Crescent",
]
]
]
];

$document = DocumentTestFactory::createHTML(DocumentTestFactory::HTML_TRANSPORT_ROUTES);
$sut = new DocumentBinder($document);
$sut->bindKeyValue("from", $from);
$sut->bindKeyValue("to", $to);

$callback = function(
Element $templateElement,
array $kvp,
int|string $key,
):array {
if($duration = $kvp["duration"]) {
/** @var DateInterval $duration */
$kvp["duration"] = $duration->format("%H:%m");
}

return $kvp;
};

$sut->bindListCallback($routesData, $callback);
$routeLiList = $document->querySelectorAll("ul>li");
self::assertCount(2, $routeLiList);
self::assertCount(count($routesData[0]["steps"]), $routeLiList[0]->querySelectorAll("ol>li"));
self::assertCount(count($routesData[1]["steps"]), $routeLiList[1]->querySelectorAll("ol>li"));

foreach($routesData as $i => $route) {
self::assertEquals($route["method"], $routeLiList[$i]->querySelector("p")->textContent);
self::assertEquals($route["duration"]->format("%H:%m"), $routeLiList[$i]->querySelector("time")->textContent);
$stepLiList = $routeLiList[$i]->querySelectorAll("ol>li");
$j = 0;

foreach($route["steps"] as $id => $step) {
$stepLi = $stepLiList[$j];
self::assertEquals($step["time"], $stepLi->querySelector("time")->textContent);
self::assertEquals($step["location"], $stepLi->querySelector("span")->textContent);
self::assertEquals("/route/step/$id", $stepLi->querySelector("a")->href);
$j++;
}
}
}

public function testBindListData_callback():void {
$salesData = [
[
"name" => "Cactus",
"count" => 14,
"price" => 5.50,
"cost" => 3.55,
],
[
"name" => "Succulent",
"count" => 9,
"price" => 3.50,
"cost" => 2.10,
]
];
$salesCallback = function(Element $template, array $listItem, string $key):array {
$totalPrice = $listItem["price"] * $listItem["count"];
$totalCost = $listItem["cost"] * $listItem["count"];

$listItem["profit"] = round($totalPrice - $totalCost, 2);
return $listItem;
};

$document = DocumentTestFactory::createHTML(DocumentTestFactory::HTML_SALES);
$sut = new DocumentBinder($document);
$sut->bindListCallback(
$salesData,
$salesCallback
);

$salesLiList = $document->querySelectorAll("ul>li");
self::assertCount(count($salesData), $salesLiList);
foreach($salesData as $i => $sale) {
$li = $salesLiList[$i];
$profitValue = round(($sale["count"] * $sale["price"]) - ($sale["count"] * $sale["cost"]), 2);
self::assertEquals($sale["name"], $li->querySelector(".name span")->textContent);
self::assertEquals($sale["count"], $li->querySelector(".count span")->textContent);
self::assertEquals($sale["price"], $li->querySelector(".price span")->textContent);
self::assertEquals($sale["cost"], $li->querySelector(".cost span")->textContent);
self::assertEquals($profitValue, $li->querySelector(".profit span")->textContent);
}
}
}
46 changes: 46 additions & 0 deletions test/phpunit/ListBinderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use ArrayIterator;
use DateInterval;
use DateTime;
use Gt\Dom\Element;
use Gt\Dom\HTMLElement\HTMLLiElement;
use Gt\DomTemplate\Bind;
use Gt\DomTemplate\ElementBinder;
Expand Down Expand Up @@ -497,4 +498,49 @@ public function testBindListData_multipleTemplateSiblings():void {
self::assertEquals($expected[$i], $li->querySelector("span")->textContent);
}
}

public function testBindListData_callback():void {
$salesData = [
[
"name" => "Cactus",
"count" => 14,
"price" => 5.50,
"cost" => 3.55,
],
[
"name" => "Succulent",
"count" => 9,
"price" => 3.50,
"cost" => 2.10,
]
];
$salesCallback = function(Element $template, array $listItem, string $key):array {
$totalPrice = $listItem["price"] * $listItem["count"];
$totalCost = $listItem["cost"] * $listItem["count"];

$listItem["profit"] = round($totalPrice - $totalCost, 2);
return $listItem;
};

$document = DocumentTestFactory::createHTML(DocumentTestFactory::HTML_SALES);
$templateCollection = new TemplateCollection($document);
$sut = new ListBinder($templateCollection);
$sut->bindListData(
$salesData,
$document,
callback: $salesCallback
);

$salesLiList = $document->querySelectorAll("ul>li");
self::assertCount(count($salesData), $salesLiList);
foreach($salesData as $i => $sale) {
$li = $salesLiList[$i];
$profitValue = round(($sale["count"] * $sale["price"]) - ($sale["count"] * $sale["cost"]), 2);
self::assertEquals($sale["name"], $li->querySelector(".name span")->textContent);
self::assertEquals($sale["count"], $li->querySelector(".count span")->textContent);
self::assertEquals($sale["price"], $li->querySelector(".price span")->textContent);
self::assertEquals($sale["cost"], $li->querySelector(".cost span")->textContent);
self::assertEquals($profitValue, $li->querySelector(".profit span")->textContent);
}
}
}
Loading

0 comments on commit d8006b5

Please sign in to comment.