Compare commits
No commits in common. "develop" and "main" have entirely different histories.
12 changed files with 0 additions and 460 deletions
|
@ -1,7 +0,0 @@
|
||||||
steps:
|
|
||||||
code-style:
|
|
||||||
image: git.digital-competence.de/woodpecker-ci/plugin-phpcs
|
|
||||||
settings:
|
|
||||||
args: Classes/ Tests/
|
|
||||||
when:
|
|
||||||
- event: [push, pull_request, manual]
|
|
|
@ -1,30 +0,0 @@
|
||||||
workspace:
|
|
||||||
base: /woodpecker
|
|
||||||
path: package
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- FLOW_VERSION: 6.3
|
|
||||||
PHP_VERSION: 7.4
|
|
||||||
- FLOW_VERSION: 7.3
|
|
||||||
PHP_VERSION: 7.4
|
|
||||||
- FLOW_VERSION: 7.3
|
|
||||||
PHP_VERSION: 8.2
|
|
||||||
- FLOW_VERSION: 8.2
|
|
||||||
PHP_VERSION: 8.2
|
|
||||||
|
|
||||||
steps:
|
|
||||||
functional-tests:
|
|
||||||
image: git.digital-competence.de/woodpecker-ci/plugin-phpunit-flow:${PHP_VERSION}
|
|
||||||
settings:
|
|
||||||
flow_version: ${FLOW_VERSION}
|
|
||||||
ssh_key:
|
|
||||||
from_secret: deploykey
|
|
||||||
hostkeys:
|
|
||||||
- digital-competence.de ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNSjVKJ+SO6wqmDSCgcJDk2ljWlD7qajsTxAuvZpTbJBg2++Zu0VxH0S1WzPVTD/D5UUbK6LVy6YSCnGlv6zmc0=
|
|
||||||
repositories:
|
|
||||||
- vcs ssh://git@digital-competence.de/Packages/DigiComp.FluidRenderFunctions
|
|
||||||
require:
|
|
||||||
- digicomp/fluid-render-functions:@dev
|
|
||||||
when:
|
|
||||||
- event: [ push, pull_request, manual ]
|
|
|
@ -1,143 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DigiComp\FluidJsonViews\ViewHelpers\Controller;
|
|
||||||
|
|
||||||
use DigiComp\FluidRenderFunctions\InvokeRenderFunctionInterface;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use Doctrine\ORM\Proxy\Proxy;
|
|
||||||
use Doctrine\ORM\Query\Parameter;
|
|
||||||
use Neos\Flow\Annotations as Flow;
|
|
||||||
use Neos\Flow\Persistence\Doctrine\Query;
|
|
||||||
use Neos\FluidAdaptor\Core\Widget\AbstractWidgetController;
|
|
||||||
use Neos\FluidAdaptor\View\TemplateView;
|
|
||||||
use Neos\Utility\TypeHandling;
|
|
||||||
|
|
||||||
class FluidJsonController extends AbstractWidgetController
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @Flow\Inject
|
|
||||||
* @var EntityManagerInterface
|
|
||||||
*/
|
|
||||||
protected $entityManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
protected $supportedMediaTypes = [
|
|
||||||
'text/html',
|
|
||||||
'application/json',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Query|null
|
|
||||||
*/
|
|
||||||
protected ?Query $query = null;
|
|
||||||
|
|
||||||
protected InvokeRenderFunctionInterface $renderFunction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Flow\InjectConfiguration(package="DigiComp.FluidJsonViews", path="limitConfiguration")
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected array $limitConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected array $searchProperties;
|
|
||||||
|
|
||||||
protected function initializeAction(): void
|
|
||||||
{
|
|
||||||
parent::initializeAction();
|
|
||||||
|
|
||||||
$this->query = clone $this->widgetConfiguration['objects']->getQuery();
|
|
||||||
$this->renderFunction = $this->widgetConfiguration['renderFunction'];
|
|
||||||
$this->searchProperties = $this->widgetConfiguration['searchProperties'];
|
|
||||||
$this->initializeDoctrineSource();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes count, objectType and doctrine's MetaDataFactory
|
|
||||||
*/
|
|
||||||
protected function initializeDoctrineSource(): void
|
|
||||||
{
|
|
||||||
// As we are working with objects which may be doctrine proxies persisted in session, its metadata have
|
|
||||||
// never been loaded in this request. So let's see these parameters and load their metadata, so the doctrine
|
|
||||||
// framework knows how to go on.
|
|
||||||
if ($this->query instanceof Query) {
|
|
||||||
$parameters = $this->query->getQueryBuilder()->getParameters();
|
|
||||||
foreach ($parameters as $parameter) {
|
|
||||||
if ($parameter instanceof Parameter && $parameter->getValue() instanceof Proxy) {
|
|
||||||
$this->entityManager->getMetadataFactory()->getMetadataFor(
|
|
||||||
TypeHandling::getTypeForValue($parameter->getValue())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function indexAction()
|
|
||||||
{
|
|
||||||
$this->view->assign('entityClassName', $this->query->getType());
|
|
||||||
if ($this->view instanceof TemplateView) {
|
|
||||||
$renderingContext = $this->request
|
|
||||||
->getInternalArgument('__widgetContext')
|
|
||||||
->getViewHelperChildNodeRenderingContext();
|
|
||||||
$this->view->assignMultiple($renderingContext->getVariableProvider()->getAll());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see https://select2.org/data-sources/ajax#request-parameters
|
|
||||||
*/
|
|
||||||
public function dataAction(
|
|
||||||
?int $limit = null,
|
|
||||||
?string $term = null,
|
|
||||||
?int $page = 0
|
|
||||||
) {
|
|
||||||
$query = $this->query;
|
|
||||||
$result['recordsTotal'] = $query->execute()->count();
|
|
||||||
if ($term !== null) {
|
|
||||||
$searchConstraint = $this->getSearchConstraints($term);
|
|
||||||
$query->matching($query->logicalAnd($searchConstraint));
|
|
||||||
}
|
|
||||||
|
|
||||||
$result['recordsFiltered'] = $query->execute()->count();
|
|
||||||
if ($limit === null) {
|
|
||||||
$limit = $this->limitConfiguration['default'];
|
|
||||||
}
|
|
||||||
if ($this->limitConfiguration['max'] !== null) {
|
|
||||||
if ($limit > $this->limitConfiguration['max'] || $limit === null) {
|
|
||||||
$limit = $this->limitConfiguration['max'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($limit !== null) {
|
|
||||||
$data = $query->setLimit($limit)->setOffset($page * $limit)->execute();
|
|
||||||
} else {
|
|
||||||
$data = $query->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
$renderFunc = $this->renderFunction;
|
|
||||||
foreach ($data as $object) {
|
|
||||||
$result['results'][] = [
|
|
||||||
'id' => $this->persistenceManager->getIdentifierByObject($object),
|
|
||||||
'text' => $renderFunc($object),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$this->response->setContentType('application/json');
|
|
||||||
return \json_encode($result, \JSON_THROW_ON_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getSearchConstraints(
|
|
||||||
string $term
|
|
||||||
): object {
|
|
||||||
$searchConstraints = [];
|
|
||||||
foreach ($this->searchProperties as $searchProperty) {
|
|
||||||
$searchConstraints[] = $this->query->like($searchProperty, '%' . $term . '%');
|
|
||||||
}
|
|
||||||
return $this->query->logicalOr($searchConstraints);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DigiComp\FluidJsonViews\ViewHelpers;
|
|
||||||
|
|
||||||
use DigiComp\FluidJsonViews\ViewHelpers\Controller\FluidJsonController;
|
|
||||||
use DigiComp\FluidRenderFunctions\ViewHelpers\Traits\ValidateRenderFunctionTrait;
|
|
||||||
use Neos\Flow\Annotations as Flow;
|
|
||||||
use Neos\Flow\Mvc\Exception\InfiniteLoopException;
|
|
||||||
use Neos\Flow\Mvc\Exception\StopActionException;
|
|
||||||
use Neos\Flow\Persistence\Doctrine\QueryResult;
|
|
||||||
use Neos\FluidAdaptor\Core\Widget\AbstractWidgetViewHelper;
|
|
||||||
use Neos\FluidAdaptor\Core\Widget\Exception\InvalidControllerException;
|
|
||||||
use Neos\FluidAdaptor\Core\Widget\Exception\MissingControllerException;
|
|
||||||
|
|
||||||
class FluidJsonViewHelper extends AbstractWidgetViewHelper
|
|
||||||
{
|
|
||||||
use ValidateRenderFunctionTrait;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
* @Flow\Inject
|
|
||||||
* @var FluidJsonController
|
|
||||||
*/
|
|
||||||
protected $controller;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
protected $ajaxWidget = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function initializeArguments(): void
|
|
||||||
{
|
|
||||||
parent::initializeArguments();
|
|
||||||
|
|
||||||
$this->registerArgument('objects', QueryResult::class, 'Objects to show in table.', true);
|
|
||||||
$this->registerArgument(
|
|
||||||
'renderFunction',
|
|
||||||
'string',
|
|
||||||
'callabe to use to render single object',
|
|
||||||
true
|
|
||||||
);
|
|
||||||
$this->registerArgument(
|
|
||||||
'searchProperties',
|
|
||||||
'array',
|
|
||||||
'an array of pathes, which should be used during search evaluation',
|
|
||||||
false,
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function validateArguments()
|
|
||||||
{
|
|
||||||
parent::validateArguments();
|
|
||||||
$this->validateRenderFunctionArgument('renderFunction');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws InfiniteLoopException
|
|
||||||
* @throws InvalidControllerException
|
|
||||||
* @throws MissingControllerException
|
|
||||||
* @throws StopActionException
|
|
||||||
*/
|
|
||||||
public function render(): string
|
|
||||||
{
|
|
||||||
return $this->initiateSubRequest();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
DigiComp:
|
|
||||||
FluidJsonViews:
|
|
||||||
limitConfiguration:
|
|
||||||
default: 1000
|
|
||||||
max: 1000
|
|
19
LICENSE
19
LICENSE
|
@ -1,19 +0,0 @@
|
||||||
Copyright (c) 2024 Ferdinand Kuhl <f.kuhl@digital-competence.de>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
28
README.md
28
README.md
|
@ -1,28 +0,0 @@
|
||||||
# DigiComp.FluidJsonViews
|
|
||||||
|
|
||||||
This package builds upon `DigiComp.FluidRenderFunctions` and uses this, to use a such defined render function to create a simple key/value json view from your QueryResult. Where the key will be the persistence identifier and the value the result of your rendered template.
|
|
||||||
|
|
||||||
Let me provide you an example:
|
|
||||||
```html
|
|
||||||
<rf:registerRenderFunction as="renderTag">
|
|
||||||
{subject.name}
|
|
||||||
</rf:registerRenderFunction>
|
|
||||||
<fj:fluidJson objects="{tags}" renderFunction="renderTag" searchProperties="{0: 'name'}">
|
|
||||||
<a href="{dataUri}">jsonView</a>
|
|
||||||
</fj:fluidJson>
|
|
||||||
```
|
|
||||||
|
|
||||||
If you fetch the jsonView you will see something like this:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"recordsTotal": 2,
|
|
||||||
"recordsFiltered": 2,
|
|
||||||
"results": [
|
|
||||||
{"id": "a310057f-869e-419e-b6fe-6c3a00fe444a", "text": "hallo 2"},
|
|
||||||
{"id": "a310057f-869e-419e-b6fe-6c3a00fe444b", "text": "hallo"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
The provided link will understand three query parameters: `limit`, `term` and `page` - all optional.
|
|
||||||
If you send a "term" parameter, it will apply an or sql search over all your given search property paths. `limit` and `page` will allow you to paginate the results.
|
|
||||||
For security and performance reasons, you can define a max limit using the provided values in Settings.yaml. There you can change the default limit (used, if not sent), or disable both (not recommended).
|
|
|
@ -1,8 +0,0 @@
|
||||||
{namespace rf=DigiComp\FluidRenderFunctions\ViewHelpers}
|
|
||||||
{namespace fj=DigiComp\FluidJsonViews\ViewHelpers}
|
|
||||||
<rf:registerRenderFunction as="renderTag">
|
|
||||||
{subject.name}
|
|
||||||
</rf:registerRenderFunction>
|
|
||||||
<fj:fluidJson objects="{tags}" renderFunction="renderTag" searchProperties="{0: 'name'}">
|
|
||||||
<a href="{dataUri}">jsonView</a>
|
|
||||||
</fj:fluidJson>
|
|
|
@ -1,4 +0,0 @@
|
||||||
<f:renderChildren arguments="{
|
|
||||||
dataUri: '{f:widget.uri(action: \'data\', format: \'json\', ajax: true)}',
|
|
||||||
entityClassName: entityClassName
|
|
||||||
}" />
|
|
|
@ -1,17 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DigiComp\FluidJsonViews\Tests\Functional\Fixtures\Controller;
|
|
||||||
|
|
||||||
use Neos\Flow\Mvc\Controller\ActionController;
|
|
||||||
use Neos\Flow\Persistence\Doctrine\Query;
|
|
||||||
use Neos\FluidAdaptor\Tests\Functional\Form\Fixtures\Domain\Model\Tag;
|
|
||||||
|
|
||||||
class TestController extends ActionController
|
|
||||||
{
|
|
||||||
public function indexAction()
|
|
||||||
{
|
|
||||||
$this->view->assign('tags', (new Query(Tag::class))->execute());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DigiComp\FluidJsonViews\Tests\Functional;
|
|
||||||
|
|
||||||
use Neos\Flow\Mvc\Routing\Route;
|
|
||||||
use Neos\Flow\Tests\FunctionalTestCase;
|
|
||||||
use Neos\FluidAdaptor\Tests\Functional\Form\Fixtures\Domain\Model\Tag;
|
|
||||||
|
|
||||||
class FluidJsonTest extends FunctionalTestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
protected static $testablePersistenceEnabled = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializer
|
|
||||||
*/
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
$route = new Route();
|
|
||||||
$route->setUriPattern('test/fluidjsonviews/test(/{@action})');
|
|
||||||
$route->setDefaults([
|
|
||||||
'@package' => 'DigiComp.FluidJsonViews',
|
|
||||||
'@subpackage' => 'Tests\Functional\Fixtures',
|
|
||||||
'@controller' => 'Test',
|
|
||||||
'@action' => 'index',
|
|
||||||
]);
|
|
||||||
$route->setAppendExceedingArguments(true);
|
|
||||||
$this->router->addRoute($route);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function itAllowsFluidToRenderWrappedArray(): void
|
|
||||||
{
|
|
||||||
$post1 = new Tag('hallo');
|
|
||||||
$this->persistenceManager->add($post1);
|
|
||||||
$post2 = new Tag('hallo 2');
|
|
||||||
$this->persistenceManager->add($post2);
|
|
||||||
$this->persistenceManager->persistAll();
|
|
||||||
|
|
||||||
$this->browser->request('http://localhost/test/fluidjsonviews/test');
|
|
||||||
$link = $this->browser->getCrawler()->selectLink('jsonView')->link()->getUri();
|
|
||||||
$response = $this->browser->request($link);
|
|
||||||
static::assertEquals('application/json', $response->getHeaderLine('Content-Type'));
|
|
||||||
$result = \json_decode((string)$response->getBody(), true);
|
|
||||||
static::assertEquals(2, $result['recordsTotal']);
|
|
||||||
static::assertCount(2, $result['results']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function itFiltersIfTermProvided(): void
|
|
||||||
{
|
|
||||||
$post1 = new Tag('hallo');
|
|
||||||
$this->persistenceManager->add($post1);
|
|
||||||
$post2 = new Tag('hallo 2');
|
|
||||||
$this->persistenceManager->add($post2);
|
|
||||||
$this->persistenceManager->persistAll();
|
|
||||||
|
|
||||||
$this->browser->request('http://localhost/test/fluidjsonviews/test');
|
|
||||||
$link = $this->browser->getCrawler()->selectLink('jsonView')->link()->getUri();
|
|
||||||
$response = $this->browser->request($link . '&term=2');
|
|
||||||
$result = \json_decode((string)$response->getBody(), true);
|
|
||||||
static::assertEquals('application/json', $response->getHeaderLine('Content-Type'));
|
|
||||||
|
|
||||||
static::assertEquals(2, $result['recordsTotal']);
|
|
||||||
static::assertEquals(1, $result['recordsFiltered']);
|
|
||||||
static::assertCount(1, $result['results']);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
{
|
|
||||||
"name": "digicomp/fluid-json-views",
|
|
||||||
"description": "create simple json views, with remote search from Fluid",
|
|
||||||
"type": "neos-package",
|
|
||||||
"require": {
|
|
||||||
"digicomp/fluid-render-functions": "^1.0.0",
|
|
||||||
"ext-json": "*",
|
|
||||||
"neos/flow": "^6.3.5 | ^7.3 | ^8.3",
|
|
||||||
"neos/fluid-adaptor": "^6.3.5 | ^7.3 | ^8.3",
|
|
||||||
"php": ">=7.4"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpunit/phpunit": "~8.5"
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"DigiComp\\FluidJsonViews\\": "Classes/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload-dev": {
|
|
||||||
"psr-4": {
|
|
||||||
"DigiComp\\FluidJsonViews\\Tests\\": "Tests/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"extra": {
|
|
||||||
"neos": {
|
|
||||||
"package-key": "DigiComp.FluidJsonViews"
|
|
||||||
},
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-develop": "1.0.x-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Ferdinand Kuhl",
|
|
||||||
"email": "f.kuhl@digital-competence.de",
|
|
||||||
"homepage": "https://www.digital-competence.de",
|
|
||||||
"role": "Developer"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"homepage": "https://git.digital-competence.de/Packages/DigiComp.FluidJsonViews",
|
|
||||||
"keywords": [
|
|
||||||
"Neos",
|
|
||||||
"Flow",
|
|
||||||
"fluid",
|
|
||||||
"ajax"
|
|
||||||
]
|
|
||||||
}
|
|
Loading…
Reference in a new issue