Skip to content

Commit

Permalink
481 remove element (#483)
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

* tweak: remove data-element attribute

* feature: remove element from bind key modifier
closes #481

* test: more tests

* tweak: keep up to date with docs

* tweak: simplify iterable objects

* test: add test to missing coverage line
  • Loading branch information
g105b authored Jan 25, 2024
1 parent 2762126 commit 143aa97
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 1 deletion.
113 changes: 113 additions & 0 deletions examples/binding/14-iterator-aggregate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php
use Gt\Dom\HTMLDocument;
use Gt\DomTemplate\Bind;
use Gt\DomTemplate\BindableCache;
use Gt\DomTemplate\Binder;
use Gt\DomTemplate\BindGetter;
use Gt\DomTemplate\DocumentBinder;
use Gt\DomTemplate\ElementBinder;
use Gt\DomTemplate\HTMLAttributeBinder;
use Gt\DomTemplate\HTMLAttributeCollection;
use Gt\DomTemplate\ListBinder;
use Gt\DomTemplate\ListElementCollection;
use Gt\DomTemplate\PlaceholderBinder;
use Gt\DomTemplate\TableBinder;

require __DIR__ . "/../../vendor/autoload.php";

// EXAMPLE CODE: https://github.com/PhpGt/DomTemplate/wiki/Binding-objects#iterator-and-iteratoraggregate-objects

$html = <<<HTML
<!DOCTYPE html>
<h1>Events for <span data-bind:text="dateString">1st Jan 2000</span>:</h1>
<ol>
<li data-list>
<time data-bind:text="eventStartTimeText">00:00</time>
<p data-bind:text="title">Title of event</p>
</li>
</ol>
HTML;

function example(Binder $binder, CalendarDay $calendarDay):void {
$binder->bindData($calendarDay);
}

class CalendarDay implements IteratorAggregate {
private DateTime $dateTime;

public function __construct(
public int $year,
public int $month,
public int $day,
private EventRepository $eventRepository,
) {
$this->dateTime = new DateTime("$year-$month-$day");
}

#[BindGetter]
public function getDateString():string {
return $this->dateTime->format("jS M Y");
}

/** @return Traversable<CalendarEvent> */
public function getIterator():Traversable {
yield from $this->eventRepository->getEventsForDate(
$this->year,
$this->month,
$this->day,
);
}
}

class CalendarEvent {
public function __construct(
public string $id,
public DateTime $eventStart,
public string $title,
) {}

#[BindGetter]
public function getEventStartTimeText():string {
return $this->eventStart->format("H:i");
}
}

// END OF EXAMPLE CODE

class EventRepository {
public function getEventsForDate(int $y, int $m, int $d):array {
return [
new CalendarEvent(uniqid(), new DateTime(), "First event"),
new CalendarEvent(uniqid(), new DateTime("+15 mins"), "Second event"),
new CalendarEvent(uniqid(), new DateTime("+2 hours"), "Third event"),
];
}
}

$calendar = new CalendarDay(2024, 02, 14, new EventRepository());
$document = new HTMLDocument($html);
$binder = new DocumentBinder($document);
$htmlAttributeBinder = new HTMLAttributeBinder();
$tableBinder = new TableBinder();
$listBinder = new ListBinder();
$placeholderBinder = new PlaceholderBinder();
$elementBinder = new ElementBinder();
$htmlAttributeCollection = new HTMLAttributeCollection();
$elementBinder->setDependencies($htmlAttributeBinder, $htmlAttributeCollection, $placeholderBinder);
$listElementCollection = new ListElementCollection($document);
$bindableCache = new BindableCache();
$listBinder->setDependencies($elementBinder, $listElementCollection, $bindableCache, $tableBinder);
$htmlAttributeBinder->setDependencies($listBinder, $tableBinder);
$binder->setDependencies(
$elementBinder,
$placeholderBinder,
$tableBinder,
$listBinder,
$listElementCollection,
$bindableCache,
);

example($binder, $calendar);
$binder->cleanupDocument();
echo $document;
11 changes: 10 additions & 1 deletion src/DocumentBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,24 @@ public function bindData(
$kvp = $kvp->asArray();
}

if(is_object($kvp) && !is_iterable($kvp)) {
// The $kvp object may be both an object with its own key-value-pairs and
// an iterable object. We can perform the two bind operations here.

$object = null;
if(is_object($kvp)) {
if($this->bindableCache->isBindable($kvp)) {
$object = $kvp;
$kvp = $this->bindableCache->convertToKvp($kvp);
}
}

foreach($kvp ?? [] as $key => $value) {
$this->bindKeyValue($key, $value, $context);
}

if(is_iterable($object)) {
$this->listBinder->bindListData($object, $context ?? $this->document);
}
}

public function bindTable(
Expand Down
11 changes: 11 additions & 0 deletions src/HTMLAttributeBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,17 @@ private function setBindProperty(
$this->listBinder->bindListData($bindValue, $element);
break;

case "remove":
$remove = $bindValue;
if(str_contains($modifier, "!")) {
$remove = !$remove;
}

if($remove) {
$element->remove();
}
break;

default:
if($modifier) {
$this->handleModifier(
Expand Down
94 changes: 94 additions & 0 deletions test/phpunit/DocumentBinderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
use Gt\DomTemplate\Test\TestHelper\Model\Customer;
use PHPUnit\Framework\TestCase;
use stdClass;
use IteratorAggregate;
use Traversable;
use ArrayIterator;

class DocumentBinderTest extends TestCase {
/**
Expand Down Expand Up @@ -1272,6 +1275,97 @@ public function test_bindData_withList_dataBindList():void {
}
}

public function testBind_remove():void {
$document = new HTMLDocument(HTMLPageContent::HTML_DATA_BIND_REMOVE);
$sut = new DocumentBinder($document);
$sut->setDependencies(...$this->documentBinderDependencies($document));

$sut->bindKeyValue("name", "Example");
$sut->bindKeyValue("isDay", true);

self::assertSame("Hello, Example!", $document->querySelector("h1")->textContent);
$dayOrNight = $document->getElementById("day-or-night");
self::assertSame("Is it day or night? It's daytime!", $dayOrNight->textContent);
}

public function testBind_removeInverse():void {
$document = new HTMLDocument(HTMLPageContent::HTML_DATA_BIND_REMOVE);
$sut = new DocumentBinder($document);
$sut->setDependencies(...$this->documentBinderDependencies($document));

$sut->bindKeyValue("name", "Example");
$sut->bindKeyValue("isDay", false);

self::assertSame("Hello, Example!", $document->querySelector("h1")->textContent);
$dayOrNight = $document->getElementById("day-or-night");
self::assertSame("Is it day or night? It's nighttime!", $dayOrNight->textContent);
}

public function testBind_removeTruthy():void {
$document = new HTMLDocument(HTMLPageContent::HTML_DATA_BIND_REMOVE);
$sut = new DocumentBinder($document);
$sut->setDependencies(...$this->documentBinderDependencies($document));

$sut->bindKeyValue("name", "Example");
$sut->bindKeyValue("isDay", "Daytime");

self::assertSame("Hello, Example!", $document->querySelector("h1")->textContent);
$dayOrNight = $document->getElementById("day-or-night");
self::assertSame("Is it day or night? It's daytime!", $dayOrNight->textContent);
}

public function testBind_removeObject():void {
$document = new HTMLDocument(HTMLPageContent::HTML_DATA_BIND_REMOVE);
$sut = new DocumentBinder($document);
$sut->setDependencies(...$this->documentBinderDependencies($document));

$obj = new class {
public string $name = "Example";

#[Bind("isDay")]
public function isTimeCurrentlyDaytime():bool {
return true;
}
};

$sut->bindData($obj);

self::assertSame("Hello, Example!", $document->querySelector("h1")->textContent);
$dayOrNight = $document->getElementById("day-or-night");
self::assertSame("Is it day or night? It's daytime!", $dayOrNight->textContent);
}

/**
* When passing an object to bindData, the object could be both
* key-value-pairs and iterable (notably an IteratorAggregate).
*/
public function testBindData_iterableObject():void {
$document = new HTMLDocument(HTMLPageContent::HTML_LIST);
$sut = new DocumentBinder($document);
$sut->setDependencies(...$this->documentBinderDependencies($document));

$obj = new class implements IteratorAggregate {
public string $id = "ABC123";
public string $name = "Example";

public function getIterator():Traversable {
return new ArrayIterator(["One", "Two", "Three", "Four"]);
}
};

$sut->bindData($obj);
$h1 = $document->querySelector("h1");
$ul = $document->querySelector("ul");
$ol = $document->querySelector("ol");

// The object should have its properties bound to the page:
self::assertSame("Example", $h1->textContent);
// but it should bind itself as an iterator to the list:
self::assertCount(4, $ul->children);
// and the un-attributed list should not change:
self::assertCount(1, $ol->children);
}

private function documentBinderDependencies(HTMLDocument $document, mixed...$otherObjectList):array {
$htmlAttributeBinder = new HTMLAttributeBinder();
$htmlAttributeCollection = new HTMLAttributeCollection();
Expand Down
8 changes: 8 additions & 0 deletions test/phpunit/TestHelper/HTMLPageContent.php
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ class HTMLPageContent {

const HTML_LIST = <<<HTML
<!doctype html>
<h1 data-bind:text="name">List name</h1>
<ul>
<li data-list data-bind:text>Template item!</li>
</ul>
Expand Down Expand Up @@ -1104,6 +1105,13 @@ class HTMLPageContent {
const HTML_MULTIPLE_BINDS_ON_SINGLE_ELEMENT = <<<HTML
<!doctype html>
<output data-bind:data-attr2="key2" data-bind:data-attr1="key1" data-bind:data-another-attr1="key1" data-existing-attr="existing-value" data-bind:id="id" data-bind:name="name" data-bind:title="name">Nothing is bound</output>
HTML;

const HTML_DATA_BIND_REMOVE = <<<HTML
<!doctype html>
<h1>Hello, <span data-bind:text="name">World</span>!</h1>
<p>This paragraph will always display.</p>
<p id="day-or-night">Is it day or night? <span data-bind:remove="?isDay">It's nighttime!</span><span data-bind:remove="?!isDay">It's daytime!</span></p>
HTML;

public static function createHTML(string $html = ""):HTMLDocument {
Expand Down

0 comments on commit 143aa97

Please sign in to comment.