Skip to content

Commit 9ecc381

Browse files
authored
Rebinding of elements and lists (#384)
* build: upgrade dom requirement and loosen version range * docs: update examples * maintenance: improve static analysis * wip: only bind lists once for #341 * feature: lists can be rebound multiple times closes #341
1 parent cc2b584 commit 9ecc381

File tree

4 files changed

+63
-47
lines changed

4 files changed

+63
-47
lines changed

src/TemplateElement.php

+13-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
class TemplateElement {
99
private string $templateParentPath;
1010
private null|Node|Element $templateNextSibling;
11+
private int $insertCount;
1112

1213
public function __construct(
1314
private Node|Element $originalElement
@@ -29,16 +30,22 @@ public function __construct(
2930
is_null($siblingContext)
3031
? null
3132
: $siblingContext;
33+
34+
$this->insertCount = 0;
3235
}
3336

3437
public function removeOriginalElement():void {
3538
$this->originalElement->remove();
3639
}
3740

3841
public function getClone():Node|Element {
39-
/** @noinspection PhpUnnecessaryLocalVariableInspection */
42+
// TODO: Bug here - the template-parent-xxx ID is being generated the same for multiple instances.
4043
/** @var Element $element */
4144
$element = $this->originalElement->cloneNode(true);
45+
// foreach($this->originalElement->ownerDocument->evaluate("./*[starts-with(@id,'template-parent-')]", $element) as $existingTemplateElement) {
46+
// $existingTemplateElement->id = uniqid("template-parent-");
47+
// }
48+
// $this->templateParentPath = new NodePathCalculator($element->parentElement);
4249
return $element;
4350
}
4451

@@ -54,7 +61,7 @@ public function insertTemplate():Node|Element {
5461
$clone,
5562
$this->getTemplateNextSibling()
5663
);
57-
64+
$this->insertCount++;
5865
return $clone;
5966
}
6067

@@ -86,4 +93,8 @@ public function getTemplateName():?string {
8693

8794
return $templateName;
8895
}
96+
97+
public function getInsertCount():int {
98+
return $this->insertCount;
99+
}
89100
}

test/phpunit/DocumentBinderTest.php

+26-18
Original file line numberDiff line numberDiff line change
@@ -685,68 +685,69 @@ public function testBindList_complexHTML():void {
685685
"duration" => new DateInterval("PT3H58M"),
686686
"method" => "Train",
687687
"steps" => [
688-
"rtv471" => [
688+
"t-rtv471" => [
689689
"time" => "07:02",
690690
"location" => "Rugeley Trent Valley",
691691
],
692-
"ltv991" => [
692+
"t-ltv991" => [
693693
"time" => "07:49",
694694
"location" => "Lichfield Trent Valley",
695695
],
696-
"tem010" => [
696+
"t-tem010" => [
697697
"time" => "08:03",
698698
"location" => "Tamworth",
699699
],
700-
"csy001" => [
700+
"t-csy001" => [
701701
"time" => "09:03",
702702
"location" => "Chesterfield",
703703
],
704-
"ep090" => [
704+
"t-ep090" => [
705705
"time" => "09:42",
706706
"location" => "Eastwood Park",
707707
],
708-
"mnn310" => [
708+
"t-mnn310" => [
709709
"time" => "10:25",
710710
"location" => "Mansfield",
711711
],
712-
"c0390" => [
712+
"t-c0390" => [
713713
"time" => "11:00",
714714
"location" => "Clipstone",
715715
]
716716
]
717-
], [
717+
],
718+
[
718719
"duration" => new DateInterval("PT4H11M"),
719720
"method" => "Bus",
720721
"steps" => [
721-
"stv472" => [
722+
"b-stv472" => [
722723
"time" => "06:20",
723724
"location" => "Rugeley Trent Valley",
724725
],
725-
"ltv050" => [
726+
"b-ltv050" => [
726727
"time" => "07:40",
727728
"location" => "Lichfield City Centre",
728729
],
729-
"ltv921" => [
730+
"b-ltv921" => [
730731
"time" => "08:00",
731732
"location" => "Mosley Street",
732733
],
733-
"sd094" => [
734+
"b-sd094" => [
734735
"time" => "08:18",
735736
"location" => "Burton-on-Trent"
736737
],
737-
"ng001" => [
738+
"b-ng001" => [
738739
"time" => "09:06",
739740
"location" => "Nottingham",
740741
],
741-
"mnn310" => [
742+
"b-mnn310" => [
742743
"time" => "10:01",
743744
"location" => "Mansfield",
744745
],
745-
"mnn209" => [
746+
"b-mnn209" => [
746747
"time" => "10:24",
747748
"location" => "Kirkby in Ashfield",
748749
],
749-
"c0353" => [
750+
"b-c0353" => [
750751
"time" => "10:31",
751752
"location" => "Greendale Crescent",
752753
]
@@ -759,14 +760,21 @@ public function testBindList_complexHTML():void {
759760
$sut->bindKeyValue("from", $from);
760761
$sut->bindKeyValue("to", $to);
761762

763+
/**
764+
* This callback function is used just to articulate the purpose
765+
* of bindListCallback - so we can manipulate each KVP element
766+
* inline with the binding - here we just format the date
767+
* differently, but in reality we could be grabbing data from
768+
* other sources, sorting, binding nested lists, etc.
769+
*/
762770
$callback = function(
763771
Element $templateElement,
764772
array $kvp,
765773
int|string $key,
766774
):array {
767775
if($duration = $kvp["duration"]) {
768776
/** @var DateInterval $duration */
769-
$kvp["duration"] = $duration->format("%H:%m");
777+
$kvp["duration"] = $duration->format("%H:%I");
770778
}
771779

772780
return $kvp;
@@ -780,7 +788,7 @@ public function testBindList_complexHTML():void {
780788

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

test/phpunit/ListBinderTest.php

+14-27
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ public function testBindListData_kvpList_object():void {
256256
}
257257
}
258258

259+
/** @noinspection PhpUnused */
259260
public function testBindListData_kvpList_instanceObject():void {
260261
$kvpList = [
261262
new class { public int $userId = 543; public string $username = "win95"; public int $orderCount = 55; },
@@ -284,46 +285,56 @@ public function testBindListData_kvpList_instanceObject():void {
284285
public function testBindListData_kvpList_instanceObjectWithBindAttributeMethods():void {
285286
$kvpList = [
286287
new class {
288+
/** @noinspection PhpUnused */
287289
#[Bind("userId")]
288290
public function getId():int {
289291
return 534;
290292
}
293+
/** @noinspection PhpUnused */
291294
#[Bind("username")]
292295
public function getUsername():string {
293296
return "win95";
294297
}
298+
/** @noinspection PhpUnused */
295299
#[Bind("this-matches-nothing")]
296300
public function getNothing():string {
297301
return "nothing!";
298302
}
303+
/** @noinspection PhpUnused */
299304
#[Bind("orderCount")]
300305
public function getTotalOrders():int {
301306
return 55;
302307
}
303308
},
304309
new class {
310+
/** @noinspection PhpUnused */
305311
#[Bind("userId")]
306312
public function getId():int {
307313
return 559;
308314
}
315+
/** @noinspection PhpUnused */
309316
#[Bind("username")]
310317
public function getUsername():string {
311318
return "seafoam";
312319
}
320+
/** @noinspection PhpUnused */
313321
#[Bind("orderCount")]
314322
public function getTotalOrders():int {
315323
return 30;
316324
}
317325
},
318326
new class {
327+
/** @noinspection PhpUnused */
319328
#[Bind("userId")]
320329
public function getId():int {
321330
return 274;
322331
}
332+
/** @noinspection PhpUnused */
323333
#[Bind("username")]
324334
public function getUsername():string {
325335
return "hammatime";
326336
}
337+
/** @noinspection PhpUnused */
327338
#[Bind("orderCount")]
328339
public function getTotalOrders():int {
329340
return 23;
@@ -343,13 +354,13 @@ public function getTotalOrders():int {
343354
$sut->bindListData($kvpList, $orderList);
344355

345356
foreach($orderList->children as $i => $li) {
346-
/** @var HTMLLiElement $li */
347357
self::assertEquals($kvpList[$i]->getId(), $li->querySelector("h3 span")->textContent);
348358
self::assertEquals($kvpList[$i]->getUsername(), $li->querySelector("h2 span")->textContent);
349359
self::assertEquals($kvpList[$i]->getTotalOrders(), $li->querySelector("p span")->textContent);
350360
}
351361
}
352362

363+
/** @noinspection PhpUnused */
353364
public function testBindListData_kvpList_instanceObjectWithBindAttributeProperties():void {
354365
$kvpList = [
355366
new class {
@@ -399,7 +410,6 @@ public function testBindListData_kvpList_instanceObjectWithBindAttributeProperti
399410
$sut->bindListData($kvpList, $orderList);
400411

401412
foreach($orderList->children as $i => $li) {
402-
/** @var HTMLLiElement $li */
403413
self::assertEquals($kvpList[$i]->id, $li->querySelector("h3 span")->textContent);
404414
self::assertEquals($kvpList[$i]->user, $li->querySelector("h2 span")->textContent);
405415
self::assertEquals($kvpList[$i]->totalOrders, $li->querySelector("p span")->textContent);
@@ -492,7 +502,7 @@ public function testBindListData_dateTime():void {
492502

493503
while($dateTime->format("Y") === $currentYear) {
494504
array_push($listData, new class(clone $dateTime) implements Stringable {
495-
public function __construct(private DateTime $dateTime) {}
505+
public function __construct(private readonly DateTime $dateTime) {}
496506
public function __toString():string {
497507
return $this->dateTime->format("F: l");
498508
}
@@ -517,7 +527,6 @@ public function testBindListData_todoList():void {
517527

518528
$todoLiElements = $document->querySelectorAll("ul>li");
519529
foreach($data as $i => $todoItem) {
520-
/** @var HTMLLiElement $li */
521530
$li = $todoLiElements[$i];
522531
self::assertEquals($todoItem["id"], $li->querySelector("[name=id]")->value);
523532
self::assertEquals($todoItem["title"], $li->querySelector("[name=title]")->value);
@@ -551,6 +560,7 @@ public function testBindListData_multipleTemplateSiblings():void {
551560
}
552561
}
553562

563+
/** @noinspection PhpUnusedParameterInspection */
554564
public function testBindListData_callback():void {
555565
$salesData = [
556566
[
@@ -595,27 +605,4 @@ public function testBindListData_callback():void {
595605
self::assertEquals($profitValue, $li->querySelector(".profit span")->textContent);
596606
}
597607
}
598-
599-
public function testBindList_twoListsSeparatedByElement():void {
600-
$blueShades = ["Periwinkle", "Ultramarine", "Liberty", "Navy", "Blurple"];
601-
$redShades = ["Brink pink", "Crimson", "Vermilion", "Scarlet"];
602-
$document = new HTMLDocument(DocumentTestFactory::HTML_TWO_SUB_LISTS_SEPARATED_BY_ELEMENT);
603-
$templateCollection = new TemplateCollection($document);
604-
$sut = new ListBinder($templateCollection);
605-
$sut->bindListData($redShades, $document, "red");
606-
$sut->bindListData($blueShades, $document, "blue");
607-
608-
$dtElements = $document->querySelectorAll("dt");
609-
$context = $dtElements[0]->nextElementSibling;
610-
for($i = 0; $i < count($blueShades); $i++) {
611-
self::assertEquals($blueShades[$i], $context->textContent);
612-
$context = $context->nextElementSibling;
613-
}
614-
615-
$context = $dtElements[1]->nextElementSibling;
616-
for($i = 0; $i < count($redShades); $i++) {
617-
self::assertEquals($redShades[$i], $context->textContent);
618-
$context = $context->nextElementSibling;
619-
}
620-
}
621608
}

test/phpunit/TestFactory/DocumentTestFactory.php

+10
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,16 @@ class DocumentTestFactory {
232232
<ol>
233233
<li>This doesn't have a data template attribute</li>
234234
</ol>
235+
HTML;
236+
237+
const HTML_LIST_TEMPLATE_REBIND = <<<HTML
238+
<!doctype html>
239+
<ul>
240+
<li data-template data-template-rebind data-bind:text>Template item!</li>
241+
</ul>
242+
<ol>
243+
<li>This doesn't have a data template attribute</li>
244+
</ol>
235245
HTML;
236246

237247
const HTML_TWO_LISTS = <<<HTML

0 commit comments

Comments
 (0)