From 0c77fe545026225326c6c6deefb00859d307e856 Mon Sep 17 00:00:00 2001 From: Joost Faassen Date: Sun, 14 Nov 2021 13:56:31 +0000 Subject: [PATCH] feat: initial checkin --- .editorconfig | 13 +++++ .gitignore | 2 + README.md | 71 ++++++++++++++++++++++++ composer.json | 23 ++++++++ src/CustomElementRenderer.php | 86 +++++++++++++++++++++++++++++ src/Twig/CustomElementExtension.php | 27 +++++++++ 6 files changed, 222 insertions(+) create mode 100755 .editorconfig create mode 100644 .gitignore create mode 100644 README.md create mode 100644 composer.json create mode 100644 src/CustomElementRenderer.php create mode 100644 src/Twig/CustomElementExtension.php diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 0000000..383dbae --- /dev/null +++ b/.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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a9875b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/README.md b/README.md new file mode 100644 index 0000000..46e6a85 --- /dev/null +++ b/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 `` + +Then you define a twig template `elements/Avatar.html.twig` such as the following: + +```html +
+
+

{{username}}

+ {% if imageUrl is defined %} + + {% endif %} + {% if bio is defined %} + {{ bio }} + {% endif %} +
+
+``` + +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 +

Example


+Check out our other projects at [linkorb.com/engineering](http://www.linkorb.com/engineering). + +Btw, we're hiring! diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..7fc697f --- /dev/null +++ b/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" +} diff --git a/src/CustomElementRenderer.php b/src/CustomElementRenderer.php new file mode 100644 index 0000000..a421668 --- /dev/null +++ b/src/CustomElementRenderer.php @@ -0,0 +1,86 @@ +twig = $twig; + } + + public function render(?string $body): string + { + if (!$body) { + return ''; + } + + // Wrap HTML to ensure a root element exists + $html = ''.$body.''; + + // Load HTML into a DOMDocument + try { + $doc = new \DOMDocument(); + // loadXml fails on `
` (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 '

Error: ' . $e->getMessage() . '

' . htmlspecialchars($html) . '
'; + } + + // 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 = '
Failed to render custom element: ' . $elementName . '.
' . $e->getMessage() . '
'; + } + + $d = new \DOMDocument(); + try { + $d->loadHTML('' . $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('', '', $html); + $html = str_replace('', '', $html); + return $html; + } +} diff --git a/src/Twig/CustomElementExtension.php b/src/Twig/CustomElementExtension.php new file mode 100644 index 0000000..939a35f --- /dev/null +++ b/src/Twig/CustomElementExtension.php @@ -0,0 +1,27 @@ +renderer = $renderer; + } + + public function getFilters() + { + return [ + new TwigFilter('custom_element_render', [$this, 'render']), + ]; + } + + public function render($body) + { + return $this->renderer->render($body); + } +}