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

Feature: Support partial rendering #9

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
47 changes: 47 additions & 0 deletions Classes/Fusion/PartialResolverImplementation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
namespace Psmb\Ajaxify\Fusion;

use Exception;
use Neos\Cache\Frontend\VariableFrontend;
use Neos\Flow\Annotations as Flow;
use Neos\Fusion\FusionObjects\AbstractFusionObject;
use Neos\Neos\View\FusionView;

/**
* Gets the content node and the Fusion path specified by the cache entry key
* for rendering the associated partial.
*/
class PartialResolverImplementation extends AbstractFusionObject
{
/**
* @Flow\Inject
* @var FusionView
*/
protected $view;

/**
* @Flow\Inject
* @var VariableFrontend
*/
protected $partialCache;

/**
* @return mixed
* @throws Exception
*/
public function evaluate()
{
$partialKey = $this->fusionValue('partialKey');

$partialContext = $this->partialCache->get($partialKey);

if (!$partialContext) {
throw new Exception(
sprintf('The partial context could not be resolved for identifier "%s".', $partialKey),
1678108923
);
}

return $partialContext;
}
}
83 changes: 83 additions & 0 deletions Classes/Fusion/PartialSerializerImplementation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php
namespace Psmb\Ajaxify\Fusion;

use Neos\Cache\Exception;
use Neos\Cache\Frontend\VariableFrontend;
use Neos\ContentRepository\Domain\Model\Node;
use Neos\Flow\Annotations as Flow;
use Neos\Fusion\FusionObjects\AbstractFusionObject;

/**
* Stores the content node and the Fusion path of the current partial
* in the cache and returns the associated cache entry key.
*/
class PartialSerializerImplementation extends AbstractFusionObject
{
/**
* @Flow\Inject
* @var VariableFrontend
*/
protected $partialCache;

/**
* @return mixed|string
* @throws Exception
*/
public function evaluate()
{
/** @var $node Node */
$node = $this->fusionValue('node');

$nodeIdentifier = (string)$node->getNodeAggregateIdentifier();
$fusionPath = $this->getPartialFusionPath();
$partialContext = [
'nodeIdentifier' => $nodeIdentifier,
'fusionPath' => $fusionPath,
];
$partialKey = sha1(implode(';', $partialContext));
$this->partialCache->set(
$partialKey,
$partialContext
);
return $partialKey;
}

/**
* Returns the Fusion path of the wrapping partial.
*
* Note that this Psmb.Ajaxify:PartialSerializer object may be nested inside a
* Psmb.Ajaxify:Ajaxify object. It is assumed that the outer of the two
* objects is in turn called at the first nesting depth of the wrapping
* partial.
*
* @return string
* @see \Neos\Neos\Fusion\ContentElementWrappingImplementation::getContentElementFusionPath
*/
protected function getPartialFusionPath()
{
$pos = strrpos($this->path, "<Psmb.Ajaxify:Ajaxify>");
if ($pos === false) {
$pos = strrpos($this->path, "<Psmb.Ajaxify:PartialSerializer>");
}

$fusionPathSegments = explode('/', substr($this->path, 0, $pos));
$numberOfFusionPathSegments = count($fusionPathSegments);
if (isset($fusionPathSegments[$numberOfFusionPathSegments - 3])
&& $fusionPathSegments[$numberOfFusionPathSegments - 3] === '__meta'
&& isset($fusionPathSegments[$numberOfFusionPathSegments - 2])
&& $fusionPathSegments[$numberOfFusionPathSegments - 2] === 'process') {

// cut off the SHORT processing syntax "__meta/process/ajaxify"
return implode('/', array_slice($fusionPathSegments, 0, -3));
}
elseif (isset($fusionPathSegments[$numberOfFusionPathSegments - 4])
&& $fusionPathSegments[$numberOfFusionPathSegments - 4] === '__meta'
&& isset($fusionPathSegments[$numberOfFusionPathSegments - 3])
&& $fusionPathSegments[$numberOfFusionPathSegments - 3] === 'process') {

// cut off the LONG processing syntax "__meta/process/ajaxify/expression"
return implode('/', array_slice($fusionPathSegments, 0, -4));
}
return implode('/', array_slice($fusionPathSegments, 0, -1));
}
}
31 changes: 0 additions & 31 deletions Classes/Fusion/RenderPathImplementation.php

This file was deleted.

57 changes: 30 additions & 27 deletions Classes/Fusion/RendererImplementation.php
Original file line number Diff line number Diff line change
@@ -1,39 +1,42 @@
<?php
namespace Psmb\Ajaxify\Fusion;

use Exception;
use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\Flow\Annotations as Flow;
use Neos\Neos\View\FusionView;
use Neos\Fusion\FusionObjects\AbstractFusionObject;
use Neos\Neos\Exception as NeosException;
use Neos\Neos\View\FusionView;

/**
* Renders a Fusion view based on a path
* Renders a partial based on a content node and a Fusion path.
*/
class RendererImplementation extends AbstractFusionObject {

/**
* @Flow\Inject
* @var FusionView
*/
protected $view;
class RendererImplementation extends AbstractFusionObject
{
/**
* @Flow\Inject
* @var FusionView
*/
protected $view;

/**
* @Flow\Inject
* @var \Neos\Cache\Frontend\VariableFrontend
*/
protected $pathsCache;
/**
* @return mixed
* @throws Exception
*/
public function evaluate()
{
$node = $this->fusionValue('node');
$fusionPath = $this->fusionValue('fusionPath');

public function evaluate() {
$this->view->setControllerContext($this->runtime->getControllerContext());
$node = $this->fusionValue('node');
$pathKey = $this->fusionValue('pathKey');
$renderPath = $this->pathsCache->get($pathKey);
if (!$renderPath) {
throw new \Exception(sprintf('Render path not found for key %s', $pathKey));
}
$this->view->setFusionPath($renderPath);
$this->view->assign('value', $node);
return $this->view->render();
}
if (!$node instanceof NodeInterface) {
throw new Exception(sprintf('The node could not be resolved.'), 1677856609);
}
if (!$fusionPath) {
throw new Exception(sprintf('The Fusion path could not be resolved.'), 1677857018);
}

$this->view->setControllerContext($this->runtime->getControllerContext());
$this->view->setFusionPath($fusionPath);
$this->view->assign('value', $node);
return $this->view->render();
}
}
2 changes: 1 addition & 1 deletion Configuration/Caches.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Psmb_Ajaxify_PathsCache:
Psmb_Ajaxify_PartialCache:
backendOptions:
defaultLifetime: 0
persistent: true
12 changes: 6 additions & 6 deletions Configuration/Objects.yaml
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
Psmb\Ajaxify\Fusion\RenderPathImplementation:
Psmb\Ajaxify\Fusion\PartialSerializerImplementation:
properties:
pathsCache:
partialCache:
object:
factoryObjectName: Neos\Flow\Cache\CacheManager
factoryMethodName: getCache
arguments:
1:
value: Psmb_Ajaxify_PathsCache
value: Psmb_Ajaxify_PartialCache

Psmb\Ajaxify\Fusion\RendererImplementation:
Psmb\Ajaxify\Fusion\PartialResolverImplementation:
properties:
pathsCache:
partialCache:
object:
factoryObjectName: Neos\Flow\Cache\CacheManager
factoryMethodName: getCache
arguments:
1:
value: Psmb_Ajaxify_PathsCache
value: Psmb_Ajaxify_PartialCache
40 changes: 37 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Psmb.Ajaxify

This package allows you to mark any part of page for asynchronous loading via AJAX with just one line of Fusion code.
Why? It helps you to speed up initial page load by delaying the load of some less relevant parts of the page, e.g. comments.
Why? It helps you to speed up initial page load by delaying the load of some less relevant parts of the page, e.g.
comments.

![demo](https://cloud.githubusercontent.com/assets/837032/25178402/5b011f40-250e-11e7-9e6c-462b8e912893.gif)

Expand All @@ -14,7 +15,7 @@ Why? It helps you to speed up initial page load by delaying the load of some les
composer require psmb/ajaxify
```

2. Add `@process.myUniqueKey = Psmb.Ajaxify:Ajaxify` on any Fusion path. **The `myUniqueKey` key of the processor MUST be globally unique.**
2. Add `@process.ajaxify = Psmb.Ajaxify:Ajaxify` on any Fusion path.


3. Add this anywhere in your Fusion code to include the sample AJAX loading script:
Expand All @@ -29,10 +30,43 @@ Or include these assets via your build tool. Or just write your own loader.
4. Done. Now part of your pages will be lazily loaded via an AJAX request.

**Note:** the Fusion component should not depend on any context variables, other than the standard ones.
If you want to reuse some EEL expression in your code base, don't put it into context, rather wrap it into `Neos.Fusion:Value` object and use it everywhere you like.
If you want to reuse some EEL expression in your code base, don't put it into context, rather wrap it
into `Neos.Fusion:Value` object and use it everywhere you like.

5. You may override the `Psmb.Ajaxify:Loader` object in order to customize the loader.

## Partial rendering in custom AJAX application

You may want to use only the partial rendering feature of this package in your custom AJAX implementation.
Therefore, get the unique partial key with `partialKey = Psmb.Ajaxify:PartialSerializer` in your Fusion path
and append it as `ajaxPartialKey` parameter to a self-reflecting URL. When you send an AJAX request to
this URL, only the rendered partial will be returned. Additional parameters can be used to fine-tune
the rendering, for example to allow pagination:

```
prototype(MyWebsite.Site:Content.PaginatedContent) < prototype(Neos.Neos:ContentComponent) {
from = ${String.toInteger(request.arguments.from) || 0}
num = 5
partialKey = Psmb.Ajaxify:PartialSerializer

renderer = Neos.Neos:ContentComponent {
items = ..
partialUrl = Neos.Neos:NodeUri {
node = ${documentNode}
additionalParams.from = ${props.from + props.num}
additionalParams.ajaxPartialKey = ${props.partialKey}
}

renderer = afx`
<Neos.Fusion:Loop items={props.items} itemName="item">
..
</Neos.Fusion:Loop>
<a href={props.partialUrl} class="js-load-via-ajax">Load next page</a>
`
}
}
```


## Usage in the Wild

Expand Down
2 changes: 1 addition & 1 deletion Resources/Private/Fusion/Override.fusion
Original file line number Diff line number Diff line change
@@ -1 +1 @@
prototype(Neos.Fusion:GlobalCacheIdentifiers).ajaxPathKey = ${request.arguments.ajaxPathKey}
prototype(Neos.Fusion:GlobalCacheIdentifiers).ajaxPartialKey = ${request.arguments.ajaxPartialKey}
Loading