Skip to content

Commit 23cbbc7

Browse files
Support sub-components
This is a bit odd in that Component doesn’t even know which components are registered in the app; sub-component handling is triggered iff the tag name contains a hyphen. I think this is reasonable, but we might still end up changing it. Note that the sub-component must be a descendent of the template root note; a template cannot just consist of a call to a sub-component. (If it does, Component::render() crashes when trying to remove the $rootNode from the cloneOwner because it’s already been replaced in handleComponent().)
1 parent daf2089 commit 23cbbc7

File tree

3 files changed

+57
-6
lines changed

3 files changed

+57
-6
lines changed

src/Component.php

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,13 @@ private function handleNode( DOMNode $node, array $data ) {
5454
$this->handleRawHtml( $node, $data );
5555

5656
if ( !$this->isRemovedFromTheDom( $node ) ) {
57-
$this->handleAttributeBinding( $node, $data );
58-
$this->handleIf( $node->childNodes, $data );
57+
if ( !$this->handleComponent( $node, $data ) ) {
58+
$this->handleAttributeBinding( $node, $data );
59+
$this->handleIf( $node->childNodes, $data );
5960

60-
foreach ( iterator_to_array( $node->childNodes ) as $childNode ) {
61-
$this->handleNode( $childNode, $data );
61+
foreach ( iterator_to_array( $node->childNodes ) as $childNode ) {
62+
$this->handleNode( $childNode, $data );
63+
}
6264
}
6365
}
6466
}
@@ -105,6 +107,30 @@ private function replaceMustacheVariables( DOMNode $node, array $data ) {
105107
}
106108
}
107109

110+
/** @return bool true if it was a component, false otherwise */
111+
private function handleComponent( DOMElement $node, array $data ): bool {
112+
if ( strpos( $node->tagName, '-' ) === false ) {
113+
return false;
114+
}
115+
$componentName = $node->tagName;
116+
117+
$componentData = [];
118+
foreach ( $node->attributes as $attribute ) {
119+
if ( str_starts_with( $attribute->name, ':' ) ) { // TODO also v-bind: ?
120+
$name = substr( $attribute->name, 1 );
121+
$value = $this->app->evaluateExpression( $attribute->value, $data );
122+
} else {
123+
$name = $attribute->name;
124+
$value = $attribute->value;
125+
}
126+
$componentData[$name] = $value;
127+
}
128+
$rendered = $this->app->renderComponentToDOM( $componentName, $componentData );
129+
// TODO use adoptNode() instead of importNode() in PHP 8.3+ (see php-src commit ed6df1f0ad)
130+
$node->replaceWith( $node->ownerDocument->importNode( $rendered, true ) );
131+
return true;
132+
}
133+
108134
private function handleAttributeBinding( DOMElement $node, array $data ) {
109135
/** @var DOMAttr $attribute */
110136
foreach ( iterator_to_array( $node->attributes ) as $attribute ) {

src/HtmlParser.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@ public function parseHtml( string $html ): DOMDocument {
4343

4444
$exception = null;
4545
foreach ( $errors as $error ) {
46-
if ( str_starts_with( $error->message, 'Tag template invalid' ) ) {
46+
$msg = $error->message;
47+
if ( str_starts_with( $msg, 'Tag ' ) && str_ends_with( $msg, " invalid\n" ) ) {
48+
// discard "Tag xyz invalid" messages from libxml2 < 2.14.0(?)
4749
continue;
4850
}
49-
$exception = new Exception( $error->message, $error->code, $exception );
51+
$exception = new Exception( $msg, $error->code, $exception );
5052
}
5153
if ( $exception !== null ) {
5254
throw $exception;

tests/php/AppTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,27 @@ public function testAppInitsComponentLazily(): void {
3838
$this->assertTrue( $called );
3939
}
4040

41+
public function testNestedComponents(): void {
42+
$app = new App( [] );
43+
$app->registerComponentTemplate( 'root', '<div><x-a :a="rootVar"></x-a></div>' );
44+
$app->registerComponentTemplate( 'x-a', '<p><x-b :b="a"></x-b></p>' );
45+
$app->registerComponentTemplate( 'x-b', '<span>{{ b }}</span>' );
46+
47+
$result = $app->renderComponent( 'root', [ 'rootVar' => 'text' ] );
48+
49+
$this->assertSame( '<div><p><span>text</span></p></div>', $result );
50+
}
51+
52+
public function testNestedComponentObjectProp(): void {
53+
$app = new App( [] );
54+
$app->registerComponentTemplate( 'root', '<div><x-a :obj="rootObj"></x-a></div>' );
55+
$app->registerComponentTemplate( 'x-a', '<p>obj = { a: {{ obj.a }}, b: {{ obj.b }} }</p>' );
56+
57+
$result = $app->renderComponent( 'root', [
58+
'rootObj' => [ 'a' => 'A', 'b' => 'B' ],
59+
] );
60+
61+
$this->assertSame( '<div><p>obj = { a: A, b: B }</p></div>', $result );
62+
}
63+
4164
}

0 commit comments

Comments
 (0)