[wip] add actions to crud

This commit is contained in:
Julien Fastré 2019-12-11 16:08:16 +01:00
parent d6354da24e
commit 199930d23a
16 changed files with 265 additions and 37 deletions

View File

@ -96,3 +96,4 @@ Version 1.5.14
Branch CRUD-Init
================
- create an api for rendering entities

View File

@ -31,6 +31,7 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Chill\MainBundle\CRUD\Resolver\Resolver;
/**
*
@ -161,7 +162,8 @@ class CRUDController extends AbstractController
}
$defaultTemplateParameters = [
'entity' => $entity
'entity' => $entity,
'crud_name' => $this->getCrudName()
];
return $this->render(
@ -193,17 +195,15 @@ class CRUDController extends AbstractController
$em->flush();
$this->onPostFlush($action, $entity, $form, $request);
$this->addFlash('succes', $this->generateFormSuccessMessage($action, $entity));
$this->addFlash('success', $this->generateFormSuccessMessage($action, $entity));
$result = $this->onBeforeRedirect($action, $entity, $form, $request);
$result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request);
if ($result instanceof Response) {
return $result;
}
return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index', [
'id' => $entity->getId()
]);
return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index');
} elseif ($form->isSubmitted()) {
$this->addFlash('error', $this->generateFormErrorMessage($action, $form));
@ -223,7 +223,11 @@ class CRUDController extends AbstractController
protected function formCreateAction($action, Request $request, $formClass = null): Response
{
$entity = $this->createEntity($action, $request);
if ($request->query->has('duplicate')) {
$entity = $this->duplicateEntity($action, $request);
} else {
$entity = $this->createEntity($action, $request);
}
$this->checkACL($action, $entity);
@ -243,9 +247,9 @@ class CRUDController extends AbstractController
$em->flush();
$this->onPostFlush($action, $entity, $form, $request);
$this->getPaginatorFactory();
$this->addFlash('succes', $this->generateFormSuccessMessage($action, $entity));
$this->addFlash('success', $this->generateFormSuccessMessage($action, $entity));
$result = $this->onBeforeRedirect($action, $entity, $form, $request);
$result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request);
if ($result instanceof Response) {
return $result;
@ -280,9 +284,17 @@ class CRUDController extends AbstractController
return $this->getDoctrine()
->getRepository($this->getEntityClass())
->find($id);
}
protected function duplicateEntity(string $action, Request $request)
{
$id = $request->query->get('duplicate_id', 0);
$originalEntity = $this->getEntity($action, $id, $request);
$this->getDoctrine()->getManager()
->detach($originalEntity);
return $originalEntity;
}
protected function getEntityClass(): string
@ -311,14 +323,7 @@ class CRUDController extends AbstractController
protected function buildDefaultRole($action)
{
if (empty($this->crudConfig['base_role'])) {
throw new \LogicException(sprintf("the base role is not defined. You must define "
. "on or override %s or %s methods", __METHOD__, "getRoleFor"));
}
return \strtoupper(
$this->crudConfig['base_role'].
'_'.
return $this->getCrudResolver()->buildDefaultRole($this->getCrudName(),
$action);
}
@ -334,21 +339,19 @@ class CRUDController extends AbstractController
$form = $this->createForm($formClass, $entity, $formOptions);
$this->addDefaultButtons($action, $form);
$this->customizeForm($action, $form);
return $form;
}
protected function addDefaultButtons($action, FormInterface $form)
protected function customizeForm($action, FormInterface $form)
{
$form->add('submit', SubmitType::class, [
'label' => $this->generateLabelForButton($action, 'submit', $form)
]);
}
protected function generateLabelForButton($action, $formName, $form)
{
return $action;
return sprintf("crud.%s.button_action_form", $action);
}
protected function generateFormErrorMessage($action, FormInterface $form): string
@ -362,13 +365,13 @@ class CRUDController extends AbstractController
{
switch ($action) {
case 'edit':
$msg = "The data have been successfully updated";
$msg = "crud.edit.success";
break;
case 'new':
$msg = "The date have been successfully created";
$msg = "crud.new.success";
break;
default:
$msg = "Your request has been successfully executed";
$msg = "crud.default.success";
}
return $this->getTranslator()->trans($msg);
@ -403,6 +406,8 @@ class CRUDController extends AbstractController
return '@ChillMain/CRUD/edit.html.twig';
case 'index':
return '@ChillMain/CRUD/index.html.twig';
case 'view':
return '@ChillMain/CRUD/view.html.twig';
default:
throw new \LogicException("the view for action $action is not "
. "defined. You should override ".__METHOD__." to add this "
@ -445,8 +450,20 @@ class CRUDController extends AbstractController
{
}
protected function onBeforeRedirect(string $action, $entity, FormInterface $form, Request $request)
protected function onBeforeRedirectAfterSubmission(string $action, $entity, FormInterface $form, Request $request)
{
$next = $request->request->get("submit", "save-and-close");
switch ($next) {
case "save-and-close":
return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index');
case "save-and-new":
return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_new');
default:
return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', [
'id' => $entity->getId()
]);
}
}
protected function getActionConfig(string $action)
@ -481,6 +498,11 @@ class CRUDController extends AbstractController
return $this->get(EventDispatcherInterface::class);
}
protected function getCrudResolver(): Resolver
{
return $this->get(Resolver::class);
}
public static function getSubscribedServices()
{
return \array_merge(
@ -490,6 +512,7 @@ class CRUDController extends AbstractController
'translator' => TranslatorInterface::class,
AuthorizationHelper::class => AuthorizationHelper::class,
EventDispatcherInterface::class => EventDispatcherInterface::class,
Resolver::class => Resolver::class,
]
);
}

View File

@ -41,10 +41,35 @@ class Resolver
*/
protected $propertyAccess;
function __construct(EntityManagerInterface $em)
/**
*
* @var array
*/
protected $crudConfig;
/**
* @deprecated
*/
const ROLE_VIEW = 'role.view';
/**
* @deprecated
*/
const ROLE_EDIT = 'role.edit';
/**
* The key to get the role necessary for the action
*/
const ROLE = 'role';
function __construct(EntityManagerInterface $em, array $crudConfig)
{
$this->em = $em;
foreach($crudConfig as $conf) {
$this->crudConfig[$conf['name']] = $conf;
}
$this->buildPropertyAccess();
}
@ -69,13 +94,37 @@ class Resolver
return $this->propertyAccess->getValue($entity, $path);
}
public function getConfigValue($key, $crudName, $action = null)
{
$config = $this->crudConfig[$crudName];
switch ($key) {
case self::ROLE:
dump($config);
return $config['actions'][$action]['role'] ?? $this->buildDefaultRole($crudName, $action);
}
}
public function buildDefaultRole($crudName, $action)
{
if (empty($this->crudConfig[$crudName]['base_role'])) {
throw new \LogicException(sprintf("the base role is not defined. You must define "
. "on or override %s or %s methods", __METHOD__, "getRoleFor"));
}
return \strtoupper(
$this->crudConfig[$crudName]['base_role'].
'_'.
$action);
}
public function getTwigTemplate($entity, $path): string
{
list($focusEntity, $subPath) = $this->getFocusedEntity($entity, $path);
$classMetadata = $this->em->getClassMetadata(\get_class($focusEntity));
$type = $classMetadata->getTypeOfField($subPath);
dump($type);
switch ($type) {
default:

View File

@ -22,6 +22,7 @@ namespace Chill\MainBundle\CRUD\Templating;
use Chill\MainBundle\CRUD\Resolver\Resolver;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\Extension\AbstractExtension;
use Twig\Environment;
@ -50,6 +51,14 @@ class TwigCRUDResolver extends AbstractExtension
];
}
public function getFunctions()
{
return [
new TwigFunction('chill_crud_config', [$this, 'getConfig'],
['is_safe' => 'html'])
];
}
public function display(Environment $env, $entity, $path): string
{
$data = $this->resolver->getData($entity, $path);
@ -57,5 +66,10 @@ class TwigCRUDResolver extends AbstractExtension
return $env->render($template, ['data' => $data, 'entity' => $entity, ]);
}
public function getConfig($configKey, $crudName, $action = null)
{
return $this->resolver->getConfigValue($configKey, $crudName, $action);
}
}

View File

@ -154,6 +154,10 @@ class Configuration implements ConfigurationInterface
->defaultNull()
->info('the role that will be required for this action. Override option `base_role`')
->end()
->scalarNode('template')
->defaultNull()
->info('the template to render the view')
->end()
->end()
->end()
->end()

View File

@ -7,6 +7,7 @@ services:
Chill\MainBundle\CRUD\Resolver\Resolver:
arguments:
$em: '@Doctrine\ORM\EntityManagerInterface'
$crudConfig: '%chill_main_crud_route_loader_config%'
Chill\MainBundle\CRUD\Templating\TwigCRUDResolver:
arguments:

View File

@ -10,6 +10,7 @@ ul.record_actions, ul.record_actions_column {
display: flex;
justify-content: flex-end;
padding: 0.5em 0;
flex-wrap: wrap-reverse;
li {
display: inline-block;

View File

@ -1,7 +1,7 @@
.sc-button {
margin-bottom: 0.5rem;
&.bt-submit, &.bt-save, &.bt-create, &.bt-new {
&.bt-submit, &.bt-save, &.bt-create, &.bt-new, &.bt-duplicate {
@include button($green, $white);
}
@ -19,6 +19,7 @@
&:not(.change-icon) {
// icons using font-awesome "old way"
&.bt-create::before,
&.bt-save::before,
&.bt-new::before,
@ -31,6 +32,14 @@
font: normal normal normal 14px/1 FontAwesome;
margin-right: 0.5em;
}
// icons using font-awesome "new svg way"
&.bt-duplicate::before {
display: inline-block;
width: 1em;
margin-right: 0.5em;
vertical-align: middle;
}
&.bt-save::before {
// add a floppy
@ -60,6 +69,10 @@
&.bt-show::before, &.bt-view::before {
content: "";
}
&.bt-duplicate::before {
content: url("./copy-solid.svg");
}
}
> i.fa {

View File

@ -0,0 +1,4 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="copy" class="svg-inline--fa fa-copy fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<!-- adapted from original work obtained from font-awesome CC-BY 4.0 International - https://fontawesome.com/license Last updated on July 12, 2018 -->
<path fill="white" d="M320 448v40c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24V120c0-13.255 10.745-24 24-24h72v296c0 30.879 25.121 56 56 56h168zm0-344V0H152c-13.255 0-24 10.745-24 24v368c0 13.255 10.745 24 24 24h272c13.255 0 24-10.745 24-24V128H344c-13.2 0-24-10.8-24-24zm120.971-31.029L375.029 7.029A24 24 0 0 0 358.059 0H352v96h96v-6.059a24 24 0 0 0-7.029-16.97z"></path>
</svg>

After

Width:  |  Height:  |  Size: 733 B

View File

@ -229,4 +229,23 @@ Log in with your new password: Connectez-vous avec votre nouveau mot de passe
# impersonate
Exit impersonation: Retour Administrateur
Impersonate: Mode fantôme
Impersonate mode: Mode fantôme
Impersonate mode: Mode fantôme
crud:
new:
button_action_form: Créer
link_edit: Modifier
save_and_close: Créer & fermer
save_and_show: Créer & voir
save_and_new: Créer & nouveau
success: Les données ont été créées
edit:
button_action_form: Enregistrer
back_to_view: Voir
save_and_close: Modifier & fermer
save_and_show: Modifier & voir
success: Les données ont été modifiées
default:
success: Les données ont été enregistrées
view:
link_duplicate: Dupliquer

View File

@ -1,13 +1,13 @@
<div class="{% block crud_content_main_div_class %}grid-10 centered{% endblock %}">
{% block crud_content_header %}
<h1>{{ 'crud.title.edit_of_%crud_name%'|trans({'%crud_name%' : crud_name }) }}</h1>
<h1>{{ ('crud.'~crud_name~'.title_edit')|trans }}</h1>
{% endblock crud_content_header %}
{% block crud_content_form %}
{{ form_start(form) }}
{% block crud_content_form_rows %}
{% for f in form if f.vars.name != 'submit' %}
{% for f in form %}
{{ form_row(f) }}
{% endfor %}
{% endblock crud_content_form_rows %}
@ -20,8 +20,30 @@
{{ 'Cancel'|trans }}
</a>
</li>
{% endblock %}
{% block content_form_actions_view %}
{% if is_granted(chill_crud_config('role', crud_name, 'view'), entity) %}
<li class="">
<a class="sc-button bt-show" href="{{ chill_return_path_or('chill_crud_'~crud_name~'_view', { 'id': entity.id }) }}">
{{ 'crud.edit.back_to_view'|trans }}
</a>
</li>
{% endif %}
{% endblock %}
<li>{{ form_widget(form.submit, { 'attr': { 'class': 'sc-button bt-edit'} } ) }}</li>
{% block content_form_actions_save_and_close %}
<li class="">
<button type="submit" name="submit" value="save-and-close" class="sc-button bt-update">
{{ 'crud.edit.save_and_close'|trans }}
</button>
</li>
{% endblock %}
{% block content_form_actions_save_and_show %}
<li class="">
<button type="submit" name="submit" value="save-and-show" class="sc-button bt-update">
{{ 'crud.edit.save_and_show'|trans }}
</button>
</li>
{% endblock %}
</ul>
{% endblock %}

View File

@ -1 +1 @@
{{ 'crud.title.edit_of_%crud_name%'|trans({'%crud_name%' : crud_name }) }}
{{ ('crud.'~crud_name~'.title_edit')|trans }}

View File

@ -21,7 +21,27 @@
</a>
</li>
{% endblock %}
<li>{{ form_widget(form.submit, { 'attr': { 'class': 'sc-button bt-new'} } ) }}</li>
{% block content_form_actions_save_and_close %}
<li class="">
<button type="submit" name="submit" value="save-and-close" class="sc-button bt-create">
{{ 'crud.new.save_and_close'|trans }}
</button>
</li>
{% endblock %}
{% block content_form_actions_save_and_show %}
<li class="">
<button type="submit" name="submit" value="save-and-show" class="sc-button bt-create">
{{ 'crud.new.save_and_show'|trans }}
</button>
</li>
{% endblock %}
{% block content_form_actions_save_and_new %}
<li class="">
<button type="submit" name="submit" value="save-and-new" class="sc-button bt-create">
{{ 'crud.new.save_and_new'|trans }}
</button>
</li>
{% endblock %}
</ul>
{% endblock %}

View File

@ -0,0 +1,46 @@
<div class="{% block crud_content_main_div_class %}grid-10 centered{% endblock %}">
{% block crud_content_header %}
<h1>{{ 'crud.%crud_name%.title_view'|trans({'%crud_name%' : crud_name }) }}</h1>
{% endblock crud_content_header %}
{% block crud_content_view %}
{% block crud_content_view_details %}
<dl class="chill_view_data">
<dt>id</dt>
<dd>{{ entity.id|default("No id") }}</dd>
</dl>
{% endblock crud_content_view_details %}
{% block crud_content_view_actions %}
<ul class="record_actions sticky-form-buttons">
{% block content_view_actions_back %}
<li class="cancel">
<a class="sc-button bt-cancel" href="{{ chill_return_path_or('chill_crud_'~crud_name~'_index') }}">
{{ 'Cancel'|trans }}
</a>
</li>
{% endblock %}
{% block content_view_actions_duplicate_link %}
{% if is_granted(chill_crud_config('role', crud_name, 'new'), entity) %}
<li>
<a class="sc-button bt-duplicate" href="{{ chill_return_path_or('chill_crud_'~crud_name~'_new', { 'duplicate_id': entity.id, 'duplicate': true }) }}">
{{ 'crud.view.link_duplicate'|trans }}
</a>
</li>
{% endif %}
{% endblock content_view_actions_duplicate_link %}
{% block content_view_actions_edit_link %}
{% if is_granted(chill_crud_config('role', crud_name, 'view'), entity) %}
<li>
<a class="sc-button bt-edit" href="{{ chill_return_path_or('chill_crud_'~crud_name~'_edit', { 'id': entity.id }) }}">
{{ 'crud.new.link_edit'|trans }}
</a>
</li>
{% endif %}
{% endblock content_view_actions_edit_link %}
</ul>
{% endblock crud_content_view_actions %}
{% endblock crud_content_view %}
</div>

View File

@ -0,0 +1 @@
{{ 'crud.%crud_name%.title_view'|trans({'%crud_name%' : crud_name }) }}

View File

@ -0,0 +1,10 @@
{% extends '@ChillMain/layout.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_view_title.html.twig') %}
{% endblock %}
{% block content %}
{% embed '@ChillMain/CRUD/_view_content.html.twig' %}
{% endembed %}
{% endblock %}