Skip to content

Commit

Permalink
feature: allow binding directly to data-element (#498)
Browse files Browse the repository at this point in the history
* feature: allow binding directly to `data-element`

* tweak: pass empty string rather than bool

* tweak: fix php doc

* tweak: fix php doc

* tweak: fix php doc

* tweak: fix php doc

* tweak: fix php doc

* tweak: fix php doc

* tweak: fix php doc

* tweak: fix php doc

* test: add test from docs
  • Loading branch information
g105b authored Nov 25, 2024
1 parent 1db4484 commit b469f18
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 9 deletions.
19 changes: 14 additions & 5 deletions src/DocumentBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,24 +159,33 @@ public function bindListCallback(
}

public function cleanupDocument():void {
/**
* @var Attr[] $xpathResult
* @phpstan-ignore varTag.nativeType
*/
$xpathResult = $this->document->evaluate(
"//*/@*[starts-with(name(), 'data-bind')] | //*/@*[starts-with(name(), 'data-list')] | //*/@*[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')] | //*/@*[starts-with(name(), 'data-element')]"
);

$elementsToRemove = [];
/** @var Attr $item */
foreach($xpathResult as $item) {
if($item->ownerElement->hasAttribute("data-element")) {
array_push($elementsToRemove, $item->ownerElement);
$ownerElement = $item->ownerElement;
if($ownerElement->hasAttribute("data-element")) {
if(!$ownerElement->hasAttribute("data-bound")) {
array_push($elementsToRemove, $ownerElement);
}
continue;
}

$item->ownerElement->removeAttribute($item->name);
$ownerElement->removeAttribute($item->name);
}

foreach($this->document->querySelectorAll("[data-element]") as $dataElement) {
$dataElement->removeAttribute("data-element");
}
foreach($this->document->querySelectorAll("[data-bound]") as $dataBound) {
$dataBound->removeAttribute("data-bound");
}

foreach($elementsToRemove as $element) {
$element->remove();
Expand Down
7 changes: 7 additions & 0 deletions src/HTMLAttributeBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ public function bind(
foreach($element->attributes as $attrName => $attr) {
$attrValue = $attr->value;

if($attrName === "data-element") {
if($attr->value === $key && $value) {
$element->setAttribute("data-bound", "");
}
}

if(!str_starts_with($attrName, "data-bind")) {
continue;
}
Expand Down Expand Up @@ -81,6 +87,7 @@ public function bind(
$value,
$modifier
);
$element->setAttribute("data-bound", "");

if(!$attr->ownerElement->hasAttribute("data-rebind")) {
array_push($attributesToRemove, $attrName);
Expand Down
2 changes: 1 addition & 1 deletion src/HTMLAttributeCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class HTMLAttributeCollection {
public function find(Element $context):XPathResult {
return $context->ownerDocument->evaluate(
"descendant-or-self::*[@*[starts-with(name(), 'data-bind')]]",
"descendant-or-self::*[@*[starts-with(name(), 'data-bind')] or (@data-element and @data-element != '')]",
$context
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/ListElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class ListElement {
const ATTRIBUTE_LIST_PARENT = "data-list-parent";

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

public function __construct(
Expand Down
2 changes: 1 addition & 1 deletion src/TableBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ public function bindTableData(
}
}

/** @param array<int,array<int,string>>|array<int,array<string,string>>|array<string,array<int,string>>|array<int, array<int,string>|array<string,string>> $array */
/** @param array<int, array<int,string>> | array<int, array<string, string>> | array<string, array<int, string>> | array<int, array<int, string>> | array<string, string> $array */
public function detectTableDataStructureType(array $array):TableDataStructureType {
if(empty($array)) {
return TableDataStructureType::NORMALISED;
Expand Down
46 changes: 45 additions & 1 deletion test/phpunit/DocumentBinderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1245,7 +1245,51 @@ public function test_keepsElementWhenBound():void {
$sut->setDependencies(...$this->documentBinderDependencies($document));
$sut->bindKeyValue("error", "Example error!");
$sut->cleanupDocument();
self::assertStringContainsStringIgnoringCase("error", (string)$document);
$errorDiv = $document->querySelector("form>div");
self::assertNotNull($errorDiv);
self::assertSame("Example error!", $errorDiv->textContent);
}

public function test_bindElementWithBindValue():void {
$document = new HTMLDocument(HTMLPageContent::HTML_REMOVE_UNBOUND_BIND_VALUE);
$sut = new DocumentBinder($document);
$sut->setDependencies(...$this->documentBinderDependencies($document));
$sut->bindKeyValue("error", true);
$sut->cleanupDocument();
$errorDiv = $document->querySelector("form>div");
self::assertNotNull($errorDiv);
self::assertSame("There has been an error!", $errorDiv->textContent);
}

public function test_bindElementRemovesMultiple():void {
$document = new HTMLDocument(HTMLPageContent::HTML_ADMIN_PANEL);
$sut = new DocumentBinder($document);
$sut->setDependencies(...$this->documentBinderDependencies($document));
$sut->bindKeyValue("isAdmin", false);
$sut->cleanupDocument();

$panelDiv = $document->querySelector("div.panel");
self::assertCount(2, $panelDiv->children);
}

public function test_bindElementRemovesMultiple_doesNotRemoveWithTrue():void {
$document = new HTMLDocument(HTMLPageContent::HTML_ADMIN_PANEL);
$sut = new DocumentBinder($document);
$sut->setDependencies(...$this->documentBinderDependencies($document));
$sut->bindKeyValue("isAdmin", true);
$sut->cleanupDocument();

$panelDiv = $document->querySelector("div.panel");
self::assertCount(4, $panelDiv->children);
}

public function test_bindElementIsRemovedWhenNotBound():void {
$document = new HTMLDocument(HTMLPageContent::HTML_REMOVE_UNBOUND_BIND_VALUE);
$sut = new DocumentBinder($document);
$sut->setDependencies(...$this->documentBinderDependencies($document));
$sut->cleanupDocument();
$errorDiv = $document->querySelector("form>div");
self::assertNull($errorDiv);
}

public function test_bindData_withList_dataBindList():void {
Expand Down
29 changes: 29 additions & 0 deletions test/phpunit/TestHelper/HTMLPageContent.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ class HTMLPageContent {
</div>
HTML;

const HTML_ADMIN_PANEL = <<<HTML
<div class="panel">
<p>You are logged in as <span data-bind:text="username">username</span></p>
<p data-element="isAdmin">You are an administrator</p>
<button name="do" value="save">Save record</button>
<button data-element="isAdmin" name="do" value="delete">Delete record</button>
</div>
HTML;


const HTML_DIFFERENT_BIND_PROPERTIES = <<<HTML
<!doctype html>
<img id="img1" class="main" src="/default.png" alt="Not bound"
Expand Down Expand Up @@ -1093,6 +1103,25 @@ class HTMLPageContent {
<button name="do" value="login">Log in!</button>
</form>
HTML;

const HTML_REMOVE_UNBOUND_BIND_VALUE = <<<HTML
<!doctype html>
<h1>Log in to the system</h1>
<form method="post">
<div data-element="error">There has been an error!</div>
<label>
<span>Your email address:</span>
<input name="email" type="email" required />
</label>
<label>
<span>Your password:</span>
<input name="password" type="password" required />
</label>
<button name="do" value="login">Log in!</button>
</form>
HTML;

const HTML_DATA_BIND_LIST = <<<HTML
Expand Down

0 comments on commit b469f18

Please sign in to comment.