Skip to content

Commit

Permalink
Add portal twig extension (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-schranz authored Sep 18, 2020
1 parent d3892b2 commit 594b6d1
Show file tree
Hide file tree
Showing 18 changed files with 387 additions and 7 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,11 @@ The [web editor extension](docs/editor.md) gives you a simple way to add classes
### 7. Icon

The [icon extension](docs/icon.md) gives you a simple way to render icomoon icons.

[More](docs/icon.md)

### 8. Portal

The [portal extension](docs/portal.md) brings react portal feature to twig.

[More](docs/portal.md)
57 changes: 57 additions & 0 deletions docs/portal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Portal Extension

The portal twig extension is inspired by [react portals](https://reactjs.org/docs/portals.html) and allow to render content at a different position.
It should help to solve z-index problems by rendering overlays outside of the other DOM elements.

## Setup

### Service Registration

The twig extension need to be registered as [symfony service](http://symfony.com/doc/current/service_container.html).

```yml
services:
Sulu\Twig\Extensions\PortalExtension: ~
```
If autoconfigure is not active you need to tag it with [twig.extension](https://symfony.com/doc/current/service_container.html#the-autoconfigure-option).
## Usage
You can use a portal to output content at another position:
```twig
<section class="containers">
{% for i in 1..3 %}
<div>
{{- 'Title ' ~ i -}}
</div>

{% portal overlays %}
<div>
{{- 'Overlay ' ~ i -}}
</div>
{% endportal %}
{% endfor %}
</section>

<section class="overlays">
{{ get_portal('overlays') }}
</section>
```

Output:

```html
<section class="containers">
<div>Title 1</div>
<div>Title 2</div>
<div>Title 3</div>
</section>

<section class="overlays">
<div>Overlay 1</div>
<div>Overlay 2</div>
<div>Overlay 3</div>
</section>
```
51 changes: 51 additions & 0 deletions src/Node/PortalNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

/*
* This file is part of Sulu.
*
* (c) Sulu GmbH
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Sulu\Twig\Extensions\Node;

use Twig\Compiler;
use Twig\Node\Node;

/**
* @internal this class is only for internal use and should not be used by someone else
*/
final class PortalNode extends Node
{
/**
* @param Node<Node> $body
*/
public function __construct(string $name, Node $body, int $lineno, string $tag = null)
{
parent::__construct(['body' => $body], ['name' => $name], $lineno, $tag);
}

public function compile(Compiler $compiler): void
{
$compiler->addDebugInfo($this);

if ($compiler->getEnvironment()->isDebug()) {
$compiler->write("ob_start();\n");
} else {
$compiler->write("ob_start(function () { return ''; });\n");
}
$compiler
->subcompile($this->getNode('body'))
;

// TODO find a better way then using a static function
$compiler->raw("\Sulu\Twig\Extensions\PortalExtension::addPortal(");
$compiler->raw("'" . $this->getAttribute('name') . "', ");
$compiler->raw("('' === \$tmp = ob_get_clean()) ? '' : new Markup(\$tmp, \$this->env->getCharset())");
$compiler->raw(");\n");
}
}
70 changes: 70 additions & 0 deletions src/PortalExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

/*
* This file is part of Sulu.
*
* (c) Sulu GmbH
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Sulu\Twig\Extensions;

use Sulu\Twig\Extensions\TokenParser\PortalTokenParser;
use Twig\Extension\AbstractExtension;
use Twig\Markup;
use Twig\TwigFunction;

class PortalExtension extends AbstractExtension
{
/**
* @var mixed[]
*/
private static $PORTALS = [];

public function getTokenParsers()
{
return [new PortalTokenParser()];
}

/**
* {@inheritdoc}
*/
public function getFunctions()
{
return [
new TwigFunction('get_portal', [$this, 'getPortal'], ['is_safe' => ['all']]),
];
}

public function getPortal(string $name): string
{
if (!isset(self::$PORTALS[$name])) {
return '';
}

$output = '';

foreach (self::$PORTALS[$name] as $portalContent) {
$output .= $portalContent;
}

unset(self::$PORTALS[$name]);

return $output;
}

/**
* @internal
*
* @param string $name
* @param string|Markup $body
*/
public static function addPortal($name, $body): void
{
self::$PORTALS[$name][] = $body;
}
}
51 changes: 51 additions & 0 deletions src/TokenParser/PortalTokenParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

/*
* This file is part of Sulu.
*
* (c) Sulu GmbH
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Sulu\Twig\Extensions\TokenParser;

use Sulu\Twig\Extensions\Node\PortalNode;
use Twig\Node\Node;
use Twig\Token;
use Twig\TokenParser\AbstractTokenParser;

/**
* @internal this class is only for internal use and should not be used by someone else
*/
final class PortalTokenParser extends AbstractTokenParser
{
/**
* @return PortalNode<Node>
*/
public function parse(Token $token): PortalNode
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);

$body = $this->parser->subparse([$this, 'decideBlockEnd'], true);
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);

return new PortalNode($name, $body, $lineno, $this->getTag());
}

public function decideBlockEnd(Token $token): bool
{
return $token->test('endportal');
}

public function getTag(): string
{
return 'portal';
}
}
35 changes: 35 additions & 0 deletions tests/Functional/BaseFunctionalTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

/*
* This file is part of Sulu.
*
* (c) Sulu GmbH
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Sulu\Twig\Extensions\Tests\Functional;

use PHPUnit\Framework\TestCase;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;

abstract class BaseFunctionalTestCase extends TestCase
{
/**
* @param array<string, bool> $options
*/
protected function getTwig(array $options = []): Environment
{
return new Environment(
new FilesystemLoader(__DIR__ . \DIRECTORY_SEPARATOR . 'templates'),
array_merge([
'debug' => true,
'strict_variables' => true,
], $options)
);
}
}
29 changes: 29 additions & 0 deletions tests/Functional/PortalExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

/*
* This file is part of Sulu.
*
* (c) Sulu GmbH
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Sulu\Twig\Extensions\Tests\Functional;

use Sulu\Twig\Extensions\PortalExtension;

class PortalExtensionTest extends BaseFunctionalTestCase
{
public function testPortal(): void
{
$twig = $this->getTwig();
$twig->addExtension(new PortalExtension());
$this->assertSame(
file_get_contents(__DIR__ . '/snapshot/portal.txt'),
$twig->render('portal/portal.html.twig')
);
}
}
11 changes: 11 additions & 0 deletions tests/Functional/snapshot/portal.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
BEFORE INCLUDE
BEFORE PORTALS
INCLUDE CONTENT
AFTER PORTALS
AFTER INCLUDE

POSITION 0
Portal A
POSITION 1
portal b
POSITION 2
5 changes: 5 additions & 0 deletions tests/Functional/templates/portal/portal-include.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
BEFORE PORTALS
{% portal a %}Portal A{% endportal %}
INCLUDE CONTENT
{% portal b %}{{ 'Portal B'|lower }}{% endportal %}
AFTER PORTALS
9 changes: 9 additions & 0 deletions tests/Functional/templates/portal/portal.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
BEFORE INCLUDE
{% include 'portal/portal-include.html.twig' %}
AFTER INCLUDE

POSITION 0
{{ get_portal('a') }}
POSITION 1
{{ get_portal('b') }}
POSITION 2
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* with this source code in the file LICENSE.
*/

namespace Sulu\Twig\Extensions\Tests;
namespace Sulu\Twig\Extensions\Tests\Unit;

use PHPUnit\Framework\TestCase;
use Sulu\Twig\Extensions\ComponentExtension;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* with this source code in the file LICENSE.
*/

namespace Sulu\Twig\Extensions\Tests;
namespace Sulu\Twig\Extensions\Tests\Unit;

use PHPUnit\Framework\TestCase;
use Sulu\Twig\Extensions\CountExtension;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* with this source code in the file LICENSE.
*/

namespace Sulu\Twig\Extensions\Tests;
namespace Sulu\Twig\Extensions\Tests\Unit;

use PHPUnit\Framework\TestCase;
use Sulu\Twig\Extensions\EditorExtension;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* with this source code in the file LICENSE.
*/

namespace Sulu\Twig\Extensions\Tests;
namespace Sulu\Twig\Extensions\Tests\Unit;

use PHPUnit\Framework\TestCase;
use Sulu\Twig\Extensions\IconExtension;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* with this source code in the file LICENSE.
*/

namespace Sulu\Twig\Extensions\Tests;
namespace Sulu\Twig\Extensions\Tests\Unit;

use PHPUnit\Framework\TestCase;
use Sulu\Twig\Extensions\ImageExtension;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* with this source code in the file LICENSE.
*/

namespace Sulu\Twig\Extensions\Tests;
namespace Sulu\Twig\Extensions\Tests\Unit;

use PHPUnit\Framework\TestCase;
use Sulu\Twig\Extensions\IntlExtension;
Expand Down
Loading

0 comments on commit 594b6d1

Please sign in to comment.