Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 0c77fe5
Showing
6 changed files
with
222 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
end_of_line = lf | ||
indent_style = space | ||
indent_size = 2 | ||
insert_final_newline = true | ||
trim_trailing_whitespace = true | ||
|
||
[*.php] | ||
indent_style = space | ||
indent_size = 4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/vendor/ | ||
composer.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
Custom Element | ||
============== | ||
|
||
This library allows you to define custom elements for usage in (Twig) templates. | ||
|
||
Think of it as light-weight SSR (Server Side Rendererd) Web Components. | ||
|
||
For example, you can create a new element such as `<Avatar username="alice" imageUrl="https://example.web/alice.webp" bio="Hello world" />` | ||
|
||
Then you define a twig template `elements/Avatar.html.twig` such as the following: | ||
|
||
```html | ||
<div class="card avatar"> | ||
<div class="card-body"> | ||
<h1>{{username}}</h1> | ||
{% if imageUrl is defined %} | ||
<img src="{{ imageUrl }}" class="avatar-image" /> | ||
{% endif %} | ||
{% if bio is defined %} | ||
<small>{{ bio }}</small> | ||
{% endif %} | ||
</div> | ||
</div> | ||
``` | ||
|
||
Your custom element can now be rendered using your custom element template. | ||
The template receives a variable for every specified attribute. | ||
|
||
Custom elements will only be rendered if they start with an upper-case character (to distinguish standard from custom elements) | ||
|
||
## Usage | ||
|
||
### Symfony | ||
|
||
Add the following to your `services.yaml` to register the renderer and the twig extension: | ||
|
||
```yaml | ||
services: | ||
LinkORB\Component\CustomElement\Twig\CustomElementExtension: | ||
tags: ['twig.extension'] | ||
LinkORB\Component\CustomElement\CustomElementRenderer: ~ | ||
``` | ||
|
||
### The renderer | ||
|
||
You can turn any raw HTML with custom elements into a rendered HTML output like this: | ||
|
||
```php | ||
$renderer = new Renderer($twig); | ||
$html = $renderer->render($htmlWithCustomElements); | ||
``` | ||
|
||
### The Twig extension | ||
|
||
You can render custom elements from any Twig template using the filter: | ||
|
||
```html | ||
<h1>Example</h1 | ||
{{ html|custom_element_render|raw }} | ||
``` | ||
|
||
## License | ||
|
||
MIT. Please refer to the [license file](LICENSE) for details. | ||
|
||
## Brought to you by the LinkORB Engineering team | ||
|
||
<img src="http://www.linkorb.com/d/meta/tier1/images/linkorbengineering-logo.png" width="200px" /><br /> | ||
Check out our other projects at [linkorb.com/engineering](http://www.linkorb.com/engineering). | ||
|
||
Btw, we're hiring! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"name": "linkorb/custom-element", | ||
"description": "Custom element", | ||
"homepage": "http://www.github.com/linkorb/custom-element", | ||
"keywords": ["php", "custom-element", "web-components", "ssr", "linkorb"], | ||
"type": "library", | ||
"authors": [ | ||
{ | ||
"name": "LinkORB Engineering", | ||
"email": "engineering@linkorb.com", | ||
"role": "Development" | ||
} | ||
], | ||
"require": { | ||
"php": ">=7.4.0" | ||
}, | ||
"autoload": { | ||
"psr-4": { | ||
"LinkORB\\Component\\CustomElement\\": "src/" | ||
} | ||
}, | ||
"license": "MIT" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
<?php | ||
|
||
namespace LinkORB\Component\CustomElement; | ||
|
||
use Twig\Environment; | ||
use RuntimeException; | ||
|
||
class CustomElementRenderer | ||
{ | ||
public function __construct(Environment $twig) | ||
{ | ||
$this->twig = $twig; | ||
} | ||
|
||
public function render(?string $body): string | ||
{ | ||
if (!$body) { | ||
return ''; | ||
} | ||
|
||
// Wrap HTML to ensure a root element exists | ||
$html = '<body>'.$body.'</body>'; | ||
|
||
// Load HTML into a DOMDocument | ||
try { | ||
$doc = new \DOMDocument(); | ||
// loadXml fails on `<br>` (i.e. no closing tag in memos) | ||
$doc->loadXml($html); | ||
// loadHtml fails on non-html elements such as `root` or `MyCoolComponent` | ||
// $doc->loadHtml($html); | ||
|
||
} catch (\Exception $e) { | ||
// echo $e->getMessage(); | ||
// echo $html;exit(); | ||
return '<h1>Error: ' . $e->getMessage() . '</h1><pre>' . htmlspecialchars($html) . '</pre>'; | ||
} | ||
|
||
// Create array of all custom element names used in the doc | ||
$elementNames = []; | ||
foreach($doc->getElementsByTagName('*') as $element ){ // Find all elements | ||
$name = $element->nodeName; | ||
// Only include element names starting with uppercase character | ||
if (ctype_upper($name[0])) { | ||
$elementNames[$name] = $name; | ||
} | ||
} | ||
// print_r($elementNames);exit(); | ||
|
||
foreach ($elementNames as $elementName) { | ||
$elements = $doc->getElementsByTagName($elementName); | ||
while (count($elements) > 0) { | ||
foreach ($elements as $element) { | ||
$data = [ | ||
'ElementName' => $element->nodeName, | ||
]; | ||
foreach ($element->attributes as $a) { | ||
$data[$a->nodeName] = $a->nodeValue; | ||
} | ||
|
||
$templateName = 'elements/' . $elementName . '.html.twig'; | ||
try { | ||
$html = $this->twig->render($templateName, $data); | ||
} catch (\Exception $e) { | ||
$html = '<div class="alert alert-danger">Failed to render custom element: <b>' . $elementName . '</b>.<br />' . $e->getMessage() . '</div>'; | ||
} | ||
|
||
$d = new \DOMDocument(); | ||
try { | ||
$d->loadHTML('<?xml version="1.0" encoding="utf-8"?>' . $html); | ||
} catch (\Exception $e) { | ||
throw new RuntimeException($e->getMessage()); | ||
} | ||
|
||
$new = $doc->importNode($d->documentElement, true); | ||
|
||
$element->parentNode->replaceChild($new, $element); | ||
} | ||
$elements = $doc->getElementsByTagName($elementName); | ||
} | ||
} | ||
$html = $doc->saveHTML(); | ||
$html = str_replace('<body>', '', $html); | ||
$html = str_replace('</body>', '', $html); | ||
return $html; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
|
||
namespace LinkORB\Component\CustomElement\Twig; | ||
|
||
use LinkORB\Component\CustomElement\CustomElementRenderer; | ||
use Twig\Extension\AbstractExtension; | ||
use Twig\TwigFilter; | ||
|
||
class CustomElementExtension extends AbstractExtension | ||
{ | ||
public function __construct(CustomElementRenderer $renderer) | ||
{ | ||
$this->renderer = $renderer; | ||
} | ||
|
||
public function getFilters() | ||
{ | ||
return [ | ||
new TwigFilter('custom_element_render', [$this, 'render']), | ||
]; | ||
} | ||
|
||
public function render($body) | ||
{ | ||
return $this->renderer->render($body); | ||
} | ||
} |