Skip to content

Commit

Permalink
feat: initial checkin
Browse files Browse the repository at this point in the history
  • Loading branch information
joostfaassen committed Nov 14, 2021
0 parents commit 0c77fe5
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .editorconfig
@@ -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
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
/vendor/
composer.lock
71 changes: 71 additions & 0 deletions README.md
@@ -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!
23 changes: 23 additions & 0 deletions composer.json
@@ -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"
}
86 changes: 86 additions & 0 deletions src/CustomElementRenderer.php
@@ -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;
}
}
27 changes: 27 additions & 0 deletions src/Twig/CustomElementExtension.php
@@ -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);
}
}

0 comments on commit 0c77fe5

Please sign in to comment.