Skip to content

Commit

Permalink
data-list replaces data-template (#417)
Browse files Browse the repository at this point in the history
* build: upgrade dom requirement and loosen version range

* docs: update examples

* feature: trim whitespace when there are only template children
closes #363

* maintenance: phpstorm analysis improvements

* test: refactor test helper class

* wip: add test data for big integration

* test: add failing test to cover nested objects
for #356

* test: add extra cases for nested object test

* feature: allow nesting of object properties
closes #356

* maintenance: static analysis improvement

* test: nested objects with bindgetter functions

* test: isolate issue #367

* test: use `data-template-parent` attribute internally instead of id
closes #367

* test: isolate functionality for #368

* wip: implementation for #368 not yet completed

* feature: data-bind:list
closes #368

* tidy: static analysis improvements

* tidy: types of reflection method improved

* tidy: remove unused import

* tidy: nullable reflection type

* tidy: remove unused elements

* docs: add nested music example

* refactor: data-list
for #416

* refactor: data-list
for #416

* refactor: data-list
for #416

* refactor: data-list
for #416
  • Loading branch information
g105b authored Mar 1, 2023
1 parent e687d24 commit ffda274
Show file tree
Hide file tree
Showing 16 changed files with 263 additions and 261 deletions.
8 changes: 4 additions & 4 deletions src/DocumentBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class DocumentBinder {
private PlaceholderBinder $placeholderBinder;
private TableBinder $tableBinder;
private ListBinder $listBinder;
private TemplateCollection $templateCollection;
private ListElementCollection $templateCollection;
private BindableCache $bindableCache;

/**
Expand All @@ -23,10 +23,10 @@ public function __construct(
?PlaceholderBinder $placeholderBinder = null,
?TableBinder $tableBinder = null,
?ListBinder $listBinder = null,
?TemplateCollection $templateCollection = null,
?ListElementCollection $templateCollection = null,
?BindableCache $bindableCache = null
) {
$this->templateCollection = $templateCollection ?? new TemplateCollection($document);
$this->templateCollection = $templateCollection ?? new ListElementCollection($document);
$this->elementBinder = $elementBinder ?? new ElementBinder();
$this->placeholderBinder = $placeholderBinder ?? new PlaceholderBinder();
$this->tableBinder = $tableBinder ?? new TableBinder($this->templateCollection);
Expand Down Expand Up @@ -140,7 +140,7 @@ public function bindListCallback(

public function cleanDatasets():void {
$xpathResult = $this->document->evaluate(
"//*/@*[starts-with(name(), 'data-bind')] | //*/@*[starts-with(name(), 'data-template')] | //*/@*[starts-with(name(), 'data-table-key')]"
"//*/@*[starts-with(name(), 'data-bind')] | //*/@*[starts-with(name(), 'data-list')] | //*/@*[starts-with(name(), 'data-template')] | //*/@*[starts-with(name(), 'data-table-key')]"
);
/** @var Attr $item */
foreach($xpathResult as $item) {
Expand Down
4 changes: 4 additions & 0 deletions src/InvalidListElementNameException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php
namespace Gt\DomTemplate;

class InvalidListElementNameException extends DomTemplateException {}
4 changes: 0 additions & 4 deletions src/InvalidTemplateElementNameException.php

This file was deleted.

54 changes: 27 additions & 27 deletions src/ListBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class ListBinder {

/** @noinspection PhpPropertyCanBeReadonlyInspection */
public function __construct(
private TemplateCollection $templateCollection,
private ListElementCollection $listElementCollection,
?BindableCache $bindableCache = null
) {
$this->bindableCache = $bindableCache ?? new BindableCache();
Expand All @@ -22,81 +22,81 @@ public function __construct(
public function bindListData(
iterable $listData,
Document|Element $context,
?string $templateName = null,
?string $listItemName = null,
?callable $callback = null,
):int {
if($context instanceof Document) {
$context = $context->documentElement;
}

if($this->isEmpty($listData)) {
$this->clearTemplateParentHTML($context, $templateName);
$this->clearListItemParentHTML($context, $listItemName);
return 0;
}

$templateItem = $this->templateCollection->get(
$listItem = $this->listElementCollection->get(
$context,
$templateName
$listItemName
);

$elementBinder = new ElementBinder();
$nestedCount = 0;
$i = -1;
foreach($listData as $listKey => $listItem) {
foreach($listData as $listKey => $listValue) {
$i++;
$t = $templateItem->insertTemplate();
$t = $listItem->insertListItem();

// If the $listItem's first value is iterable, then treat this as a nested list.
if($this->isNested($listItem)) {
// If the $listValue's first value is iterable, then treat this as a nested list.
if($this->isNested($listValue)) {
$elementBinder->bind(null, $listKey, $t);
$nestedCount += $this->bindListData(
$listItem,
$listValue,
$t,
$templateName
$listItemName
);
continue;
}

if(is_object($listItem) && method_exists($listItem, "asArray")) {
$listItem = $listItem->asArray();
if(is_object($listValue) && method_exists($listValue, "asArray")) {
$listValue = $listValue->asArray();
}
elseif(is_object($listItem) && !is_iterable($listItem)) {
if($this->bindableCache->isBindable($listItem)) {
$listItem = $this->bindableCache->convertToKvp($listItem);
elseif(is_object($listValue) && !is_iterable($listValue)) {
if($this->bindableCache->isBindable($listValue)) {
$listValue = $this->bindableCache->convertToKvp($listValue);
}
}

if($callback) {
$listItem = call_user_func(
$listValue = call_user_func(
$callback,
$t,
$listItem,
$listValue,
$listKey,
);
}

if(is_null($listItem)) {
if(is_null($listValue)) {
continue;
}

if($this->isKVP($listItem)) {
if($this->isKVP($listValue)) {
$elementBinder->bind(null, $listKey, $t);

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

if($this->isNested($value)) {
$elementBinder->bind(null, $key, $t);
$nestedCount += $this->bindListData(
$value,
$t,
$templateName
$listItemName
);
}
}
}
else {
$elementBinder->bind(null, $listItem, $t);
$elementBinder->bind(null, $listValue, $t);
}
}

Expand All @@ -115,12 +115,12 @@ private function isEmpty(iterable $listData):bool {
}
}

private function clearTemplateParentHTML(
private function clearListItemParentHTML(
Element $context,
?string $templateName
?string $listName
):void {
$template = $this->templateCollection->get($context, $templateName);
$parent = $template->getTemplateParent();
$listElement = $this->listElementCollection->get($context, $listName);
$parent = $listElement->getListItemParent();
$parent->innerHTML = trim($parent->innerHTML ?? "");
}

Expand Down
51 changes: 26 additions & 25 deletions src/TemplateElement.php → src/ListElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,31 @@
use Gt\Dom\Text;
use Throwable;

class TemplateElement {
const ATTRIBUTE_TEMPLATE_PARENT = "data-template-parent";
class ListElement {
const ATTRIBUTE_LIST_PARENT = "data-list-parent";

private string $templateParentPath;
private null|Node|Element $templateNextSibling;
private string $listItemParentPath;
private null|Node|Element $listItemNextSibling;
private int $insertCount;

public function __construct(
private Node|Element $originalElement
private readonly Node|Element $originalElement
) {
$parentElement = $this->originalElement->parentElement;
if(!$parentElement->getAttribute(self::ATTRIBUTE_TEMPLATE_PARENT)) {
$parentElement->setAttribute(self::ATTRIBUTE_TEMPLATE_PARENT, uniqid("template-parent-"));
if(!$parentElement->getAttribute(self::ATTRIBUTE_LIST_PARENT)) {
$parentElement->setAttribute(self::ATTRIBUTE_LIST_PARENT, uniqid("list-parent-"));
}

$this->templateParentPath = new NodePathCalculator($parentElement);
$this->listItemParentPath = new NodePathCalculator($parentElement);

$siblingContext = $this->originalElement;
while($siblingContext = $siblingContext->nextElementSibling) {
if(!$siblingContext->hasAttribute("data-template")) {
if(!$siblingContext->hasAttribute("data-list")
&& !$siblingContext->hasAttribute("data-template")) {
break;
}
}
$this->templateNextSibling =
$this->listItemNextSibling =
is_null($siblingContext)
? null
: $siblingContext;
Expand All @@ -40,7 +41,7 @@ public function __construct(
public function removeOriginalElement():void {
$this->originalElement->remove();
try {
$parent = $this->getTemplateParent();
$parent = $this->getListItemParent();
if(count($parent->children) === 0) {
if($firstNode = $parent->childNodes[0] ?? null) {
if(trim($firstNode->wholeText) === "") {
Expand Down Expand Up @@ -71,20 +72,20 @@ public function getClone():Node|Element {
* originally extracted from the document, returning the newly-inserted
* clone.
*/
public function insertTemplate():Element {
public function insertListItem():Element {
$clone = $this->getClone();
$templateParent = $this->getTemplateParent();
$templateParent->insertBefore(
$listItemParent = $this->getListItemParent();
$listItemParent->insertBefore(
$clone,
$this->getTemplateNextSibling()
$this->getListItemNextSibling()
);
$this->insertCount++;
return $clone;
}

public function getTemplateParent():Node|Element {
public function getListItemParent():Node|Element {
$matches = $this->originalElement->ownerDocument->evaluate(
$this->templateParentPath
$this->listItemParentPath
);
do {
/** @var Element $parent */
Expand All @@ -95,20 +96,20 @@ public function getTemplateParent():Node|Element {
return $parent;
}

public function getTemplateNextSibling():null|Node|Element {
return $this->templateNextSibling ?? null;
public function getListItemNextSibling():null|Node|Element {
return $this->listItemNextSibling ?? null;
}

public function getTemplateName():?string {
$templateName = $this->originalElement->getAttribute("data-template");
if(strlen($templateName) === 0) {
public function getListItemName():?string {
$listName = $this->originalElement->getAttribute("data-list") ?? $this->originalElement->getAttribute("data-template");
if(strlen($listName) === 0) {
return null;
}
elseif($templateName[0] === "/") {
throw new InvalidTemplateElementNameException("A template's name must not start with a forward slash (\"$templateName\")");
elseif($listName[0] === "/") {
throw new InvalidListElementNameException("A list's name must not start with a forward slash (\"$listName\")");
}

return $templateName;
return $listName;
}

public function getInsertCount():int {
Expand Down
18 changes: 9 additions & 9 deletions src/TemplateCollection.php → src/ListElementCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
use Gt\Dom\Document;
use Gt\Dom\Element;

class TemplateCollection {
/** @var array<string, TemplateElement> */
class ListElementCollection {
/** @var array<string, ListElement> */
private array $elementKVP;

public function __construct(
Expand All @@ -18,14 +18,14 @@ public function __construct(
public function get(
Element|Document $context,
?string $templateName = null
):TemplateElement {
):ListElement {
if($context instanceof Document) {
$context = $context->documentElement;
}

if($templateName) {
if(!isset($this->elementKVP[$templateName])) {
throw new TemplateElementNotFoundInContextException("Template element with name \"$templateName\" can not be found within the context $context->tagName element.");
throw new ListElementNotFoundInContextException("List element with name \"$templateName\" can not be found within the context $context->tagName element.");
}
return $this->elementKVP[$templateName];
}
Expand All @@ -36,10 +36,10 @@ public function get(
private function extractTemplates(Document $document):void {
$dataTemplateArray = [];
/** @var Element $element */
foreach($document->querySelectorAll("[data-template]") as $element) {
$templateElement = new TemplateElement($element);
foreach($document->querySelectorAll("[data-list],[data-template]") as $element) {
$templateElement = new ListElement($element);
$nodePath = (string)(new NodePathCalculator($element));
$key = $templateElement->getTemplateName() ?? $nodePath;
$key = $templateElement->getListItemName() ?? $nodePath;
$dataTemplateArray[$key] = $templateElement;
}

Expand All @@ -58,7 +58,7 @@ private function extractTemplates(Document $document):void {
$this->elementKVP = array_reverse($dataTemplateArray, true);
}

private function findMatch(Element $context):TemplateElement {
private function findMatch(Element $context):ListElement {
$contextPath = (string)(new NodePathCalculator($context));
/** @noinspection RegExpRedundantEscape */
$contextPath = preg_replace(
Expand All @@ -85,7 +85,7 @@ private function findMatch(Element $context):TemplateElement {
}
}

throw new TemplateElementNotFoundInContextException(
throw new ListElementNotFoundInContextException(
"There is no unnamed template element in the context element ($context->tagName)."
);
}
Expand Down
4 changes: 4 additions & 0 deletions src/ListElementNotFoundInContextException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php
namespace Gt\DomTemplate;

class ListElementNotFoundInContextException extends DomTemplateException {}
4 changes: 2 additions & 2 deletions src/NodePathCalculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ public function __toString():string {
$contextPath = strtolower($context->tagName);

$attrPath = "";
if($dataTemplateParent = $context->getAttribute(TemplateElement::ATTRIBUTE_TEMPLATE_PARENT)) {
if($dataTemplateParent = $context->getAttribute(ListElement::ATTRIBUTE_LIST_PARENT)) {
$attrPath .= "@"
. TemplateElement::ATTRIBUTE_TEMPLATE_PARENT
. ListElement::ATTRIBUTE_LIST_PARENT
. "='$dataTemplateParent'";
}

Expand Down
8 changes: 4 additions & 4 deletions src/TableBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
class TableBinder {
/** @noinspection PhpPropertyCanBeReadonlyInspection */
public function __construct(
private ?TemplateCollection $templateCollection = null,
private ?ListElementCollection $templateCollection = null,
private ?ElementBinder $elementBinder = null,
private ?HTMLAttributeBinder $htmlAttributeBinder = null,
private ?HTMLAttributeCollection $htmlAttributeCollection = null,
Expand Down Expand Up @@ -90,14 +90,14 @@ public function bindTableData(
}

$templateCollection = $this->templateCollection
?? new TemplateCollection($context->ownerDocument);
?? new ListElementCollection($context->ownerDocument);

foreach($tableData as $rowData) {
try {
$trTemplate = $templateCollection->get($tbody);
$tr = $trTemplate->insertTemplate();
$tr = $trTemplate->insertListItem();
}
catch(TemplateElementNotFoundInContextException) {
catch(ListElementNotFoundInContextException) {
$tr = $tbody->insertRow();
}

Expand Down
4 changes: 0 additions & 4 deletions src/TemplateElementNotFoundInContextException.php

This file was deleted.

Loading

0 comments on commit ffda274

Please sign in to comment.