Skip to content

Commit 2c8d296

Browse files
committed
Add embedded relations
1 parent 6d0170d commit 2c8d296

File tree

7 files changed

+173
-8
lines changed

7 files changed

+173
-8
lines changed

src/Controller/AbstractCrudController.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,16 @@ public function index(AdminContext $context)
114114
$fields = FieldCollection::new($this->configureFields(Crud::PAGE_INDEX));
115115
$filters = $this->get(FilterFactory::class)->create($context->getCrud()->getFiltersConfig(), $fields, $context->getEntity());
116116
$queryBuilder = $this->createIndexQueryBuilder($context->getSearch(), $context->getEntity(), $fields, $filters);
117+
118+
if($embedContext = $context->getRequest()->query->get('embedContext'))
119+
{
120+
$filterProperty = $embedContext['mappedBy'];
121+
$filterValue = $embedContext['embeddedIn'];
122+
$queryBuilder->andWhere($queryBuilder->expr()->eq(sprintf('%s.%s', current($queryBuilder->getRootAliases()), $filterProperty), $filterValue));
123+
124+
$fields->unset($fields->get($filterProperty));
125+
}
126+
117127
$paginator = $this->get(PaginatorFactory::class)->create($queryBuilder);
118128

119129
$entities = $this->get(EntityFactory::class)->createCollection($context->getEntity(), $paginator->getResults());
@@ -122,7 +132,7 @@ public function index(AdminContext $context)
122132

123133
$responseParameters = $this->configureResponseParameters(KeyValueStore::new([
124134
'pageName' => Crud::PAGE_INDEX,
125-
'templateName' => 'crud/index',
135+
'templateName' => $embedContext ? 'crud/embedded' : 'crud/index',
126136
'entities' => $entities,
127137
'paginator' => $paginator,
128138
'global_actions' => $globalActions,

src/Field/EmbedField.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace EasyCorp\Bundle\EasyAdminBundle\Field;
4+
5+
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
6+
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
7+
8+
/**
9+
* @author Lukas Lücke <[email protected]>
10+
*/
11+
final class EmbedField implements FieldInterface
12+
{
13+
use FieldTrait;
14+
15+
public static function new(string $propertyName, ?string $label = null): self
16+
{
17+
return (new self())
18+
->setProperty($propertyName)
19+
->setLabel($label)
20+
->setTemplateName('crud/field/embed');
21+
}
22+
}

src/Registry/TemplateRegistry.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ final class TemplateRegistry
1515
'crud/paginator' => '@EasyAdmin/crud/paginator.html.twig',
1616
'crud/index' => '@EasyAdmin/crud/index.html.twig',
1717
'crud/detail' => '@EasyAdmin/crud/detail.html.twig',
18+
'crud/embedded' => '@EasyAdmin/crud/embedded.html.twig',
1819
'crud/new' => '@EasyAdmin/crud/new.html.twig',
1920
'crud/edit' => '@EasyAdmin/crud/edit.html.twig',
2021
'crud/action' => '@EasyAdmin/crud/action.html.twig',
@@ -35,6 +36,7 @@ final class TemplateRegistry
3536
'crud/field/datetimetz' => '@EasyAdmin/crud/field/datetimetz.html.twig',
3637
'crud/field/decimal' => '@EasyAdmin/crud/field/decimal.html.twig',
3738
'crud/field/email' => '@EasyAdmin/crud/field/email.html.twig',
39+
'crud/field/embed' => '@EasyAdmin/crud/field/embed.html.twig',
3840
'crud/field/float' => '@EasyAdmin/crud/field/float.html.twig',
3941
'crud/field/generic' => '@EasyAdmin/crud/field/generic.html.twig',
4042
'crud/field/hidden' => '@EasyAdmin/crud/field/hidden.html.twig',
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
2+
{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #}
3+
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
4+
{% set has_footer = entities|length != 0 %}
5+
{% set has_batch_actions = false %}
6+
{% set some_results_are_hidden = false %}
7+
{% set sort_field_name = app.request.get('sort')|keys|first %}
8+
{% set sort_order = app.request.get('sort')|first %}
9+
10+
{{ block("main", "@EasyAdmin/crud/index.html.twig") }}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
2+
{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #}
3+
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
4+
{% set current_url = ea_url() %}
5+
6+
{% set target_entity = field.doctrineMetadata.get('targetEntity') %}
7+
{% set crud_id = ea.crudControllers.findCrudIdByCrudFqcn(ea.crudControllers.findCrudFqcnByEntityFqcn(target_entity)) %}
8+
9+
{% set url = ea_url().unset('entityId').setCrudId(crud_id).setAction('index').set('embedContext', {
10+
mappedBy: field.doctrineMetadata.get('mappedBy'),
11+
embeddedIn: entity.primaryKeyValue,
12+
crud_id: ea.crudControllers.findCrudIdByCrudFqcn(ea.crud.controllerFqcn),
13+
fieldName: field.property
14+
}) %}
15+
16+
{% set id_suffix = '-'~field.property %}
17+
18+
<div id="embed{{ id_suffix }}" class="position-relative embed-loading">
19+
<div class="position-absolute text-center embed-spinner">
20+
<div class="spinner-border text-primary spinner-border-lg mt-2"></div>
21+
</div>
22+
<div class="embed-content"></div>
23+
</div>
24+
25+
{{ include('@EasyAdmin/crud/includes/_delete_form.html.twig', {crud_id: crud_id, referrer: current_url, id_suffix: id_suffix}, with_context = false) }}
26+
27+
<style>
28+
.embed-spinner {
29+
display: none;
30+
top: 50%;
31+
left: 50%;
32+
margin-top: -1rem;
33+
margin-left: -1rem;
34+
z-index: 10;
35+
}
36+
37+
.embed-loading .embed-spinner {
38+
display: block;
39+
}
40+
41+
.embed-loading .embed-content {
42+
opacity: 0.5;
43+
pointer-events: none;
44+
}
45+
</style>
46+
47+
<script>
48+
window.addEventListener('load', () => {
49+
const referrer = '{{ current_url|raw }}#embed{{ id_suffix }}'
50+
const initialUrl = '{{ url|raw }}'
51+
52+
const embed = document.querySelector('#embed{{ id_suffix }}');
53+
const embedContent = embed.querySelector('.embed-content');
54+
55+
const load = (url) => {
56+
embed.classList.add('embed-loading');
57+
fetch(url)
58+
.then(it => it.text())
59+
.then(it => embedContent.innerHTML = it)
60+
.then(() => {
61+
// override referrer of actions, so we get back to the "main" view afterwards, not the embed
62+
embedContent.querySelectorAll('.actions a').forEach(action => {
63+
const target = new URL(action.href)
64+
target.searchParams.set('referrer', referrer);
65+
action.href = target.toString();
66+
})
67+
68+
// remove "index" delete form/modal
69+
embedContent.querySelectorAll('#modal-delete, #delete-form').forEach(it => it.remove());
70+
// initialize correct delete modal
71+
embedContent.querySelectorAll('.action-delete').forEach(action => {
72+
action.addEventListener('click', function (e) {
73+
e.preventDefault();
74+
const id = $(this).parents('tr').first().data('id');
75+
76+
$('#modal-delete{{ id_suffix }}').modal({backdrop: true, keyboard: true})
77+
.off('click', '#modal-delete-button{{ id_suffix }}')
78+
.on('click', '#modal-delete-button{{ id_suffix }}', function () {
79+
let deleteForm = $('#delete-form{{ id_suffix }}');
80+
deleteForm.attr('action', deleteForm.attr('action').replace('__entityId_placeholder__', id));
81+
deleteForm.trigger('submit');
82+
});
83+
});
84+
})
85+
86+
// intercept sort and pagination
87+
embedContent.querySelectorAll('thead a, .pagination a').forEach(link => {
88+
link.addEventListener('click', evt => {
89+
evt.preventDefault();
90+
load(link.href)
91+
})
92+
})
93+
94+
// intercept search
95+
embedContent.querySelector('.form-action-search form').addEventListener('submit', evt => {
96+
evt.preventDefault();
97+
const data = new FormData(evt.target);
98+
const params = new URLSearchParams(data).toString()
99+
const target = new URL(url);
100+
target.search = params.toString();
101+
load(target.toString())
102+
})
103+
104+
// highlight results
105+
const searchQuery = new URL(url).searchParams.get('query');
106+
if(searchQuery) {
107+
$(embedContent).find('table tbody td:not(.actions)').highlight($.merge([searchQuery], searchQuery.split(' ')));
108+
}
109+
110+
// can be used to re-initialize dynamic content
111+
document.dispatchEvent(new Event('ea.embed.content-loaded'))
112+
})
113+
.finally(() => embed.classList.remove('embed-loading'))
114+
;
115+
}
116+
117+
load(initialUrl);
118+
})
119+
</script>

src/Resources/views/crud/includes/_delete_form.html.twig

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
22
{% set delete_url = ea_url()
3-
.setCrudId(app.request.query.get('crudId'))
3+
.setCrudId(crud_id ?? app.request.query.get('crudId'))
44
.setAction('delete')
55
.setEntityId(entity_id ?? '__entityId_placeholder__')
6-
.removeReferrer() %}
6+
%}
7+
{% set delete_url = referrer is defined ? delete_url.set('referrer', referrer) : delete_url.removeReferrer() %}
8+
{% set id_suffix = id_suffix|default(null) %}
79

8-
<form action="{{ delete_url }}" method="post" id="delete-form" style="display: none">
10+
<form action="{{ delete_url }}" method="post" id="delete-form{{ id_suffix }}" style="display: none">
911
<input type="hidden" name="token" value="{{ csrf_token('ea-delete') }}" />
1012
</form>
1113

12-
<div id="modal-delete" class="modal fade">
14+
<div id="modal-delete{{ id_suffix }}" class="modal fade">
1315
<div class="modal-dialog">
1416
<div class="modal-content">
1517
<div class="modal-body">
@@ -21,7 +23,7 @@
2123
<span class="btn-label">{{ 'action.cancel'|trans([], 'EasyAdminBundle') }}</span>
2224
</button>
2325

24-
<button type="button" data-dismiss="modal" class="btn btn-danger" id="modal-delete-button" formtarget="delete-form">
26+
<button type="button" data-dismiss="modal" class="btn btn-danger" id="modal-delete-button{{ id_suffix }}" formtarget="delete-form{{ id_suffix }}">
2527
<span class="btn-label">{{ 'action.delete'|trans([], 'EasyAdminBundle') }}</span>
2628
</button>
2729
</div>

src/Resources/views/crud/index.html.twig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@
5959
{% block search_form %}
6060
{# reset the referrer and page number whenever a new query is performed #}
6161
{% set query_parameters = ea.request.query.all|merge({
62-
referrer: null, page: 1,
63-
}) %}
62+
referrer: null, page: 1
63+
})|filter((v, k) => k != 'query') %}
6464

6565
{# browsers remove the query string when submitting forms using GET;
6666
that's why all query string parameters are added as hidden form fields #}

0 commit comments

Comments
 (0)