Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embed banners support #4

Merged
merged 5 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased
## [Unreleased]

## 1.1.0 - 2023-12-01
## [1.2.0] - 2024-04-04
### Added
- Added integration of the new AMP API fields `mode` and `dimensions`.
- Added possibility to render embed banners.
- Added new rendering mode `embed` (`EmbedRenderingMode`) for the Latte bridge.

### Changed
- Updated docs

## [1.1.0] - 2023-12-01
### Added
- Added ability to provide custom options for each banner through Renderer or Latte macro.
- Added support for native lazy loading. Feature can be enabled through banner options `'loading' => 'lazy'` and `'loading-offset' => <offset>` (for multiple positions only).
Expand All @@ -20,3 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Initial release.

[Unreleased]: https://github.com/68publishers/amp-client-js/compare/v1.2.0...HEAD
[1.2.0]: https://github.com/68publishers/amp-client-php/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/68publishers/amp-client-php/compare/v1.0.0...v1.1.0
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ $ composer require 68publishers/amp-client

## Versions compatibility matrix

| PHP client version | JS client version | PHP version | AMP version | API version |
|:------------------:|:-----------------:|:------------:|:-----------:|:-----------:|
| `>=1.0.0` | `>=1.4.0` | `>=7.4` | `>=2.12.0` | `1` |
| PHP client version | JS client version | AMP version | API version |
|:------------------:|:-----------------:|:-----------:|:-----------:|
| `~1.0.0` | `~1.4.0` | `2.12.0` | `1` |
| `~1.1.0` | `~1.4.0` | `2.12.0` | `1` |
| `~1.2.0` | `~1.5.0` | `>=2.13.0` | `1` |

## Integration without a framework

Expand Down
1 change: 1 addition & 0 deletions docs/integration-with-nette-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ The following rendering modes are available:

- **direct** ([DirectRenderingMode](../src/Bridge/Latte/RenderingMode/DirectRenderingMode.php)) - The default mode, API is requested separately for each banner.
- **client_side** ([ClientSideRenderingMode](../src/Bridge/Latte/RenderingMode/ClientSideRenderingMode.php)) - Renders only a wrapper element and leaves loading banners on the JavaScript client. Banners are loaded by calling the `attachBanners()` function.
- **embed** ([EmbedRenderingMode](../src/Bridge/Latte/RenderingMode/EmbedRenderingMode.php)) - Same behavior as `client_side`, but passes the information to the JavaScript client that the banner should be rendered as `embed`.
- **queued_in_presenter_context** ([QueuedRenderingInPresenterContextMode](../src/Bridge/Latte/RenderingMode/QueuedRenderingInPresenterContextMode.php)) - Renders only HTML comments as placeholders and stores requested positions in a queue. It will request, render and place all banners to them positions at once before the presenter returns a response.
- **queued** ([QueuedRenderingMode](../src/Bridge/Latte/RenderingMode/QueuedRenderingMode.php)) - Same behavior as `queued_in_presenter_context`, but it doesn't take into account whether the website template is currently being rendered through the Nette application. It is more suited for an integration without a framework.

Expand Down
52 changes: 52 additions & 0 deletions docs/integration-without-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* [Fetching banners](#fetching-banners)
* [Rendering banners](#rendering-banners)
* [Rendering banners on the client side](#rendering-banners-on-the-client-side)
* [Rendering embed (iframe) banners](#rendering-embed-iframe-banners)
* [Lazy loading of image banners](#lazy-loading-of-image-banners)
* [Templates overwriting](#templates-overwriting)
* [Rendering banners using Latte](#rendering-banners-using-latte)
Expand Down Expand Up @@ -192,6 +193,56 @@ echo $renderer->renderClientSide(new Position('homepage.promo'), ['class' => 'my

Banners rendered in this way will be loaded by the JavaScript client when its `attachBanners()` function is called.

### Rendering embed (iframe) banners

Banners can be rendered in "embed" mode, which means they are inside the `<iframe>` tag.
This rendering mode is again controlled by the JavaScript client.

```php
use SixtyEightPublishers\AmpClient\Renderer\Renderer;
use SixtyEightPublishers\AmpClient\Request\ValueObject\Position;
use SixtyEightPublishers\AmpClient\Renderer\ClientSideMode;

$renderer = Renderer::create();

echo $renderer->renderClientSide(
position: new Position('homepage.top'),
mode: ClientSideMode::embed(),
);
```

The information that the banner should be rendered in the `<iframe>` tag can also be returned by AMP.
If we want to follow this behavior, we need to condition the rendering ourselves.

```php
use SixtyEightPublishers\AmpClient\AmpClientInterface;
use SixtyEightPublishers\AmpClient\Renderer\RendererInterface;
use SixtyEightPublishers\AmpClient\Request\BannersRequest;
use SixtyEightPublishers\AmpClient\Request\ValueObject\Position;
use SixtyEightPublishers\AmpClient\Renderer\ClientSideMode;

/** @var AmpClientInterface $client */
/** @var RendererInterface $renderer */

$request = new BannersRequest([
new Position('homepage.top'),
]);

$response = $client->fetchBanners($request);
$position = $response->getPosition('homepage.top');

if ($position::ModeEmbed === $position->getMode()) {
echo $renderer->renderClientSide(
position: $request->getPosition('homepage.top'),
mode: ClientSideMode::embed(),
);
} else {
echo $renderer->render($position);
}
```

⚠️ Only image banners on `single` and `random` positions are now fully compatible with `embed` mode. Rendering other types via `embed` mode is not recommended.

### Lazy loading of image banners

The default client templates support [native lazy loading](https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading#images_and_iframes) of images.
Expand Down Expand Up @@ -367,6 +418,7 @@ The following rendering modes are available:

- **direct** ([DirectRenderingMode](../src/Bridge/Latte/RenderingMode/DirectRenderingMode.php)) - The default mode, API is requested separately for each banner.
- **client_side** ([ClientSideRenderingMode](../src/Bridge/Latte/RenderingMode/ClientSideRenderingMode.php)) - Renders only a wrapper element and leaves loading banners on the JavaScript client. Banners are loaded by calling the `attachBanners()` function.
- **embed** ([EmbedRenderingMode](../src/Bridge/Latte/RenderingMode/EmbedRenderingMode.php)) - Same behavior as `client_side`, but passes the information to the JavaScript client that the banner should be rendered as `embed`.
- **queued** ([QueuedRenderingMode](../src/Bridge/Latte/RenderingMode/QueuedRenderingMode.php)) - Renders only HTML comments as placeholders and stores requested positions in a queue. It will request and render all banners at once when the `RendererProvider::renderQueuedPositions()` method is called.
- **queued_in_presenter_context** ([QueuedRenderingInPresenterContextMode](../src/Bridge/Latte/RenderingMode/QueuedRenderingInPresenterContextMode.php)) - Same behavior as `queued` but in the context of a Presenter only. Usable with Nette applications only.

Expand Down
38 changes: 32 additions & 6 deletions src/Bridge/Latte/RendererProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
use SixtyEightPublishers\AmpClient\Bridge\Latte\Event\ConfigureClientEvent;
use SixtyEightPublishers\AmpClient\Bridge\Latte\Event\ConfigureClientEventHandlerInterface;
use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\DirectRenderingMode;
use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\EmbedRenderingMode;
use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\RenderingModeInterface;
use SixtyEightPublishers\AmpClient\Exception\AmpExceptionInterface;
use SixtyEightPublishers\AmpClient\Exception\RendererException;
use SixtyEightPublishers\AmpClient\Renderer\ClientSideMode;
use SixtyEightPublishers\AmpClient\Renderer\RendererInterface;
use SixtyEightPublishers\AmpClient\Request\BannersRequest;
use SixtyEightPublishers\AmpClient\Request\ValueObject\BannerResource;
Expand Down Expand Up @@ -88,7 +90,11 @@ public function __invoke(object $globals, string $positionCode, array $options =
$position = $this->createPosition($positionCode, $options);

if ($renderingMode->shouldBePositionRenderedClientSide($position)) {
return $this->renderClientSidePosition($position, $options);
return $this->renderClientSidePosition(
$position,
$options,
$renderingMode instanceof EmbedRenderingMode ? ClientSideMode::embed() : ClientSideMode::managed(),
);
}

if ($renderingMode->shouldBePositionQueued($position, $globals)) {
Expand All @@ -101,7 +107,17 @@ public function __invoke(object $globals, string $positionCode, array $options =
return '';
}

return $this->renderPosition($response->getPosition($positionCode), $options);
$responsePosition = $response->getPosition($positionCode);

if ($responsePosition::ModeEmbed === $responsePosition->getMode()) {
return $this->renderClientSidePosition(
$position,
$options,
ClientSideMode::embed(),
);
}

return $this->renderPosition($responsePosition, $options);
}

public function setDebugMode(bool $debugMode): self
Expand Down Expand Up @@ -202,13 +218,23 @@ public function renderQueuedPositions($output)
$replacements = array_filter(
array_map(
function (array $item) use ($response): ?string {
$responsePosition = $response->getPosition($item[0]->getCode());
$requestPosition = $item[0];
$options = $item[1];
$responsePosition = $response->getPosition($requestPosition->getCode());

if (null === $responsePosition) {
return null;
}

return $this->renderPosition($responsePosition, $item[1]);
if ($responsePosition::ModeEmbed === $responsePosition->getMode()) {
return $this->renderClientSidePosition(
$requestPosition,
$options,
ClientSideMode::embed(),
);
}

return $this->renderPosition($responsePosition, $options);
},
$this->queue,
),
Expand Down Expand Up @@ -301,13 +327,13 @@ private function renderPosition(ResponsePosition $position, array $options): str
/**
* @param array<string, mixed> $options
*/
private function renderClientSidePosition(RequestPosition $position, array $options): string
private function renderClientSidePosition(RequestPosition $position, array $options, ClientSideMode $mode): string
{
try {
$elementAttributes = (array) ($options[self::OptionAttributes] ?? []);
$bannerOptions = (array) ($options[self::OptionOptions] ?? []);

return $this->renderer->renderClientSide($position, $elementAttributes, $bannerOptions);
return $this->renderer->renderClientSide($position, $elementAttributes, $bannerOptions, $mode);
} catch (RendererException $e) {
if ($this->debugMode) {
throw $e;
Expand Down
32 changes: 32 additions & 0 deletions src/Bridge/Latte/RenderingMode/EmbedRenderingMode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode;

use SixtyEightPublishers\AmpClient\Request\ValueObject\Position;

final class EmbedRenderingMode implements RenderingModeInterface
{
public const Name = 'embed';

public function getName(): string
{
return self::Name;
}

public function supportsQueues(): bool
{
return false;
}

public function shouldBePositionQueued(Position $position, object $globals): bool
{
return false;
}

public function shouldBePositionRenderedClientSide(Position $position): bool
{
return true;
}
}
2 changes: 2 additions & 0 deletions src/Bridge/Nette/DI/AmpClientLatteExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use SixtyEightPublishers\AmpClient\Bridge\Latte\RendererProvider;
use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\ClientSideRenderingMode;
use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\DirectRenderingMode;
use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\EmbedRenderingMode;
use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\QueuedRenderingInPresenterContextMode;
use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\QueuedRenderingMode;
use SixtyEightPublishers\AmpClient\Bridge\Nette\Application\AttachPresenterHandlersOnApplicationHandler;
Expand All @@ -38,6 +39,7 @@ final class AmpClientLatteExtension extends CompilerExtension
QueuedRenderingMode::Name => QueuedRenderingMode::class,
QueuedRenderingInPresenterContextMode::Name => QueuedRenderingInPresenterContextMode::class,
ClientSideRenderingMode::Name => ClientSideRenderingMode::class,
EmbedRenderingMode::Name => EmbedRenderingMode::class,
];

private bool $debugMode;
Expand Down
4 changes: 4 additions & 0 deletions src/Renderer/AmpBannerExternalAttribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ public function __toString(): string
'rotationSeconds' => $this->position->getRotationSeconds(),
'displayType' => $this->position->getDisplayType(),
'breakpointType' => $this->position->getBreakpointType(),
'dimensions' => [
'width' => $this->position->getDimensions()->getWidth(),
'height' => $this->position->getDimensions()->getHeight(),
],
],
'state' => [
'value' => $this->state,
Expand Down
64 changes: 64 additions & 0 deletions src/Renderer/ClientSideMode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\AmpClient\Renderer;

use InvalidArgumentException;
use function in_array;
use function sprintf;

final class ClientSideMode
{
public const Modes = [
self::Managed,
self::Embed,
];

private const Managed = 'managed';
private const Embed = 'embed';

private string $value;

private function __construct(string $value)
{
$this->value = $value;
}

public static function managed(): self
{
return new self(self::Managed);
}

public static function embed(): self
{
return new self(self::Embed);
}

public static function fromValue(string $value): self
{
if (!in_array($value, self::Modes, true)) {
throw new InvalidArgumentException(sprintf(
'Value "%s" is not valid client side mode.',
$value,
));
}

return new self($value);
}

public function getValue(): string
{
return $this->value;
}

public function isManaged(): bool
{
return self::Managed === $this->value;
}

public function isEmbed(): bool
{
return self::Embed === $this->value;
}
}
5 changes: 3 additions & 2 deletions src/Renderer/Latte/LatteRendererBridge.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace SixtyEightPublishers\AmpClient\Renderer\Latte;

use Latte\Engine;
use SixtyEightPublishers\AmpClient\Renderer\ClientSideMode;
use SixtyEightPublishers\AmpClient\Renderer\Latte\Templates\ClientSideTemplate;
use SixtyEightPublishers\AmpClient\Renderer\Latte\Templates\MultipleTemplate;
use SixtyEightPublishers\AmpClient\Renderer\Latte\Templates\NotFoundTemplate;
Expand Down Expand Up @@ -83,11 +84,11 @@ public function renderMultiple(ResponsePosition $position, array $banners, array
);
}

public function renderClientSide(RequestPosition $position, array $elementAttributes = [], array $options = []): string
public function renderClientSide(RequestPosition $position, ClientSideMode $mode, array $elementAttributes = [], array $options = []): string
{
return $this->getLatte()->renderToString(
$this->templates->getTemplateFile(Templates::ClientSide),
new ClientSideTemplate($position, $elementAttributes, $options),
new ClientSideTemplate($position, $mode, $elementAttributes, $options),
);
}

Expand Down
5 changes: 5 additions & 0 deletions src/Renderer/Latte/Templates/ClientSideTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@

namespace SixtyEightPublishers\AmpClient\Renderer\Latte\Templates;

use SixtyEightPublishers\AmpClient\Renderer\ClientSideMode;
use SixtyEightPublishers\AmpClient\Request\ValueObject\Position;

final class ClientSideTemplate
{
public Position $position;

public ClientSideMode $mode;

/** @var array<string, scalar|null> */
public array $elementAttributes;

Expand All @@ -22,10 +25,12 @@ final class ClientSideTemplate
*/
public function __construct(
Position $position,
ClientSideMode $mode,
array $elementAttributes,
array $options
) {
$this->position = $position;
$this->mode = $mode;
$this->elementAttributes = $elementAttributes;
$this->options = $options;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Renderer/Latte/Templates/clientSide.latte
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
{var $helpers = SixtyEightPublishers\AmpClient\Renderer\Latte\Helpers::class}

<div data-amp-banner="{$position->getCode()}"
n:attr="array_merge($helpers::createResourceAttributes($position), $helpers::createOptionAttributes($options), $elementAttributes)">
n:attr="array_merge(['data-amp-mode' => $mode->isEmbed() ? $mode->getValue() : null], $helpers::createResourceAttributes($position), $helpers::createOptionAttributes($options), $elementAttributes)">
</div>
Loading
Loading